2022-04-10 17:13:06 +01:00
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
2024-01-17 15:20:13 +00:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2022-04-10 17:13:06 +01:00
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
2024-01-17 15:20:13 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2022-04-10 17:13:06 +01:00
|
|
|
|
2024-01-17 15:20:13 +00:00
|
|
|
module Simplex.Chat.Core
|
|
|
|
( simplexChatCore,
|
|
|
|
runSimplexChat,
|
|
|
|
sendChatCmdStr,
|
|
|
|
sendChatCmd,
|
|
|
|
)
|
|
|
|
where
|
2022-04-10 17:13:06 +01:00
|
|
|
|
|
|
|
import Control.Logger.Simple
|
2024-01-17 15:20:13 +00:00
|
|
|
import Control.Monad
|
2022-04-10 17:13:06 +01:00
|
|
|
import Control.Monad.Reader
|
2024-01-17 15:20:13 +00:00
|
|
|
import Data.List (find)
|
2022-04-10 17:13:06 +01:00
|
|
|
import qualified Data.Text as T
|
|
|
|
import Data.Text.Encoding (encodeUtf8)
|
2024-01-17 15:20:13 +00:00
|
|
|
import Data.Time.Clock (getCurrentTime)
|
|
|
|
import Data.Time.LocalTime (getCurrentTimeZone)
|
2022-04-10 17:13:06 +01:00
|
|
|
import Simplex.Chat
|
|
|
|
import Simplex.Chat.Controller
|
2024-12-20 16:54:24 +04:00
|
|
|
import Simplex.Chat.Library.Commands
|
2023-02-18 17:39:16 +00:00
|
|
|
import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..))
|
2024-01-17 15:20:13 +00:00
|
|
|
import Simplex.Chat.Store.Profiles
|
2022-04-10 17:13:06 +01:00
|
|
|
import Simplex.Chat.Types
|
2024-01-17 15:20:13 +00:00
|
|
|
import Simplex.Chat.View (serializeChatResponse)
|
2024-12-28 12:35:34 +00:00
|
|
|
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..))
|
2025-01-10 15:27:29 +04:00
|
|
|
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction)
|
2023-03-27 18:34:48 +01:00
|
|
|
import System.Exit (exitFailure)
|
2024-01-17 15:20:13 +00:00
|
|
|
import System.IO (hFlush, stdout)
|
|
|
|
import Text.Read (readMaybe)
|
2022-04-10 17:13:06 +01:00
|
|
|
import UnliftIO.Async
|
|
|
|
|
2023-10-11 09:50:11 +01:00
|
|
|
simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO ()
|
2025-05-02 12:23:05 +01:00
|
|
|
simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations}, maintenance} chat =
|
2023-02-28 23:26:08 +00:00
|
|
|
case logAgent of
|
|
|
|
Just level -> do
|
|
|
|
setLogLevel level
|
|
|
|
withGlobalLogging logCfg initRun
|
|
|
|
_ -> initRun
|
2022-04-10 17:13:06 +01:00
|
|
|
where
|
2025-01-10 15:27:29 +04:00
|
|
|
initRun = createChatDatabase dbOptions confirm' >>= either exit run
|
2024-11-30 20:51:35 +00:00
|
|
|
confirm' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations
|
2023-03-27 18:34:48 +01:00
|
|
|
exit e = do
|
|
|
|
putStrLn $ "Error opening database: " <> show e
|
|
|
|
exitFailure
|
|
|
|
run db@ChatDatabase {chatStore} = do
|
2024-01-17 15:20:13 +00:00
|
|
|
u_ <- getSelectActiveUser chatStore
|
2025-05-02 12:23:05 +01:00
|
|
|
let backgroundMode = not maintenance
|
|
|
|
cc <- newChatController db u_ cfg opts backgroundMode
|
2024-01-17 15:20:13 +00:00
|
|
|
u <- maybe (createActiveUser cc) pure u_
|
|
|
|
unless testView $ putStrLn $ "Current user: " <> userStr u
|
2022-06-06 16:23:47 +01:00
|
|
|
runSimplexChat opts u cc chat
|
2022-04-10 17:13:06 +01:00
|
|
|
|
2022-06-06 16:23:47 +01:00
|
|
|
runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO ()
|
|
|
|
runSimplexChat ChatOpts {maintenance} u cc chat
|
|
|
|
| maintenance = wait =<< async (chat u cc)
|
|
|
|
| otherwise = do
|
2024-07-17 14:14:19 +01:00
|
|
|
a1 <- runReaderT (startChatController True True) cc
|
2023-11-26 18:16:37 +00:00
|
|
|
a2 <- async $ chat u cc
|
|
|
|
waitEither_ a1 a2
|
2022-04-10 17:13:06 +01:00
|
|
|
|
2023-08-01 20:54:51 +01:00
|
|
|
sendChatCmdStr :: ChatController -> String -> IO ChatResponse
|
2023-09-27 11:41:02 +03:00
|
|
|
sendChatCmdStr cc s = runReaderT (execChatCommand Nothing . encodeUtf8 $ T.pack s) cc
|
2023-08-01 20:54:51 +01:00
|
|
|
|
|
|
|
sendChatCmd :: ChatController -> ChatCommand -> IO ChatResponse
|
|
|
|
sendChatCmd cc cmd = runReaderT (execChatCommand' cmd) cc
|
2024-01-17 15:20:13 +00:00
|
|
|
|
2024-12-28 12:35:34 +00:00
|
|
|
getSelectActiveUser :: DBStore -> IO (Maybe User)
|
2024-01-17 15:20:13 +00:00
|
|
|
getSelectActiveUser st = do
|
|
|
|
users <- withTransaction st getUsers
|
|
|
|
case find activeUser users of
|
|
|
|
Just u -> pure $ Just u
|
|
|
|
Nothing -> selectUser users
|
|
|
|
where
|
|
|
|
selectUser :: [User] -> IO (Maybe User)
|
|
|
|
selectUser = \case
|
|
|
|
[] -> pure Nothing
|
2024-09-21 13:07:27 +01:00
|
|
|
[user] -> Just <$> withTransaction st (`setActiveUser` user)
|
2024-01-17 15:20:13 +00:00
|
|
|
users -> do
|
|
|
|
putStrLn "Select user profile:"
|
|
|
|
forM_ (zip [1 :: Int ..] users) $ \(n, user) -> putStrLn $ show n <> ": " <> userStr user
|
|
|
|
loop
|
|
|
|
where
|
|
|
|
loop = do
|
|
|
|
nStr <- getWithPrompt $ "user number (1 .. " <> show (length users) <> ")"
|
|
|
|
case readMaybe nStr :: Maybe Int of
|
|
|
|
Nothing -> putStrLn "not a number" >> loop
|
|
|
|
Just n
|
|
|
|
| n <= 0 || n > length users -> putStrLn "invalid user number" >> loop
|
2024-09-21 13:07:27 +01:00
|
|
|
| otherwise ->
|
|
|
|
let user = users !! (n - 1)
|
|
|
|
in Just <$> withTransaction st (`setActiveUser` user)
|
2024-01-17 15:20:13 +00:00
|
|
|
|
|
|
|
createActiveUser :: ChatController -> IO User
|
|
|
|
createActiveUser cc = do
|
|
|
|
putStrLn
|
|
|
|
"No user profiles found, it will be created now.\n\
|
|
|
|
\Please choose your display name.\n\
|
|
|
|
\It will be sent to your contacts when you connect.\n\
|
|
|
|
\It is only stored on your device and you can change it later."
|
|
|
|
loop
|
|
|
|
where
|
|
|
|
loop = do
|
|
|
|
displayName <- T.pack <$> getWithPrompt "display name"
|
|
|
|
let profile = Just Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing}
|
2024-08-06 16:13:36 +01:00
|
|
|
execChatCommand' (CreateActiveUser NewUser {profile, pastTimestamp = False}) `runReaderT` cc >>= \case
|
2024-01-17 15:20:13 +00:00
|
|
|
CRActiveUser user -> pure user
|
|
|
|
r -> do
|
|
|
|
ts <- getCurrentTime
|
|
|
|
tz <- getCurrentTimeZone
|
|
|
|
putStrLn $ serializeChatResponse (Nothing, Nothing) ts tz Nothing r
|
|
|
|
loop
|
|
|
|
|
|
|
|
getWithPrompt :: String -> IO String
|
|
|
|
getWithPrompt s = putStr (s <> ": ") >> hFlush stdout >> getLine
|
|
|
|
|
|
|
|
userStr :: User -> String
|
|
|
|
userStr User {localDisplayName, profile = LocalProfile {fullName}} =
|
|
|
|
T.unpack $ localDisplayName <> if T.null fullName || localDisplayName == fullName then "" else " (" <> fullName <> ")"
|