From cb355684b66baac876f41b95f9dd1c69b7cae1dc Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 15 Nov 2022 23:06:44 +0100 Subject: [PATCH] (#170) Funogram: enable message thread filtering and sending --- Emulsion.Settings/Settings.fs | 3 + Emulsion.Telegram/Funogram.fs | 32 +++++++--- Emulsion.Tests/SettingsTests.fs | 1 + Emulsion.Tests/Telegram/FunogramTests.fs | 74 +++++++++++++++++++++++- README.md | 2 + emulsion.example.json | 3 +- 6 files changed, 105 insertions(+), 10 deletions(-) diff --git a/Emulsion.Settings/Settings.fs b/Emulsion.Settings/Settings.fs index 511f23e8..3c720692 100644 --- a/Emulsion.Settings/Settings.fs +++ b/Emulsion.Settings/Settings.fs @@ -22,6 +22,7 @@ type XmppSettings = { type TelegramSettings = { Token: string GroupId: int64 + MessageThreadId: int64 option } type LogSettings = { @@ -63,6 +64,7 @@ let private readTimeSpan defaultVal key section = |> Option.defaultValue defaultVal let read (config : IConfiguration) : EmulsionSettings = + let int64Opt: string -> int64 option = Option.ofObj >> Option.map int64 let uint64OrDefault value ``default`` = value |> Option.ofObj @@ -83,6 +85,7 @@ let read (config : IConfiguration) : EmulsionSettings = let readTelegram (section : IConfigurationSection) = { Token = section["token"] GroupId = int64 section["groupId"] + MessageThreadId = int64Opt section["messageThreadId"] } let readLog(section: IConfigurationSection) = { Directory = section["directory"] diff --git a/Emulsion.Telegram/Funogram.fs b/Emulsion.Telegram/Funogram.fs index 10b5e7d5..7584373c 100644 --- a/Emulsion.Telegram/Funogram.fs +++ b/Emulsion.Telegram/Funogram.fs @@ -287,10 +287,15 @@ module MessageConverter = let messageText = text.Substring messageTextOffset Authored { author = authorName; text = messageText } + let (|ForumTopicCreatedMessage|_|) (m: FunogramMessage option) = + match m with + | Some m when Option.isSome m.ForumTopicCreated -> Some m + | _ -> None + let internal read (selfUserId: int64) (message: FunogramMessage, links: TelegramThreadLinks): ThreadMessage = let mainMessage = extractMessageContent message links.ContentLinks match message.ReplyToMessage with - | None -> { main = mainMessage; replyTo = None } + | None | ForumTopicCreatedMessage _ -> { main = mainMessage; replyTo = None } | Some replyTo -> let replyToMessage = if isSelfMessage selfUserId replyTo @@ -310,9 +315,14 @@ let private extractLinkData logger databaseSettings hostingSettings message = let internal processMessage (logger: ILogger) (databaseSettings: DatabaseSettings option) (hostingSettings: HostingSettings option) - (context: {| SelfUserId: int64; GroupId: int64 |}) + (context: {| SelfUserId: int64; GroupId: int64; MessageThreadId: int64 option |}) (message: FunogramMessage): Message option = - if context.GroupId = message.Chat.Id + let correctGroup = context.GroupId = message.Chat.Id + let correctThread = + match context.MessageThreadId with + | None -> true + | _ -> message.MessageThreadId = context.MessageThreadId + if correctGroup && correctThread then message |> extractLinkData logger databaseSettings hostingSettings @@ -324,12 +334,14 @@ let internal processMessage (logger: ILogger) let private updateArrived databaseSettings hostingSettings groupId + messageThreadId (logger: ILogger) onMessage (ctx: Bot.UpdateContext) = let readContext = {| SelfUserId = ctx.Me.Id GroupId = groupId + MessageThreadId = messageThreadId |} Bot.processCommands ctx [ fun ctx -> @@ -338,7 +350,7 @@ let private updateArrived databaseSettings logger.Information("Incoming Telegram message: {Message}", msg) match processMessage logger databaseSettings hostingSettings readContext msg with | Some m -> onMessage(TelegramMessage m) - | None -> logger.Warning "Message from unidentified source ignored" + | None -> () true | _ -> false ] |> ignore @@ -355,10 +367,11 @@ let sendGetFile (botConfig: BotConfig) (fileId: string): Async = async { } let sendMessage (settings: TelegramSettings) (botConfig: BotConfig) (OutgoingMessage content): Async = + let groupId = Int(int64 settings.GroupId) + let threadId = settings.MessageThreadId let sendHtmlMessage (groupId: ChatId) text = - Req.SendMessage.Make(chatId = groupId, text = text, parseMode = ParseMode.HTML) + Req.SendMessage.Make(chatId = groupId, text = text, ?messageThreadId = threadId, parseMode = ParseMode.HTML) - let groupId = Int(int64 settings.GroupId) let message = prepareHtmlMessage content async { let! result = send botConfig (sendHtmlMessage groupId message) @@ -373,5 +386,10 @@ let run (logger: ILogger) (botConfig: BotConfig) (onMessage: IncomingMessage -> unit): Async = Bot.startBot botConfig - (updateArrived databaseSettings hostingSettings telegramSettings.GroupId logger onMessage) + (updateArrived databaseSettings + hostingSettings + telegramSettings.GroupId + telegramSettings.MessageThreadId + logger + onMessage) None diff --git a/Emulsion.Tests/SettingsTests.fs b/Emulsion.Tests/SettingsTests.fs index 57b05769..686f1409 100644 --- a/Emulsion.Tests/SettingsTests.fs +++ b/Emulsion.Tests/SettingsTests.fs @@ -44,6 +44,7 @@ let private testConfiguration = { Telegram = { Token = "token" GroupId = testGroupId + MessageThreadId = None } Log = { Directory = "/tmp/" diff --git a/Emulsion.Tests/Telegram/FunogramTests.fs b/Emulsion.Tests/Telegram/FunogramTests.fs index 176572aa..8f809de9 100644 --- a/Emulsion.Tests/Telegram/FunogramTests.fs +++ b/Emulsion.Tests/Telegram/FunogramTests.fs @@ -547,12 +547,38 @@ module ReadMessageTests = readMessage reply ) + [] + let ``Reply to forum topic creation shouldn't be taken into account``(): unit = + let originalMessage = { + defaultMessage with + ForumTopicCreated = Some <| ForumTopicCreated.Create("Topic", 0L) + } + let replyMessage = createReplyMessage (Some replyingUser) (Some "text") originalMessage + Assert.Equal( + authoredTelegramMessage "@replyingUser" "text", + readMessage replyMessage + ) + module ProcessMessageTests = + let private processMessageOpt o = + processMessage Logger.None None None o + let private processMessage = - Funogram.processMessage Logger.None None None {| SelfUserId = selfUserId; GroupId = groupId |} + processMessageOpt {| SelfUserId = selfUserId; GroupId = groupId; MessageThreadId = None |} + + [] + let ``Message with correct group is not ignored``(): unit = + let message = { + createMessage (Some originalUser) (Some "test") with + Chat = Chat.Create( + id = groupId, + ``type`` = ChatType.SuperGroup + ) + } + Assert.True(Option.isSome <| processMessage message) [] - let messageFromOtherChatShouldBeIgnored(): unit = + let ``Message from other chat is ignored``(): unit = let message = { createMessage (Some originalUser) (Some "test") with Chat = Chat.Create( id = 0L, @@ -560,6 +586,50 @@ module ProcessMessageTests = ) } Assert.Equal(None, processMessage message) + [] + let ``Message from incorrect thread is ignored``(): unit = + let message = { + createMessage (Some originalUser) (Some "test") with + Chat = Chat.Create( + id = groupId, + ``type`` = ChatType.SuperGroup + ) + MessageThreadId = Some 123L + + } + Assert.Equal(None, processMessageOpt {| + SelfUserId = selfUserId + GroupId = groupId + MessageThreadId = Some 234L + |} message) + + let ``Message with any thread id is not ignored if thread id is not set``(): unit = + let message = { + createMessage (Some originalUser) (Some "test") with + Chat = Chat.Create( + id = groupId, + ``type`` = ChatType.SuperGroup + ) + MessageThreadId = Some 123L + } + Assert.True(Option.isSome <| processMessage message) + + let ``Message with correct thread id is not ignored``(): unit = + let threadId = 236L + let message = { + createMessage (Some originalUser) (Some "test") with + Chat = Chat.Create( + id = groupId, + ``type`` = ChatType.SuperGroup + ) + MessageThreadId = Some threadId + } + Assert.True(Option.isSome <| processMessageOpt {| + SelfUserId = selfUserId + GroupId = groupId + MessageThreadId = Some threadId + |} message) + module ProcessSendResultTests = [] let processResultShouldDoNothingOnOk(): unit = diff --git a/README.md b/README.md index 8755dc8d..eaab7e22 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ All the other settings are required, except the `database`, `hosting` and `fileC Note that `pingInterval` of `null` disables XMPP ping support. +`telegram.messageThreadId` allows to connect the bot to a particular message thread: any messages from the other threads will be ignored, and the bot will send its messages to the selected thread only. + ### Telegram Content Proxy There's Telegram content proxy support, for XMPP users to access Telegram content without directly opening links on t.me. diff --git a/emulsion.example.json b/emulsion.example.json index 0d064ede..23d4b970 100644 --- a/emulsion.example.json +++ b/emulsion.example.json @@ -12,7 +12,8 @@ }, "telegram": { "token": "999999999:aaaaaaaaaaaaaaaaaaaaaaaaa_777777777", - "groupId": 12312312312 + "groupId": 12312312312, + "messageThreadId": 123456 }, "log": { "directory": "./logs/"