Skip to content

Commit

Permalink
added message deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
Szer committed Sep 28, 2023
1 parent 9e6bbc0 commit 82dcc88
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 24 deletions.
50 changes: 47 additions & 3 deletions src/VahterBanBot/Bot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,17 @@ let banInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient)
let aggregateBanResultInLogMsg
(logger: ILogger)
(message: Message)
(deletedUserMessages: int)
(banResults: Result<string * int64, string * int64 * exn> []) =

let vahterUserId = message.From.Id
let vahterUsername = message.From.Username

let targetUserId = message.ReplyToMessage.From.Id
let targetUsername = message.ReplyToMessage.From.Username
let logMsgBuilder = StringBuilder()
%logMsgBuilder.AppendLine($"Result of ban {targetUsername} ({targetUserId}) in chats:")
%logMsgBuilder.AppendLine($"Vahter {vahterUsername}({vahterUserId}) banned {targetUsername} ({targetUserId})")
%logMsgBuilder.AppendLine($"Deleted {deletedUserMessages} messages in chats:")

(logMsgBuilder, banResults)
||> Array.fold (fun (sb: StringBuilder) result ->
Expand All @@ -94,11 +99,18 @@ let onUpdate
(logger: ILogger)
(message: Message) = task {

// early return if if we can't process it
if isNull message || isNull message.From then
logger.LogWarning "Received update without message"
else

// upserting user to DB
let! _ =
DbUser.newUser message.From
|> DB.upsertUser

// check if message comes from channel, we should delete it immediately
elif botConfig.ShouldDeleteChannelMessages && isChannelMessage message then
if botConfig.ShouldDeleteChannelMessages && isChannelMessage message then

do! botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.MessageId)
let probablyChannelName =
Expand All @@ -116,17 +128,41 @@ let onUpdate
let deleteCmdTask = botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.MessageId)
// delete message that was replied to
let deleteReplyTask = botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.ReplyToMessage.MessageId)
// update user in DB
let banUserInDb =
message.ReplyToMessage.From
|> DbUser.newUser
|> DbUser.banUser message.From.Id (Option.ofObj message.Text)
|> DB.upsertUser

let deletedUserMessagesTask = task {
let fromUserId = message.ReplyToMessage.From.Id

// delete all recorded messages from user in all chats
let! allUserMessages = DB.getUserMessages fromUserId
for msg in allUserMessages do
// try to delete each message separately
try
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)

// delete recorded messages from DB
return! DB.deleteUserMessages fromUserId
}

// try ban user in all monitored chats
let! banResults = banInAllChats botConfig botClient message.ReplyToMessage.From.Id
let! deletedUserMessages = deletedUserMessagesTask

// produce aggregated log message
let logMsg = aggregateBanResultInLogMsg logger message banResults
let logMsg = aggregateBanResultInLogMsg logger message deletedUserMessages banResults

// log both to logger and to logs channel
let! _ = botClient.SendTextMessageAsync(ChatId(botConfig.LogsChannelId), logMsg)
logger.LogInformation logMsg

let! _ = banUserInDb
do! deleteCmdTask
do! deleteReplyTask

Expand All @@ -136,4 +172,12 @@ let onUpdate
let deleteCmdTask = botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.MessageId)
let! _ = botClient.SendTextMessageAsync(ChatId(message.Chat.Id), "pong")
do! deleteCmdTask

// if message is not a command, just save it ID to DB
else
let! _ =
message
|> DbMessage.newMessage
|> DB.insertMessage
()
}
103 changes: 96 additions & 7 deletions src/VahterBanBot/DB.fs
Original file line number Diff line number Diff line change
@@ -1,40 +1,129 @@
module VahterBanBot.DB

open System.Threading.Tasks
open Npgsql
open VahterBanBot.Types
open Dapper
open VahterBanBot.Utils

let private connString = getEnv "DATABASE_URL"

