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}",