Skip to content

Commit

Permalink
added unban command
Browse files Browse the repository at this point in the history
  • Loading branch information
Szer committed Dec 4, 2023
1 parent 50e27b5 commit be4fe2b
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 23 deletions.
126 changes: 103 additions & 23 deletions src/VahterBanBot/Bot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ let isPingCommand (message: Message) =

let isBanCommand (message: Message) =
message.Text = "/ban"

let isUnbanCommand (message: Message) =
message.Text = "/unban"

let isBanOnReplyCommand (message: Message) =
isBanCommand message &&
Expand All @@ -38,7 +41,8 @@ let isBannedPersonAdmin (botConfig: BotConfiguration) (message: Message) =

let isKnownCommand (message: Message) =
isPingCommand message ||
isBanCommand message
isBanCommand message ||
isUnbanCommand message

let isBanAuthorized (botConfig: BotConfiguration) (message: Message) (logger: ILogger) =
let fromUserId = message.From.Id
Expand All @@ -51,16 +55,16 @@ let isBanAuthorized (botConfig: BotConfiguration) (message: Message) (logger: IL
// check that user is allowed to ban others
if isMessageFromAdmin botConfig message then
if not(isMessageFromAllowedChats botConfig message) then
logger.LogWarning $"User {fromUsername} {fromUserId} tried to ban user {targetUsername} ({targetUserId}) from not allowed chat {chatUsername} ({chatId})"
logger.LogWarning $"User {fromUsername} {fromUserId} tried to (un)ban user {targetUsername} ({targetUserId}) from not allowed chat {chatUsername} ({chatId})"
false
// check that user is not trying to ban other admins
elif isBannedPersonAdmin botConfig message then
logger.LogWarning $"User {fromUsername} ({fromUserId}) tried to ban admin {targetUsername} ({targetUserId}) in chat {chatUsername} ({chatId}"
logger.LogWarning $"User {fromUsername} ({fromUserId}) tried to (un)ban admin {targetUsername} ({targetUserId}) in chat {chatUsername} ({chatId}"
false
else
true
else
logger.LogWarning $"User {fromUsername} ({fromUserId}) tried to ban user {targetUsername} ({targetUserId}) without being admin in chat {chatUsername} ({chatId}"
logger.LogWarning $"User {fromUsername} ({fromUserId}) tried to (un)ban user {targetUsername} ({targetUserId}) without being admin in chat {chatUsername} ({chatId}"
false

let banInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient) targetUserId = task {
Expand All @@ -77,49 +81,84 @@ let banInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient)
return! Task.WhenAll banTasks
}

let unbanInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient) targetUserId = task {
let banTasks =
botConfig.ChatsToMonitor
|> Seq.map (fun (KeyValue(chatUserName, chatId)) -> task {
// unban user in each chat
try
do! botClient.UnbanChatMemberAsync(ChatId chatId, targetUserId, true)
return Ok(chatUserName, chatId)
with e ->
return Error (chatUserName, chatId, e)
})
return! Task.WhenAll banTasks
}

let safeTaskAwait onError (task: Task) =
task.ContinueWith(fun (t: Task) ->
if t.IsFaulted then
onError t.Exception
)

let aggregateBanResultInLogMsg
(logger: ILogger)
let aggregateResultInLogMsg
(isBan: bool)
(message: Message)
(deletedUserMessages: int)
(banResults: Result<string * int64, string * int64 * exn> []) =
(targetUserId: int64)
(targetUserName: string option)
(logger: ILogger)
(deletedUserMessages: int) // 0 for unban
(results: Result<string * int64, string * int64 * exn> []) =

let resultType = if isBan then "ban" else "unban"
let sanitizedUsername =
targetUserName
|> Option.map prependUsername
|> Option.defaultValue "{NO_USERNAME}"

let vahterUserId = message.From.Id
let vahterUsername = message.From.Username
let chatName = message.Chat.Username
let chatId = message.Chat.Id

let targetUserId = message.ReplyToMessage.From.Id
let targetUsername = message.ReplyToMessage.From.Username
let logMsgBuilder = StringBuilder()
%logMsgBuilder.Append($"Vahter {prependUsername vahterUsername}({vahterUserId}) banned {prependUsername targetUsername} ({targetUserId}) in {prependUsername chatName}({chatId})")
%logMsgBuilder.Append($"Vahter {prependUsername vahterUsername}({vahterUserId}) {resultType}ned {sanitizedUsername} ({targetUserId}) in {prependUsername chatName}({chatId})")

// we don't want to spam logs channel if all is good
let allChatsOk = banResults |> Array.forall Result.isOk
let allChatsOk = results |> Array.forall Result.isOk
if allChatsOk then
%logMsgBuilder.AppendLine " in all chats"
logMsgBuilder.AppendLine $"Deleted {deletedUserMessages} messages"
|> string
if isBan then
%logMsgBuilder.AppendLine $"Deleted {deletedUserMessages} messages"
else

%logMsgBuilder.AppendLine ""
%logMsgBuilder.AppendLine $"Deleted {deletedUserMessages} messages in chats:"
if isBan then
%logMsgBuilder.AppendLine ""
%logMsgBuilder.AppendLine $"Deleted {deletedUserMessages} messages in chats:"

(logMsgBuilder, banResults)
(logMsgBuilder, results)
||> Array.fold (fun (sb: StringBuilder) result ->
match result with
| Ok (chatUsername, chatId) ->
sb.AppendLine($"{prependUsername chatUsername} ({chatId}) - OK")
| Error (chatUsername, chatId, e) ->
logger.LogError($"Failed to ban user {prependUsername targetUsername} ({targetUserId}) in chat {prependUsername chatUsername} ({chatId})", e)
logger.LogError($"Failed to {resultType} user {sanitizedUsername} ({targetUserId}) in chat {prependUsername chatUsername} ({chatId})", e)
sb.AppendLine($"{prependUsername chatUsername} ({chatId}) - FAILED. {e.Message}")
)
|> string
) |> ignore
string logMsgBuilder

let aggregateBanResultInLogMsg message =
aggregateResultInLogMsg
true
message
message.ReplyToMessage.From.Id
(Some message.ReplyToMessage.From.Username)

let aggregateUnbanResultInLogMsg message targetUserId targetUsername =
aggregateResultInLogMsg
false
message
targetUserId
targetUsername

let ping
(botClient: ITelegramBotClient)
Expand Down Expand Up @@ -206,7 +245,7 @@ let banOnReply
let! deletedUserMessages = deletedUserMessagesTask

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

// log both to logger and to logs channel
do! botClient.SendTextMessageAsync(ChatId(botConfig.LogsChannelId), logMsg) |> taskIgnore
Expand All @@ -216,6 +255,45 @@ let banOnReply
do! deleteReplyTask
}

let unban
(botClient: ITelegramBotClient)
(botConfig: BotConfiguration)
(message: Message)
(logger: ILogger) = task {
use banOnReplyActivity = botActivity.StartActivity("unban")
%banOnReplyActivity
.SetTag("vahterId", message.From.Id)
.SetTag("vahterUsername", message.From.Username)
let targetUserId = message.Text.Split(" ")[1] |> int64
%banOnReplyActivity.SetTag("targetId", targetUserId)

let! user = DB.getUserById targetUserId
let unbanUserTask = task {
if user.IsSome then
%banOnReplyActivity.SetTag("targetUsername", user.Value.Username)
let! unbannedUser =
user.Value
|> DbUser.unban
|> DB.upsertUser
return Some unbannedUser
else
return None
}
let targetUsername = user |> Option.bind (fun u -> u.Username)

// try unban user in all monitored chats
let! unbanResults = unbanInAllChats botConfig botClient targetUserId

// produce aggregated log message
let logMsg = aggregateUnbanResultInLogMsg message targetUserId targetUsername logger 0 unbanResults

// log both to logger and to logs channel
do! botClient.SendTextMessageAsync(ChatId(botConfig.LogsChannelId), logMsg) |> taskIgnore
logger.LogInformation logMsg

do! unbanUserTask.Ignore()
}

let onUpdate
(botClient: ITelegramBotClient)
(botConfig: BotConfiguration)
Expand Down Expand Up @@ -256,9 +334,11 @@ let onUpdate
do! botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.MessageId)
|> safeTaskAwait (fun e -> logger.LogError ($"Failed to delete ping message {message.MessageId} from chat {message.Chat.Id}", e))
}
// check that user is allowed to ban others
// check that user is allowed to (un)ban others
if isBanOnReplyCommand message && isBanAuthorized botConfig message logger then
do! banOnReply botClient botConfig message logger
elif isUnbanCommand message && isBanAuthorized botConfig message logger then
do! unban botClient botConfig message logger

// ping command for testing that bot works and you can talk to it
elif isPingCommand message then
Expand Down
10 changes: 10 additions & 0 deletions src/VahterBanBot/DB.fs
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,13 @@ ORDER BY killCountTotal DESC
let! stats = conn.QueryAsync<VahterStat>(sql, {| banInterval = banInterval |})
return { interval = banInterval; stats = Array.ofSeq stats }
}

let getUserById (userId: int64): Task<DbUser option> =
task {
use conn = new NpgsqlConnection(connString)

//language=postgresql
let sql = "SELECT * FROM \"user\" WHERE id = @userId"
let! users = conn.QueryAsync<DbUser>(sql, {| userId = userId |})
return users |> Seq.tryHead
}
7 changes: 7 additions & 0 deletions src/VahterBanBot/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@ type DbUser =
Banned_At = Some DateTime.UtcNow
Ban_Reason = reason
Updated_At = DateTime.UtcNow }
member this.Unban() =
{ this with
Banned_By = None
Banned_At = None
Ban_Reason = None
Updated_At = DateTime.UtcNow }

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

[<CLIMutable>]
type DbMessage =
Expand Down

0 comments on commit be4fe2b

Please sign in to comment.