let upsertUser (user: User) =
let upsertUser (user: DbUser): Task<DbUser> =
task {
use conn = new NpgsqlConnection(connString)

//language=postgresql
let sql =
"""
INSERT INTO "user" (id, username)
VALUES (@id, @username)
INSERT INTO "user" (id, username, ban_reason, banned_at, banned_by, created_at, updated_at)
VALUES (@id, @username, @banReason, @bannedAt, @bannedBy, @createdAt, @updatedAt)
ON CONFLICT (id) DO UPDATE
SET username =
CASE
WHEN EXCLUDED.username != "user".username THEN EXCLUDED.username
WHEN EXCLUDED.username != "user".username THEN COALESCE(EXCLUDED.username, "user".username)
ELSE "user".username
END,
updated_at =
CASE
WHEN EXCLUDED.username != "user".username THEN timezone('utc'::TEXT, NOW())
WHEN EXCLUDED.updated_at != "user".updated_at THEN EXCLUDED.username
ELSE "user".updated_at
END,
ban_reason =
CASE
WHEN EXCLUDED.ban_reason != "user".ban_reason THEN EXCLUDED.ban_reason
ELSE "user".ban_reason
END,
banned_at =
CASE
WHEN EXCLUDED.banned_at != "user".banned_at THEN EXCLUDED.ban_reason
ELSE "user".banned_at
END,
banned_by =
CASE
WHEN EXCLUDED.banned_by != "user".banned_by THEN EXCLUDED.ban_reason
ELSE "user".banned_by
END,
created_at =
CASE
WHEN EXCLUDED.created_at != "user".created_at THEN EXCLUDED.ban_reason
ELSE "user".created_at
END,
updated_at =
CASE
WHEN EXCLUDED.updated_at != "user".updated_at THEN EXCLUDED.ban_reason
ELSE "user".updated_at
END
RETURNING *;
"""

let! insertedUser =
conn.QueryAsync<User>(
conn.QueryAsync<DbUser>(
sql,
{| id = user.Id; username = user.Username |}
{| id = user.Id
username = user.Username
banReason = user.Ban_Reason
bannedAt = user.Banned_At
bannedBy = user.Banned_By
createdAt = user.Created_At
updatedAt = user.Updated_At |}
)

return insertedUser |> Seq.head
}

let insertMessage (message: DbMessage): Task<DbMessage> =
task {
use conn = new NpgsqlConnection(connString)

//language=postgresql
let sql =
"""
INSERT INTO message (chat_id, message_id, user_id, created_at)
VALUES (@chatId, @messageId, @userId, @createdAt)
ON CONFLICT (chat_id, message_id) DO NOTHING RETURNING *;
"""

let! insertedMessage =
conn.QueryAsync<DbMessage>(
sql,
{| chatId = message.Chat_Id
messageId = message.Message_Id
userId = message.User_Id
createdAt = message.Created_At |}
)

return
insertedMessage
|> Seq.tryHead
|> Option.defaultValue message
}

let getUserMessages (userId: int64): Task<DbMessage array> =
task {
use conn = new NpgsqlConnection(connString)

//language=postgresql
let sql = "SELECT * FROM message WHERE user_id = @userId"

let! messages =
conn.QueryAsync<DbMessage>(
sql,
{| userId = userId |}
)
return Array.ofSeq messages
}

let deleteUserMessages (userId: int64): Task<int> =
task {
use conn = new NpgsqlConnection(connString)

//language=postgresql
let sql = "DELETE FROM message WHERE user_id = @userId"

let! messagesDeleted =
conn.ExecuteAsync(
sql,
{| userId = userId |}
)
return messagesDeleted
}
13 changes: 0 additions & 13 deletions src/VahterBanBot/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,6 @@ let webApp = choose [
logger.LogError(e, "Unexpected error while processing update")
return! Successful.OK() next ctx
})

GET >=> routef "/user/%d" (fun id next ctx -> task {
let logger = ctx.GetLogger<Root>()
try
let! upsertedUser =
User.newUser id
|> DB.upsertUser

return! json upsertedUser next ctx
with e ->
logger.LogError(e, "Unexpected error while processing user")
return! ServerErrors.INTERNAL_ERROR() next ctx
})
]
]

Expand Down
26 changes: 25 additions & 1 deletion src/VahterBanBot/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type BotConfiguration =
ShouldDeleteChannelMessages: bool }

[<CLIMutable>]
type User =
type DbUser =
{ Id: int64
Username: string option
Banned_By: int64 option
Expand All @@ -31,3 +31,27 @@ type User =
Ban_Reason = None
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)

member this.Ban(vahter: int64, ?reason: String) =
{ this with
Banned_By = Some vahter
Banned_At = Some DateTime.UtcNow
Ban_Reason = reason
Updated_At = DateTime.UtcNow }

module DbUser =
let banUser vahter reason (user: DbUser) = user.Ban(vahter, ?reason = reason)

type DbMessage =
{ Chat_Id: int64
Message_Id: int
User_Id: int64
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 }
20 changes: 20 additions & 0 deletions src/migrations/V2__added-chat-id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
DROP TABLE message;

CREATE TABLE "message"
(
id BIGSERIAL NOT NULL PRIMARY KEY,
chat_id BIGINT NOT NULL,
message_id INT NOT NULL,
user_id BIGINT NOT NULL
CONSTRAINT message_user_id_fkey
REFERENCES "user" (id)
ON DELETE CASCADE,

created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::TEXT, NOW())
);

CREATE UNIQUE INDEX message_chat_id_message_id_uindex
ON "message" (chat_id, message_id);

CREATE INDEX message_user_id_index
ON "message" (user_id);

0 comments on commit 82dcc88

Please sign in to comment.