summaryrefslogtreecommitdiff
path: root/Omni/Agent
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-13 00:44:27 -0500
committerBen Sima <ben@bensima.com>2025-12-13 00:44:27 -0500
commit6bbf81f41c318a4200156e58707c807b230a601c (patch)
tree68bb23ec635f6657ba6cb4c6804cfef44d90a4d5 /Omni/Agent
parent4ff40843e7a6801b7785bfff7f4e9e8fff4e27d4 (diff)
telegram: add group chat support
- Only respond in groups when @mentioned or replied to - Add ChatType to TelegramMessage (private/group/supergroup/channel) - Add getMe API call to fetch bot username on startup - Add shouldRespondInGroup helper function
Diffstat (limited to 'Omni/Agent')
-rw-r--r--Omni/Agent/Telegram.hs43
-rw-r--r--Omni/Agent/Telegram/Types.hs47
2 files changed, 86 insertions, 4 deletions
diff --git a/Omni/Agent/Telegram.hs b/Omni/Agent/Telegram.hs
index c55dc5a..ffad4c7 100644
--- a/Omni/Agent/Telegram.hs
+++ b/Omni/Agent/Telegram.hs
@@ -184,6 +184,29 @@ getUpdates cfg offset = do
_ -> pure []
_ -> pure []
+getBotUsername :: Types.TelegramConfig -> IO (Maybe Text)
+getBotUsername cfg = do
+ let url =
+ Text.unpack (Types.tgApiBaseUrl cfg)
+ <> "/bot"
+ <> Text.unpack (Types.tgBotToken cfg)
+ <> "/getMe"
+ result <-
+ try <| do
+ req <- HTTP.parseRequest url
+ HTTP.httpLBS req
+ case result of
+ Left (_ :: SomeException) -> pure Nothing
+ Right response -> do
+ let body = HTTP.getResponseBody response
+ case Aeson.decode body of
+ Just (Aeson.Object obj) -> case KeyMap.lookup "result" obj of
+ Just (Aeson.Object userObj) -> case KeyMap.lookup "username" userObj of
+ Just (Aeson.String username) -> pure (Just username)
+ _ -> pure Nothing
+ _ -> pure Nothing
+ _ -> pure Nothing
+
sendMessage :: Types.TelegramConfig -> Int -> Text -> IO ()
sendMessage cfg chatId text = do
let url =
@@ -231,6 +254,12 @@ runTelegramBot tgConfig provider = do
putText "Starting Telegram bot..."
offsetVar <- newTVarIO 0
+ botUsername <- getBotUsername tgConfig
+ case botUsername of
+ Nothing -> putText "Warning: could not get bot username, group mentions may not work"
+ Just name -> putText <| "Bot username: @" <> name
+ let botName = fromMaybe "bot" botUsername
+
_ <- forkIO (reminderLoop tgConfig)
putText "Reminder loop started (checking every 5 minutes)"
@@ -249,28 +278,34 @@ runTelegramBot tgConfig provider = do
messages <- getUpdates tgConfig offset
forM_ messages <| \msg -> do
atomically (writeTVar offsetVar (Types.tmUpdateId msg + 1))
- handleMessage tgConfig provider engineCfg msg
+ handleMessage tgConfig provider engineCfg botName msg
when (null messages) <| threadDelay 1000000
handleMessage ::
Types.TelegramConfig ->
Provider.Provider ->
Engine.EngineConfig ->
+ Text ->
Types.TelegramMessage ->
IO ()
-handleMessage tgConfig provider engineCfg msg = do
+handleMessage tgConfig provider engineCfg botUsername msg = do
let userName =
Types.tmUserFirstName msg
<> maybe "" (" " <>) (Types.tmUserLastName msg)
chatId = Types.tmChatId msg
usrId = Types.tmUserId msg
+ unless (Types.shouldRespondInGroup botUsername msg) <| do
+ when (Types.isGroupChat msg)
+ <| putText
+ <| "Ignoring group message (not mentioned): "
+ <> Text.take 50 (Types.tmText msg)
+
unless (Types.isUserAllowed tgConfig usrId) <| do
putText <| "Unauthorized user: " <> tshow usrId <> " (" <> userName <> ")"
sendMessage tgConfig chatId "sorry, you're not authorized to use this bot."
- pure ()
- when (Types.isUserAllowed tgConfig usrId) <| do
+ when (Types.shouldRespondInGroup botUsername msg && Types.isUserAllowed tgConfig usrId) <| do
sendTypingAction tgConfig chatId
user <- Memory.getOrCreateUserByTelegramId usrId userName
diff --git a/Omni/Agent/Telegram/Types.hs b/Omni/Agent/Telegram/Types.hs
index 2db6a52..d240786 100644
--- a/Omni/Agent/Telegram/Types.hs
+++ b/Omni/Agent/Telegram/Types.hs
@@ -19,6 +19,7 @@ module Omni.Agent.Telegram.Types
TelegramPhoto (..),
TelegramVoice (..),
TelegramReplyMessage (..),
+ ChatType (..),
-- * Parsing
parseUpdate,
@@ -31,6 +32,8 @@ module Omni.Agent.Telegram.Types
-- * Utilities
isPdf,
isSupportedVoiceFormat,
+ isGroupChat,
+ shouldRespondInGroup,
-- * Testing
main,
@@ -81,6 +84,7 @@ test =
TelegramMessage
{ tmUpdateId = 123,
tmChatId = 456,
+ tmChatType = Private,
tmUserId = 789,
tmUserFirstName = "Test",
tmUserLastName = Just "User",
@@ -319,9 +323,28 @@ instance Aeson.FromJSON TelegramReplyMessage where
<*> (v .:? "from_last_name")
<*> (v .:? "text" .!= "")
+data ChatType = Private | Group | Supergroup | Channel
+ deriving (Show, Eq, Generic)
+
+instance Aeson.ToJSON ChatType where
+ toJSON Private = Aeson.String "private"
+ toJSON Group = Aeson.String "group"
+ toJSON Supergroup = Aeson.String "supergroup"
+ toJSON Channel = Aeson.String "channel"
+
+instance Aeson.FromJSON ChatType where
+ parseJSON = Aeson.withText "ChatType" parseChatType
+ where
+ parseChatType "private" = pure Private
+ parseChatType "group" = pure Group
+ parseChatType "supergroup" = pure Supergroup
+ parseChatType "channel" = pure Channel
+ parseChatType _ = pure Private
+
data TelegramMessage = TelegramMessage
{ tmUpdateId :: Int,
tmChatId :: Int,
+ tmChatType :: ChatType,
tmUserId :: Int,
tmUserFirstName :: Text,
tmUserLastName :: Maybe Text,
@@ -338,6 +361,7 @@ instance Aeson.ToJSON TelegramMessage where
Aeson.object
[ "update_id" .= tmUpdateId m,
"chat_id" .= tmChatId m,
+ "chat_type" .= tmChatType m,
"user_id" .= tmUserId m,
"user_first_name" .= tmUserFirstName m,
"user_last_name" .= tmUserLastName m,
@@ -353,6 +377,7 @@ instance Aeson.FromJSON TelegramMessage where
Aeson.withObject "TelegramMessage" <| \v ->
(TelegramMessage </ (v .: "update_id"))
<*> (v .: "chat_id")
+ <*> (v .:? "chat_type" .!= Private)
<*> (v .: "user_id")
<*> (v .: "user_first_name")
<*> (v .:? "user_last_name")
@@ -385,6 +410,12 @@ parseUpdate val = do
chatId <- case KeyMap.lookup "id" chatObj of
Just (Aeson.Number n) -> Just (round n)
_ -> Nothing
+ let chatType = case KeyMap.lookup "type" chatObj of
+ Just (Aeson.String "private") -> Private
+ Just (Aeson.String "group") -> Group
+ Just (Aeson.String "supergroup") -> Supergroup
+ Just (Aeson.String "channel") -> Channel
+ _ -> Private
Aeson.Object fromObj <- KeyMap.lookup "from" msgObj
userId <- case KeyMap.lookup "id" fromObj of
Just (Aeson.Number n) -> Just (round n)
@@ -419,6 +450,7 @@ parseUpdate val = do
TelegramMessage
{ tmUpdateId = updateId,
tmChatId = chatId,
+ tmChatType = chatType,
tmUserId = userId,
tmUserFirstName = firstName,
tmUserLastName = lastName,
@@ -547,3 +579,18 @@ isSupportedVoiceFormat voice =
Just "audio/x-opus+ogg" -> True
Nothing -> True
_ -> False
+
+isGroupChat :: TelegramMessage -> Bool
+isGroupChat msg = tmChatType msg `elem` [Group, Supergroup]
+
+shouldRespondInGroup :: Text -> TelegramMessage -> Bool
+shouldRespondInGroup botUsername msg
+ | not (isGroupChat msg) = True
+ | isMentioned = True
+ | isReplyToBot = True
+ | otherwise = False
+ where
+ msgText = Text.toLower (tmText msg)
+ mention = "@" <> Text.toLower botUsername
+ isMentioned = mention `Text.isInfixOf` msgText
+ isReplyToBot = isJust (tmReplyTo msg)