{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module Simplex.Chat.Terminal where import Control.Monad import Control.Monad.IO.Class (liftIO) import qualified Data.List.NonEmpty as L import Simplex.Chat (defaultChatConfig) import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Help (chatWelcome) import Simplex.Chat.Library.Commands (_defaultNtfServers) import Simplex.Chat.Operators import Simplex.Chat.Operators.Presets (operatorSimpleXChat, simplexChatRelays) import Simplex.Chat.Options import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Client (NetworkConfig (..), SMPProxyFallback (..), SMPProxyMode (..), defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) import System.Terminal (Key, Modifiers) import UnliftIO.STM #if !defined(dbPostgres) import Control.Exception (handle, throwIO) import qualified Data.ByteArray as BA import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Database.SQLite.Simple (SQLError (..)) import qualified Database.SQLite.Simple as DB import Simplex.Chat.Options.DB import System.IO (hFlush, hSetEcho, stdin, stdout) #endif terminalChatConfig :: ChatConfig terminalChatConfig = defaultChatConfig { presetServers = PresetServers { operators = [ PresetOperator { operator = Just operatorSimpleXChat, smp = map (presetServer True) [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ], useSMP = 3, xftp = map (presetServer True) $ L.toList defaultXFTPServers, useXFTP = 3, chatRelays = simplexChatRelays, useChatRelays = 2 } ], ntf = _defaultNtfServers, netCfg = defaultNetworkConfig { smpProxyMode = SPMUnknown, smpProxyFallback = SPFAllowProtected } }, deviceNameForRemote = "SimpleX CLI" } simplexChatTerminal :: WithTerminal t => ChatConfig -> ChatOpts -> t -> IO () simplexChatTerminal cfg options t = run options where #if defined(dbPostgres) run opts = simplexChatCore cfg opts $ \u cc -> do ct <- newChatTerminal t opts when (firstTime cc) . printToTerminal ct $ chatWelcome u runChatTerminal ct cc opts #else run opts@ChatOpts {coreOptions = coreOptions@CoreChatOpts {dbOptions}} = handle checkDBKeyError . simplexChatCore cfg opts $ \u cc -> do ct <- newChatTerminal t opts when (firstTime cc) . printToTerminal ct $ chatWelcome u runChatTerminal ct cc opts where checkDBKeyError :: SQLError -> IO () checkDBKeyError e = case sqlError e of DB.ErrorNotADatabase -> do putStrLn $ "Database file is invalid or " <> if BA.null (dbKey dbOptions) then "encrypted." else "you passed an incorrect encryption key." run =<< getKeyOpts _ -> throwIO e getKeyOpts :: IO ChatOpts getKeyOpts = do putStr "Enter database encryption key (Ctrl-C to exit):" hFlush stdout hSetEcho stdin False key <- getLine hSetEcho stdin True putStrLn "" pure opts {coreOptions = coreOptions {dbOptions = dbOptions {dbKey = BA.convert $ encodeUtf8 $ T.pack key}}} #endif runChatTerminal :: ChatTerminal -> ChatController -> ChatOpts -> IO () runChatTerminal ct cc opts = do keyQ <- newTQueueIO raceAny_ [runKeyReader ct keyQ, runTerminalInput ct cc keyQ, runTerminalOutput ct cc opts, runInputLoop ct cc] runKeyReader :: ChatTerminal -> TQueue (Key, Modifiers) -> IO () runKeyReader ct q = withChatTerm ct $ forever $ getKey >>= liftIO . atomically . writeTQueue q