From 42f24f2f19d71b7110c778b9463d39b64bbabe8a Mon Sep 17 00:00:00 2001 From: Nikolay Kochulin Date: Sun, 8 Dec 2019 16:41:10 +0000 Subject: [PATCH] Add request chains --- JetKarmaBot/Commands/AwardCommand.cs | 205 ++++++++---------- JetKarmaBot/Commands/ChangeLocaleCommand.cs | 124 +++++------ JetKarmaBot/Commands/CommandString.cs | 4 +- JetKarmaBot/Commands/CurrenciesCommand.cs | 32 +-- JetKarmaBot/Commands/HelpCommand.cs | 50 ++--- JetKarmaBot/Commands/IChatCommand.cs | 4 +- JetKarmaBot/Commands/LeaderboardCommand.cs | 70 +++--- JetKarmaBot/Commands/StatusCommand.cs | 108 ++++----- JetKarmaBot/Config.cs | 4 +- JetKarmaBot/JetKarmaBot.cs | 54 ++--- JetKarmaBot/Models/Chat.cs | 2 +- .../{ => Services/Handling}/CommandRouter.cs | 26 ++- JetKarmaBot/Services/Handling/DbHandler.cs | 54 +++++ JetKarmaBot/Services/Handling/RequestChain.cs | 33 +++ .../Services/Handling/RequestContext.cs | 28 +++ .../Services/{ => Handling}/TimeoutManager.cs | 67 +++--- 16 files changed, 456 insertions(+), 409 deletions(-) rename JetKarmaBot/{ => Services/Handling}/CommandRouter.cs (80%) create mode 100644 JetKarmaBot/Services/Handling/DbHandler.cs create mode 100644 JetKarmaBot/Services/Handling/RequestChain.cs create mode 100644 JetKarmaBot/Services/Handling/RequestContext.cs rename JetKarmaBot/Services/{ => Handling}/TimeoutManager.cs (56%) diff --git a/JetKarmaBot/Commands/AwardCommand.cs b/JetKarmaBot/Commands/AwardCommand.cs index 56e541d..5f5e834 100644 --- a/JetKarmaBot/Commands/AwardCommand.cs +++ b/JetKarmaBot/Commands/AwardCommand.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Telegram.Bot; -using Telegram.Bot.Args; -using Telegram.Bot.Types; using Perfusion; -using JetKarmaBot.Services; +using JetKarmaBot.Services.Handling; using NLog; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -16,120 +13,111 @@ namespace JetKarmaBot.Commands { public IReadOnlyCollection Names => new[] { "award", "revoke" }; [Inject] private Logger log; - [Inject] private TimeoutManager Timeout; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) - { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; + var db = ctx.Database; + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; - var awarder = args.Message.From; - string awardTypeText = null; - int recipientId = default(int); - foreach (string arg in cmd.Parameters) + var awarder = ctx.EventArgs.Message.From; + string awardTypeText = null; + int recipientId = default(int); + foreach (string arg in ctx.Command.Parameters) + { + if (arg.StartsWith('@')) { - if (arg.StartsWith('@')) + if (recipientId != default(int)) { - if (recipientId != default(int)) - { - await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } - recipientId = await db.Users.Where(x => x.Username == arg).Select(x => x.UserId).FirstOrDefaultAsync(); - if (recipientId == default(int)) - { - await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errbadusername"]); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } + await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); + return false; } + recipientId = await db.Users.Where(x => x.Username == arg).Select(x => x.UserId).FirstOrDefaultAsync(); + if (recipientId == default(int)) + { + await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errbadusername"]); + return false; + } + } + else + { + if (awardTypeText == null) + awardTypeText = arg; else { - if (awardTypeText == null) - awardTypeText = arg; - else - { - await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } + await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); + return false; } } - - if (args.Message.ReplyToMessage != null && recipientId == default(int)) - { - recipientId = args.Message.ReplyToMessage.From.Id; - } - - if (recipientId == default(int)) - { - await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } - - - bool awarding = cmd.Command == "award"; - - if (awarder.Id == recipientId) - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.award.errawardself"], - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } - - if (CommandRouter.Me.Id == recipientId) - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - awarding - ? currentLocale["jetkarmabot.award.errawardbot"] - : currentLocale["jetkarmabot.award.errrevokebot"], - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("AwardFailure", awarder.Id, db); - return true; - } - - var text = args.Message.Text; - global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null - ? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText) - : await db.AwardTypes.FindAsync((sbyte)1); - await db.Awards.AddAsync(new Models.Award() - { - AwardTypeId = awardType.AwardTypeId, - Amount = (sbyte)(awarding ? 1 : -1), - FromId = awarder.Id, - ToId = recipientId, - ChatId = args.Message.Chat.Id - }); - await db.SaveChangesAsync(); - - var recUserName = (await db.Users.FindAsync(recipientId)).Username; - - log.Debug($"Awarded {(awarding ? 1 : -1)}{awardType.Symbol} to {recUserName}"); - - string message = awarding - ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), recUserName) - : string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), recUserName); - - var currentCount = await db.Awards - .Where(aw => aw.ToId == recipientId && aw.AwardTypeId == awardType.AwardTypeId && aw.ChatId == args.Message.Chat.Id) - .SumAsync(aw => aw.Amount); - - var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, currentCount, awardType.Symbol); - - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - response, - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("AwardSuccess", awarder.Id, db); - return true; } + + if (ctx.EventArgs.Message.ReplyToMessage != null && recipientId == default(int)) + { + recipientId = ctx.EventArgs.Message.ReplyToMessage.From.Id; + } + + if (recipientId == default(int)) + { + await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]); + return false; + } + + + bool awarding = ctx.Command.Command == "award"; + + if (awarder.Id == recipientId) + { + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + currentLocale["jetkarmabot.award.errawardself"], + replyToMessageId: ctx.EventArgs.Message.MessageId); + return false; + } + + if (ctx.GetFeature().Router.Me.Id == recipientId) + { + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + awarding + ? currentLocale["jetkarmabot.award.errawardbot"] + : currentLocale["jetkarmabot.award.errrevokebot"], + replyToMessageId: ctx.EventArgs.Message.MessageId); + return false; + } + + var text = ctx.EventArgs.Message.Text; + global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null + ? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText) + : await db.AwardTypes.FindAsync((sbyte)1); + + var prevCount = await db.Awards + .Where(aw => aw.ToId == recipientId && aw.AwardTypeId == awardType.AwardTypeId && aw.ChatId == ctx.EventArgs.Message.Chat.Id) + .SumAsync(aw => aw.Amount); + + await db.Awards.AddAsync(new Models.Award() + { + AwardTypeId = awardType.AwardTypeId, + Amount = (sbyte)(awarding ? 1 : -1), + FromId = awarder.Id, + ToId = recipientId, + ChatId = ctx.EventArgs.Message.Chat.Id + }); + + var recUserName = (await db.Users.FindAsync(recipientId)).Username; + + log.Debug($"Awarded {(awarding ? 1 : -1)}{awardType.Symbol} to {recUserName}"); + + string message = awarding + ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), recUserName) + : string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), recUserName); + + + var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, prevCount + (awarding ? 1 : -1), awardType.Symbol); + + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + response, + replyToMessageId: ctx.EventArgs.Message.MessageId); + return true; } private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc) @@ -144,10 +132,7 @@ namespace JetKarmaBot.Commands } } - [Inject] KarmaContextFactory Db { get; set; } - [Inject] TelegramBotClient Client { get; set; } [Inject] Localization Locale { get; set; } - [Inject] ChatCommandRouter CommandRouter { get; set; } public string Description => "Awards/revokes an award to a user."; public string DescriptionID => "jetkarmabot.award.help"; diff --git a/JetKarmaBot/Commands/ChangeLocaleCommand.cs b/JetKarmaBot/Commands/ChangeLocaleCommand.cs index f2d3cda..06b47c7 100644 --- a/JetKarmaBot/Commands/ChangeLocaleCommand.cs +++ b/JetKarmaBot/Commands/ChangeLocaleCommand.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using Telegram.Bot; -using Telegram.Bot.Args; using Perfusion; -using JetKarmaBot.Services; +using JetKarmaBot.Services.Handling; using NLog; using System.Linq; using System.Threading.Tasks; @@ -13,76 +11,68 @@ namespace JetKarmaBot.Commands { public IReadOnlyCollection Names => new[] { "changelocale", "locale" }; [Inject] private Logger log; - [Inject] private TimeoutManager Timeout; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) + var db = ctx.Database; + var cmd = ctx.Command; + var args = ctx.EventArgs; + + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + if (cmd.Parameters.Length < 1) { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - if (cmd.Parameters.Length < 1) - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.changelocale.getlocale"], - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); - return true; - } - else if (cmd.Parameters[0] == "list") - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" - + string.Join("\n", Locale.Select(a => a.Key)), - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); - return true; - } - else if (cmd.Parameters[0] == "all") - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.changelocale.errorall"], - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); - return true; - } - string localeId; - if (Locale.ContainsLocale(cmd.Parameters[0])) - localeId = cmd.Parameters[0]; - else - try - { - localeId = Locale.FindByCommonName(cmd.Parameters[0]).Name; - } - catch (LocalizationException e) - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)), - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); - return true; - } - (await db.Chats.FindAsync(args.Message.Chat.Id)).Locale = localeId; - log.Debug($"Changed language of chat {args.Message.Chat.Id} to {localeId}"); - await db.SaveChangesAsync(); - - currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - - await Client.SendTextMessageAsync( - args.Message.Chat.Id, -(currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "") - + currentLocale["jetkarmabot.changelocale.justchanged"], - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("LocaleSuccess", args.Message.From.Id, db); - return true; + await ctx.Client.SendTextMessageAsync( + args.Message.Chat.Id, + currentLocale["jetkarmabot.changelocale.getlocale"], + replyToMessageId: args.Message.MessageId); + return false; } + else if (cmd.Parameters[0] == "list") + { + await ctx.Client.SendTextMessageAsync( + args.Message.Chat.Id, + currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" + + string.Join("\n", Locale.Select(a => a.Key)), + replyToMessageId: args.Message.MessageId); + return false; + } + else if (cmd.Parameters[0] == "all") + { + await ctx.Client.SendTextMessageAsync( + args.Message.Chat.Id, + currentLocale["jetkarmabot.changelocale.errorall"], + replyToMessageId: args.Message.MessageId); + return false; + } + string localeId; + if (Locale.ContainsLocale(cmd.Parameters[0])) + localeId = cmd.Parameters[0]; + else + try + { + localeId = Locale.FindByCommonName(cmd.Parameters[0]).Name; + } + catch (LocalizationException e) + { + await ctx.Client.SendTextMessageAsync( + args.Message.Chat.Id, + currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)), + replyToMessageId: args.Message.MessageId); + return false; + } + (await db.Chats.FindAsync(args.Message.Chat.Id)).Locale = localeId; + log.Debug($"Changed language of chat {args.Message.Chat.Id} to {localeId}"); + + currentLocale = Locale[localeId]; + + await ctx.Client.SendTextMessageAsync( + args.Message.Chat.Id, +(currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "") + + currentLocale["jetkarmabot.changelocale.justchanged"], + replyToMessageId: args.Message.MessageId); + return true; } - [Inject] KarmaContextFactory Db { get; set; } - [Inject] TelegramBotClient Client { get; set; } [Inject] Localization Locale { get; set; } public string Description => "Switches current chat locale to [locale]"; diff --git a/JetKarmaBot/Commands/CommandString.cs b/JetKarmaBot/Commands/CommandString.cs index 5bf3448..9a330d5 100644 --- a/JetKarmaBot/Commands/CommandString.cs +++ b/JetKarmaBot/Commands/CommandString.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; namespace JetKarmaBot.Commands @@ -40,7 +38,7 @@ namespace JetKarmaBot.Commands string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null; string[] parameters = words.Skip(1).ToArray(); - result = new CommandString(cmd, parameters) { UserName = username}; + result = new CommandString(cmd, parameters) { UserName = username }; return true; } diff --git a/JetKarmaBot/Commands/CurrenciesCommand.cs b/JetKarmaBot/Commands/CurrenciesCommand.cs index a4a452a..e8de560 100644 --- a/JetKarmaBot/Commands/CurrenciesCommand.cs +++ b/JetKarmaBot/Commands/CurrenciesCommand.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using Telegram.Bot.Args; using Perfusion; -using JetKarmaBot.Services; -using Telegram.Bot; +using JetKarmaBot.Services.Handling; using Telegram.Bot.Types.Enums; using System.Linq; using System.Threading.Tasks; @@ -12,10 +10,7 @@ namespace JetKarmaBot.Commands { public class CurrenciesCommand : IChatCommand { - [Inject] KarmaContextFactory Db; - [Inject] TelegramBotClient Client { get; set; } [Inject] Localization Locale { get; set; } - [Inject] TimeoutManager Timeout { get; set; } public IReadOnlyCollection Names => new[] { "currencies", "awardtypes" }; public string Description => "Shows all award types"; @@ -24,21 +19,18 @@ namespace JetKarmaBot.Commands public IReadOnlyCollection Arguments => new ChatCommandArgument[] { }; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) - { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - string resp = currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", - (await db.AwardTypes.ToListAsync()).Select(x => $"{x.Symbol} ({x.CommandName}) {currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}")); - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - resp, - replyToMessageId: args.Message.MessageId, - parseMode: ParseMode.Html); - await Timeout.ApplyCost("Currencies", args.Message.From.Id, db); - return true; - } + var db = ctx.Database; + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + string resp = currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", + (await db.AwardTypes.ToListAsync()).Select(x => $"{x.Symbol} ({x.CommandName}) {currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}")); + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + resp, + replyToMessageId: ctx.EventArgs.Message.MessageId, + parseMode: ParseMode.Html); + return true; } } } \ No newline at end of file diff --git a/JetKarmaBot/Commands/HelpCommand.cs b/JetKarmaBot/Commands/HelpCommand.cs index 4c67a02..26fad0d 100644 --- a/JetKarmaBot/Commands/HelpCommand.cs +++ b/JetKarmaBot/Commands/HelpCommand.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using Telegram.Bot.Args; using Perfusion; -using JetKarmaBot.Services; -using Telegram.Bot; +using JetKarmaBot.Services.Handling; using Telegram.Bot.Types.Enums; using System.Threading.Tasks; @@ -10,11 +8,7 @@ namespace JetKarmaBot.Commands { public class HelpCommand : IChatCommand { - [Inject] KarmaContextFactory Db; - [Inject] TelegramBotClient Client { get; set; } [Inject] Localization Locale { get; set; } - [Inject] TimeoutManager Timeout { get; set; } - [Inject] ChatCommandRouter Router; public IReadOnlyCollection Names => new[] { "help" }; public string Description => "Displays help text for all(one) command(s)"; @@ -30,30 +24,28 @@ namespace JetKarmaBot.Commands } }; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) + var db = ctx.Database; + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + var router = ctx.GetFeature().Router; + if (ctx.Command.Parameters.Length < 1) { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - await Timeout.ApplyCost("Help", args.Message.From.Id, db); - if (cmd.Parameters.Length < 1) - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - Router.GetHelpText(currentLocale), - replyToMessageId: args.Message.MessageId, - parseMode: ParseMode.Html); - return true; - } - else - { - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - Router.GetHelpTextFor(cmd.Parameters[0], currentLocale), - replyToMessageId: args.Message.MessageId, - parseMode: ParseMode.Html); - return true; - } + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + router.GetHelpText(currentLocale), + replyToMessageId: ctx.EventArgs.Message.MessageId, + parseMode: ParseMode.Html); + return true; + } + else + { + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + router.GetHelpTextFor(ctx.Command.Parameters[0], currentLocale), + replyToMessageId: ctx.EventArgs.Message.MessageId, + parseMode: ParseMode.Html); + return true; } } } diff --git a/JetKarmaBot/Commands/IChatCommand.cs b/JetKarmaBot/Commands/IChatCommand.cs index 9ebe00c..7565fdd 100644 --- a/JetKarmaBot/Commands/IChatCommand.cs +++ b/JetKarmaBot/Commands/IChatCommand.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Telegram.Bot.Args; +using JetKarmaBot.Services.Handling; namespace JetKarmaBot.Commands { @@ -11,7 +11,7 @@ namespace JetKarmaBot.Commands string DescriptionID { get; } IReadOnlyCollection Arguments { get; } - Task Execute(CommandString cmd, MessageEventArgs messageEventArgs); + Task Execute(RequestContext ctx); } public struct ChatCommandArgument diff --git a/JetKarmaBot/Commands/LeaderboardCommand.cs b/JetKarmaBot/Commands/LeaderboardCommand.cs index 2b55495..b376b05 100644 --- a/JetKarmaBot/Commands/LeaderboardCommand.cs +++ b/JetKarmaBot/Commands/LeaderboardCommand.cs @@ -1,15 +1,9 @@ -using System; using System.Linq; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Perfusion; -using Telegram.Bot; -using Telegram.Bot.Args; -using Telegram.Bot.Types; -using JetKarmaBot.Models; -using JetKarmaBot.Services; +using JetKarmaBot.Services.Handling; namespace JetKarmaBot.Commands { @@ -17,48 +11,42 @@ namespace JetKarmaBot.Commands { public IReadOnlyCollection Names => new[] { "leaderboard" }; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) - { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - var asker = args.Message.From; - var awardTypeName = cmd.Parameters.FirstOrDefault(); + var db = ctx.Database; + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + var asker = ctx.EventArgs.Message.From; + var awardTypeName = ctx.Command.Parameters.FirstOrDefault(); - string response; + string response; - if (string.IsNullOrWhiteSpace(awardTypeName)) - awardTypeName = "star"; + if (string.IsNullOrWhiteSpace(awardTypeName)) + awardTypeName = "star"; - var awardTypeIdQuery = from awt in db.AwardTypes - where awt.CommandName == awardTypeName - select awt.AwardTypeId; - var awardTypeId = await awardTypeIdQuery.FirstAsync(); - var awardType = await db.AwardTypes.FindAsync(awardTypeId); + var awardTypeIdQuery = from awt in db.AwardTypes + where awt.CommandName == awardTypeName + select awt.AwardTypeId; + var awardTypeId = await awardTypeIdQuery.FirstAsync(); + var awardType = await db.AwardTypes.FindAsync(awardTypeId); - response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" + string.Join('\n', - await Task.WhenAll((await db.Awards - .Where(x => x.ChatId == args.Message.Chat.Id && x.AwardTypeId == awardTypeId) - .GroupBy(x => x.ToId) - .Select(x => new { UserId = x.Key, Amount = x.Sum(y => y.Amount) }) - .OrderByDescending(x => x.Amount) - .Take(5) - .ToListAsync()) - .Select(async (x, index) => $"{index + 1}. {(await db.Users.FindAsync(x.UserId)).Username} - {x.Amount}")) - ); + response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" + string.Join('\n', + await Task.WhenAll((await db.Awards + .Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id && x.AwardTypeId == awardTypeId) + .GroupBy(x => x.ToId) + .Select(x => new { UserId = x.Key, Amount = x.Sum(y => y.Amount) }) + .OrderByDescending(x => x.Amount) + .Take(5) + .ToListAsync()) + .Select(async (x, index) => $"{index + 1}. {(await db.Users.FindAsync(x.UserId)).Username} - {x.Amount}")) + ); - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - response, - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("Leaderboard", args.Message.From.Id, db); - return true; - } + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + response, + replyToMessageId: ctx.EventArgs.Message.MessageId); + return true; } - [Inject] KarmaContextFactory Db { get; set; } - [Inject] TelegramBotClient Client { get; set; } - [Inject] TimeoutManager Timeout { get; set; } [Inject] Localization Locale { get; set; } public string Description => "Shows the people with the most of a specific award."; diff --git a/JetKarmaBot/Commands/StatusCommand.cs b/JetKarmaBot/Commands/StatusCommand.cs index f9db04b..fb5c6ad 100644 --- a/JetKarmaBot/Commands/StatusCommand.cs +++ b/JetKarmaBot/Commands/StatusCommand.cs @@ -1,13 +1,7 @@ -using System; using System.Linq; using System.Collections.Generic; -using System.Text; using Perfusion; -using Telegram.Bot; -using Telegram.Bot.Args; -using Telegram.Bot.Types; -using JetKarmaBot.Models; -using JetKarmaBot.Services; +using JetKarmaBot.Services.Handling; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -17,68 +11,62 @@ namespace JetKarmaBot.Commands { public IReadOnlyCollection Names => new[] { "status" }; - public async Task Execute(CommandString cmd, MessageEventArgs args) + public async Task Execute(RequestContext ctx) { - using (var db = Db.GetContext()) + var db = ctx.Database; + var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + var asker = ctx.EventArgs.Message.From; + var awardTypeName = ctx.Command.Parameters.FirstOrDefault(); + bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; + + string response; + + if (string.IsNullOrWhiteSpace(awardTypeName)) { - var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - var asker = args.Message.From; - var awardTypeName = cmd.Parameters.FirstOrDefault(); - bool isPrivate = args.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; - - string response; - - if (string.IsNullOrWhiteSpace(awardTypeName)) - { - // var awards = db.Awards.Where(x => x.ToId == asker.Id) - // .GroupBy(x => x.AwardTypeId) - // .Select(x => new { AwardTypeId = x.Key, Amount = x.Sum(y => y.Amount) }); - if (!db.Awards.Any(x => x.ToId == asker.Id && (x.ChatId == args.Message.Chat.Id || isPrivate))) - response = currentLocale["jetkarmabot.status.havenothing"]; - else - { - var awardsQuery = from award in db.Awards - where award.ToId == asker.Id && (award.ChatId == args.Message.Chat.Id || isPrivate) - group award by award.AwardTypeId into g - join awardType in db.AwardTypes - on g.Key equals awardType.AwardTypeId - select new { AwardTypeId = g.Key, AwardTypeSymbol = awardType.Symbol, Amount = g.Sum(x => x.Amount) }; - var awardsByType = await awardsQuery.ToListAsync(); - response = currentLocale["jetkarmabot.status.listalltext"] + "\n" - + string.Join("\n", - awardsByType.Select(a => $" - {a.AwardTypeSymbol} {a.Amount}") - ); - - } - } + // var awards = db.Awards.Where(x => x.ToId == asker.Id) + // .GroupBy(x => x.AwardTypeId) + // .Select(x => new { AwardTypeId = x.Key, Amount = x.Sum(y => y.Amount) }); + if (!db.Awards.Any(x => x.ToId == asker.Id && (x.ChatId == ctx.EventArgs.Message.Chat.Id || isPrivate))) + response = currentLocale["jetkarmabot.status.havenothing"]; else { - var awardTypeIdQuery = from awt in db.AwardTypes - where awt.CommandName == awardTypeName - select awt.AwardTypeId; - var awardTypeId = await awardTypeIdQuery.FirstAsync(); - var awardType = await db.AwardTypes.FindAsync(awardTypeId); + var awardsQuery = from award in db.Awards + where award.ToId == asker.Id && (award.ChatId == ctx.EventArgs.Message.Chat.Id || isPrivate) + group award by award.AwardTypeId into g + join awardType in db.AwardTypes + on g.Key equals awardType.AwardTypeId + select new { AwardTypeId = g.Key, AwardTypeSymbol = awardType.Symbol, Amount = g.Sum(x => x.Amount) }; + var awardsByType = await awardsQuery.ToListAsync(); + response = currentLocale["jetkarmabot.status.listalltext"] + "\n" + + string.Join("\n", + awardsByType.Select(a => $" - {a.AwardTypeSymbol} {a.Amount}") + ); - response = string.Format(currentLocale["jetkarmabot.status.listspecifictext"], - await db.Awards.Where( - x => x.AwardTypeId == awardTypeId - && x.ToId == asker.Id - && x.ChatId == args.Message.Chat.Id) - .SumAsync(x => x.Amount), awardType.Symbol); } - - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - response, - replyToMessageId: args.Message.MessageId); - await Timeout.ApplyCost("Status", args.Message.From.Id, db); - return true; } + else + { + var awardTypeIdQuery = from awt in db.AwardTypes + where awt.CommandName == awardTypeName + select awt.AwardTypeId; + var awardTypeId = await awardTypeIdQuery.FirstAsync(); + var awardType = await db.AwardTypes.FindAsync(awardTypeId); + + response = string.Format(currentLocale["jetkarmabot.status.listspecifictext"], + await db.Awards.Where( + x => x.AwardTypeId == awardTypeId + && x.ToId == asker.Id + && x.ChatId == ctx.EventArgs.Message.Chat.Id) + .SumAsync(x => x.Amount), awardType.Symbol); + } + + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + response, + replyToMessageId: ctx.EventArgs.Message.MessageId); + return true; } - [Inject] KarmaContextFactory Db { get; set; } - [Inject] TelegramBotClient Client { get; set; } - [Inject] TimeoutManager Timeout { get; set; } [Inject] Localization Locale { get; set; } public string Description => "Shows the amount of awards that you have"; diff --git a/JetKarmaBot/Config.cs b/JetKarmaBot/Config.cs index 18d3562..39af916 100644 --- a/JetKarmaBot/Config.cs +++ b/JetKarmaBot/Config.cs @@ -27,8 +27,8 @@ namespace JetKarmaBot public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2; public Dictionary CommandCostsSeconds { get; private set; } = new Dictionary() { - {"AwardSuccessful", 60*15}, - {"AwardFailed", 60*5}, + {"JetKarmaBot.Commands.AwardCommand (OK)", 60*15}, + {"JetKarmaBot.Commands.AwardCommand (ERR)", 60*5}, {"Default", 60*5} }; public int SaveIntervalSeconds { get; private set; } = 60 * 5; diff --git a/JetKarmaBot/JetKarmaBot.cs b/JetKarmaBot/JetKarmaBot.cs index f027b9d..f8170b7 100644 --- a/JetKarmaBot/JetKarmaBot.cs +++ b/JetKarmaBot/JetKarmaBot.cs @@ -1,16 +1,15 @@ using JetKarmaBot.Commands; using JetKarmaBot.Models; using JetKarmaBot.Services; +using JetKarmaBot.Services.Handling; using Perfusion; using System; -using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Args; -using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; namespace JetKarmaBot @@ -25,6 +24,7 @@ namespace JetKarmaBot TelegramBotClient Client { get; set; } ChatCommandRouter Commands; + RequestChain Chain; Task timeoutWaitTask; CancellationTokenSource timeoutWaitTaskToken; @@ -44,6 +44,7 @@ namespace JetKarmaBot timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token); await InitCommands(Container); + InitChain(Container); Client.OnMessage += BotOnMessageReceived; Client.StartReceiving(); @@ -78,48 +79,12 @@ namespace JetKarmaBot { using (KarmaContext db = Db.GetContext()) { - await AddUserToDatabase(db, args.Message.From); - var checkResult = await Timeout.Check(args.Message.From.Id, db); - if (checkResult == TimeoutManager.CheckResult.Limited) - { - Locale currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; - await Client.SendTextMessageAsync( - args.Message.Chat.Id, - currentLocale["jetkarmabot.ratelimit"], - replyToMessageId: args.Message.MessageId); - await Timeout.SetMessaged(args.Message.From.Id, db); - return; - } - else if (checkResult != TimeoutManager.CheckResult.NonLimited) - { - return; - } - if (args.Message.ReplyToMessage != null) - await AddUserToDatabase(db, args.Message.ReplyToMessage.From); - if (!db.Chats.Any(x => x.ChatId == args.Message.Chat.Id)) - db.Chats.Add(new Models.Chat - { - ChatId = args.Message.Chat.Id - }); - await db.SaveChangesAsync(); + RequestContext ctx = new RequestContext(Client, args, cmd, db); + await Chain.Handle(ctx); } - await Commands.Execute(cmd, args); }); } - private async Task AddUserToDatabase(KarmaContext db, Telegram.Bot.Types.User u) - { - string un; - if (u.Username == null) - un = u.FirstName + (u.LastName != null ? " " + u.LastName : ""); - else - un = "@" + u.Username; - if (!db.Users.Any(x => x.UserId == u.Id)) - await db.Users.AddAsync(new Models.User { UserId = u.Id, Username = un }); - else - (await db.Users.FindAsync(u.Id)).Username = un; - } - async Task InitCommands(IContainer c) { c.Add(); @@ -136,6 +101,15 @@ namespace JetKarmaBot } } + void InitChain(IContainer c) + { + Chain = new RequestChain(); + Chain.Add(new DbHandler(DbHandler.SaveType.From | DbHandler.SaveType.LateDbChanges)); + Chain.Add(Timeout); + Chain.Add(new DbHandler(DbHandler.SaveType.To | DbHandler.SaveType.Chat)); + Chain.Add(Commands); + } + #endregion #region IDisposable diff --git a/JetKarmaBot/Models/Chat.cs b/JetKarmaBot/Models/Chat.cs index ddb6076..2344f37 100644 --- a/JetKarmaBot/Models/Chat.cs +++ b/JetKarmaBot/Models/Chat.cs @@ -11,7 +11,7 @@ namespace JetKarmaBot.Models } public long ChatId { get; set; } - public string Locale { get; set; } + public string Locale { get; set; } = "ru-RU"; public bool IsAdministrator { get; set; } public virtual ICollection Awards { get; set; } diff --git a/JetKarmaBot/CommandRouter.cs b/JetKarmaBot/Services/Handling/CommandRouter.cs similarity index 80% rename from JetKarmaBot/CommandRouter.cs rename to JetKarmaBot/Services/Handling/CommandRouter.cs index 164d7bb..b2354c5 100644 --- a/JetKarmaBot/CommandRouter.cs +++ b/JetKarmaBot/Services/Handling/CommandRouter.cs @@ -1,6 +1,4 @@ using JetKarmaBot.Commands; -using JetKarmaBot.Services; -using Microsoft.EntityFrameworkCore; using NLog; using Perfusion; using System; @@ -8,12 +6,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Telegram.Bot; -using Telegram.Bot.Args; -namespace JetKarmaBot +namespace JetKarmaBot.Services.Handling { - public class ChatCommandRouter + public class ChatCommandRouter : IRequestHandler { + public class Feature + { + public Type CommandType; + public ChatCommandRouter Router; + public bool Succeded; + } public Telegram.Bot.Types.User Me { get; private set; } [Inject] private Logger log; [Inject] private TelegramBotClient Client { get; set; } @@ -23,16 +26,21 @@ namespace JetKarmaBot Me = await Client.GetMeAsync(); } - public Task Execute(CommandString cmd, MessageEventArgs args) + public Task Handle(RequestContext ctx, Func next) { log.Debug("Message received"); + CommandString cmd = ctx.Command; + Feature feature = new Feature() { Router = this }; + ctx.Features.Add(feature); try { if (commands.ContainsKey(cmd.Command)) { - log.Debug($"Handling message via {commands[cmd.Command].GetType().Name}"); - return commands[cmd.Command].Execute(cmd, args); + feature.CommandType = commands[cmd.Command].GetType(); + log.Debug($"Handling message via {feature.CommandType.Name}"); + async Task processCommand() => feature.Succeded = await commands[cmd.Command].Execute(ctx); + return processCommand(); } } catch (Exception e) @@ -41,7 +49,7 @@ namespace JetKarmaBot log.Error(e); } - return Task.FromResult(false); + return next(ctx); } public void Add(IChatCommand c) diff --git a/JetKarmaBot/Services/Handling/DbHandler.cs b/JetKarmaBot/Services/Handling/DbHandler.cs new file mode 100644 index 0000000..431843f --- /dev/null +++ b/JetKarmaBot/Services/Handling/DbHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading.Tasks; +using JetKarmaBot.Models; +using Microsoft.EntityFrameworkCore; + +namespace JetKarmaBot.Services.Handling +{ + public class DbHandler : IRequestHandler + { + [Flags] + public enum SaveType + { + From = 1 << 0, + To = 1 << 1, + Chat = 1 << 2, + LateDbChanges = 1 << 3 + } + private SaveType type; + public DbHandler(SaveType type) + { + this.type = type; + } + public async Task Handle(RequestContext ctx, Func next) + { + KarmaContext db = ctx.Database; + if (type.HasFlag(SaveType.From)) + await AddUserToDatabase(db, ctx.EventArgs.Message.From); + if (type.HasFlag(SaveType.To)) + if (ctx.EventArgs.Message.ReplyToMessage != null) + await AddUserToDatabase(db, ctx.EventArgs.Message.ReplyToMessage.From); + if (type.HasFlag(SaveType.Chat)) + if (!await db.Chats.AnyAsync(x => x.ChatId == ctx.EventArgs.Message.Chat.Id)) + db.Chats.Add(new Models.Chat + { + ChatId = ctx.EventArgs.Message.Chat.Id + }); + await next(ctx); + if (type.HasFlag(SaveType.LateDbChanges)) + await db.SaveChangesAsync(); + } + private async Task AddUserToDatabase(KarmaContext db, Telegram.Bot.Types.User u) + { + string un; + if (u.Username == null) + un = u.FirstName + (u.LastName != null ? " " + u.LastName : ""); + else + un = "@" + u.Username; + if (!await db.Users.AnyAsync(x => x.UserId == u.Id)) + await db.Users.AddAsync(new Models.User { UserId = u.Id, Username = un }); + else + (await db.Users.FindAsync(u.Id)).Username = un; + } + } +} \ No newline at end of file diff --git a/JetKarmaBot/Services/Handling/RequestChain.cs b/JetKarmaBot/Services/Handling/RequestChain.cs new file mode 100644 index 0000000..f71f99a --- /dev/null +++ b/JetKarmaBot/Services/Handling/RequestChain.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace JetKarmaBot.Services.Handling +{ + public interface IRequestHandler + { + Task Handle(RequestContext ctx, Func next); + } + public class RequestChain : IRequestHandler + { + List handlerStack = new List(); + public async Task Handle(RequestContext ctx, Func next = null) + { + int i = 0; + Func chainNext = null; + chainNext = (newCtx) => + { + if (i == handlerStack.Count) return Task.CompletedTask; + IRequestHandler handler = handlerStack[i++]; + return handler.Handle(newCtx, chainNext); + }; + await chainNext(ctx); + if (next != null) + await next(ctx); + } + public void Add(IRequestHandler handler) + { + handlerStack.Add(handler); + } + } +} \ No newline at end of file diff --git a/JetKarmaBot/Services/Handling/RequestContext.cs b/JetKarmaBot/Services/Handling/RequestContext.cs new file mode 100644 index 0000000..58ab2b0 --- /dev/null +++ b/JetKarmaBot/Services/Handling/RequestContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetKarmaBot.Commands; +using JetKarmaBot.Models; +using Telegram.Bot; +using Telegram.Bot.Args; + +namespace JetKarmaBot.Services.Handling +{ + public class RequestContext : IServiceProvider + { + public ITelegramBotClient Client { get; } + public MessageEventArgs EventArgs { get; } + public CommandString Command { get; } + public KarmaContext Database { get; } + public ICollection Features { get; } = new List(); + public RequestContext(ITelegramBotClient client, MessageEventArgs args, CommandString cmd, KarmaContext db) + { + Client = client; + EventArgs = args; + Command = cmd; + Database = db; + } + public object GetService(Type serviceType) => Features.First(x => x.GetType() == serviceType); + public T GetFeature() => (T)Features.First(x => x is T); + } +} \ No newline at end of file diff --git a/JetKarmaBot/Services/TimeoutManager.cs b/JetKarmaBot/Services/Handling/TimeoutManager.cs similarity index 56% rename from JetKarmaBot/Services/TimeoutManager.cs rename to JetKarmaBot/Services/Handling/TimeoutManager.cs index 14c120f..828e76d 100644 --- a/JetKarmaBot/Services/TimeoutManager.cs +++ b/JetKarmaBot/Services/Handling/TimeoutManager.cs @@ -4,11 +4,12 @@ using System; using System.Threading.Tasks; using JetKarmaBot.Models; using System.Threading; +using System.Linq; -namespace JetKarmaBot.Services +namespace JetKarmaBot.Services.Handling { [Singleton] - public class TimeoutManager + public class TimeoutManager : IRequestHandler { public struct TimeoutStats { @@ -17,14 +18,16 @@ namespace JetKarmaBot.Services } [Inject] private KarmaContextFactory Db; [Inject] private Config cfg; + [Inject] private Localization Locale; public Dictionary TimeoutCache = new Dictionary(); - public async Task ApplyCost(string name, int uid, KarmaContext db) + private async Task ApplyCost(string name, bool succeded, int uid, KarmaContext db) { - if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name, out var costSeconds)) - if (!cfg.Timeout.CommandCostsSeconds.TryGetValue("Default", out costSeconds)) - { - throw new LocalizationException("Default key not present"); - } + if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name + (succeded ? " (OK)" : "(ERR)"), out var costSeconds)) + if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name, out costSeconds)) + if (!cfg.Timeout.CommandCostsSeconds.TryGetValue("Default", out costSeconds)) + { + throw new LocalizationException("Default key not present"); + } await PopulateStats(uid, db); DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds); if (TimeoutCache[uid].CooldownDate >= debtLimit) @@ -37,23 +40,7 @@ namespace JetKarmaBot.Services }; TimeoutCache[uid] = TimeoutCache[uid]; } - public enum CheckResult - { - NonLimited, Limited, LimitedSent - } - public async Task Check(int uid, KarmaContext db) - { - await PopulateStats(uid, db); - DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds); - return TimeoutCache[uid].CooldownDate < debtLimit - ? CheckResult.NonLimited - : (TimeoutCache[uid].TimeoutMessaged ? CheckResult.LimitedSent : CheckResult.Limited); - } - public async Task SetMessaged(int uid, KarmaContext db) - { - await PopulateStats(uid, db); - TimeoutCache[uid] = new TimeoutStats() { TimeoutMessaged = true, CooldownDate = TimeoutCache[uid].CooldownDate }; - } + private async Task PopulateStats(int uid, KarmaContext db) { if (!TimeoutCache.ContainsKey(uid)) @@ -83,5 +70,35 @@ namespace JetKarmaBot.Services await Save(ct); } } + + public async Task Handle(RequestContext ctx, Func next) + { + int uid = ctx.EventArgs.Message.From.Id; + await PopulateStats(uid, ctx.Database); + DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds); + if (debtLimit < TimeoutCache[uid].CooldownDate) + { + if (!TimeoutCache[uid].TimeoutMessaged) + { + Locale currentLocale = Locale[(await ctx.Database.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale]; + await ctx.Client.SendTextMessageAsync( + ctx.EventArgs.Message.Chat.Id, + currentLocale["jetkarmabot.ratelimit"], + replyToMessageId: ctx.EventArgs.Message.MessageId); + TimeoutCache[uid] = new TimeoutStats() { TimeoutMessaged = true, CooldownDate = TimeoutCache[uid].CooldownDate }; + } + return; + } + + await next(ctx); + + var routerFeature = ctx.GetFeature(); + await ApplyCost(getTypeName(routerFeature.CommandType), routerFeature.Succeded, uid, ctx.Database); + } + private string getTypeName(Type t) + { + return (t.DeclaringType == null ? t.Namespace + "." + t.Name : getTypeName(t.DeclaringType) + "." + t.Name) + + (t.GenericTypeArguments.Length > 0 ? "<" + string.Join(",", t.GenericTypeArguments.Select(getTypeName)) + ">" : ""); + } } } \ No newline at end of file