From 9a83a4d00b2c541090964fbbcd643d273a8bac30 Mon Sep 17 00:00:00 2001 From: Ayrat Hudaygulov Date: Mon, 8 Jul 2024 11:01:50 +0100 Subject: [PATCH] even more message data (#24) --- src/VahterBanBot.Tests/ContainerTestBase.fs | 9 +-- src/VahterBanBot.Tests/MessageTests.fs | 38 +++++++++++ src/VahterBanBot.Tests/PingTests.fs | 16 ++--- .../VahterBanBot.Tests.fsproj | 1 + src/VahterBanBot/Bot.fs | 14 ++-- src/VahterBanBot/DB.fs | 39 +++-------- src/VahterBanBot/Types.fs | 65 ++++++++++--------- .../V4__even-more-messages-info.sql | 5 ++ 8 files changed, 107 insertions(+), 80 deletions(-) create mode 100644 src/VahterBanBot.Tests/MessageTests.fs create mode 100644 src/migrations/V4__even-more-messages-info.sql diff --git a/src/VahterBanBot.Tests/ContainerTestBase.fs b/src/VahterBanBot.Tests/ContainerTestBase.fs index 28d05ff..5e17395 100644 --- a/src/VahterBanBot.Tests/ContainerTestBase.fs +++ b/src/VahterBanBot.Tests/ContainerTestBase.fs @@ -12,6 +12,7 @@ open Npgsql open Telegram.Bot.Types open Testcontainers.PostgreSql open VahterBanBot.Tests.TgMessageUtils +open VahterBanBot.Types open Xunit open Dapper @@ -158,12 +159,12 @@ type VahterTestContainers() = Tg.chat(id = -42, username = "dotnetru") ] - member _.MessageExist(msg: Message) = task { + member _.TryGetDbMessage(msg: Message) = task { use conn = new NpgsqlConnection(publicConnectionString) //language=postgresql - let sql = "SELECT COUNT(*) FROM message WHERE chat_id = @chatId AND message_id = @messageId" - let! count = conn.QuerySingleAsync(sql, {| chatId = msg.Chat.Id; messageId = msg.MessageId |}) - return count = 1 + let sql = "SELECT * FROM message WHERE chat_id = @chatId AND message_id = @messageId" + let! dbMessage = conn.QueryAsync(sql, {| chatId = msg.Chat.Id; messageId = msg.MessageId |}) + return dbMessage |> Seq.tryHead } member _.MessageBanned(msg: Message) = task { diff --git a/src/VahterBanBot.Tests/MessageTests.fs b/src/VahterBanBot.Tests/MessageTests.fs new file mode 100644 index 0000000..bb8de53 --- /dev/null +++ b/src/VahterBanBot.Tests/MessageTests.fs @@ -0,0 +1,38 @@ +module VahterBanBot.Tests.MessageTests + +open System +open System.Net +open VahterBanBot.Types +open Telegram.Bot.Types +open Telegram.Bot.Types.Enums +open VahterBanBot.Tests.ContainerTestBase +open VahterBanBot.Tests.TgMessageUtils +open Xunit + +type MessageTests(fixture: VahterTestContainers) = + + [] + let ``All data from the message being saved`` () = task { + // record just a message with some additional data + let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0]) + msgUpdate.Message.Entities <- [| MessageEntity(Type = MessageEntityType.Code, Offset = 0, Length = 6) |] + msgUpdate.Message.Sticker <- Sticker(Type = StickerType.Mask, Width = 512, Height = 512, FileId = "sticker-id", FileUniqueId = "sticker-uid") + let! _ = fixture.SendMessage msgUpdate + + // assert that the message got recorded correctly + let! dbMsg = fixture.TryGetDbMessage msgUpdate.Message + Assert.True dbMsg.IsSome + + let msg = msgUpdate.Message + let date = DateTimeOffset(msg.Date).ToUnixTimeSeconds() + + Assert.Equal( + dbMsg.Value, + { chat_id = msgUpdate.Message.Chat.Id + message_id = msgUpdate.Message.MessageId + user_id = msgUpdate.Message.From.Id + text = msgUpdate.Message.Text + raw_message = $"""{{"chat": {{"id": -666, "type": "unknown", "username": "pro.hell"}}, "date": {date}, "from": {{"id": {msg.From.Id}, "is_bot": false, "first_name": "{msg.From.FirstName}"}}, "text": "{msg.Text}", "sticker": {{"type": "mask", "width": 512, "height": 512, "file_id": "sticker-id", "is_video": false, "is_animated": false, "file_unique_id": "sticker-uid"}}, "entities": [{{"type": "code", "length": 6, "offset": 0}}], "message_id": {msg.MessageId}}}""" + created_at = dbMsg.Value.created_at } + ) + } diff --git a/src/VahterBanBot.Tests/PingTests.fs b/src/VahterBanBot.Tests/PingTests.fs index cb5abd8..4456e0e 100644 --- a/src/VahterBanBot.Tests/PingTests.fs +++ b/src/VahterBanBot.Tests/PingTests.fs @@ -13,16 +13,16 @@ type PingTests(fixture: VahterTestContainers) = let msg = Tg.quickMsg(chat = fixture.ChatsToMonitor[0]) // assert that the message is not in the db - let! msgExist = fixture.MessageExist msg.Message - Assert.False msgExist + let! dbMsg = fixture.TryGetDbMessage msg.Message + Assert.False dbMsg.IsSome // send the message to the bot let! resp = fixture.SendMessage msg Assert.Equal(HttpStatusCode.OK, resp.StatusCode) // assert that the message is in the db - let! msgExist = fixture.MessageExist msg.Message - Assert.True msgExist + let! dbMsg = fixture.TryGetDbMessage msg.Message + Assert.True dbMsg.IsSome } [] @@ -31,16 +31,16 @@ type PingTests(fixture: VahterTestContainers) = let msg = Tg.quickMsg(chat = Tg.chat()) // assert that the message is not in the db - let! msgExist = fixture.MessageExist msg.Message - Assert.False msgExist + let! dbMsg = fixture.TryGetDbMessage msg.Message + Assert.False dbMsg.IsSome // send the message to the bot let! resp = fixture.SendMessage msg Assert.Equal(HttpStatusCode.OK, resp.StatusCode) // assert that the message is still not in the db - let! msgExist = fixture.MessageExist msg.Message - Assert.False msgExist + let! dbMsg = fixture.TryGetDbMessage msg.Message + Assert.False dbMsg.IsSome } interface IAssemblyFixture diff --git a/src/VahterBanBot.Tests/VahterBanBot.Tests.fsproj b/src/VahterBanBot.Tests/VahterBanBot.Tests.fsproj index bc1eaba..526b262 100644 --- a/src/VahterBanBot.Tests/VahterBanBot.Tests.fsproj +++ b/src/VahterBanBot.Tests/VahterBanBot.Tests.fsproj @@ -12,6 +12,7 @@ + diff --git a/src/VahterBanBot/Bot.fs b/src/VahterBanBot/Bot.fs index 7f4c891..9233be8 100644 --- a/src/VahterBanBot/Bot.fs +++ b/src/VahterBanBot/Bot.fs @@ -277,11 +277,11 @@ let banOnReply use _ = botActivity .StartActivity("deleteMsg") - .SetTag("msgId", msg.Message_Id) - .SetTag("chatId", msg.Chat_Id) - do! botClient.DeleteMessageAsync(ChatId(msg.Chat_Id), msg.Message_Id) + .SetTag("msgId", msg.message_id) + .SetTag("chatId", msg.chat_id) + do! botClient.DeleteMessageAsync(ChatId(msg.chat_id), msg.message_id) with e -> - logger.LogError ($"Failed to delete message {msg.Message_Id} from chat {msg.Chat_Id}", e) + logger.LogError ($"Failed to delete message {msg.message_id} from chat {msg.chat_id}", e) }) |> Task.WhenAll |> taskIgnore @@ -363,9 +363,9 @@ let unban let! user = DB.getUserById targetUserId if user.IsSome then - %banOnReplyActivity.SetTag("targetUsername", user.Value.Username) + %banOnReplyActivity.SetTag("targetUsername", user.Value.username) - let targetUsername = user |> Option.bind (fun u -> u.Username) + let targetUsername = user |> Option.bind (_.username) // try unban user in all monitored chats let! unbanResults = unbanInAllChats botConfig botClient targetUserId @@ -505,7 +505,7 @@ let onUpdate if isNull message || isNull message.From then logger.LogWarning "Received update without message" else - + // early return if we don't monitor this chat if not (botConfig.ChatsToMonitor.ContainsValue message.Chat.Id) then () diff --git a/src/VahterBanBot/DB.fs b/src/VahterBanBot/DB.fs index ed0df9a..1125651 100644 --- a/src/VahterBanBot/DB.fs +++ b/src/VahterBanBot/DB.fs @@ -17,21 +17,14 @@ let upsertUser (user: DbUser): Task = let sql = """ INSERT INTO "user" (id, username, created_at, updated_at) -VALUES (@id, @username, @createdAt, @updatedAt) +VALUES (@id, @username, @created_at, @updated_at) ON CONFLICT (id) DO UPDATE SET username = COALESCE("user".username, EXCLUDED.username), updated_at = GREATEST(EXCLUDED.updated_at, "user".updated_at) RETURNING *; """ - let! insertedUser = - conn.QueryAsync( - sql, - {| id = user.Id - username = user.Username - createdAt = user.Created_At - updatedAt = user.Updated_At |} - ) + let! insertedUser = conn.QueryAsync(sql, user) return insertedUser |> Seq.head } @@ -43,19 +36,12 @@ let insertMessage (message: DbMessage): Task = //language=postgresql let sql = """ -INSERT INTO message (chat_id, message_id, user_id, created_at) -VALUES (@chatId, @messageId, @userId, @createdAt) +INSERT INTO message (chat_id, message_id, user_id, text, raw_message, created_at) +VALUES (@chat_id, @message_id, @user_id, @text, @raw_message::JSONB, @created_at) ON CONFLICT (chat_id, message_id) DO NOTHING RETURNING *; """ - let! insertedMessage = - conn.QueryAsync( - sql, - {| chatId = message.Chat_Id - messageId = message.Message_Id - userId = message.User_Id - createdAt = message.Created_At |} - ) + let! insertedMessage = conn.QueryAsync(sql, message) return insertedMessage @@ -71,19 +57,10 @@ let banUser (banned: DbBanned): Task = let sql = """ INSERT INTO banned (message_id, message_text, banned_user_id, banned_at, banned_in_chat_id, banned_in_chat_username, banned_by) -VALUES (@messageId, @messageText, @bannedUserId, @bannedAt, @bannedInChatId, @bannedInChatUsername, @bannedBy) +VALUES (@message_id, @message_text, @banned_user_id, @banned_at, @banned_in_chat_id, @banned_in_chat_username, @banned_by) """ - let! _ = conn.ExecuteAsync( - sql, - {| messageId = banned.Message_Id - messageText = banned.Message_text - bannedUserId = banned.Banned_User_Id - bannedAt = banned.Banned_At - bannedInChatId = banned.Banned_In_Chat_Id - bannedInChatUsername = banned.Banned_In_Chat_username - bannedBy = banned.Banned_By |} - ) + let! _ = conn.ExecuteAsync(sql, banned) return banned } @@ -100,7 +77,7 @@ let getUserMessages (userId: int64): Task = let deleteMsgs (msg: DbMessage[]): Task = task { - let msgIds = msg |> Array.map (fun m -> m.Message_Id) + let msgIds = msg |> Array.map (_.message_id) use conn = new NpgsqlConnection(connString) //language=postgresql diff --git a/src/VahterBanBot/Types.fs b/src/VahterBanBot/Types.fs index b03a612..1da2bff 100644 --- a/src/VahterBanBot/Types.fs +++ b/src/VahterBanBot/Types.fs @@ -3,6 +3,7 @@ open System open System.Collections.Generic open System.Text +open Newtonsoft.Json open Utils [] @@ -23,52 +24,56 @@ type BotConfiguration = [] type DbUser = - { Id: int64 - Username: string option - Updated_At: DateTime - Created_At: DateTime } + { id: int64 + username: string option + updated_at: DateTime + created_at: DateTime } static member newUser(id, ?username: string) = - { Id = id - Username = username - Updated_At = DateTime.UtcNow - Created_At = DateTime.UtcNow } + { id = id + username = username + updated_at = DateTime.UtcNow + created_at = DateTime.UtcNow } static member newUser(user: Telegram.Bot.Types.User) = DbUser.newUser (id = user.Id, ?username = Option.ofObj user.Username) [] type DbBanned = - { Message_Id: int option - Message_text: string - Banned_User_Id: int64 - Banned_At: DateTime - Banned_In_Chat_Id: int64 option - Banned_In_Chat_username: string option - Banned_By: int64 } + { message_id: int option + message_text: string + banned_user_id: int64 + banned_at: DateTime + banned_in_chat_id: int64 option + banned_in_chat_username: string option + banned_by: int64 } module DbBanned = let banMessage (vahter: int64) (message: Telegram.Bot.Types.Message) = if isNull message.From || isNull message.Chat then failwith "Message should have a user and a chat" - { Message_Id = Some message.MessageId - Message_text = message.Text - Banned_User_Id = message.From.Id - Banned_At = DateTime.UtcNow - Banned_In_Chat_Id = Some message.Chat.Id - Banned_In_Chat_username = Some message.Chat.Username - Banned_By = vahter } + { message_id = Some message.MessageId + message_text = message.Text + banned_user_id = message.From.Id + banned_at = DateTime.UtcNow + banned_in_chat_id = Some message.Chat.Id + banned_in_chat_username = Some message.Chat.Username + banned_by = vahter } [] type DbMessage = - { Chat_Id: int64 - Message_Id: int - User_Id: int64 - Created_At: DateTime } + { chat_id: int64 + message_id: int + user_id: int64 + text: string + raw_message: string + created_at: DateTime } static member newMessage(message: Telegram.Bot.Types.Message) = - { Chat_Id = message.Chat.Id - Message_Id = message.MessageId - User_Id = message.From.Id - Created_At = DateTime.UtcNow } + { chat_id = message.Chat.Id + message_id = message.MessageId + user_id = message.From.Id + created_at = DateTime.UtcNow + text = message.Text + raw_message = JsonConvert.SerializeObject message } [] type VahterStat = diff --git a/src/migrations/V4__even-more-messages-info.sql b/src/migrations/V4__even-more-messages-info.sql new file mode 100644 index 0000000..f0dc8b3 --- /dev/null +++ b/src/migrations/V4__even-more-messages-info.sql @@ -0,0 +1,5 @@ +ALTER TABLE message + ADD COLUMN text TEXT, + ADD COLUMN raw_message JSONB; + +CREATE INDEX message_raw_message_idx ON message USING GIN (raw_message);