mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
apps: update chat bots, readme (#1928)
* apps: update chat bots, readme * CLI readme * broadcast bot * delete messages from non-publishers, better replies, support forwarding low-res images and links * typo Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * change --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
parent
6f68840b3a
commit
a0351d6f99
16 changed files with 332 additions and 17 deletions
|
@ -24,7 +24,7 @@
|
||||||
- 🔐 Double ratchet end-to-end encryption, with additional encryption layer.
|
- 🔐 Double ratchet end-to-end encryption, with additional encryption layer.
|
||||||
- 📱 Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084).
|
- 📱 Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084).
|
||||||
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
|
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
|
||||||
- 🖥 Available as a terminal (console) app / CLI on Linux, MacOS, Windows.
|
- 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
|
||||||
|
|
||||||
**NEW**: Security audit by [Trail of Bits](https://www.trailofbits.com/about), the [new website](https://simplex.chat) and v4.2 released! [See the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
|
**NEW**: Security audit by [Trail of Bits](https://www.trailofbits.com/about), the [new website](https://simplex.chat) and v4.2 released! [See the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
|
||||||
|
|
||||||
|
|
1
apps/ios/README.md
Normal file
1
apps/ios/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# SimpleX Chat iOS app
|
|
@ -33,6 +33,9 @@ welcomeGetOpts = do
|
||||||
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db"
|
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db"
|
||||||
pure opts
|
pure opts
|
||||||
|
|
||||||
|
welcomeMessage :: String
|
||||||
|
welcomeMessage = "Hello! I am a simple squaring bot.\nIf you send me a number, I will calculate its square"
|
||||||
|
|
||||||
mySquaringBot :: User -> ChatController -> IO ()
|
mySquaringBot :: User -> ChatController -> IO ()
|
||||||
mySquaringBot _user cc = do
|
mySquaringBot _user cc = do
|
||||||
initializeBotAddress cc
|
initializeBotAddress cc
|
||||||
|
@ -41,14 +44,13 @@ mySquaringBot _user cc = do
|
||||||
case resp of
|
case resp of
|
||||||
CRContactConnected _ contact _ -> do
|
CRContactConnected _ contact _ -> do
|
||||||
contactConnected contact
|
contactConnected contact
|
||||||
void . sendMsg contact $ "Hello! I am a simple squaring bot - if you send me a number, I will calculate its square"
|
sendMessage cc contact welcomeMessage
|
||||||
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content}) -> do
|
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do
|
||||||
let msg = T.unpack $ ciContentToText content
|
let msg = T.unpack $ ciContentToText mc
|
||||||
number_ = readMaybe msg :: Maybe Integer
|
number_ = readMaybe msg :: Maybe Integer
|
||||||
void . sendMsg contact $ case number_ of
|
sendMessage cc contact $ case number_ of
|
||||||
Nothing -> "\"" <> msg <> "\" is not a number"
|
|
||||||
Just n -> msg <> " * " <> msg <> " = " <> show (n * n)
|
Just n -> msg <> " * " <> msg <> " = " <> show (n * n)
|
||||||
|
_ -> "\"" <> msg <> "\" is not a number"
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
where
|
where
|
||||||
sendMsg Contact {contactId} msg = sendChatCmd cc $ "/_send @" <> show contactId <> " text " <> msg
|
|
||||||
contactConnected Contact {localDisplayName} = putStrLn $ T.unpack localDisplayName <> " connected"
|
contactConnected Contact {localDisplayName} = putStrLn $ T.unpack localDisplayName <> " connected"
|
||||||
|
|
13
apps/simplex-bot-advanced/README.md
Normal file
13
apps/simplex-bot-advanced/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Advanced SimpleX Chat bot example
|
||||||
|
|
||||||
|
In most cases [a simple REPL bot](../simplex-bot/) is sufficient, but in cases you want to program more advanced communication scenarios you may take a more complex event-based approach, as in this example.
|
||||||
|
|
||||||
|
Event-based approach allows you:
|
||||||
|
|
||||||
|
- decide whether to connect to a user or not depending on any factors, e.g. user display name.
|
||||||
|
- disconnect from users who send too many messages or send messages that bot finds inappropriate.
|
||||||
|
- react to message deletions and editing.
|
||||||
|
- process reply messages differently, taking the original message into account.
|
||||||
|
- process and send images and voice messages.
|
||||||
|
- create groups of users, e.g. to connect 2 users.
|
||||||
|
- etc.
|
|
@ -14,11 +14,14 @@ main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
opts <- welcomeGetOpts
|
opts <- welcomeGetOpts
|
||||||
simplexChatCore terminalChatConfig opts Nothing $
|
simplexChatCore terminalChatConfig opts Nothing $
|
||||||
chatBotRepl "Hello! I am a simple squaring bot - if you send me a number, I will calculate its square" $ \msg ->
|
chatBotRepl welcomeMessage $ \_contact msg ->
|
||||||
case readMaybe msg :: Maybe Integer of
|
pure $ case readMaybe msg :: Maybe Integer of
|
||||||
Just n -> msg <> " * " <> msg <> " = " <> show (n * n)
|
Just n -> msg <> " * " <> msg <> " = " <> show (n * n)
|
||||||
_ -> "\"" <> msg <> "\" is not a number"
|
_ -> "\"" <> msg <> "\" is not a number"
|
||||||
|
|
||||||
|
welcomeMessage :: String
|
||||||
|
welcomeMessage = "Hello! I am a simple squaring bot.\nIf you send me a number, I will calculate its square"
|
||||||
|
|
||||||
welcomeGetOpts :: IO ChatOpts
|
welcomeGetOpts :: IO ChatOpts
|
||||||
welcomeGetOpts = do
|
welcomeGetOpts = do
|
||||||
appDir <- getAppUserDataDirectory "simplex"
|
appDir <- getAppUserDataDirectory "simplex"
|
||||||
|
|
17
apps/simplex-bot/README.md
Normal file
17
apps/simplex-bot/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Simple SimpleX Chat bot example
|
||||||
|
|
||||||
|
This chat bot is a Haskell implementation of a REPL chat bot.
|
||||||
|
|
||||||
|
All you have to do to create your bot based on this example is to provide a welcome message for connecting users and a function of type `Contact -> String -> IO String`. This function should transform the sent message into a reply message, ignoring any system messages related to preferences and user profile changes.
|
||||||
|
|
||||||
|
This bot example calculates the square of the number that is sent to it, but you can program it to do other things, simply by changing REPL function:
|
||||||
|
|
||||||
|
- a more advanced calculator (e.g., based on [this one](https://github.com/jonathanknowles/haskell-calculator)).
|
||||||
|
- translation to/from any language.
|
||||||
|
- lookup of market quotes.
|
||||||
|
- search of the information.
|
||||||
|
- AI-powered dialogue – the bot can maintain any conversation state based on the contact.
|
||||||
|
- provide any other online service via chat UI.
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
Please share any bots you create with us, we will add to this page and can host them if you like!
|
74
apps/simplex-broadcast-bot/Main.hs
Normal file
74
apps/simplex-broadcast-bot/Main.hs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Control.Concurrent.Async
|
||||||
|
import Control.Concurrent.STM
|
||||||
|
import Control.Monad.Reader
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Options
|
||||||
|
import Simplex.Chat.Bot
|
||||||
|
import Simplex.Chat.Controller
|
||||||
|
import Simplex.Chat.Core
|
||||||
|
import Simplex.Chat.Messages
|
||||||
|
import Simplex.Chat.Options
|
||||||
|
import Simplex.Chat.Protocol (MsgContent (..))
|
||||||
|
import Simplex.Chat.Terminal (terminalChatConfig)
|
||||||
|
import Simplex.Chat.Types
|
||||||
|
import System.Directory (getAppUserDataDirectory)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
opts@BroadcastBotOpts {chatOptions} <- welcomeGetOpts
|
||||||
|
simplexChatCore terminalChatConfig chatOptions Nothing $ broadcastBot opts
|
||||||
|
|
||||||
|
welcomeGetOpts :: IO BroadcastBotOpts
|
||||||
|
welcomeGetOpts = do
|
||||||
|
appDir <- getAppUserDataDirectory "simplex"
|
||||||
|
opts@BroadcastBotOpts {chatOptions = ChatOpts {dbFilePrefix}} <- getBroadcastBotOpts appDir "simplex_status_bot"
|
||||||
|
putStrLn $ "SimpleX Chat Bot v" ++ versionNumber
|
||||||
|
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db"
|
||||||
|
pure opts
|
||||||
|
|
||||||
|
broadcastBot :: BroadcastBotOpts -> User -> ChatController -> IO ()
|
||||||
|
broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _user cc = do
|
||||||
|
initializeBotAddress cc
|
||||||
|
race_ (forever $ void getLine) . forever $ do
|
||||||
|
(_, resp) <- atomically . readTBQueue $ outputQ cc
|
||||||
|
case resp of
|
||||||
|
CRContactConnected _ ct _ -> do
|
||||||
|
contactConnected ct
|
||||||
|
sendMessage cc ct welcomeMessage
|
||||||
|
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc})
|
||||||
|
| publisher `elem` publishers ->
|
||||||
|
if allowContent mc
|
||||||
|
then do
|
||||||
|
sendChatCmd cc "/contacts" >>= \case
|
||||||
|
CRContactsList _ cts -> do
|
||||||
|
let cts' = filter broadcastTo cts
|
||||||
|
forM_ cts' $ \ct' -> sendComposedMessage cc ct' Nothing mc
|
||||||
|
sendReply $ "Forwarded to " <> show (length cts') <> " contact(s)"
|
||||||
|
r -> putStrLn $ "Error getting contacts list: " <> show r
|
||||||
|
else sendReply "!1 Message is not supported!"
|
||||||
|
| otherwise -> do
|
||||||
|
sendReply prohibitedMessage
|
||||||
|
deleteMessage cc ct $ chatItemId' ci
|
||||||
|
where
|
||||||
|
sendReply = sendComposedMessage cc ct (Just $ chatItemId' ci) . textMsgContent
|
||||||
|
publisher = Publisher {contactId = contactId' ct, localDisplayName = localDisplayName' ct}
|
||||||
|
allowContent = \case
|
||||||
|
MCText _ -> True
|
||||||
|
MCLink {} -> True
|
||||||
|
MCImage {} -> True
|
||||||
|
_ -> False
|
||||||
|
broadcastTo ct'@Contact {activeConn = conn@Connection {connStatus}} =
|
||||||
|
(connStatus == ConnSndReady || connStatus == ConnReady)
|
||||||
|
&& not (connDisabled conn)
|
||||||
|
&& contactId' ct' /= contactId' ct
|
||||||
|
_ -> pure ()
|
||||||
|
where
|
||||||
|
contactConnected ct = putStrLn $ T.unpack (localDisplayName' ct) <> " connected"
|
97
apps/simplex-broadcast-bot/Options.hs
Normal file
97
apps/simplex-broadcast-bot/Options.hs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{-# LANGUAGE ApplicativeDo #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Options where
|
||||||
|
|
||||||
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||||
|
import Data.Int (Int64)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Data.Text.Encoding (encodeUtf8)
|
||||||
|
import Options.Applicative
|
||||||
|
import Simplex.Chat.Controller (updateStr, versionNumber, versionString)
|
||||||
|
import Simplex.Chat.Options
|
||||||
|
import Simplex.Messaging.Parsers (parseAll)
|
||||||
|
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||||
|
|
||||||
|
data Publisher = Publisher
|
||||||
|
{ contactId :: Int64,
|
||||||
|
localDisplayName :: Text
|
||||||
|
}
|
||||||
|
deriving (Eq)
|
||||||
|
|
||||||
|
data BroadcastBotOpts = BroadcastBotOpts
|
||||||
|
{ chatOptions :: ChatOpts,
|
||||||
|
publishers :: [Publisher],
|
||||||
|
welcomeMessage :: String,
|
||||||
|
prohibitedMessage :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultWelcomeMessage :: [Publisher] -> String
|
||||||
|
defaultWelcomeMessage ps = "Hello! I am a broadcast bot.\nI broadcast messages to all connected users from " <> publisherNames ps <> "."
|
||||||
|
|
||||||
|
defaultProhibitedMessage :: [Publisher] -> String
|
||||||
|
defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " <> publisherNames ps <> ". Your message is deleted."
|
||||||
|
|
||||||
|
publisherNames :: [Publisher] -> String
|
||||||
|
publisherNames = T.unpack . T.intercalate ", " . map (("@" <>) . localDisplayName)
|
||||||
|
|
||||||
|
broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts
|
||||||
|
broadcastBotOpts appDir defaultDbFileName = do
|
||||||
|
chatOptions <- chatOptsP appDir defaultDbFileName
|
||||||
|
publishers <-
|
||||||
|
option
|
||||||
|
parsePublishers
|
||||||
|
( long "publishers"
|
||||||
|
<> metavar "PUBLISHERS"
|
||||||
|
<> help "Comma-separated list of publishers in the format CONTACT_ID:DISPLAY_NAME whose messages will be broadcasted"
|
||||||
|
<> value []
|
||||||
|
)
|
||||||
|
welcomeMessage_ <-
|
||||||
|
optional $
|
||||||
|
strOption
|
||||||
|
( long "welcome"
|
||||||
|
<> metavar "WELCOME"
|
||||||
|
<> help "Welcome message to be sent to all connecting users (default message will list allowed publishers)"
|
||||||
|
)
|
||||||
|
prohibitedMessage_ <-
|
||||||
|
optional $
|
||||||
|
strOption
|
||||||
|
( long "prohibited"
|
||||||
|
<> metavar "PROHIBITED"
|
||||||
|
<> help "Reply to non-publishers who try to send messages (default reply will list allowed publishers)"
|
||||||
|
<> showDefault
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
BroadcastBotOpts
|
||||||
|
{ chatOptions,
|
||||||
|
publishers,
|
||||||
|
welcomeMessage = fromMaybe (defaultWelcomeMessage publishers) welcomeMessage_,
|
||||||
|
prohibitedMessage = fromMaybe (defaultProhibitedMessage publishers) prohibitedMessage_
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePublishers :: ReadM [Publisher]
|
||||||
|
parsePublishers = eitherReader $ parseAll publishersP . encodeUtf8 . T.pack
|
||||||
|
|
||||||
|
publishersP :: A.Parser [Publisher]
|
||||||
|
publishersP = publisherP `A.sepBy1` A.char ','
|
||||||
|
where
|
||||||
|
publisherP = do
|
||||||
|
contactId <- A.decimal <* A.char ':'
|
||||||
|
localDisplayName <- safeDecodeUtf8 <$> A.takeTill (A.inClass ", ")
|
||||||
|
pure Publisher {contactId, localDisplayName}
|
||||||
|
|
||||||
|
getBroadcastBotOpts :: FilePath -> FilePath -> IO BroadcastBotOpts
|
||||||
|
getBroadcastBotOpts appDir defaultDbFileName =
|
||||||
|
execParser $
|
||||||
|
info
|
||||||
|
(helper <*> versionOption <*> broadcastBotOpts appDir defaultDbFileName)
|
||||||
|
(header versionStr <> fullDesc <> progDesc "Start chat bot with DB_FILE file and use SERVER as SMP server")
|
||||||
|
where
|
||||||
|
versionStr = versionString versionNumber
|
||||||
|
versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version")
|
||||||
|
versionAndUpdate = versionStr <> "\n" <> updateStr
|
9
apps/simplex-broadcast-bot/README.md
Normal file
9
apps/simplex-broadcast-bot/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# SimpleX broadcast bot
|
||||||
|
|
||||||
|
The bot allows anybody to connect to it and re-broadcasts all messages received from specified users configured via CLI options (publishers) to all users.
|
||||||
|
|
||||||
|
Welcome message and reply to the users who are not set as publishers can also be configured via CLI options.
|
||||||
|
|
||||||
|
It's a poor man's feed, until we have a better support for feeds in SimpleX Chat.
|
||||||
|
|
||||||
|
We use it to broadcast status notifications for SimpleX Chat servers when we do any maintenance or in case they become unavailable.
|
3
apps/simplex-chat/README.md
Normal file
3
apps/simplex-chat/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# SimpleX Chat CLI app
|
||||||
|
|
||||||
|
See [repo REAMDE](../../README.md#zap-quick-installation-of-a-terminal-app) for installation and usage instructions.
|
|
@ -88,6 +88,14 @@ executables:
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -threaded
|
- -threaded
|
||||||
|
|
||||||
|
simplex-broadcast-bot:
|
||||||
|
source-dirs: apps/simplex-broadcast-bot
|
||||||
|
main: Main.hs
|
||||||
|
dependencies:
|
||||||
|
- simplex-chat
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
simplex-chat-test:
|
simplex-chat-test:
|
||||||
source-dirs: tests
|
source-dirs: tests
|
||||||
|
|
|
@ -235,6 +235,54 @@ executable simplex-bot-advanced
|
||||||
if flag(swift)
|
if flag(swift)
|
||||||
cpp-options: -DswiftJSON
|
cpp-options: -DswiftJSON
|
||||||
|
|
||||||
|
executable simplex-broadcast-bot
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules:
|
||||||
|
Options
|
||||||
|
Paths_simplex_chat
|
||||||
|
hs-source-dirs:
|
||||||
|
apps/simplex-broadcast-bot
|
||||||
|
ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||||
|
build-depends:
|
||||||
|
aeson ==2.0.*
|
||||||
|
, ansi-terminal >=0.10 && <0.12
|
||||||
|
, async ==2.2.*
|
||||||
|
, attoparsec ==0.14.*
|
||||||
|
, base >=4.7 && <5
|
||||||
|
, base64-bytestring >=1.0 && <1.3
|
||||||
|
, bytestring ==0.10.*
|
||||||
|
, composition ==1.0.*
|
||||||
|
, containers ==0.6.*
|
||||||
|
, cryptonite >=0.27 && <0.30
|
||||||
|
, direct-sqlcipher ==2.3.*
|
||||||
|
, directory ==1.3.*
|
||||||
|
, email-validate ==2.3.*
|
||||||
|
, exceptions ==0.10.*
|
||||||
|
, filepath ==1.4.*
|
||||||
|
, http-types ==0.12.*
|
||||||
|
, mtl ==2.2.*
|
||||||
|
, network >=3.1.2.7 && <3.2
|
||||||
|
, optparse-applicative >=0.15 && <0.17
|
||||||
|
, process ==1.6.*
|
||||||
|
, random >=1.1 && <1.3
|
||||||
|
, record-hasfield ==1.0.*
|
||||||
|
, simple-logger ==0.1.*
|
||||||
|
, simplex-chat
|
||||||
|
, simplexmq >=3.4
|
||||||
|
, socks ==0.6.*
|
||||||
|
, sqlcipher-simple ==0.4.*
|
||||||
|
, stm ==2.5.*
|
||||||
|
, template-haskell ==2.16.*
|
||||||
|
, terminal ==0.2.*
|
||||||
|
, text ==1.2.*
|
||||||
|
, time ==1.9.*
|
||||||
|
, unliftio ==0.2.*
|
||||||
|
, unliftio-core ==0.2.*
|
||||||
|
, zip ==1.7.*
|
||||||
|
default-language: Haskell2010
|
||||||
|
if flag(swift)
|
||||||
|
cpp-options: -DswiftJSON
|
||||||
|
|
||||||
executable simplex-chat
|
executable simplex-chat
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
other-modules:
|
other-modules:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
{-# LANGUAGE GADTs #-}
|
{-# LANGUAGE GADTs #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
@ -8,17 +9,21 @@ module Simplex.Chat.Bot where
|
||||||
import Control.Concurrent.Async
|
import Control.Concurrent.Async
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Simplex.Chat.Controller
|
import Simplex.Chat.Controller
|
||||||
import Simplex.Chat.Core
|
import Simplex.Chat.Core
|
||||||
import Simplex.Chat.Messages
|
import Simplex.Chat.Messages
|
||||||
|
import Simplex.Chat.Protocol (MsgContent (..))
|
||||||
import Simplex.Chat.Store
|
import Simplex.Chat.Store
|
||||||
import Simplex.Chat.Types (Contact (..), User (..))
|
import Simplex.Chat.Types (Contact (..), IsContact (..), User (..))
|
||||||
import Simplex.Messaging.Encoding.String (strEncode)
|
import Simplex.Messaging.Encoding.String (strEncode)
|
||||||
|
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||||
import System.Exit (exitFailure)
|
import System.Exit (exitFailure)
|
||||||
|
|
||||||
chatBotRepl :: String -> (String -> String) -> User -> ChatController -> IO ()
|
chatBotRepl :: String -> (Contact -> String -> IO String) -> User -> ChatController -> IO ()
|
||||||
chatBotRepl welcome answer _user cc = do
|
chatBotRepl welcome answer _user cc = do
|
||||||
initializeBotAddress cc
|
initializeBotAddress cc
|
||||||
race_ (forever $ void getLine) . forever $ do
|
race_ (forever $ void getLine) . forever $ do
|
||||||
|
@ -27,9 +32,9 @@ chatBotRepl welcome answer _user cc = do
|
||||||
CRContactConnected _ contact _ -> do
|
CRContactConnected _ contact _ -> do
|
||||||
contactConnected contact
|
contactConnected contact
|
||||||
void $ sendMsg contact welcome
|
void $ sendMsg contact welcome
|
||||||
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content}) -> do
|
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do
|
||||||
let msg = T.unpack $ ciContentToText content
|
let msg = T.unpack $ ciContentToText mc
|
||||||
void . sendMsg contact $ answer msg
|
void $ sendMsg contact =<< answer contact msg
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
where
|
where
|
||||||
sendMsg Contact {contactId} msg = sendChatCmd cc $ "/_send @" <> show contactId <> " text " <> msg
|
sendMsg Contact {contactId} msg = sendChatCmd cc $ "/_send @" <> show contactId <> " text " <> msg
|
||||||
|
@ -49,3 +54,33 @@ initializeBotAddress cc = do
|
||||||
showBotAddress uri = do
|
showBotAddress uri = do
|
||||||
putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri)
|
putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri)
|
||||||
void $ sendChatCmd cc "/auto_accept on"
|
void $ sendChatCmd cc "/auto_accept on"
|
||||||
|
|
||||||
|
sendMessage :: ChatController -> Contact -> String -> IO ()
|
||||||
|
sendMessage cc ct = sendComposedMessage cc ct Nothing . textMsgContent
|
||||||
|
|
||||||
|
sendComposedMessage :: ChatController -> Contact -> Maybe ChatItemId -> MsgContent -> IO ()
|
||||||
|
sendComposedMessage cc ct quotedItemId msgContent = do
|
||||||
|
let cm = ComposedMessage {filePath = Nothing, quotedItemId, msgContent}
|
||||||
|
sendChatCmd cc ("/_send @" <> show (contactId' ct) <> " json " <> jsonEncode cm) >>= \case
|
||||||
|
CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to " <> contactInfo ct
|
||||||
|
r -> putStrLn $ "unexpected send message response: " <> show r
|
||||||
|
where
|
||||||
|
jsonEncode = T.unpack . safeDecodeUtf8 . LB.toStrict . J.encode
|
||||||
|
|
||||||
|
deleteMessage :: ChatController -> Contact -> ChatItemId -> IO ()
|
||||||
|
deleteMessage cc ct chatItemId = do
|
||||||
|
let cmd = "/_delete item @" <> show (contactId' ct) <> " " <> show chatItemId <> " internal"
|
||||||
|
sendChatCmd cc cmd >>= \case
|
||||||
|
CRChatItemDeleted {} -> printLog cc CLLInfo $ "deleted message from " <> contactInfo ct
|
||||||
|
r -> putStrLn $ "unexpected delete message response: " <> show r
|
||||||
|
|
||||||
|
textMsgContent :: String -> MsgContent
|
||||||
|
textMsgContent = MCText . T.pack
|
||||||
|
|
||||||
|
printLog :: ChatController -> ChatLogLevel -> String -> IO ()
|
||||||
|
printLog cc level s
|
||||||
|
| logLevel (config cc) <= level = putStrLn s
|
||||||
|
| otherwise = pure ()
|
||||||
|
|
||||||
|
contactInfo :: Contact -> String
|
||||||
|
contactInfo Contact {contactId, localDisplayName} = T.unpack localDisplayName <> " (" <> show contactId <> ")"
|
||||||
|
|
|
@ -572,6 +572,10 @@ data ComposedMessage = ComposedMessage
|
||||||
}
|
}
|
||||||
deriving (Show, Generic, FromJSON)
|
deriving (Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON ComposedMessage where
|
||||||
|
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
data NtfMsgInfo = NtfMsgInfo {msgTs :: UTCTime, msgFlags :: MsgFlags}
|
data NtfMsgInfo = NtfMsgInfo {msgTs :: UTCTime, msgFlags :: MsgFlags}
|
||||||
deriving (Show, Generic)
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
module Simplex.Chat.Options
|
module Simplex.Chat.Options
|
||||||
( ChatOpts (..),
|
( ChatOpts (..),
|
||||||
|
chatOptsP,
|
||||||
getChatOpts,
|
getChatOpts,
|
||||||
smpServersP,
|
smpServersP,
|
||||||
fullNetworkConfig,
|
fullNetworkConfig,
|
||||||
|
@ -43,8 +44,8 @@ data ChatOpts = ChatOpts
|
||||||
maintenance :: Bool
|
maintenance :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
chatOpts :: FilePath -> FilePath -> Parser ChatOpts
|
chatOptsP :: FilePath -> FilePath -> Parser ChatOpts
|
||||||
chatOpts appDir defaultDbFileName = do
|
chatOptsP appDir defaultDbFileName = do
|
||||||
dbFilePrefix <-
|
dbFilePrefix <-
|
||||||
strOption
|
strOption
|
||||||
( long "database"
|
( long "database"
|
||||||
|
@ -229,7 +230,7 @@ getChatOpts :: FilePath -> FilePath -> IO ChatOpts
|
||||||
getChatOpts appDir defaultDbFileName =
|
getChatOpts appDir defaultDbFileName =
|
||||||
execParser $
|
execParser $
|
||||||
info
|
info
|
||||||
(helper <*> versionOption <*> chatOpts appDir defaultDbFileName)
|
(helper <*> versionOption <*> chatOptsP appDir defaultDbFileName)
|
||||||
(header versionStr <> fullDesc <> progDesc "Start chat with DB_FILE file and use SERVER as SMP server")
|
(header versionStr <> fullDesc <> progDesc "Start chat with DB_FILE file and use SERVER as SMP server")
|
||||||
where
|
where
|
||||||
versionStr = versionString versionNumber
|
versionStr = versionString versionNumber
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue