From c66fa83409ae0e1216fb9e3631b8c390c3c7ec2a Mon Sep 17 00:00:00 2001 From: Kirill Poletaev Date: Thu, 11 Jan 2024 00:16:18 +0300 Subject: [PATCH] Workers requested features (Soft Ban) (#1) * USE_POLLING for better development experience * Soft ban feature --- src/VahterBanBot/Bot.fs | 97 ++++++++++++++++++++++++++++++++++++- src/VahterBanBot/Program.fs | 24 ++++++++- src/VahterBanBot/Types.fs | 3 +- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/VahterBanBot/Bot.fs b/src/VahterBanBot/Bot.fs index ecd3251..4fbef47 100644 --- a/src/VahterBanBot/Bot.fs +++ b/src/VahterBanBot/Bot.fs @@ -26,6 +26,13 @@ let isBanCommand (message: Message) = let isUnbanCommand (message: Message) = message.Text.StartsWith "/unban " +let isSoftBanCommand (message: Message) = + message.Text.StartsWith "/sban" + +let isSoftBanOnReplyCommand (message: Message) = + isSoftBanCommand message && + message.ReplyToMessage <> null + let isBanOnReplyCommand (message: Message) = isBanCommand message && message.ReplyToMessage <> null @@ -42,7 +49,8 @@ let isBannedPersonAdmin (botConfig: BotConfiguration) (message: Message) = let isKnownCommand (message: Message) = isPingCommand message || isBanCommand message || - isUnbanCommand message + isUnbanCommand message || + isSoftBanCommand message let isBanAuthorized (botConfig: BotConfiguration) @@ -87,6 +95,31 @@ let banInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient) return! Task.WhenAll banTasks } +let softBanInChat (botClient: ITelegramBotClient) (chatId: ChatId) targetUserId (duration: int) = task { + let permissions = ChatPermissions( + CanSendMessages = false, + CanSendAudios = false, + CanSendDocuments = false, + CanSendPhotos = false, + CanSendVideos = false, + CanSendVideoNotes = false, + CanSendVoiceNotes = false, + CanSendPolls = false, + CanSendOtherMessages = false, + CanAddWebPagePreviews = false, + CanChangeInfo = false, + CanInviteUsers = false, + CanPinMessages = false, + CanManageTopics = false + ) + let untilDate = DateTime.Now.AddHours duration + try + do! botClient.RestrictChatMemberAsync(chatId, targetUserId, permissions, Nullable(), untilDate) + return Ok(chatId, targetUserId) + with e -> + return Error(chatId, targetUserId, e) +} + let unbanInAllChats (botConfig: BotConfiguration) (botClient: ITelegramBotClient) targetUserId = task { let banTasks = botConfig.ChatsToMonitor @@ -166,6 +199,15 @@ let aggregateUnbanResultInLogMsg message targetUserId targetUsername = targetUserId targetUsername +let softBanResultInLogMsg (message: Message) (duration: int) = + let logMsgBuilder = StringBuilder() + %logMsgBuilder.Append $"Vahter @{message.From.Username}({message.From.Id}) " + %logMsgBuilder.Append $"softbanned @{message.ReplyToMessage.From.Username}({message.ReplyToMessage.From.Id}) " + %logMsgBuilder.Append $"in @{message.Chat.Username}({message.Chat.Id}) " + %logMsgBuilder.Append $"until {DateTime.Now.AddHours duration}" + string logMsgBuilder + + let ping (botClient: ITelegramBotClient) (message: Message) = task { @@ -261,6 +303,45 @@ let banOnReply do! deleteReplyTask } +let softBanOnReply + (botClient: ITelegramBotClient) + (botConfig: BotConfiguration) + (message: Message) + (logger: ILogger) = task { + use banOnReplyActivity = botActivity.StartActivity("softBanOnReply") + %banOnReplyActivity + .SetTag("vahterId", message.From.Id) + .SetTag("vahterUsername", message.From.Username) + .SetTag("targetId", message.ReplyToMessage.From.Id) + .SetTag("targetUsername", message.ReplyToMessage.From.Username) + + let deleteReplyTask = task { + use _ = + botActivity + .StartActivity("deleteReplyMsg") + .SetTag("msgId", message.ReplyToMessage.MessageId) + .SetTag("chatId", message.Chat.Id) + .SetTag("chatUsername", message.Chat.Username) + do! botClient.DeleteMessageAsync(ChatId(message.Chat.Id), message.ReplyToMessage.MessageId) + |> safeTaskAwait (fun e -> logger.LogError ($"Failed to delete reply message {message.ReplyToMessage.MessageId} from chat {message.Chat.Id}", e)) + } + + let maybeDurationString = message.Text.Split " " |> Seq.last + // use last value as soft ban duration + let duration = + match Int32.TryParse maybeDurationString with + | true, x -> x + | _ -> 24 // 1 day should be enough + + let logText = softBanResultInLogMsg message duration + + do! softBanInChat botClient (ChatId message.Chat.Id) message.ReplyToMessage.From.Id duration |> taskIgnore + do! deleteReplyTask + + do! botClient.SendTextMessageAsync(ChatId(botConfig.LogsChannelId), logText) |> taskIgnore + logger.LogInformation logText +} + let unban (botClient: ITelegramBotClient) (botConfig: BotConfiguration) @@ -366,7 +447,19 @@ let onUpdate false if authed then do! unban botClient botConfig message logger targetUserId - + elif isSoftBanOnReplyCommand message then + let targetUserId = message.ReplyToMessage.From.Id + let targetUsername = Option.ofObj message.ReplyToMessage.From.Username + let authed = + isBanAuthorized + botConfig + message + logger + targetUserId + targetUsername + true + if authed then + do! softBanOnReply botClient botConfig message logger // ping command for testing that bot works and you can talk to it elif isPingCommand message then do! ping botClient message diff --git a/src/VahterBanBot/Program.fs b/src/VahterBanBot/Program.fs index 1699cd2..6da9215 100644 --- a/src/VahterBanBot/Program.fs +++ b/src/VahterBanBot/Program.fs @@ -1,14 +1,18 @@ open System open System.Collections.Generic +open System.Threading +open System.Threading.Tasks open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Http open Microsoft.Extensions.Logging open Microsoft.FSharp.Core open Newtonsoft.Json open Telegram.Bot +open Telegram.Bot.Polling open Telegram.Bot.Types open Giraffe open Microsoft.Extensions.DependencyInjection +open Telegram.Bot.Types.Enums open VahterBanBot open VahterBanBot.Cleanup open VahterBanBot.Utils @@ -32,7 +36,8 @@ let botConf = ChatsToMonitor = getEnv "CHATS_TO_MONITOR" |> JsonConvert.DeserializeObject<_> AllowedUsers = getEnv "ALLOWED_USERS" |> JsonConvert.DeserializeObject<_> ShouldDeleteChannelMessages = getEnvOr "SHOULD_DELETE_CHANNEL_MESSAGES" "true" |> bool.Parse - IgnoreSideEffects = getEnvOr "IGNORE_SIDE_EFFECTS" "false" |> bool.Parse } + IgnoreSideEffects = getEnvOr "IGNORE_SIDE_EFFECTS" "false" |> bool.Parse + UsePolling = getEnvOr "USE_POLLING" "false" |> bool.Parse } let validateApiKey (ctx : HttpContext) = match ctx.TryGetRequestHeader "X-Telegram-Bot-Api-Secret-Token" with @@ -140,4 +145,21 @@ app.Logger.LogInformation startLogMsg if not botConf.IgnoreSideEffects then telegramClient.SendTextMessageAsync(ChatId(botConf.LogsChannelId), startLogMsg).Wait() +// Dev mode only +if botConf.UsePolling then + let pollingHandler = { + new IUpdateHandler with + member x.HandleUpdateAsync (botClient: ITelegramBotClient, update: Update, cancellationToken: CancellationToken) = + task { + if update.Message <> null && update.Message.Type = MessageType.Text then + let ctx = app.Services.CreateScope() + let logger = ctx.ServiceProvider.GetRequiredService>() + let client = ctx.ServiceProvider.GetRequiredService() + do! onUpdate client botConf logger update.Message + } + member x.HandlePollingErrorAsync (botClient: ITelegramBotClient, ex: Exception, cancellationToken: CancellationToken) = + Task.CompletedTask + } + telegramClient.StartReceiving(pollingHandler, null, CancellationToken.None) + server.Wait() diff --git a/src/VahterBanBot/Types.fs b/src/VahterBanBot/Types.fs index bcd1a7b..31cb88e 100644 --- a/src/VahterBanBot/Types.fs +++ b/src/VahterBanBot/Types.fs @@ -14,7 +14,8 @@ type BotConfiguration = ChatsToMonitor: Dictionary AllowedUsers: Dictionary ShouldDeleteChannelMessages: bool - IgnoreSideEffects: bool } + IgnoreSideEffects: bool + UsePolling: bool } [] type DbUser =