From ff63d6dfaa11666d89841267c44c63460fb36e5e Mon Sep 17 00:00:00 2001 From: Sergey Gut Date: Sat, 22 Jan 2022 17:13:38 +0300 Subject: [PATCH] Code cleanup --- Core/Core.cs | 12 +- Core/Data/BotUserValue.cs | 3 +- Core/Extensions/DbContextExtension.cs | 40 ++++++ .../RepositoryExtension.CoreContext.cs | 87 +++++++++++++ Core/Extensions/RepositoryExtension.cs | 121 +----------------- Core/Service/EntityFormatter.cs | 83 ++++++++++++ modules/ChatManagement/Handler/Reputation.cs | 4 +- modules/ChatManagement/Handler/Rules.cs | 4 +- modules/ChatManagement/Handler/Warn.cs | 4 +- .../Handler/Fun/AbstractAnimationReply.cs | 4 +- modules/RichSiteSummary/Handler/FeedList.cs | 4 +- modules/RichSiteSummary/Service/RssFetch.cs | 2 +- modules/UserGroupTag/Handler.cs | 2 +- 13 files changed, 232 insertions(+), 138 deletions(-) create mode 100644 Core/Extensions/DbContextExtension.cs create mode 100644 Core/Extensions/RepositoryExtension.CoreContext.cs create mode 100644 Core/Service/EntityFormatter.cs diff --git a/Core/Core.cs b/Core/Core.cs index f404d9a..d733d09 100644 --- a/Core/Core.cs +++ b/Core/Core.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -107,7 +109,7 @@ namespace Kruzya.TelegramBot.Core /// /// The full path to assembly file /// if success, null otherwise. - private IEnumerable LoadAssembly(string path) + private IEnumerable? LoadAssembly(string path) { Assembly assembly; try @@ -150,7 +152,7 @@ namespace Kruzya.TelegramBot.Core var module = _modules.FirstOrDefault(module => module.GetType() == moduleType); if (module == null) { - module = (Module) Activator.CreateInstance(moduleType, this); + module = (Module) Activator.CreateInstance(moduleType, this)!; Console.WriteLine($"Started module {module.GetType().Name}"); _modules.Add(module); } @@ -169,12 +171,12 @@ namespace Kruzya.TelegramBot.Core var possiblePaths = new List(); if (args.RequestingAssembly != null) { - var modulePath = Path.GetDirectoryName(args.RequestingAssembly.Location); + var modulePath = Path.GetDirectoryName(args.RequestingAssembly.Location)!; possiblePaths.Add(modulePath); possiblePaths.Add(args.RequestingAssembly.Location.Replace(".dll", "")); } - possiblePaths.Add(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); + possiblePaths.Add(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!); possiblePaths.Add(Environment.CurrentDirectory); foreach (var basePath in possiblePaths) diff --git a/Core/Data/BotUserValue.cs b/Core/Data/BotUserValue.cs index f4f1622..3922469 100644 --- a/Core/Data/BotUserValue.cs +++ b/Core/Data/BotUserValue.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Text.Json; +using System.Text.Json.Serialization; using System.Xml.Serialization; namespace Kruzya.TelegramBot.Core.Data @@ -35,7 +36,7 @@ namespace Kruzya.TelegramBot.Core.Data public void SetValue(T value) { Value = JsonSerializer.SerializeToUtf8Bytes(value, new JsonSerializerOptions { WriteIndented = false, - IgnoreNullValues = true }); + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); ValueType = value.GetType().FullName; } } diff --git a/Core/Extensions/DbContextExtension.cs b/Core/Extensions/DbContextExtension.cs new file mode 100644 index 0000000..a325a10 --- /dev/null +++ b/Core/Extensions/DbContextExtension.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore; + +namespace Kruzya.TelegramBot.Core.Extensions; + +public static class DbContextExtension +{ + /// + /// Marks the entity as created. + /// + /// + /// Entity for marking as created. + [Obsolete("DbContextExtension.MarkAsCreated is obsolete, use DbSet.Add instead")] + public static void MarkAsCreated(this DbContext dbContext, object entity) => + dbContext.Entry(entity).State = EntityState.Added; + + /// + /// Marks the entity as deleted. + /// + /// + /// Entity for marking as deleted. + [Obsolete("DbContextExtension.MarkAsDeleted is obsolete, use DbSet.Remove instead")] + public static void MarkAsDeleted(this DbContext dbContext, object entity) => + dbContext.Entry(entity).State = EntityState.Deleted; + + /// + /// Marks the entity as modified. + /// + /// + /// Entity for marking as modified. + [Obsolete("DbContextExtension.MarkAsModified is obsolete, use DbContext.Update instead")] + public static void MarkAsModified(this DbContext dbContext, object entity) + { + var entry = dbContext.Entry(entity); + if (entry.State == EntityState.Added) + return; + + entry.State = EntityState.Modified; + } +} diff --git a/Core/Extensions/RepositoryExtension.CoreContext.cs b/Core/Extensions/RepositoryExtension.CoreContext.cs new file mode 100644 index 0000000..bf89ce4 --- /dev/null +++ b/Core/Extensions/RepositoryExtension.CoreContext.cs @@ -0,0 +1,87 @@ +using System.Threading.Tasks; +using Kruzya.TelegramBot.Core.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Kruzya.TelegramBot.Core.Extensions +{ + public static partial class RepositoryExtension + { + #region BotUser + + public static async Task FindOrCreate(this DbSet repository, long chatId, long userId) + { + var user = await repository.SingleOrDefaultAsync(e => e.ChatId == chatId && e.UserId == userId); + if (user == null) + { + user = repository.Create(); + user.ChatId = chatId; + user.UserId = userId; + } + + return user; + } + + #endregion + + #region BotUserValue + + #region FindOption + + public static async Task FindOption(this DbSet repository, long chatId, + string option) => await repository.FindOption(chatId, 0, option); + + public static async Task FindOption(this DbSet repository, BotUser user, + string option) => await repository.FindOption(user.ChatId, user.UserId, option); + + public static async Task FindOption(this DbSet repository, long chatId, long userId, + string option) => await repository.SingleOrDefaultAsync(e => + e.BotUser.ChatId == chatId && e.BotUser.UserId == userId && e.Name == option); + + #endregion + + #region FindOrCreateOption + + public static async Task FindOrCreateOption(this DbSet repository, long chatId, + string option) => await repository.FindOrCreateOption(chatId, 0, option); + + public static async Task FindOrCreateOption(this DbSet repository, BotUser user, + string option) => await repository.FindOrCreateOption(user.ChatId, user.UserId, option); + + public static async Task FindOrCreateOption(this DbSet repository, long chatId, + long userId, string option) + { + var opt = await repository.FindOption(chatId, userId, option); + if (opt == null) + { + opt = repository.Create(); + opt.Name = option; + opt.BotUser = await repository.GetService().Users.FindOrCreate(chatId, userId); + } + + return opt; + } + + #endregion + + #region SetOptionValue + + public static async Task SetOptionValue(this DbSet repository, long chatId, string option, + T val) => await repository.SetOptionValue(chatId, 0, option, val); + + public static async Task SetOptionValue(this DbSet repository, BotUser user, string option, + T val) => await repository.SetOptionValue(user.ChatId, user.UserId, option, val); + + public static async Task SetOptionValue(this DbSet repository, long chatId, long userId, + string option, T val) + { + var opt = await repository.FindOrCreateOption(chatId, userId, option); + opt.SetValue(val); + repository.Update(opt); + } + + #endregion + + #endregion + } +} diff --git a/Core/Extensions/RepositoryExtension.cs b/Core/Extensions/RepositoryExtension.cs index 0c2b834..e44e2c4 100644 --- a/Core/Extensions/RepositoryExtension.cs +++ b/Core/Extensions/RepositoryExtension.cs @@ -5,44 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; namespace Kruzya.TelegramBot.Core.Extensions { - public static class RepositoryExtension + public static partial class RepositoryExtension { - #region DbContext - - /// - /// Marks the entity as created. - /// - /// - /// Entity for marking as created. - public static void MarkAsCreated(this DbContext dbContext, object entity) => - dbContext.Entry(entity).State = EntityState.Added; - - /// - /// Marks the entity as deleted. - /// - /// - /// Entity for marking as deleted. - public static void MarkAsDeleted(this DbContext dbContext, object entity) => - dbContext.Entry(entity).State = EntityState.Deleted; - - /// - /// Marks the entity as modified. - /// - /// - /// Entity for marking as modified. - public static void MarkAsModified(this DbContext dbContext, object entity) - { - var entry = dbContext.Entry(entity); - if (entry.State == EntityState.Added) - return; - - entry.State = EntityState.Modified; - } - - #endregion - - #region Repository - public static TEntity Create(this DbSet repository) where TEntity : class, new() { var entity = new TEntity(); @@ -50,88 +14,5 @@ namespace Kruzya.TelegramBot.Core.Extensions return entity; } - - #endregion - - #region Core-context repository extensions - - #region BotUser - - public static async Task FindOrCreate(this DbSet repository, long chatId, long userId) - { - var user = await repository.SingleOrDefaultAsync(e => e.ChatId == chatId && e.UserId == userId); - if (user == null) - { - user = repository.Create(); - user.ChatId = chatId; - user.UserId = userId; - } - - return user; - } - - #endregion - - #region BotUserValue - - #region FindOption - - public static async Task FindOption(this DbSet repository, long chatId, - string option) => await repository.FindOption(chatId, 0, option); - - public static async Task FindOption(this DbSet repository, BotUser user, - string option) => await repository.FindOption(user.ChatId, user.UserId, option); - - public static async Task FindOption(this DbSet repository, long chatId, long userId, - string option) => await repository.SingleOrDefaultAsync(e => - e.BotUser.ChatId == chatId && e.BotUser.UserId == userId && e.Name == option); - - #endregion - - #region FindOrCreateOption - - public static async Task FindOrCreateOption(this DbSet repository, long chatId, - string option) => await repository.FindOrCreateOption(chatId, 0, option); - - public static async Task FindOrCreateOption(this DbSet repository, BotUser user, - string option) => await repository.FindOrCreateOption(user.ChatId, user.UserId, option); - - public static async Task FindOrCreateOption(this DbSet repository, long chatId, - long userId, string option) - { - var opt = await repository.FindOption(chatId, userId, option); - if (opt == null) - { - opt = repository.Create(); - opt.Name = option; - opt.BotUser = await repository.GetService().Users.FindOrCreate(chatId, userId); - } - - return opt; - } - - #endregion - - #region SetOptionValue - - public static async Task SetOptionValue(this DbSet repository, long chatId, string option, - T val) => await repository.SetOptionValue(chatId, 0, option, val); - - public static async Task SetOptionValue(this DbSet repository, BotUser user, string option, - T val) => await repository.SetOptionValue(user.ChatId, user.UserId, option, val); - - public static async Task SetOptionValue(this DbSet repository, long chatId, long userId, - string option, T val) - { - var opt = await repository.FindOrCreateOption(chatId, userId, option); - opt.SetValue(val); - repository.Update(opt); - } - - #endregion - - #endregion - - #endregion } } \ No newline at end of file diff --git a/Core/Service/EntityFormatter.cs b/Core/Service/EntityFormatter.cs new file mode 100644 index 0000000..e53d4d8 --- /dev/null +++ b/Core/Service/EntityFormatter.cs @@ -0,0 +1,83 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace Kruzya.TelegramBot.Core.Service; + +internal class EntityFormatter +{ + private static readonly Dictionary HtmlMap = new() + { + { MessageEntityType.Bold, "{0}" }, + { MessageEntityType.Code, "{0}" }, + { MessageEntityType.Italic, "{0}" }, + { MessageEntityType.TextMention, "{0}" }, + { MessageEntityType.Pre, "
{0}
" }, + { MessageEntityType.Underline, "{0}" }, + { MessageEntityType.TextLink, "{0}" } + }; + + private static readonly Dictionary MarkdownMap = new() + { + { MessageEntityType.Bold, "*{0}*" }, + { MessageEntityType.Code, "```{0}```" }, + { MessageEntityType.Italic, "_{0}_" }, + { MessageEntityType.TextMention, "[{0}](tg://user?id={1})" }, + { MessageEntityType.Pre, "```{0}```" }, + { MessageEntityType.Underline, "__{0}__" }, + { MessageEntityType.TextLink, "[{0}]({1})" } + }; + + /// + /// Performs the formatting a text representation of message from entity with + /// + /// + /// + /// + public string? FormatMessage(Message msg, ParseMode parseMode = ParseMode.Html) + { + var text = ""; + var entities = msg.Entities; + + if (entities != null) + { + // Suppress the warning about possibility using empty ref (CS8602). + text = msg.Text!; + + // TODO: Currently, this algo don't support inherited entities. This need thinking. + foreach (var entity in entities.Reverse()) + { + var entityText = text.Substring(entity.Offset, entity.Length); + + text = text.Replace(entityText, FormatEntity(entity, entityText, parseMode)); + } + } + + return text; + } + + public string FormatEntity(MessageEntity entity, string text, ParseMode parseMode) + { + var dict = parseMode switch + { + ParseMode.Html => HtmlMap, + ParseMode.Markdown => MarkdownMap, + ParseMode.MarkdownV2 => MarkdownMap, + _ => throw new Exception("Unsupported parse mode") + }; + + var arg = entity.Type switch + { + MessageEntityType.TextMention => entity.User!.Id.ToString(), + MessageEntityType.TextLink => entity.Url!, + _ => "" + }; + + return dict[entity.Type].Replace("{0}", text) + .Replace("{1}", arg); + } +} diff --git a/modules/ChatManagement/Handler/Reputation.cs b/modules/ChatManagement/Handler/Reputation.cs index 460b0b7..f8ffbb5 100644 --- a/modules/ChatManagement/Handler/Reputation.cs +++ b/modules/ChatManagement/Handler/Reputation.cs @@ -114,7 +114,7 @@ namespace West.TelegramBot.ChatManagement.Handler } receiverRepOption.SetValue(receiverRepCount); - Db.MarkAsModified(receiverRepOption); + Db.Update(receiverRepOption); await Reply($"{From.ToHtml()}({await GetUserReputation(From)}) " + $"{action} репутацию {receiver.ToHtml()}({Math.Round(receiverRepCount, 2)})"); @@ -138,7 +138,7 @@ namespace West.TelegramBot.ChatManagement.Handler reputation = Math.Round(reputation, 2); var opt = await GetReputationOption(RepliedUser); opt.SetValue(reputation); - Db.MarkAsModified(opt); + Db.Update(opt); await Reply( new HtmlString() diff --git a/modules/ChatManagement/Handler/Rules.cs b/modules/ChatManagement/Handler/Rules.cs index 809809a..675d4a6 100644 --- a/modules/ChatManagement/Handler/Rules.cs +++ b/modules/ChatManagement/Handler/Rules.cs @@ -19,7 +19,7 @@ namespace West.TelegramBot.ChatManagement.Handler var opt = await Db.UserValues.FindOrCreateOption(Chat.Id, "rulesMsgId"); opt.SetValue(replyToMessage.MessageId); - Db.MarkAsModified(opt); + Db.Update(opt); } [Command(InChat.Public, "rules", CommandParseMode.Both)] @@ -37,7 +37,7 @@ namespace West.TelegramBot.ChatManagement.Handler } catch (ApiRequestException) { - Db.MarkAsDeleted(opt); + Db.UserValues.Remove(opt); throw; } } diff --git a/modules/ChatManagement/Handler/Warn.cs b/modules/ChatManagement/Handler/Warn.cs index bfff8ad..9e5816e 100644 --- a/modules/ChatManagement/Handler/Warn.cs +++ b/modules/ChatManagement/Handler/Warn.cs @@ -24,7 +24,7 @@ namespace West.TelegramBot.ChatManagement.Handler var warnOption = await GetWarnOption(warnUser); var warnCount = warnOption!.GetValue() + 1; warnOption.SetValue(warnCount); - Db.MarkAsModified(warnOption); + Db.Update(warnOption); await Bot.SendTextMessageAsync(Chat.Id, $"{warnUser.ToHtml()}, Вам выдано предупреждение! " + $"Текущее кол-во: {warnCount}/3", @@ -49,7 +49,7 @@ namespace West.TelegramBot.ChatManagement.Handler warnCount = Math.Max(warnCount - 1, 0); warnOption.SetValue(warnCount); - Db.MarkAsModified(warnOption); + Db.Update(warnOption); await Bot.SendTextMessageAsync(Chat.Id, $"{RepliedUser.ToHtml()}, с Вас снято предупреждение! " + diff --git a/modules/Entertainment/Handler/Fun/AbstractAnimationReply.cs b/modules/Entertainment/Handler/Fun/AbstractAnimationReply.cs index 12fea45..ffcf7c6 100644 --- a/modules/Entertainment/Handler/Fun/AbstractAnimationReply.cs +++ b/modules/Entertainment/Handler/Fun/AbstractAnimationReply.cs @@ -47,7 +47,7 @@ namespace West.Entertainment.Handler.Fun var animationFileOption = await GetAnimationFileOption(); animationFileOption.SetValue(animation.FileId); - _db.MarkAsModified(animationFileOption); + _db.Update(animationFileOption); } // ReSharper disable once MemberCanBeProtected.Global @@ -77,7 +77,7 @@ namespace West.Entertainment.Handler.Fun replyToMessageId: GetMessageIdToReply() ?? Message?.MessageId); nextUseOption.SetValue(DateTime.Now + Cooldown); - _db.MarkAsModified(nextUseOption); + _db.Update(nextUseOption); return true; } diff --git a/modules/RichSiteSummary/Handler/FeedList.cs b/modules/RichSiteSummary/Handler/FeedList.cs index df123de..c354ad0 100644 --- a/modules/RichSiteSummary/Handler/FeedList.cs +++ b/modules/RichSiteSummary/Handler/FeedList.cs @@ -290,7 +290,7 @@ namespace Kruzya.TelegramBot.RichSiteSummary.Handler .Where(subscriber => subscriber.Feed == feed && subscriber.SubscriberId == chat.Id) .Select(subscriber => subscriber.SubscriptionId).FirstAsync(); - _dbContext.MarkAsDeleted(_dbContext.Find(subId)); + _dbContext.Subscriptions.Remove(_dbContext.Find(subId)); } else { @@ -298,7 +298,7 @@ namespace Kruzya.TelegramBot.RichSiteSummary.Handler subscription.Feed = feed; subscription.SubscriberId = chat.Id; - _dbContext.MarkAsCreated(subscription); + _dbContext.Subscriptions.Add(subscription); } await _dbContext.SaveChangesAsync(); diff --git a/modules/RichSiteSummary/Service/RssFetch.cs b/modules/RichSiteSummary/Service/RssFetch.cs index db592ca..4dbc914 100644 --- a/modules/RichSiteSummary/Service/RssFetch.cs +++ b/modules/RichSiteSummary/Service/RssFetch.cs @@ -109,7 +109,7 @@ namespace Kruzya.TelegramBot.RichSiteSummary.Service await ProcessFeed(feed, dbContext); feed.UpdatedAt = DateTime.Now; - dbContext.MarkAsModified(feed); + dbContext.Update(feed); } await dbContext.SaveChangesAsync(); diff --git a/modules/UserGroupTag/Handler.cs b/modules/UserGroupTag/Handler.cs index 8f748e5..657dedd 100644 --- a/modules/UserGroupTag/Handler.cs +++ b/modules/UserGroupTag/Handler.cs @@ -56,7 +56,7 @@ namespace UserGroupTag tagDictionary[groupName] = memberList; userGroupOption.SetValue(tagDictionary); - _db.MarkAsModified(userGroupOption); + _db.Update(userGroupOption); _userCache.SetValue(repliedMessage.From.Id, repliedMessage.From, Int32.MaxValue); await Bot.SendTextMessageAsync(chatId, $"{from.ToHtml()} добавлен в группу {groupName}",