From a4a44198c579062038daa717a248bf821633b95f Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 00:18:53 +0200 Subject: [PATCH 1/8] [reputation] Move reputation to separate module. --- .../CommonHandler.cs | 6 +- Core/Extensions/DbContextExtension.cs | 2 +- Core/Service/IReputation.cs | 13 ++ Kruzya.TelegramBot.sln | 9 +- modules/ChatManagement/ChatManagement.cs | 9 +- modules/ChatManagement/Handler/Ban.cs | 3 +- modules/ChatManagement/Handler/Reputation.cs | 183 ------------------ modules/ChatManagement/Handler/Rules.cs | 66 +++---- modules/ChatManagement/Handler/Warn.cs | 3 +- modules/Reputation/Data/ReputationContext.cs | 20 ++ modules/Reputation/Data/UserReputation.cs | 10 + .../Data/UserReputationContextFactory.cs | 21 ++ modules/Reputation/Handler/Debug.cs | 64 ++++++ modules/Reputation/Handler/Reputation.cs | 175 +++++++++++++++++ .../20220213182703_Initial.Designer.cs | 43 ++++ .../Migrations/20220213182703_Initial.cs | 40 ++++ .../ReputationContextModelSnapshot.cs | 40 ++++ modules/Reputation/Reputation.cs | 25 +++ modules/Reputation/Reputation.csproj | 29 +++ .../Reputation/Service/ReputationService.cs | 62 ++++++ 20 files changed, 595 insertions(+), 228 deletions(-) rename modules/ChatManagement/Handler/ManagementHandler.cs => Core/CommonHandler.cs (92%) create mode 100644 Core/Service/IReputation.cs delete mode 100644 modules/ChatManagement/Handler/Reputation.cs create mode 100644 modules/Reputation/Data/ReputationContext.cs create mode 100644 modules/Reputation/Data/UserReputation.cs create mode 100644 modules/Reputation/Data/UserReputationContextFactory.cs create mode 100644 modules/Reputation/Handler/Debug.cs create mode 100644 modules/Reputation/Handler/Reputation.cs create mode 100644 modules/Reputation/Migrations/20220213182703_Initial.Designer.cs create mode 100644 modules/Reputation/Migrations/20220213182703_Initial.cs create mode 100644 modules/Reputation/Migrations/ReputationContextModelSnapshot.cs create mode 100644 modules/Reputation/Reputation.cs create mode 100644 modules/Reputation/Reputation.csproj create mode 100644 modules/Reputation/Service/ReputationService.cs diff --git a/modules/ChatManagement/Handler/ManagementHandler.cs b/Core/CommonHandler.cs similarity index 92% rename from modules/ChatManagement/Handler/ManagementHandler.cs rename to Core/CommonHandler.cs index 1fb3be6..dee081e 100644 --- a/modules/ChatManagement/Handler/ManagementHandler.cs +++ b/Core/CommonHandler.cs @@ -9,9 +9,9 @@ using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace West.TelegramBot.ChatManagement.Handler +namespace Kruzya.TelegramBot.Core { - public abstract class ManagementHandler : BotEventHandler + public abstract class CommonHandler : BotEventHandler { protected readonly CoreContext Db; @@ -19,7 +19,7 @@ namespace West.TelegramBot.ChatManagement.Handler protected User? RepliedUser => Message?.ReplyToMessage?.From; - protected ManagementHandler(CoreContext db) + protected CommonHandler(CoreContext db) { Db = db; } diff --git a/Core/Extensions/DbContextExtension.cs b/Core/Extensions/DbContextExtension.cs index 847d109..b425039 100644 --- a/Core/Extensions/DbContextExtension.cs +++ b/Core/Extensions/DbContextExtension.cs @@ -45,7 +45,7 @@ public static class DbContextExtension dbContext.Update(entity); dbContext.SaveChanges(); } - catch (DbUpdateConcurrencyException e) + catch (DbUpdateConcurrencyException) { dbContext.Add(entity); dbContext.SaveChanges(); diff --git a/Core/Service/IReputation.cs b/Core/Service/IReputation.cs new file mode 100644 index 0000000..de51f00 --- /dev/null +++ b/Core/Service/IReputation.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Telegram.Bot.Types; + +namespace Kruzya.TelegramBot.Core.Service +{ + public interface IReputation + { + public Task GetUserReputationAsync(Chat chat, User user); + public Task SetUserReputationAsync(Chat chat, User user, double value); + public Task GetReputationDiffAsync(Chat chat, User sender, User receiver); + public Task IncrementReputationAsync(Chat chat, User user, double diff); + } +} diff --git a/Kruzya.TelegramBot.sln b/Kruzya.TelegramBot.sln index 72016fc..0dcdad6 100644 --- a/Kruzya.TelegramBot.sln +++ b/Kruzya.TelegramBot.sln @@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserGroupTag", "modules\Use EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entertainment", "modules\Entertainment\Entertainment.csproj", "{1D62101B-C2B1-4E79-8F7A-690E44E3EE81}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatQuotes", "modules\ChatQuotes\ChatQuotes.csproj", "{04F16325-8F44-4250-9B2E-0F77E8F65CBF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatQuotes", "modules\ChatQuotes\ChatQuotes.csproj", "{04F16325-8F44-4250-9B2E-0F77E8F65CBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reputation", "modules\Reputation\Reputation.csproj", "{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -71,6 +73,10 @@ Global {04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Release|Any CPU.Build.0 = Release|Any CPU + {03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,6 +90,7 @@ Global {5B89E22A-78CF-4037-BE0D-5F88EF47022B} = {C7821F15-DEDD-474F-A575-A296D4B58F10} {1D62101B-C2B1-4E79-8F7A-690E44E3EE81} = {C7821F15-DEDD-474F-A575-A296D4B58F10} {04F16325-8F44-4250-9B2E-0F77E8F65CBF} = {C7821F15-DEDD-474F-A575-A296D4B58F10} + {03F3C7D5-6FEF-4A83-9191-501091BFC1AC} = {C7821F15-DEDD-474F-A575-A296D4B58F10} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5BA73C3B-D5FC-4942-9091-504325CDC308} diff --git a/modules/ChatManagement/ChatManagement.cs b/modules/ChatManagement/ChatManagement.cs index 782feb6..8cb5e01 100644 --- a/modules/ChatManagement/ChatManagement.cs +++ b/modules/ChatManagement/ChatManagement.cs @@ -1,11 +1,10 @@ using Kruzya.TelegramBot.Core; -namespace West.TelegramBot.ChatManagement +namespace West.TelegramBot.ChatManagement; + +public class ChatManagement : Module { - public class ChatManagement : Module + public ChatManagement(Core core) : base(core) { - public ChatManagement(Core core) : base(core) - { - } } } \ No newline at end of file diff --git a/modules/ChatManagement/Handler/Ban.cs b/modules/ChatManagement/Handler/Ban.cs index f90d21d..019e812 100644 --- a/modules/ChatManagement/Handler/Ban.cs +++ b/modules/ChatManagement/Handler/Ban.cs @@ -3,13 +3,14 @@ using System.Threading.Tasks; using BotFramework.Attributes; using BotFramework.Enums; +using Kruzya.TelegramBot.Core; using Kruzya.TelegramBot.Core.Data; using Telegram.Bot; using Telegram.Bot.Types; namespace West.TelegramBot.ChatManagement.Handler { - public class Ban : ManagementHandler + public class Ban : CommonHandler { public Ban(CoreContext db) : base(db) {} diff --git a/modules/ChatManagement/Handler/Reputation.cs b/modules/ChatManagement/Handler/Reputation.cs deleted file mode 100644 index d8e560c..0000000 --- a/modules/ChatManagement/Handler/Reputation.cs +++ /dev/null @@ -1,183 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using BotFramework.Attributes; -using BotFramework.Enums; -using BotFramework.Utils; -using Kruzya.TelegramBot.Core.Data; -using Kruzya.TelegramBot.Core.Extensions; -using Kruzya.TelegramBot.Core.Service; -using Microsoft.Extensions.Logging; -using Telegram.Bot; -using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums; - -namespace West.TelegramBot.ChatManagement.Handler -{ - public class Reputation : ManagementHandler - { - private readonly UserService _userService; - private readonly ILogger _logger; - - private static readonly List RepUpChars = new() - { - "+", - "👍", - "👍🏼", - "👍🏽", - "👍🏾", - "👍🏿", - "👍🏻" - }; - private static readonly List RepDownChars = new() - { - "-", - "👎", - "👎🏼", - "👎🏽", - "👎🏾", - "👎🏿", - "👎🏻" - }; - - public Reputation(CoreContext db, UserService userService, ILogger logger) : base(db) - { - _userService = userService; - _logger = logger; - } - - [Command("whois", CommandParseMode.Both)] - public async Task HandleWhois() - { - var user = RepliedUser ?? From; - var message = $"{user.ToHtml()}\n"; - if (user.IsBot) - { - message += "Бот"; - } - else - { - message += $"{await GetChatMemberStatusString(user)}\n"; - message += $"Репутация: {await GetUserReputation(user)}\n"; - message += $"Предупреждения: {(await GetWarnOption(user)).GetValue()}"; - } - - await Reply(message); - } - - [Message(InChat.Public, MessageFlag.HasText | MessageFlag.HasSticker)] - public async Task HandleReputation() - { - var text = Message?.Text; - - if (string.IsNullOrEmpty(text)) - { - text = Message?.Sticker?.Emoji; - } - if (text == null) - { - return; - } - - var receiver = RepliedUser; - var canChangeRep = receiver is { IsBot: false } && !From.IsBot && receiver.Id != From.Id; - - if (canChangeRep && (RepUpChars.Contains(text) || RepDownChars.Contains(text))) - { - var receiverRepOption = await GetReputationOption(receiver!); - var receiverRepCount = receiverRepOption.GetValue(); - var senderRepCount = (await GetReputationOption(From)).GetValue(); - - if (senderRepCount < 0) - { - await Reply(new HtmlString() - .Code("Ты не можешь голосовать с отрицательной репутацией.") - .ToString() - ); - return; - } - - var diff = Math.Round(senderRepCount == 0 ? 1 : Math.Sqrt(senderRepCount), 2); - _logger.LogDebug("Calculated diff: {Diff}", diff); - - string action; - if (RepUpChars.Contains(text)) - { - receiverRepCount += diff; - action = "увеличил"; - } - else - { - receiverRepCount -= diff; - action = "уменьшил"; - } - - _logger.LogDebug("Saving receiver rep with value: {Value}", receiverRepCount); - receiverRepOption.SetValue(receiverRepCount); - Db.AddOrUpdate(receiverRepOption); - _logger.LogDebug("Updated value: {Value}", receiverRepOption.GetValue()); - - await Reply($"{From.ToHtml()}({await GetUserReputation(From)}) " + - $"{action} репутацию {receiver.ToHtml()}({Math.Round(receiverRepCount, 2)})"); - } - } - - [ParametrizedCommand(InChat.Public, "setrep", CommandParseMode.Both)] - public async Task HandleSetRep(double reputation) - { - if (!_userService.IsUserSuper(From)) - { - _logger.LogInformation("{User} attempted to call /setrep in {Chat}", From, Chat.Title); - return; - } - - if (RepliedUser == null || RepliedUser.IsBot) - { - return; - } - - reputation = Math.Round(reputation, 2); - var opt = await GetReputationOption(RepliedUser); - opt.SetValue(reputation); - Db.AddOrUpdate(opt); - - await Reply( - new HtmlString() - .Code("Пользователю ") - .UserMention(RepliedUser) - .Code($" установлена репутация: {reputation}") - .ToString() - ); - } - - private async Task GetUserReputation(User user) - { - return Math.Round((await GetReputationOption(user)).GetValue(), 2); - } - - private async Task GetReputationOption(User user) - { - return await Db.UserValues.FindOrCreateOption(Chat.Id, user.Id, "reputation"); - } - - private async Task GetChatMemberStatusString(User user) - { - var customStatus = _userService.GetUserCustomStatus(user.Id); - if (customStatus != null) - { - return customStatus; - } - - var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id); - return chatMember.Status switch - { - ChatMemberStatus.Creator => "Владелец", - ChatMemberStatus.Restricted => "Заблокированный", - ChatMemberStatus.Administrator => "Администратор", - _ => "Пользователь" - }; - } - } -} \ No newline at end of file diff --git a/modules/ChatManagement/Handler/Rules.cs b/modules/ChatManagement/Handler/Rules.cs index bbebfdd..b9be098 100644 --- a/modules/ChatManagement/Handler/Rules.cs +++ b/modules/ChatManagement/Handler/Rules.cs @@ -1,47 +1,47 @@ using System.Threading.Tasks; using BotFramework.Attributes; using BotFramework.Enums; +using Kruzya.TelegramBot.Core; using Kruzya.TelegramBot.Core.Data; using Kruzya.TelegramBot.Core.Extensions; using Telegram.Bot; using Telegram.Bot.Exceptions; -namespace West.TelegramBot.ChatManagement.Handler +namespace West.TelegramBot.ChatManagement.Handler; + +class Rules : CommonHandler { - class Rules : ManagementHandler + [Command(InChat.Public, "setrules", CommandParseMode.Both)] + public async Task HandleSetRules() { - [Command(InChat.Public, "setrules", CommandParseMode.Both)] - public async Task HandleSetRules() - { - var replyToMessage = Message?.ReplyToMessage; + var replyToMessage = Message?.ReplyToMessage; - if (replyToMessage == null || !await Bot.IsUserAdminAsync(Chat, From)) return; + if (replyToMessage == null || !await Bot.IsUserAdminAsync(Chat, From)) return; - var opt = await Db.UserValues.FindOrCreateOption(Chat.Id, "rulesMsgId"); - opt.SetValue(replyToMessage.MessageId); - Db.AddOrUpdate(opt); - } - - [Command(InChat.Public, "rules", CommandParseMode.Both)] - public async Task HandleRules() - { - var opt = await Db.UserValues.FindOption(Chat.Id, "rulesMsgId"); - if (opt == null) - { - return; - } - - try - { - await Bot.CopyMessageAsync(Chat.Id, Chat.Id, opt.GetValue()); - } - catch (ApiRequestException) - { - Db.UserValues.Remove(opt); - throw; - } - } - - public Rules(CoreContext db) : base(db) {} + var opt = await Db.UserValues.FindOrCreateOption(Chat.Id, "rulesMsgId"); + opt.SetValue(replyToMessage.MessageId); + Db.AddOrUpdate(opt); } + + [Command(InChat.Public, "rules", CommandParseMode.Both)] + public async Task HandleRules() + { + var opt = await Db.UserValues.FindOption(Chat.Id, "rulesMsgId"); + if (opt == null) + { + return; + } + + try + { + await Bot.CopyMessageAsync(Chat.Id, Chat.Id, opt.GetValue()); + } + catch (ApiRequestException) + { + Db.UserValues.Remove(opt); + throw; + } + } + + public Rules(CoreContext db) : base(db) {} } \ No newline at end of file diff --git a/modules/ChatManagement/Handler/Warn.cs b/modules/ChatManagement/Handler/Warn.cs index ee3d8a4..72419e4 100644 --- a/modules/ChatManagement/Handler/Warn.cs +++ b/modules/ChatManagement/Handler/Warn.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using BotFramework.Attributes; using BotFramework.Enums; +using Kruzya.TelegramBot.Core; using Kruzya.TelegramBot.Core.Data; using Kruzya.TelegramBot.Core.Extensions; using Telegram.Bot; @@ -11,7 +12,7 @@ using Telegram.Bot.Types.Enums; namespace West.TelegramBot.ChatManagement.Handler { - public class Warn : ManagementHandler + public class Warn : CommonHandler { public Warn(CoreContext db) : base(db) {} diff --git a/modules/Reputation/Data/ReputationContext.cs b/modules/Reputation/Data/ReputationContext.cs new file mode 100644 index 0000000..c93f941 --- /dev/null +++ b/modules/Reputation/Data/ReputationContext.cs @@ -0,0 +1,20 @@ +using Kruzya.TelegramBot.Core.EF; +using Microsoft.EntityFrameworkCore; + +namespace West.TelegramBot.Reputation.Data; + +public class ReputationContext : AutoSaveDbContext +{ + public DbSet UserReputation { get; set; } = null!; + + public ReputationContext(DbContextOptions options) : base(options) + { + Database.EnsureCreated(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(e => new {e.ChatId, e.UserId}); + modelBuilder.Entity().HasIndex(e => e.ChatId); + } +} \ No newline at end of file diff --git a/modules/Reputation/Data/UserReputation.cs b/modules/Reputation/Data/UserReputation.cs new file mode 100644 index 0000000..8326a9e --- /dev/null +++ b/modules/Reputation/Data/UserReputation.cs @@ -0,0 +1,10 @@ +namespace West.TelegramBot.Reputation.Data; + +public class UserReputation +{ + public long UserId { get; set; } + + public long ChatId { get; set; } + + public double Value { get; set; } +} \ No newline at end of file diff --git a/modules/Reputation/Data/UserReputationContextFactory.cs b/modules/Reputation/Data/UserReputationContextFactory.cs new file mode 100644 index 0000000..50f8b23 --- /dev/null +++ b/modules/Reputation/Data/UserReputationContextFactory.cs @@ -0,0 +1,21 @@ +#nullable enable +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace West.TelegramBot.Reputation.Data; + +public class ReputationContextFactory : IDesignTimeDbContextFactory +{ + public ReputationContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + if (args.Length != 1) + { + throw new InvalidDataException("You should pass DSN as CLI argument in order to use this."); + } + var dsn = args[0]; + optionsBuilder.UseMySql(dsn, ServerVersion.AutoDetect(dsn)); + + return new ReputationContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/modules/Reputation/Handler/Debug.cs b/modules/Reputation/Handler/Debug.cs new file mode 100644 index 0000000..c9c3962 --- /dev/null +++ b/modules/Reputation/Handler/Debug.cs @@ -0,0 +1,64 @@ +using BotFramework; +using BotFramework.Attributes; +using BotFramework.Enums; +using Kruzya.TelegramBot.Core.Data; +using Kruzya.TelegramBot.Core.Service; +using Microsoft.EntityFrameworkCore; +using West.TelegramBot.Reputation.Data; + +namespace West.TelegramBot.Reputation.Handler; + +public class Debug : BotEventHandler +{ + private readonly CoreContext _coreContext; + private readonly ReputationContext _reputationContext; + private readonly UserService _userService; + + + public Debug(CoreContext coreContext, ReputationContext reputationContext, UserService userService) + { + _coreContext = coreContext; + _reputationContext = reputationContext; + _userService = userService; + } + + [Command(InChat.Public, "dbg_migrate", CommandParseMode.Both)] + public async Task HandleMigrate() + { + if (!_userService.IsUserSuper(From)) + { + return; + } + + var reputationValues = _coreContext.UserValues.AsNoTracking() + .Where(value => value.Name == "reputation").Include(v => v.BotUser); + + foreach (var reputationValue in reputationValues) + { + var value = reputationValue.GetValue(); + if (value == 0D) + { + continue; + } + + var userReputation = new UserReputation + { + ChatId = reputationValue.BotUser.ChatId, + UserId = reputationValue.BotUser.UserId, + Value = value + }; + + var repList = _reputationContext.UserReputation; + if (repList.Contains(userReputation)) + { + repList.Update(userReputation); + } + else + { + repList.Add(userReputation); + } + } + + await _reputationContext.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/modules/Reputation/Handler/Reputation.cs b/modules/Reputation/Handler/Reputation.cs new file mode 100644 index 0000000..28cb0be --- /dev/null +++ b/modules/Reputation/Handler/Reputation.cs @@ -0,0 +1,175 @@ +#nullable enable + +using BotFramework.Attributes; +using BotFramework.Enums; +using BotFramework.Utils; +using Kruzya.TelegramBot.Core; +using Kruzya.TelegramBot.Core.Data; +using Kruzya.TelegramBot.Core.Extensions; +using Kruzya.TelegramBot.Core.Service; +using Microsoft.Extensions.Logging; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace West.TelegramBot.Reputation.Handler; + +public class Reputation : CommonHandler +{ + private readonly UserService _userService; + private readonly ILogger _logger; + private readonly IReputation _reputationService; + + private static readonly List RepUpChars = new() + { + "+", + "👍", + "👍🏼", + "👍🏽", + "👍🏾", + "👍🏿", + "👍🏻" + }; + private static readonly List RepDownChars = new() + { + "-", + "👎", + "👎🏼", + "👎🏽", + "👎🏾", + "👎🏿", + "👎🏻" + }; + + public Reputation( + CoreContext db, + UserService userService, + IReputation reputationService, + ILogger logger + ) : base(db) + { + _reputationService = reputationService; + _userService = userService; + _logger = logger; + } + + [Command("whois", CommandParseMode.Both)] + public async Task HandleWhois() + { + var user = RepliedUser ?? From; + var message = $"{user.ToHtml()}\n"; + if (user.IsBot) + { + message += "Бот"; + } + else + { + message += $"{await GetChatMemberStatusString(user)}\n"; + message += $"Репутация: {await GetUserReputation(user)}\n"; + message += $"Предупреждения: {(await GetWarnOption(user)).GetValue()}"; + } + + await Reply(message); + } + + [Message(InChat.Public, MessageFlag.HasText | MessageFlag.HasSticker)] + public async Task HandleReputation() + { + var text = Message?.Text; + + if (string.IsNullOrEmpty(text)) + { + text = Message?.Sticker?.Emoji; + } + if (text == null) + { + return; + } + + var receiver = RepliedUser; + var canChangeRep = receiver is { IsBot: false } && !From.IsBot && receiver.Id != From.Id; + + if (canChangeRep && (RepUpChars.Contains(text) || RepDownChars.Contains(text))) + { + var senderRepCount = await GetUserReputation(From); + + if (senderRepCount < 0) + { + await Reply(new HtmlString() + .Code("Ты не можешь голосовать с отрицательной репутацией.") + .ToString() + ); + return; + } + + var diff = await _reputationService.GetReputationDiffAsync(Chat, From, receiver); + _logger.LogDebug("Calculated diff: {Diff}", diff); + + string action; + if (RepUpChars.Contains(text)) + { + action = "увеличил"; + } + else + { + diff *= -1; + action = "уменьшил"; + } + + await _reputationService.IncrementReputationAsync(Chat, receiver, diff); + + var receiverRepCount = await GetUserReputation(receiver!); + _logger.LogDebug("Updated value: {Value}", receiverRepCount); + + await Reply($"{From.ToHtml()}({await GetUserReputation(From)}) " + + $"{action} репутацию {receiver.ToHtml()}({Math.Round(receiverRepCount, 2)})"); + } + } + + [ParametrizedCommand(InChat.Public, "setrep", CommandParseMode.Both)] + public async Task HandleSetRep(double reputation) + { + if (!_userService.IsUserSuper(From)) + { + _logger.LogInformation("{User} attempted to call /setrep in {Chat}", From, Chat.Title); + return; + } + + if (RepliedUser == null || RepliedUser.IsBot) + { + return; + } + + reputation = await _reputationService.SetUserReputationAsync(Chat, RepliedUser, reputation); + await Reply( + new HtmlString() + .Code("Пользователю ") + .UserMention(RepliedUser) + .Code($" установлена репутация: {reputation}") + .ToString() + ); + } + + private async Task GetUserReputation(User user) + { + return await _reputationService.GetUserReputationAsync(Chat, user); + } + + private async Task GetChatMemberStatusString(User user) + { + var customStatus = _userService.GetUserCustomStatus(user.Id); + if (customStatus != null) + { + return customStatus; + } + + var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id); + return chatMember.Status switch + { + ChatMemberStatus.Creator => "Владелец", + ChatMemberStatus.Restricted => "Заблокированный", + ChatMemberStatus.Administrator => "Администратор", + _ => "Пользователь" + }; + } +} \ No newline at end of file diff --git a/modules/Reputation/Migrations/20220213182703_Initial.Designer.cs b/modules/Reputation/Migrations/20220213182703_Initial.Designer.cs new file mode 100644 index 0000000..f2f58b8 --- /dev/null +++ b/modules/Reputation/Migrations/20220213182703_Initial.Designer.cs @@ -0,0 +1,43 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using West.TelegramBot.Reputation.Data; + +#nullable disable + +namespace West.TelegramBot.Reputation.Migrations +{ + [DbContext(typeof(ReputationContext))] + [Migration("20220213182703_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b => + { + b.Property("ChatId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("ChatId", "UserId"); + + b.HasIndex("ChatId"); + + b.ToTable("UserReputation"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/Reputation/Migrations/20220213182703_Initial.cs b/modules/Reputation/Migrations/20220213182703_Initial.cs new file mode 100644 index 0000000..f922ab9 --- /dev/null +++ b/modules/Reputation/Migrations/20220213182703_Initial.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace West.TelegramBot.Reputation.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "UserReputation", + columns: table => new + { + UserId = table.Column(type: "bigint", nullable: false), + ChatId = table.Column(type: "bigint", nullable: false), + Value = table.Column(type: "double", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserReputation", x => new { x.ChatId, x.UserId }); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_UserReputation_ChatId", + table: "UserReputation", + column: "ChatId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserReputation"); + } + } +} diff --git a/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs b/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs new file mode 100644 index 0000000..f3997db --- /dev/null +++ b/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs @@ -0,0 +1,40 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using West.TelegramBot.Reputation.Data; + +#nullable disable + +namespace West.TelegramBot.Reputation.Migrations +{ + [DbContext(typeof(ReputationContext))] + partial class ReputationContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b => + { + b.Property("ChatId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("ChatId", "UserId"); + + b.HasIndex("ChatId"); + + b.ToTable("UserReputation"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/Reputation/Reputation.cs b/modules/Reputation/Reputation.cs new file mode 100644 index 0000000..b23fcc1 --- /dev/null +++ b/modules/Reputation/Reputation.cs @@ -0,0 +1,25 @@ +using Kruzya.TelegramBot.Core; +using Kruzya.TelegramBot.Core.Service; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using West.TelegramBot.Reputation.Data; +using West.TelegramBot.Reputation.Service; + +namespace West.TelegramBot.Reputation +{ + public class Reputation : Module + { + public Reputation(Core core) : base(core) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + var dsn = Configuration.GetConnectionString("Reputation"); + services.AddDbContext(options => options.UseMySql(dsn, ServerVersion.AutoDetect(dsn))); + + services.AddScoped(); + } + } +} \ No newline at end of file diff --git a/modules/Reputation/Reputation.csproj b/modules/Reputation/Reputation.csproj new file mode 100644 index 0000000..7d3549f --- /dev/null +++ b/modules/Reputation/Reputation.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + TelegramBot.Reputation + West.TelegramBot.Reputation + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/modules/Reputation/Service/ReputationService.cs b/modules/Reputation/Service/ReputationService.cs new file mode 100644 index 0000000..00bc57b --- /dev/null +++ b/modules/Reputation/Service/ReputationService.cs @@ -0,0 +1,62 @@ +using Kruzya.TelegramBot.Core.Extensions; +using Kruzya.TelegramBot.Core.Service; +using Microsoft.EntityFrameworkCore; +using Telegram.Bot.Types; +using West.TelegramBot.Reputation.Data; + +namespace West.TelegramBot.Reputation.Service; + +public class ReputationService : IReputation +{ + private readonly ReputationContext _reputationContext; + + public ReputationService(ReputationContext reputationContext) + { + _reputationContext = reputationContext; + } + + public async Task GetUserReputationAsync(Chat chat, User user) + { + var rep = await FindUserReputation(chat, user); + + return rep?.Value ?? 0; + } + + public async Task SetUserReputationAsync(Chat chat, User user, double value) + { + var rep = await FindUserReputation(chat, user) ?? new UserReputation + { + ChatId = chat.Id, + UserId = user.Id + }; + + rep.Value = Math.Round(value, 2); + _reputationContext.AddOrUpdate(rep); + + return rep.Value; + } + + public async Task IncrementReputationAsync(Chat chat, User user, double diff) + { + await SetUserReputationAsync(chat, user, await GetUserReputationAsync(chat, user) + diff); + } + + public async Task GetReputationDiffAsync(Chat chat, User sender, User receiver) + { + var senderRep = await FindUserReputation(chat, sender); + + if (senderRep == null) + { + return 1; + } + + var senderValue = senderRep.Value; + return Math.Round(senderValue == 0 ? 1 : Math.Sqrt(senderValue), 2); + } + + private async Task FindUserReputation(Chat chat, User user) + { + return await _reputationContext.UserReputation.SingleOrDefaultAsync( + e => e.ChatId == chat.Id && e.UserId == user.Id); + } +} \ No newline at end of file From 2d4b36f8c3b1b3e90ddb4185ad3a0f42ebedddd8 Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 00:22:08 +0200 Subject: [PATCH 2/8] [ci] Update Dockerfile to build reputation module. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index da3dfa6..167e1dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ COPY ./modules/ChatManagement/*.csproj ./modules/ChatManagement/ COPY ./modules/Entertainment/*.csproj ./modules/Entertainment/ COPY ./modules/UserGroupTag/*.csproj ./modules/UserGroupTag/ COPY ./modules/ChatQuotes/*.csproj ./modules/ChatQuotes/ +COPY ./modules/Reputation/*.csproj ./modules/Reputation/ RUN dotnet restore Kruzya.TelegramBot.sln # Copy all project files From d3ec6cfb4b70cb2097485ee48194149969c38f8c Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 10:20:07 +0200 Subject: [PATCH 3/8] [whois] Move whois to separate handler in ChatManagement. --- modules/ChatManagement/Handler/Whois.cs | 67 ++++++++++++++++++++++++ modules/Reputation/Handler/Reputation.cs | 39 -------------- 2 files changed, 67 insertions(+), 39 deletions(-) create mode 100644 modules/ChatManagement/Handler/Whois.cs diff --git a/modules/ChatManagement/Handler/Whois.cs b/modules/ChatManagement/Handler/Whois.cs new file mode 100644 index 0000000..2b38905 --- /dev/null +++ b/modules/ChatManagement/Handler/Whois.cs @@ -0,0 +1,67 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using BotFramework.Attributes; +using BotFramework.Enums; +using Kruzya.TelegramBot.Core; +using Kruzya.TelegramBot.Core.Data; +using Kruzya.TelegramBot.Core.Extensions; +using Kruzya.TelegramBot.Core.Service; +using Microsoft.Extensions.DependencyInjection; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace West.TelegramBot.ChatManagement.Handler; + +public class WhoIs : CommonHandler +{ + private readonly IReputation? _reputation; + private readonly UserService _userService; + + public WhoIs(CoreContext db, UserService userService, IServiceProvider provider) : base(db) + { + _reputation = provider.GetService(); + _userService = userService; + } + + [Command("whois", CommandParseMode.Both)] + public async Task HandleWhoIs() + { + var user = RepliedUser ?? From; + var message = $"{user.ToHtml()}\n"; + if (user.IsBot) + { + message += "Бот"; + } + else + { + message += $"{await GetChatMemberStatusString(user)}\n"; + if (_reputation != null) + { + message += $"Репутация: {await _reputation.GetUserReputationAsync(Chat, user)}\n"; + } + message += $"Предупреждения: {(await GetWarnOption(user)).GetValue()}"; + } + + await Reply(message); + } + + private async Task GetChatMemberStatusString(User user) + { + var customStatus = _userService.GetUserCustomStatus(user.Id); + if (customStatus != null) + { + return customStatus; + } + + var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id); + return chatMember.Status switch + { + ChatMemberStatus.Creator => "Владелец", + ChatMemberStatus.Restricted => "Заблокированный", + ChatMemberStatus.Administrator => "Администратор", + _ => "Пользователь" + }; + } +} \ No newline at end of file diff --git a/modules/Reputation/Handler/Reputation.cs b/modules/Reputation/Handler/Reputation.cs index 28cb0be..71451a0 100644 --- a/modules/Reputation/Handler/Reputation.cs +++ b/modules/Reputation/Handler/Reputation.cs @@ -8,9 +8,7 @@ using Kruzya.TelegramBot.Core.Data; using Kruzya.TelegramBot.Core.Extensions; using Kruzya.TelegramBot.Core.Service; using Microsoft.Extensions.Logging; -using Telegram.Bot; using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums; namespace West.TelegramBot.Reputation.Handler; @@ -53,25 +51,6 @@ public class Reputation : CommonHandler _logger = logger; } - [Command("whois", CommandParseMode.Both)] - public async Task HandleWhois() - { - var user = RepliedUser ?? From; - var message = $"{user.ToHtml()}\n"; - if (user.IsBot) - { - message += "Бот"; - } - else - { - message += $"{await GetChatMemberStatusString(user)}\n"; - message += $"Репутация: {await GetUserReputation(user)}\n"; - message += $"Предупреждения: {(await GetWarnOption(user)).GetValue()}"; - } - - await Reply(message); - } - [Message(InChat.Public, MessageFlag.HasText | MessageFlag.HasSticker)] public async Task HandleReputation() { @@ -154,22 +133,4 @@ public class Reputation : CommonHandler { return await _reputationService.GetUserReputationAsync(Chat, user); } - - private async Task GetChatMemberStatusString(User user) - { - var customStatus = _userService.GetUserCustomStatus(user.Id); - if (customStatus != null) - { - return customStatus; - } - - var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id); - return chatMember.Status switch - { - ChatMemberStatus.Creator => "Владелец", - ChatMemberStatus.Restricted => "Заблокированный", - ChatMemberStatus.Administrator => "Администратор", - _ => "Пользователь" - }; - } } \ No newline at end of file From 05778f7960ea95f3f04fbc93c5c9c77000980aa4 Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 10:21:35 +0200 Subject: [PATCH 4/8] [reputation] Use file-scoped namespaces. --- .../Migrations/20220213182703_Initial.cs | 43 +++++++++---------- .../ReputationContextModelSnapshot.cs | 43 +++++++++---------- modules/Reputation/Reputation.cs | 21 +++++---- 3 files changed, 52 insertions(+), 55 deletions(-) diff --git a/modules/Reputation/Migrations/20220213182703_Initial.cs b/modules/Reputation/Migrations/20220213182703_Initial.cs index f922ab9..85524a7 100644 --- a/modules/Reputation/Migrations/20220213182703_Initial.cs +++ b/modules/Reputation/Migrations/20220213182703_Initial.cs @@ -2,16 +2,16 @@ #nullable disable -namespace West.TelegramBot.Reputation.Migrations -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); +namespace West.TelegramBot.Reputation.Migrations; - migrationBuilder.CreateTable( +public partial class Initial : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( name: "UserReputation", columns: table => new { @@ -23,18 +23,17 @@ namespace West.TelegramBot.Reputation.Migrations { table.PrimaryKey("PK_UserReputation", x => new { x.ChatId, x.UserId }); }) - .Annotation("MySql:CharSet", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.CreateIndex( - name: "IX_UserReputation_ChatId", - table: "UserReputation", - column: "ChatId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UserReputation"); - } + migrationBuilder.CreateIndex( + name: "IX_UserReputation_ChatId", + table: "UserReputation", + column: "ChatId"); } -} + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserReputation"); + } +} \ No newline at end of file diff --git a/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs b/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs index f3997db..ee5bd09 100644 --- a/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs +++ b/modules/Reputation/Migrations/ReputationContextModelSnapshot.cs @@ -5,36 +5,35 @@ using West.TelegramBot.Reputation.Data; #nullable disable -namespace West.TelegramBot.Reputation.Migrations +namespace West.TelegramBot.Reputation.Migrations; + +[DbContext(typeof(ReputationContext))] +partial class ReputationContextModelSnapshot : ModelSnapshot { - [DbContext(typeof(ReputationContext))] - partial class ReputationContextModelSnapshot : ModelSnapshot + protected override void BuildModel(ModelBuilder modelBuilder) { - protected override void BuildModel(ModelBuilder modelBuilder) - { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder + .HasAnnotation("ProductVersion", "6.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); - modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b => - { - b.Property("ChatId") - .HasColumnType("bigint"); + modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b => + { + b.Property("ChatId") + .HasColumnType("bigint"); - b.Property("UserId") - .HasColumnType("bigint"); + b.Property("UserId") + .HasColumnType("bigint"); - b.Property("Value") - .HasColumnType("double"); + b.Property("Value") + .HasColumnType("double"); - b.HasKey("ChatId", "UserId"); + b.HasKey("ChatId", "UserId"); - b.HasIndex("ChatId"); + b.HasIndex("ChatId"); - b.ToTable("UserReputation"); - }); + b.ToTable("UserReputation"); + }); #pragma warning restore 612, 618 - } } -} +} \ No newline at end of file diff --git a/modules/Reputation/Reputation.cs b/modules/Reputation/Reputation.cs index b23fcc1..ff524b8 100644 --- a/modules/Reputation/Reputation.cs +++ b/modules/Reputation/Reputation.cs @@ -6,20 +6,19 @@ using Microsoft.Extensions.DependencyInjection; using West.TelegramBot.Reputation.Data; using West.TelegramBot.Reputation.Service; -namespace West.TelegramBot.Reputation +namespace West.TelegramBot.Reputation; + +public class Reputation : Module { - public class Reputation : Module + public Reputation(Core core) : base(core) { - public Reputation(Core core) : base(core) - { - } + } - public override void ConfigureServices(IServiceCollection services) - { - var dsn = Configuration.GetConnectionString("Reputation"); - services.AddDbContext(options => options.UseMySql(dsn, ServerVersion.AutoDetect(dsn))); + public override void ConfigureServices(IServiceCollection services) + { + var dsn = Configuration.GetConnectionString("Reputation"); + services.AddDbContext(options => options.UseMySql(dsn, ServerVersion.AutoDetect(dsn))); - services.AddScoped(); - } + services.AddScoped(); } } \ No newline at end of file From f80b5d0da86484275ebd4c30b3247bc7beb875c4 Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 10:35:28 +0200 Subject: [PATCH 5/8] [cm] Use file-scoped namespaces. --- modules/ChatManagement/Handler/Ban.cs | 53 ++++++++-------- modules/ChatManagement/Handler/Warn.cs | 87 +++++++++++++------------- 2 files changed, 69 insertions(+), 71 deletions(-) diff --git a/modules/ChatManagement/Handler/Ban.cs b/modules/ChatManagement/Handler/Ban.cs index 019e812..2d96447 100644 --- a/modules/ChatManagement/Handler/Ban.cs +++ b/modules/ChatManagement/Handler/Ban.cs @@ -8,37 +8,36 @@ using Kruzya.TelegramBot.Core.Data; using Telegram.Bot; using Telegram.Bot.Types; -namespace West.TelegramBot.ChatManagement.Handler +namespace West.TelegramBot.ChatManagement.Handler; + +public class Ban : CommonHandler { - public class Ban : CommonHandler + public Ban(CoreContext db) : base(db) {} + + [Command("ban", CommandParseMode.Both)] + public async Task HandleBan() { - public Ban(CoreContext db) : base(db) {} + if (!await CanPunish()) return; - [Command("ban", CommandParseMode.Both)] - public async Task HandleBan() - { - if (!await CanPunish()) return; + await BanMember(RepliedUser!); + } - await BanMember(RepliedUser!); - } + [Command("unban", CommandParseMode.Both)] + public async Task HandleUnban() + { + if (!await CanPunish()) return; - [Command("unban", CommandParseMode.Both)] - public async Task HandleUnban() - { - if (!await CanPunish()) return; - - await Bot.RestrictChatMemberAsync(Chat.Id, RepliedUser!.Id, - new ChatPermissions - { - CanSendMessages = true, - CanChangeInfo = true, - CanInviteUsers = true, - CanPinMessages = true, - CanSendPolls = true, - CanSendMediaMessages = true, - CanSendOtherMessages = true, - CanAddWebPagePreviews = true - }); - } + await Bot.RestrictChatMemberAsync(Chat.Id, RepliedUser!.Id, + new ChatPermissions + { + CanSendMessages = true, + CanChangeInfo = true, + CanInviteUsers = true, + CanPinMessages = true, + CanSendPolls = true, + CanSendMediaMessages = true, + CanSendOtherMessages = true, + CanAddWebPagePreviews = true + }); } } \ No newline at end of file diff --git a/modules/ChatManagement/Handler/Warn.cs b/modules/ChatManagement/Handler/Warn.cs index 72419e4..282ef39 100644 --- a/modules/ChatManagement/Handler/Warn.cs +++ b/modules/ChatManagement/Handler/Warn.cs @@ -10,54 +10,53 @@ using Kruzya.TelegramBot.Core.Extensions; using Telegram.Bot; using Telegram.Bot.Types.Enums; -namespace West.TelegramBot.ChatManagement.Handler +namespace West.TelegramBot.ChatManagement.Handler; + +public class Warn : CommonHandler { - public class Warn : CommonHandler + public Warn(CoreContext db) : base(db) {} + + [Command("warn", CommandParseMode.Both)] + public async Task HandleWarn() { - public Warn(CoreContext db) : base(db) {} - - [Command("warn", CommandParseMode.Both)] - public async Task HandleWarn() + if (!await CanPunish()) return; + + var warnUser = RepliedUser!; + var warnOption = await GetWarnOption(warnUser); + var warnCount = warnOption!.GetValue() + 1; + warnOption.SetValue(warnCount); + Db.AddOrUpdate(warnOption); + + await Bot.SendTextMessageAsync(Chat.Id, $"{warnUser.ToHtml()}, Вам выдано предупреждение! " + + $"Текущее кол-во: {warnCount}/3", + disableNotification: true, + parseMode: ParseMode.Html); + + if (warnCount == 3) { - if (!await CanPunish()) return; - - var warnUser = RepliedUser!; - var warnOption = await GetWarnOption(warnUser); - var warnCount = warnOption!.GetValue() + 1; - warnOption.SetValue(warnCount); + await BanMember(warnUser); + warnOption.SetValue(0); Db.AddOrUpdate(warnOption); - - await Bot.SendTextMessageAsync(Chat.Id, $"{warnUser.ToHtml()}, Вам выдано предупреждение! " + - $"Текущее кол-во: {warnCount}/3", - disableNotification: true, - parseMode: ParseMode.Html); - - if (warnCount == 3) - { - await BanMember(warnUser); - warnOption.SetValue(0); - Db.AddOrUpdate(warnOption); - } - } - - [Command("unwarn", CommandParseMode.Both)] - public async Task HandleUnwarn() - { - if (!await CanPunish()) return; - - var warnOption = await GetWarnOption(RepliedUser!); - var warnCount = warnOption.GetValue(); - if (warnCount == 0) return; - - warnCount = Math.Max(warnCount - 1, 0); - warnOption.SetValue(warnCount); - Db.AddOrUpdate(warnOption); - - await Bot.SendTextMessageAsync(Chat.Id, - $"{RepliedUser.ToHtml()}, с Вас снято предупреждение! " + - $"Текущее кол-во: {warnCount}/3", - disableNotification: true, - parseMode: ParseMode.Html); } } + + [Command("unwarn", CommandParseMode.Both)] + public async Task HandleUnwarn() + { + if (!await CanPunish()) return; + + var warnOption = await GetWarnOption(RepliedUser!); + var warnCount = warnOption.GetValue(); + if (warnCount == 0) return; + + warnCount = Math.Max(warnCount - 1, 0); + warnOption.SetValue(warnCount); + Db.AddOrUpdate(warnOption); + + await Bot.SendTextMessageAsync(Chat.Id, + $"{RepliedUser.ToHtml()}, с Вас снято предупреждение! " + + $"Текущее кол-во: {warnCount}/3", + disableNotification: true, + parseMode: ParseMode.Html); + } } \ No newline at end of file From e885613c585a0779c4f8d4c9008c2833f9f3ab1e Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 14:39:55 +0200 Subject: [PATCH 6/8] [reputation] Add rating. --- Core/Data/IReputationEntity.cs | 8 +++ Core/Service/IReputation.cs | 5 +- Core/Service/UserService.cs | 51 ++++++++++++++++++- modules/Reputation/Data/UserReputation.cs | 8 +-- modules/Reputation/Handler/Reputation.cs | 21 ++++++-- .../Reputation/Service/ReputationService.cs | 13 ++++- 6 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 Core/Data/IReputationEntity.cs diff --git a/Core/Data/IReputationEntity.cs b/Core/Data/IReputationEntity.cs new file mode 100644 index 0000000..282d3b3 --- /dev/null +++ b/Core/Data/IReputationEntity.cs @@ -0,0 +1,8 @@ +namespace Kruzya.TelegramBot.Core.Data; + +public interface IReputationEntity +{ + public long UserId { get; set; } + public long ChatId { get; set; } + public double Value { get; set; } +} \ No newline at end of file diff --git a/Core/Service/IReputation.cs b/Core/Service/IReputation.cs index de51f00..ab1472d 100644 --- a/Core/Service/IReputation.cs +++ b/Core/Service/IReputation.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kruzya.TelegramBot.Core.Data; using Telegram.Bot.Types; namespace Kruzya.TelegramBot.Core.Service @@ -9,5 +11,6 @@ namespace Kruzya.TelegramBot.Core.Service public Task SetUserReputationAsync(Chat chat, User user, double value); public Task GetReputationDiffAsync(Chat chat, User sender, User receiver); public Task IncrementReputationAsync(Chat chat, User user, double diff); + public Task> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0); } } diff --git a/Core/Service/UserService.cs b/Core/Service/UserService.cs index 251d02e..bf9368d 100644 --- a/Core/Service/UserService.cs +++ b/Core/Service/UserService.cs @@ -3,7 +3,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using BotFramework.Abstractions; +using Kruzya.TelegramBot.Core.Cache; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Telegram.Bot; using Telegram.Bot.Types; namespace Kruzya.TelegramBot.Core.Service @@ -11,10 +16,17 @@ namespace Kruzya.TelegramBot.Core.Service public class UserService { private readonly List _superUsers; - private readonly Dictionary _statusMap; + private readonly ICacheStorage _userCache; + private readonly ITelegramBotClient _bot; + private readonly ILogger _logger; - public UserService(IConfiguration configuration) + + public UserService( + IConfiguration configuration, + IBotInstance bot, + ICacheStorage userCache, + ILogger logger) { _superUsers = configuration.GetSection("SuperUsers") .GetChildren() @@ -24,6 +36,41 @@ namespace Kruzya.TelegramBot.Core.Service _statusMap = configuration.GetSection("CustomMemberStatus") .GetChildren() .ToDictionary(x => Convert.ToInt64(x.Key), x => x.Value); + + _userCache = userCache; + _bot = bot.BotClient; + _logger = logger; + } + + public async Task GetUser(long userId, long chatId, bool bypassCache = false) + { + if (bypassCache || !_userCache.TryGetValue(userId, out var user)) + { + if (bypassCache) + { + _logger.LogDebug("Bypassing cache."); + } + else + { + _logger.LogDebug("User {UserId} is not found in cache. Fetching from Telegram API.", userId); + } + + user = await GetUserByChat(chatId, userId); + CacheUser(user); + } + + return user; + } + + private void CacheUser(User user) + { + // TODO: config entry for TTL + _userCache.SetValue(user.Id, user, Convert.ToInt32(TimeSpan.FromHours(1).TotalSeconds)); + } + + private async Task GetUserByChat(long chatId, long userId) + { + return (await _bot.GetChatMemberAsync(chatId, userId)).User; } public bool IsUserSuper(User user) diff --git a/modules/Reputation/Data/UserReputation.cs b/modules/Reputation/Data/UserReputation.cs index 8326a9e..3b06cf8 100644 --- a/modules/Reputation/Data/UserReputation.cs +++ b/modules/Reputation/Data/UserReputation.cs @@ -1,10 +1,10 @@ -namespace West.TelegramBot.Reputation.Data; +using Kruzya.TelegramBot.Core.Data; -public class UserReputation +namespace West.TelegramBot.Reputation.Data; + +public class UserReputation : IReputationEntity { public long UserId { get; set; } - public long ChatId { get; set; } - public double Value { get; set; } } \ No newline at end of file diff --git a/modules/Reputation/Handler/Reputation.cs b/modules/Reputation/Handler/Reputation.cs index 71451a0..590076d 100644 --- a/modules/Reputation/Handler/Reputation.cs +++ b/modules/Reputation/Handler/Reputation.cs @@ -17,7 +17,7 @@ public class Reputation : CommonHandler private readonly UserService _userService; private readonly ILogger _logger; private readonly IReputation _reputationService; - + private static readonly List RepUpChars = new() { "+", @@ -43,8 +43,7 @@ public class Reputation : CommonHandler CoreContext db, UserService userService, IReputation reputationService, - ILogger logger - ) : base(db) + ILogger logger) : base(db) { _reputationService = reputationService; _userService = userService; @@ -129,6 +128,22 @@ public class Reputation : CommonHandler ); } + [Command(InChat.Public, "top", CommandParseMode.Both)] + public async Task HandleTop() + { + var msg = new HtmlString(); + msg.TextBr("Топ репутации чата: "); + var i = 1; + foreach (var rep in await _reputationService.GetChatRatingAsync(Chat)) + { + var user = await _userService.GetUser(rep.UserId, rep.ChatId); + msg.Text($"{i}. ").UserMention(user).Text($" ({rep.Value})").Br(); + i++; + } + + await Bot.SendHtmlStringAsync(Chat, msg); + } + private async Task GetUserReputation(User user) { return await _reputationService.GetUserReputationAsync(Chat, user); diff --git a/modules/Reputation/Service/ReputationService.cs b/modules/Reputation/Service/ReputationService.cs index 00bc57b..07e0a16 100644 --- a/modules/Reputation/Service/ReputationService.cs +++ b/modules/Reputation/Service/ReputationService.cs @@ -1,4 +1,5 @@ -using Kruzya.TelegramBot.Core.Extensions; +using Kruzya.TelegramBot.Core.Data; +using Kruzya.TelegramBot.Core.Extensions; using Kruzya.TelegramBot.Core.Service; using Microsoft.EntityFrameworkCore; using Telegram.Bot.Types; @@ -54,6 +55,16 @@ public class ReputationService : IReputation return Math.Round(senderValue == 0 ? 1 : Math.Sqrt(senderValue), 2); } + public async Task> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0) + { + return await _reputationContext.UserReputation + .Where(r => r.ChatId == chat.Id && r.Value != 0D) + .OrderByDescending(r => r.Value) + .Skip(offset) + .Take(limit) + .ToListAsync(); + } + private async Task FindUserReputation(Chat chat, User user) { return await _reputationContext.UserReputation.SingleOrDefaultAsync( From 46a0e375c526061321077309ce9bcf8c8c109eb9 Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 14:42:40 +0200 Subject: [PATCH 7/8] [reputation] Fetch only positive reputation for rating. --- modules/Reputation/Service/ReputationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Reputation/Service/ReputationService.cs b/modules/Reputation/Service/ReputationService.cs index 07e0a16..d3877b0 100644 --- a/modules/Reputation/Service/ReputationService.cs +++ b/modules/Reputation/Service/ReputationService.cs @@ -58,7 +58,7 @@ public class ReputationService : IReputation public async Task> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0) { return await _reputationContext.UserReputation - .Where(r => r.ChatId == chat.Id && r.Value != 0D) + .Where(r => r.ChatId == chat.Id && r.Value > 0D) .OrderByDescending(r => r.Value) .Skip(offset) .Take(limit) From 12a4a0ae83e1eed5b69a288a4eb2885d9f786986 Mon Sep 17 00:00:00 2001 From: West14 <30056636+West14@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:00:51 +0200 Subject: [PATCH 8/8] More detailed logging in UserService. Rename WhoIs handler to math the classname. --- Core/Service/UserService.cs | 2 +- modules/ChatManagement/Handler/{Whois.cs => WhoIs.cs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename modules/ChatManagement/Handler/{Whois.cs => WhoIs.cs} (100%) diff --git a/Core/Service/UserService.cs b/Core/Service/UserService.cs index bf9368d..5fa261a 100644 --- a/Core/Service/UserService.cs +++ b/Core/Service/UserService.cs @@ -48,7 +48,7 @@ namespace Kruzya.TelegramBot.Core.Service { if (bypassCache) { - _logger.LogDebug("Bypassing cache."); + _logger.LogDebug("Bypassing cache for {UserId}.", userId); } else { diff --git a/modules/ChatManagement/Handler/Whois.cs b/modules/ChatManagement/Handler/WhoIs.cs similarity index 100% rename from modules/ChatManagement/Handler/Whois.cs rename to modules/ChatManagement/Handler/WhoIs.cs