diff --git a/JetKarmaBot/Commands/AwardCommand.cs b/JetKarmaBot/Commands/AwardCommand.cs index 2798f2e..25730f3 100644 --- a/JetKarmaBot/Commands/AwardCommand.cs +++ b/JetKarmaBot/Commands/AwardCommand.cs @@ -1,162 +1,156 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Perfusion; -using JetKarmaBot.Services.Handling; -using NLog; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using NLog; +using JetKarmaBot.Services.Handling; using JetKarmaBot.Models; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +class AwardCommand : IChatCommand { - class AwardCommand : IChatCommand + public IReadOnlyCollection Names => new[] { "award", "revoke" }; + [Inject] private Logger log; + + public async Task Execute(RequestContext ctx) { - public IReadOnlyCollection Names => new[] { "award", "revoke" }; - [Inject] private Logger log; + var db = ctx.GetFeature(); + var currentLocale = ctx.GetFeature(); - public async Task Execute(RequestContext ctx) + var awarder = ctx.EventArgs.Message.From; + + if (Timeout.TimeoutCache[awarder.Id].PreviousAwardDate.AddSeconds(Config.Timeout.AwardTimeSeconds) > DateTime.Now) { - var db = ctx.GetFeature(); - var currentLocale = ctx.GetFeature(); - - var awarder = ctx.EventArgs.Message.From; - - if (Timeout.TimeoutCache[awarder.Id].PreviousAwardDate.AddSeconds(Config.Timeout.AwardTimeSeconds) > DateTime.Now) - { - ctx.GetFeature().Multiplier = 0; // Doesn't count as success or failure - if (!Timeout.TimeoutCache[awarder.Id].TimeoutMessaged) - await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]); - Timeout.TimeoutCache[awarder.Id].TimeoutMessaged = true; - return false; - } - - string awardTypeText = null; - long recipientId = default; - foreach (string arg in ctx.Command.Parameters) - { - if (arg.StartsWith('@')) - { - if (recipientId != default(int)) - { - await ctx.SendMessage(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.SendMessage(currentLocale["jetkarmabot.award.errbadusername"]); - return false; - } - } - else - { - if (awardTypeText == null) - awardTypeText = arg; - else - { - await ctx.SendMessage(currentLocale["jetkarmabot.award.errdup"]); - return false; - } - } - } - - if (ctx.EventArgs.Message.ReplyToMessage != null && recipientId == default) - { - recipientId = ctx.EventArgs.Message.ReplyToMessage.From.Id; - } - - if (recipientId == default(int)) - { - await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardnoreply"]); - return false; - } - - - bool awarding = ctx.Command.Command == "award"; - - if (awarder.Id == recipientId) - { - await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardself"]); - return false; - } - - if (ctx.GetFeature().Router.Me.Id == recipientId) - { - await ctx.SendMessage(awarding - ? currentLocale["jetkarmabot.award.errawardbot"] - : currentLocale["jetkarmabot.award.errrevokebot"]); - 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.SendMessage(response); - Timeout.TimeoutCache[awarder.Id].PreviousAwardDate = DateTime.Now; - return true; + ctx.GetFeature().Multiplier = 0; // Doesn't count as success or failure + if (!Timeout.TimeoutCache[awarder.Id].TimeoutMessaged) + await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]); + Timeout.TimeoutCache[awarder.Id].TimeoutMessaged = true; + return false; } - private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc) + string awardTypeText = null; + long recipientId = default; + foreach (string arg in ctx.Command.Parameters) { - if (loc.ContainsKey($"jetkarmabot.awardtypes.accusative.{awardType.CommandName}")) + if (arg.StartsWith('@')) { - return loc[$"jetkarmabot.awardtypes.accusative.{awardType.CommandName}"]; + if (recipientId != default(int)) + { + await ctx.SendMessage(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.SendMessage(currentLocale["jetkarmabot.award.errbadusername"]); + return false; + } } else { - return awardType.Name; + if (awardTypeText == null) + awardTypeText = arg; + else + { + await ctx.SendMessage(currentLocale["jetkarmabot.award.errdup"]); + return false; + } } } - [Inject] Localization Locale { get; set; } - [Inject] TimeoutManager Timeout { get; set; } - [Inject] Config Config { get; set; } + if (ctx.EventArgs.Message.ReplyToMessage != null && recipientId == default) + { + recipientId = ctx.EventArgs.Message.ReplyToMessage.From.Id; + } - public string Description => "Awards/revokes an award to a user."; - public string DescriptionID => "jetkarmabot.award.help"; + if (recipientId == default(int)) + { + await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardnoreply"]); + return false; + } - public IReadOnlyCollection Arguments => new ChatCommandArgument[] { - new ChatCommandArgument() { - Name="awardtype", - Required=false, - Type=ChatCommandArgumentType.String, - Description="The award to grant to/strip of the specified user", - DescriptionID="jetkarmabot.award.awardtypehelp" - }, - new ChatCommandArgument() { - Name="to", - Required=false, - Type=ChatCommandArgumentType.String, - Description="The user to award it to.", - DescriptionID="jetkarmabot.award.tohelp" - } - }; + + bool awarding = ctx.Command.Command == "award"; + + if (awarder.Id == recipientId) + { + await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardself"]); + return false; + } + + if (ctx.GetFeature().Router.Me.Id == recipientId) + { + await ctx.SendMessage(awarding + ? currentLocale["jetkarmabot.award.errawardbot"] + : currentLocale["jetkarmabot.award.errrevokebot"]); + 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.SendMessage(response); + Timeout.TimeoutCache[awarder.Id].PreviousAwardDate = DateTime.Now; + return true; } + + private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc) + { + if (loc.ContainsKey($"jetkarmabot.awardtypes.accusative.{awardType.CommandName}")) + { + return loc[$"jetkarmabot.awardtypes.accusative.{awardType.CommandName}"]; + } + else + { + return awardType.Name; + } + } + + [Inject] Localization Locale { get; set; } + [Inject] TimeoutManager Timeout { get; set; } + [Inject] Config Config { get; set; } + + public string Description => "Awards/revokes an award to a user."; + public string DescriptionID => "jetkarmabot.award.help"; + + public IReadOnlyCollection Arguments => new ChatCommandArgument[] { + new ChatCommandArgument() { + Name="awardtype", + Required=false, + Type=ChatCommandArgumentType.String, + Description="The award to grant to/strip of the specified user", + DescriptionID="jetkarmabot.award.awardtypehelp" + }, + new ChatCommandArgument() { + Name="to", + Required=false, + Type=ChatCommandArgumentType.String, + Description="The user to award it to.", + DescriptionID="jetkarmabot.award.tohelp" + } + }; } diff --git a/JetKarmaBot/Commands/ChangeLocaleCommand.cs b/JetKarmaBot/Commands/ChangeLocaleCommand.cs index 977d966..ea690fb 100644 --- a/JetKarmaBot/Commands/ChangeLocaleCommand.cs +++ b/JetKarmaBot/Commands/ChangeLocaleCommand.cs @@ -1,80 +1,75 @@ -using System.Collections.Generic; -using Perfusion; -using JetKarmaBot.Services.Handling; using NLog; -using System.Linq; -using System.Threading.Tasks; +using JetKarmaBot.Services.Handling; using JetKarmaBot.Models; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +class LocaleCommand : IChatCommand { - class LocaleCommand : IChatCommand + public IReadOnlyCollection Names => new[] { "changelocale", "locale" }; + [Inject] private Logger log; + + public async Task Execute(RequestContext ctx) { - public IReadOnlyCollection Names => new[] { "changelocale", "locale" }; - [Inject] private Logger log; + var db = ctx.GetFeature(); + var currentLocale = ctx.GetFeature(); + var cmd = ctx.Command; + var args = ctx.EventArgs; - public async Task Execute(RequestContext ctx) + if (cmd.Parameters.Length < 1) { - var db = ctx.GetFeature(); - var currentLocale = ctx.GetFeature(); - var cmd = ctx.Command; - var args = ctx.EventArgs; - - if (cmd.Parameters.Length < 1) - { - await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.getlocale"]); - return false; - } - else if (cmd.Parameters[0] == "list") - { - await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" - + string.Join("\n", Locale.Select(a => a.Key))); - return false; - } - else if (cmd.Parameters[0] == "all") - { - await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.errorall"]); - 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.SendMessage( - currentLocale["jetkarmabot.changelocale.toomany"] + "\n" - + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name))); - 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.SendMessage( - (currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "") - + currentLocale["jetkarmabot.changelocale.justchanged"]); - return true; + await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.getlocale"]); + return false; } - - [Inject] Localization Locale { get; set; } - - public string Description => "Switches current chat locale to [locale]"; - public string DescriptionID => "jetkarmabot.changelocale.help"; - - public IReadOnlyCollection Arguments => new ChatCommandArgument[] { - new ChatCommandArgument() { - Name="locale", - Required=false, - Type=ChatCommandArgumentType.String, - Description="The locale to switch to. Can be \"list\" to list all possible locales. Also can be empty to get current locale.", - DescriptionID="jetkarmabot.changelocale.localehelp" + else if (cmd.Parameters[0] == "list") + { + await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" + + string.Join("\n", Locale.Select(a => a.Key))); + return false; + } + else if (cmd.Parameters[0] == "all") + { + await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.errorall"]); + 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.SendMessage( + currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name))); + 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.SendMessage( + (currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "") + + currentLocale["jetkarmabot.changelocale.justchanged"]); + return true; } + + [Inject] Localization Locale { get; set; } + + public string Description => "Switches current chat locale to [locale]"; + public string DescriptionID => "jetkarmabot.changelocale.help"; + + public IReadOnlyCollection Arguments => new ChatCommandArgument[] { + new ChatCommandArgument() { + Name="locale", + Required=false, + Type=ChatCommandArgumentType.String, + Description="The locale to switch to. Can be \"list\" to list all possible locales. Also can be empty to get current locale.", + DescriptionID="jetkarmabot.changelocale.localehelp" + } + }; } diff --git a/JetKarmaBot/Commands/CommandString.cs b/JetKarmaBot/Commands/CommandString.cs index 9a330d5..8240258 100644 --- a/JetKarmaBot/Commands/CommandString.cs +++ b/JetKarmaBot/Commands/CommandString.cs @@ -1,51 +1,48 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +public class CommandString { - public class CommandString + public CommandString(string command, params string[] parameters) { - public CommandString(string command, params string[] parameters) - { - Command = command; - Parameters = parameters; - } + Command = command; + Parameters = parameters; + } - public string Command { get; } - public string UserName { get; set; } = null; - public string[] Parameters { get; } + public string Command { get; } + public string UserName { get; set; } = null; + public string[] Parameters { get; } - public static readonly char[] WS_CHARS = new[] { ' ', '\r', '\n', '\n' }; + public static readonly char[] WS_CHARS = new[] { ' ', '\r', '\n', '\n' }; - public static bool TryParse(string s, out CommandString result) - { - result = null; - if (string.IsNullOrWhiteSpace(s) || s[0] != '/') - return false; + public static bool TryParse(string s, out CommandString result) + { + result = null; + if (string.IsNullOrWhiteSpace(s) || s[0] != '/') + return false; - string[] words = s.Split(WS_CHARS, StringSplitOptions.RemoveEmptyEntries); + string[] words = s.Split(WS_CHARS, StringSplitOptions.RemoveEmptyEntries); - if (!words.Any()) - return false; + if (!words.Any()) + return false; - var cmdRegex = new Regex(@"/(?\w+)(@(?\w+))?"); - var match = cmdRegex.Match(words.First()); - if (!match.Success) - return false; + var cmdRegex = new Regex(@"/(?\w+)(@(?\w+))?"); + var match = cmdRegex.Match(words.First()); + if (!match.Success) + return false; - string cmd = match.Groups["cmd"].Captures[0].Value; - string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null; - string[] parameters = words.Skip(1).ToArray(); + string cmd = match.Groups["cmd"].Captures[0].Value; + 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 }; - return true; - } + result = new CommandString(cmd, parameters) { UserName = username }; + return true; + } - public static CommandString Parse(string s) - { - if (TryParse(s, out var c)) return c; - throw new ArgumentException($"\"{s}\" is not a command"); - } + public static CommandString Parse(string s) + { + if (TryParse(s, out var c)) return c; + throw new ArgumentException($"\"{s}\" is not a command"); } } diff --git a/JetKarmaBot/Commands/CurrenciesCommand.cs b/JetKarmaBot/Commands/CurrenciesCommand.cs index 4da45ea..dda6d10 100644 --- a/JetKarmaBot/Commands/CurrenciesCommand.cs +++ b/JetKarmaBot/Commands/CurrenciesCommand.cs @@ -1,33 +1,28 @@ -using System.Collections.Generic; -using Perfusion; +using Microsoft.EntityFrameworkCore; using JetKarmaBot.Services.Handling; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; using JetKarmaBot.Models; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +public class CurrenciesCommand : IChatCommand { - public class CurrenciesCommand : IChatCommand + [Inject] Localization Locale { get; set; } + public IReadOnlyCollection Names => new[] { "currencies", "awardtypes" }; + + public string Description => "Shows all award types"; + public string DescriptionID => "jetkarmabot.currencies.help"; + + public IReadOnlyCollection Arguments => new ChatCommandArgument[] { + }; + + public async Task Execute(RequestContext ctx) { - [Inject] Localization Locale { get; set; } - public IReadOnlyCollection Names => new[] { "currencies", "awardtypes" }; - - public string Description => "Shows all award types"; - public string DescriptionID => "jetkarmabot.currencies.help"; - - public IReadOnlyCollection Arguments => new ChatCommandArgument[] { - }; - - public async Task Execute(RequestContext ctx) - { - var db = ctx.GetFeature(); - var currentLocale = ctx.GetFeature(); - await ctx.SendMessage( - currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", - (await db.AwardTypes.ToListAsync()) - .Select(x => $"{x.Symbol} ({x.CommandName}) {currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}"))); - return true; - } + var db = ctx.GetFeature(); + var currentLocale = ctx.GetFeature(); + await ctx.SendMessage( + currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", + (await db.AwardTypes.ToListAsync()) + .Select(x => $"{x.Symbol} ({x.CommandName}) {currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}"))); + return true; } } \ No newline at end of file diff --git a/JetKarmaBot/Commands/HelpCommand.cs b/JetKarmaBot/Commands/HelpCommand.cs index dd90aee..bdf9d54 100644 --- a/JetKarmaBot/Commands/HelpCommand.cs +++ b/JetKarmaBot/Commands/HelpCommand.cs @@ -1,44 +1,38 @@ -using System.Collections.Generic; -using Perfusion; -using JetKarmaBot.Services.Handling; -using Telegram.Bot.Types.Enums; -using System.Threading.Tasks; -using JetKarmaBot.Models; +using JetKarmaBot.Services.Handling; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +public class HelpCommand : IChatCommand { - public class HelpCommand : IChatCommand + [Inject] Localization Locale { get; set; } + public IReadOnlyCollection Names => new[] { "help" }; + + public string Description => "Displays help text for all(one) command(s)"; + public string DescriptionID => "jetkarmabot.help.help"; + + public IReadOnlyCollection Arguments => new ChatCommandArgument[] { + new ChatCommandArgument() { + Name="command", + Required=false, + Type=ChatCommandArgumentType.String, + Description="The command to return help text for. If empty shows all commands.", + DescriptionID="jetkarmabot.help.commandhelp" + } + }; + + public async Task Execute(RequestContext ctx) { - [Inject] Localization Locale { get; set; } - public IReadOnlyCollection Names => new[] { "help" }; - - public string Description => "Displays help text for all(one) command(s)"; - public string DescriptionID => "jetkarmabot.help.help"; - - public IReadOnlyCollection Arguments => new ChatCommandArgument[] { - new ChatCommandArgument() { - Name="command", - Required=false, - Type=ChatCommandArgumentType.String, - Description="The command to return help text for. If empty shows all commands.", - DescriptionID="jetkarmabot.help.commandhelp" - } - }; - - public async Task Execute(RequestContext ctx) + var currentLocale = ctx.GetFeature(); + var router = ctx.GetFeature().Router; + if (ctx.Command.Parameters.Length < 1) { - var currentLocale = ctx.GetFeature(); - var router = ctx.GetFeature().Router; - if (ctx.Command.Parameters.Length < 1) - { - await ctx.SendMessage(router.GetHelpText(currentLocale)); - return true; - } - else - { - await ctx.SendMessage(router.GetHelpTextFor(ctx.Command.Parameters[0], currentLocale)); - return true; - } + await ctx.SendMessage(router.GetHelpText(currentLocale)); + return true; + } + else + { + await ctx.SendMessage(router.GetHelpTextFor(ctx.Command.Parameters[0], currentLocale)); + return true; } } } diff --git a/JetKarmaBot/Commands/IChatCommand.cs b/JetKarmaBot/Commands/IChatCommand.cs index 7565fdd..269654e 100644 --- a/JetKarmaBot/Commands/IChatCommand.cs +++ b/JetKarmaBot/Commands/IChatCommand.cs @@ -1,32 +1,29 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using JetKarmaBot.Services.Handling; +using JetKarmaBot.Services.Handling; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +public interface IChatCommand { - public interface IChatCommand - { - IReadOnlyCollection Names { get; } - string Description { get; } - string DescriptionID { get; } - IReadOnlyCollection Arguments { get; } + IReadOnlyCollection Names { get; } + string Description { get; } + string DescriptionID { get; } + IReadOnlyCollection Arguments { get; } - Task Execute(RequestContext ctx); - } - - public struct ChatCommandArgument - { - public string Name; - public bool Required; - public ChatCommandArgumentType Type; - public string Description; - public string DescriptionID; - } - - public enum ChatCommandArgumentType - { - Boolean, - String, - Integer, - } + Task Execute(RequestContext ctx); +} + +public struct ChatCommandArgument +{ + public string Name; + public bool Required; + public ChatCommandArgumentType Type; + public string Description; + public string DescriptionID; +} + +public enum ChatCommandArgumentType +{ + Boolean, + String, + Integer, } diff --git a/JetKarmaBot/Commands/LeaderboardCommand.cs b/JetKarmaBot/Commands/LeaderboardCommand.cs index de6be82..4f56a26 100644 --- a/JetKarmaBot/Commands/LeaderboardCommand.cs +++ b/JetKarmaBot/Commands/LeaderboardCommand.cs @@ -1,70 +1,65 @@ -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Perfusion; using JetKarmaBot.Services.Handling; using JetKarmaBot.Models; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +class LeaderboardCommand : IChatCommand { - class LeaderboardCommand : IChatCommand + public IReadOnlyCollection Names => new[] { "leaderboard" }; + + public async Task Execute(RequestContext ctx) { - public IReadOnlyCollection Names => new[] { "leaderboard" }; - - public async Task Execute(RequestContext ctx) + bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; + var currentLocale = ctx.GetFeature(); + if (isPrivate) { - bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; - var currentLocale = ctx.GetFeature(); - if (isPrivate) - { - await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardself"]); - return true; - } - - var db = ctx.GetFeature(); - var asker = ctx.EventArgs.Message.From; - var awardTypeName = ctx.Command.Parameters.FirstOrDefault(); - - 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 topEarners = await db.Awards - .Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id && x.AwardTypeId == awardTypeId) - .GroupBy(x => x.To) - .Select(x => new { User = x.Key, Amount = x.Sum(y => y.Amount) }) - .OrderByDescending(x => x.Amount) - .Take(5) - .ToListAsync(); - - var response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" - + string.Join('\n', topEarners.Select((x, index) - => $"{index + 1}. {x.User.Username} - {x.Amount}") - ); - - await ctx.SendMessage(response); + await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardself"]); return true; } - [Inject] Localization Locale { get; set; } + var db = ctx.GetFeature(); + var asker = ctx.EventArgs.Message.From; + var awardTypeName = ctx.Command.Parameters.FirstOrDefault(); - public string Description => "Shows the people with the most of a specific award."; - public string DescriptionID => "jetkarmabot.leaderboard.help"; + if (string.IsNullOrWhiteSpace(awardTypeName)) + awardTypeName = "star"; - public IReadOnlyCollection Arguments => new ChatCommandArgument[] { - new ChatCommandArgument() { - Name="awardtype", - Required=true, - Type=ChatCommandArgumentType.String, - Description="The awardtype to show a leaderboard for.", - DescriptionID= "jetkarmabot.leaderboard.awardtypehelp" - } - }; + 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 topEarners = await db.Awards + .Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id && x.AwardTypeId == awardTypeId) + .GroupBy(x => x.To) + .Select(x => new { User = x.Key, Amount = x.Sum(y => y.Amount) }) + .OrderByDescending(x => x.Amount) + .Take(5) + .ToListAsync(); + + var response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" + + string.Join('\n', topEarners.Select((x, index) + => $"{index + 1}. {x.User.Username} - {x.Amount}") + ); + + await ctx.SendMessage(response); + return true; } + + [Inject] Localization Locale { get; set; } + + public string Description => "Shows the people with the most of a specific award."; + public string DescriptionID => "jetkarmabot.leaderboard.help"; + + public IReadOnlyCollection Arguments => new ChatCommandArgument[] { + new ChatCommandArgument() { + Name="awardtype", + Required=true, + Type=ChatCommandArgumentType.String, + Description="The awardtype to show a leaderboard for.", + DescriptionID= "jetkarmabot.leaderboard.awardtypehelp" + } + }; } diff --git a/JetKarmaBot/Commands/StatusCommand.cs b/JetKarmaBot/Commands/StatusCommand.cs index 008aba1..4a8601c 100644 --- a/JetKarmaBot/Commands/StatusCommand.cs +++ b/JetKarmaBot/Commands/StatusCommand.cs @@ -1,56 +1,51 @@ -using System.Linq; -using System.Collections.Generic; -using Perfusion; -using JetKarmaBot.Services.Handling; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using JetKarmaBot.Services.Handling; using JetKarmaBot.Models; -namespace JetKarmaBot.Commands +namespace JetKarmaBot.Commands; + +class StatusCommand : IChatCommand { - class StatusCommand : IChatCommand + public IReadOnlyCollection Names => ["status"]; + + public async Task Execute(RequestContext ctx) { - public IReadOnlyCollection Names => ["status"]; + var db = ctx.GetFeature(); + var currentLocale = ctx.GetFeature(); + var asker = ctx.EventArgs.Message.From; + bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; - public async Task Execute(RequestContext ctx) + string response; + + var awards = db.Awards.Where(x => x.ToId == asker.Id); + if (!isPrivate) + awards = awards.Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id); + + if (!awards.Any()) + response = currentLocale["jetkarmabot.status.havenothing"]; + else { - var db = ctx.GetFeature(); - var currentLocale = ctx.GetFeature(); - var asker = ctx.EventArgs.Message.From; - bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; - - string response; - - var awards = db.Awards.Where(x => x.ToId == asker.Id); - if (!isPrivate) - awards = awards.Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id); - - if (!awards.Any()) - response = currentLocale["jetkarmabot.status.havenothing"]; - else - { - var aq = db.AwardTypes.GroupJoin( - awards, - at => at.AwardTypeId, - aw => aw.AwardTypeId, - (at, aws) => new {at.Symbol, Amount = aws.Sum(aw => aw.Amount) }); - - var awardsByType = await aq.ToListAsync(); - - response = - currentLocale["jetkarmabot.status.listalltext"] + "\n" - + string.Join("\n", awardsByType.Select(a => $" - {a.Symbol} {a.Amount}")); - } - - await ctx.SendMessage(response); - return true; + var aq = db.AwardTypes.GroupJoin( + awards, + at => at.AwardTypeId, + aw => aw.AwardTypeId, + (at, aws) => new {at.Symbol, Amount = aws.Sum(aw => aw.Amount) }); + + var awardsByType = await aq.ToListAsync(); + + response = + currentLocale["jetkarmabot.status.listalltext"] + "\n" + + string.Join("\n", awardsByType.Select(a => $" - {a.Symbol} {a.Amount}")); } - [Inject] Localization Locale { get; set; } - - public string Description => "Shows the amount of awards that you have"; - public string DescriptionID => "jetkarmabot.status.help"; - - public IReadOnlyCollection Arguments => []; + await ctx.SendMessage(response); + return true; } + + [Inject] Localization Locale { get; set; } + + public string Description => "Shows the amount of awards that you have"; + public string DescriptionID => "jetkarmabot.status.help"; + + public IReadOnlyCollection Arguments => []; } diff --git a/JetKarmaBot/Config.cs b/JetKarmaBot/Config.cs index 66f0cd1..59ca932 100644 --- a/JetKarmaBot/Config.cs +++ b/JetKarmaBot/Config.cs @@ -2,76 +2,74 @@ using Newtonsoft.Json; using JsonNet.PrivateSettersContractResolvers; using Newtonsoft.Json.Linq; -using System.Collections.Generic; -namespace JetKarmaBot +namespace JetKarmaBot; + +public class Config : ConfigBase { - public class Config : ConfigBase + public Config(string path) : base(path) { } + + public string ApiKey { get; private set; } + public string ConnectionString { get; private set; } + + public class ProxySettings { - public Config(string path) : base(path) { } - - public string ApiKey { get; private set; } - public string ConnectionString { get; private set; } - - public class ProxySettings - { - public string Url { get; private set; } - public int Port { get; private set; } - public string Login { get; private set; } - public string Password { get; private set; } - } - - public ProxySettings Proxy { get; private set; } - public class TimeoutConfig - { - public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2; - public Dictionary CommandCostsSeconds { get; private set; } = new Dictionary() - { - {"JetKarmaBot.Commands.AwardCommand (OK)", 60*15}, - {"JetKarmaBot.Commands.AwardCommand (ERR)", 60*5}, - {"Default", 60*5} - }; - public int SaveIntervalSeconds { get; private set; } = 60 * 5; - public double AwardTimeSeconds { get; private set; } = 60; - } - public TimeoutConfig Timeout { get; private set; } = new TimeoutConfig(); - public bool SqlDebug { get; private set; } = false; + public string Url { get; private set; } + public int Port { get; private set; } + public string Login { get; private set; } + public string Password { get; private set; } } - public abstract class ConfigBase + public ProxySettings Proxy { get; private set; } + public class TimeoutConfig { - public ConfigBase(string path) + public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2; + public Dictionary CommandCostsSeconds { get; private set; } = new Dictionary() { - JObject configJson; + {"JetKarmaBot.Commands.AwardCommand (OK)", 60*15}, + {"JetKarmaBot.Commands.AwardCommand (ERR)", 60*5}, + {"Default", 60*5} + }; + public int SaveIntervalSeconds { get; private set; } = 60 * 5; + public double AwardTimeSeconds { get; private set; } = 60; + } + public TimeoutConfig Timeout { get; private set; } = new TimeoutConfig(); + public bool SqlDebug { get; private set; } = false; +} - if (File.Exists(path)) +public abstract class ConfigBase +{ + public ConfigBase(string path) + { + JObject configJson; + + if (File.Exists(path)) + { + configJson = JObject.Parse(File.ReadAllText(path)); + + using (var sr = configJson.CreateReader()) { - configJson = JObject.Parse(File.ReadAllText(path)); - - using (var sr = configJson.CreateReader()) + var settings = new JsonSerializerSettings { - var settings = new JsonSerializerSettings - { - ContractResolver = new PrivateSetterContractResolver() - }; - JsonSerializer.CreateDefault(settings).Populate(sr, this); - } + ContractResolver = new PrivateSetterContractResolver() + }; + JsonSerializer.CreateDefault(settings).Populate(sr, this); } - else configJson = new JObject(); + } + else configJson = new JObject(); - configJson.Merge(JToken.FromObject(this), new JsonMergeSettings - { - MergeArrayHandling = MergeArrayHandling.Union - }); + configJson.Merge(JToken.FromObject(this), new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Union + }); - try // populate possible missing properties in file - { - File.WriteAllText(path, configJson.ToString(Formatting.Indented)); - } - catch (IOException e) - { - System.Diagnostics.Debug.WriteLine(e); - } + try // populate possible missing properties in file + { + File.WriteAllText(path, configJson.ToString(Formatting.Indented)); + } + catch (IOException e) + { + System.Diagnostics.Debug.WriteLine(e); } } } diff --git a/JetKarmaBot/Extensions/IReadOnlyDictionaryExtensions.cs b/JetKarmaBot/Extensions/IReadOnlyDictionaryExtensions.cs index bb97ffe..79f09ef 100644 --- a/JetKarmaBot/Extensions/IReadOnlyDictionaryExtensions.cs +++ b/JetKarmaBot/Extensions/IReadOnlyDictionaryExtensions.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; +namespace JetKarmaBot; -namespace JetKarmaBot +public static class IReadOnlyDictionaryExtensions { - public static class IReadOnlyDictionaryExtensions + public static TValue GetOrDefault(this IReadOnlyDictionary dict, TKey key) { - public static TValue GetOrDefault(this IReadOnlyDictionary dict, TKey key) - { - TValue res = default(TValue); - if (key != null) - dict.TryGetValue(key, out res); - return res; - } + TValue res = default; + if (key != null) + dict.TryGetValue(key, out res); + return res; } } diff --git a/JetKarmaBot/Extensions/PrivateSettersContractResolvers.cs b/JetKarmaBot/Extensions/PrivateSettersContractResolvers.cs index 2dc1cb0..d186f27 100644 --- a/JetKarmaBot/Extensions/PrivateSettersContractResolvers.cs +++ b/JetKarmaBot/Extensions/PrivateSettersContractResolvers.cs @@ -3,43 +3,42 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; // ReSharper disable once CheckNamespace -namespace JsonNet.PrivateSettersContractResolvers +namespace JsonNet.PrivateSettersContractResolvers; + +public class PrivateSetterContractResolver : DefaultContractResolver { - public class PrivateSetterContractResolver : DefaultContractResolver + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var jProperty = base.CreateProperty(member, memberSerialization); - if (jProperty.Writable) - return jProperty; - - jProperty.Writable = member.IsPropertyWithSetter(); - + var jProperty = base.CreateProperty(member, memberSerialization); + if (jProperty.Writable) return jProperty; - } + + jProperty.Writable = member.IsPropertyWithSetter(); + + return jProperty; } +} - public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver +public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver +{ + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var jProperty = base.CreateProperty(member, memberSerialization); - if (jProperty.Writable) - return jProperty; - - jProperty.Writable = member.IsPropertyWithSetter(); - + var jProperty = base.CreateProperty(member, memberSerialization); + if (jProperty.Writable) return jProperty; - } + + jProperty.Writable = member.IsPropertyWithSetter(); + + return jProperty; } +} - internal static class MemberInfoExtensions +internal static class MemberInfoExtensions +{ + internal static bool IsPropertyWithSetter(this MemberInfo member) { - internal static bool IsPropertyWithSetter(this MemberInfo member) - { - var property = member as PropertyInfo; + var property = member as PropertyInfo; - return property?.GetSetMethod(true) != null; - } + return property?.GetSetMethod(true) != null; } } \ No newline at end of file diff --git a/JetKarmaBot/GlobalUsings.cs b/JetKarmaBot/GlobalUsings.cs new file mode 100644 index 0000000..af4318c --- /dev/null +++ b/JetKarmaBot/GlobalUsings.cs @@ -0,0 +1,6 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; +global using System.Threading.Tasks; +global using Perfusion; diff --git a/JetKarmaBot/JetKarmaBot.cs b/JetKarmaBot/JetKarmaBot.cs index b658baf..057e587 100644 --- a/JetKarmaBot/JetKarmaBot.cs +++ b/JetKarmaBot/JetKarmaBot.cs @@ -3,140 +3,135 @@ using JetKarmaBot.Models; using JetKarmaBot.Services; using JetKarmaBot.Services.Handling; using NLog; -using Perfusion; -using System; -using System.Threading; -using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace JetKarmaBot +namespace JetKarmaBot; + +public class JetKarmaBot : IDisposable { - public class JetKarmaBot : IDisposable + [Inject] Config Config { get; set; } + [Inject] IContainer Container { get; set; } + [Inject] KarmaContextFactory Db { get; set; } + [Inject] TimeoutManager Timeout { get; set; } + [Inject] Localization Locale { get; set; } + [Inject] Logger Log { get; set; } + + + TelegramBotClient Client { get; set; } + ChatCommandRouter Commands; + RequestChain Chain; + Task timeoutWaitTask; + CancellationTokenSource timeoutWaitTaskToken; + private bool stopped = false; + + public async Task Init() { - [Inject] Config Config { get; set; } - [Inject] IContainer Container { get; set; } - [Inject] KarmaContextFactory Db { get; set; } - [Inject] TimeoutManager Timeout { get; set; } - [Inject] Localization Locale { get; set; } - [Inject] Logger Log { get; set; } + using (KarmaContext db = Db.GetContext()) + await db.Database.EnsureCreatedAsync(); + + Client = new TelegramBotClient(Config.ApiKey); + Container.AddInstance(Client); + timeoutWaitTaskToken = new CancellationTokenSource(); + timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token); - TelegramBotClient Client { get; set; } - ChatCommandRouter Commands; - RequestChain Chain; - Task timeoutWaitTask; - CancellationTokenSource timeoutWaitTaskToken; - private bool stopped = false; + await InitCommands(Container); + InitChain(Container); - public async Task Init() - { - using (KarmaContext db = Db.GetContext()) - await db.Database.EnsureCreatedAsync(); - - Client = new TelegramBotClient(Config.ApiKey); - Container.AddInstance(Client); - - timeoutWaitTaskToken = new CancellationTokenSource(); - timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token); - - await InitCommands(Container); - InitChain(Container); - - var receiverOptions = new ReceiverOptions { AllowedUpdates = new[] { UpdateType.Message } }; - Client.StartReceiving( - HandleUpdateAsync, - HandleErrorAsync, - receiverOptions); - } - - public async Task Stop() - { - if (stopped) return; - Client?.CloseAsync(); - timeoutWaitTaskToken?.Cancel(); - try - { - if (timeoutWaitTask != null) - await timeoutWaitTask; - } - catch (OperationCanceledException) { } - await Timeout?.Save(); - Dispose(); - stopped = true; - } - - #region service - - Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) - { - Log.Error(exception, "Exception while handling API message"); - return Task.CompletedTask; - } - - async Task HandleUpdateAsync(ITelegramBotClient sender, Update update, CancellationToken cancellationToken) - { - if (update.Type != UpdateType.Message || update?.Message?.Type != MessageType.Text) - return; - var message = update.Message!; - - try - { - if (message == null || message.Type != MessageType.Text) - return; - if (!CommandString.TryParse(message.Text, out var cmd)) - return; - if (cmd.UserName != null && cmd.UserName != Commands.Me.Username) - return; - - RequestContext ctx = new RequestContext(Client, update, cmd); - await Chain.Handle(ctx); - } - catch (Exception e) - { - Log.Error(e, "Exception while handling message {0}", message); - } - } - - async Task InitCommands(IContainer c) - { - c.Add(); - c.Add(); - c.Add(); - c.Add(); - c.Add(); - c.Add(); - Commands = c.GetInstance(); - await Commands.Start(); - foreach (IChatCommand cmd in c.GetInstances()) - { - Commands.Add(cmd); - } - } - - void InitChain(IContainer c) - { - Chain = c.ResolveObject(new RequestChain()); - Chain.Add(c.GetInstance()); - Chain.Add(c.GetInstance()); - Chain.Add(Timeout); - Chain.Add(c.GetInstance()); - Chain.Add(Commands); - } - - #endregion - - #region IDisposable - - public void Dispose() - { - timeoutWaitTaskToken?.Dispose(); - timeoutWaitTask?.Dispose(); - } - - #endregion + var receiverOptions = new ReceiverOptions { AllowedUpdates = new[] { UpdateType.Message } }; + Client.StartReceiving( + HandleUpdateAsync, + HandleErrorAsync, + receiverOptions); } + + public async Task Stop() + { + if (stopped) return; + Client?.CloseAsync(); + timeoutWaitTaskToken?.Cancel(); + try + { + if (timeoutWaitTask != null) + await timeoutWaitTask; + } + catch (OperationCanceledException) { } + await Timeout?.Save(); + Dispose(); + stopped = true; + } + + #region service + + Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) + { + Log.Error(exception, "Exception while handling API message"); + return Task.CompletedTask; + } + + async Task HandleUpdateAsync(ITelegramBotClient sender, Update update, CancellationToken cancellationToken) + { + if (update.Type != UpdateType.Message || update?.Message?.Type != MessageType.Text) + return; + var message = update.Message!; + + try + { + if (message == null || message.Type != MessageType.Text) + return; + if (!CommandString.TryParse(message.Text, out var cmd)) + return; + if (cmd.UserName != null && cmd.UserName != Commands.Me.Username) + return; + + RequestContext ctx = new RequestContext(Client, update, cmd); + await Chain.Handle(ctx); + } + catch (Exception e) + { + Log.Error(e, "Exception while handling message {0}", message); + } + } + + async Task InitCommands(IContainer c) + { + c.Add(); + c.Add(); + c.Add(); + c.Add(); + c.Add(); + c.Add(); + Commands = c.GetInstance(); + await Commands.Start(); + foreach (IChatCommand cmd in c.GetInstances()) + { + Commands.Add(cmd); + } + } + + void InitChain(IContainer c) + { + Chain = c.ResolveObject(new RequestChain()); + Chain.Add(c.GetInstance()); + Chain.Add(c.GetInstance()); + Chain.Add(Timeout); + Chain.Add(c.GetInstance()); + Chain.Add(Commands); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + timeoutWaitTaskToken?.Dispose(); + timeoutWaitTask?.Dispose(); + } + + #endregion } diff --git a/JetKarmaBot/Models/Award.cs b/JetKarmaBot/Models/Award.cs index 6c7af09..7eced45 100644 --- a/JetKarmaBot/Models/Award.cs +++ b/JetKarmaBot/Models/Award.cs @@ -1,25 +1,22 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; -namespace JetKarmaBot.Models +namespace JetKarmaBot.Models; + +public partial class Award { - public partial class Award - { - public int AwardId { get; set; } - public long ChatId { get; set; } - public long FromId { get; set; } - public long ToId { get; set; } - public sbyte AwardTypeId { get; set; } - public sbyte Amount { get; set; } - public DateTime Date { get; set; } - [ForeignKey("AwardTypeId")] - public virtual AwardType AwardType { get; set; } - [ForeignKey("ChatId")] - public virtual Chat Chat { get; set; } - [ForeignKey("FromId")] - public virtual User From { get; set; } - [ForeignKey("ToId")] - public virtual User To { get; set; } - } + public int AwardId { get; set; } + public long ChatId { get; set; } + public long FromId { get; set; } + public long ToId { get; set; } + public sbyte AwardTypeId { get; set; } + public sbyte Amount { get; set; } + public DateTime Date { get; set; } + [ForeignKey("AwardTypeId")] + public virtual AwardType AwardType { get; set; } + [ForeignKey("ChatId")] + public virtual Chat Chat { get; set; } + [ForeignKey("FromId")] + public virtual User From { get; set; } + [ForeignKey("ToId")] + public virtual User To { get; set; } } diff --git a/JetKarmaBot/Models/AwardType.cs b/JetKarmaBot/Models/AwardType.cs index 2afeea9..91986bb 100644 --- a/JetKarmaBot/Models/AwardType.cs +++ b/JetKarmaBot/Models/AwardType.cs @@ -1,21 +1,17 @@ -using System; -using System.Collections.Generic; +namespace JetKarmaBot.Models; -namespace JetKarmaBot.Models +public partial class AwardType { - public partial class AwardType + public AwardType() { - public AwardType() - { - Awards = new HashSet(); - } - - public sbyte AwardTypeId { get; set; } - public string CommandName { get; set; } - public string Name { get; set; } - public string Symbol { get; set; } - public string Description { get; set; } - - public virtual ICollection Awards { get; set; } + Awards = new HashSet(); } + + public sbyte AwardTypeId { get; set; } + public string CommandName { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } + public string Description { get; set; } + + public virtual ICollection Awards { get; set; } } diff --git a/JetKarmaBot/Models/Chat.cs b/JetKarmaBot/Models/Chat.cs index 2344f37..895cc55 100644 --- a/JetKarmaBot/Models/Chat.cs +++ b/JetKarmaBot/Models/Chat.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; +namespace JetKarmaBot.Models; -namespace JetKarmaBot.Models +public partial class Chat { - public partial class Chat + public Chat() { - public Chat() - { - Awards = new HashSet(); - } - - public long ChatId { get; set; } - public string Locale { get; set; } = "ru-RU"; - public bool IsAdministrator { get; set; } - - public virtual ICollection Awards { get; set; } + Awards = new HashSet(); } + + public long ChatId { get; set; } + public string Locale { get; set; } = "ru-RU"; + public bool IsAdministrator { get; set; } + + public virtual ICollection Awards { get; set; } } diff --git a/JetKarmaBot/Models/KarmaContext.cs b/JetKarmaBot/Models/KarmaContext.cs index 88f4078..4c379d5 100644 --- a/JetKarmaBot/Models/KarmaContext.cs +++ b/JetKarmaBot/Models/KarmaContext.cs @@ -1,183 +1,179 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Perfusion; +using Microsoft.EntityFrameworkCore; -namespace JetKarmaBot.Models +namespace JetKarmaBot.Models; + +[Transient] +public partial class KarmaContext : DbContext { - [Transient] - public partial class KarmaContext : DbContext + public KarmaContext() { - public KarmaContext() + } + + public KarmaContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Awards { get; set; } + public virtual DbSet AwardTypes { get; set; } + public virtual DbSet Chats { get; set; } + public virtual DbSet Users { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => { - } + entity.ToTable("award"); - public KarmaContext(DbContextOptions options) - : base(options) + entity.HasIndex(e => e.AwardId) + .HasDatabaseName("awardid_UNIQUE") + .IsUnique(); + + entity.HasIndex(e => e.AwardTypeId) + .HasDatabaseName("fk_awardtype_idx"); + + entity.HasIndex(e => e.ChatId) + .HasDatabaseName("fk_chat_idx"); + + entity.HasIndex(e => e.FromId) + .HasDatabaseName("fk_from_idx"); + + entity.HasIndex(e => e.ToId) + .HasDatabaseName("fk_to_idx"); + + entity.Property(e => e.AwardId) + .HasColumnName("awardid") + .HasColumnType("int(11)"); + + entity.Property(e => e.Amount) + .HasColumnName("amount") + .HasColumnType("tinyint(3)") + .HasDefaultValueSql("'1'"); + + entity.Property(e => e.AwardTypeId) + .HasColumnName("awardtypeid") + .HasColumnType("tinyint(3)"); + + entity.Property(e => e.ChatId) + .HasColumnName("chatid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.Date) + .HasColumnName("date") + .HasColumnType("datetime") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + entity.Property(e => e.FromId) + .HasColumnName("fromid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.ToId) + .HasColumnName("toid") + .HasColumnType("bigint(20)"); + + entity.HasOne(d => d.AwardType) + .WithMany(p => p.Awards) + .HasForeignKey(d => d.AwardTypeId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_awardtype"); + + entity.HasOne(d => d.Chat) + .WithMany(p => p.Awards) + .HasForeignKey(d => d.ChatId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_chat"); + + entity.HasOne(d => d.From) + .WithMany(p => p.AwardsFrom) + .HasForeignKey(d => d.FromId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_from"); + + entity.HasOne(d => d.To) + .WithMany(p => p.AwardsTo) + .HasForeignKey(d => d.ToId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_to"); + }); + + modelBuilder.Entity(entity => { - } + entity.ToTable("awardtype"); - public virtual DbSet Awards { get; set; } - public virtual DbSet AwardTypes { get; set; } - public virtual DbSet Chats { get; set; } - public virtual DbSet Users { get; set; } + entity.HasIndex(e => e.AwardTypeId) + .HasDatabaseName("awardtypeid_UNIQUE") + .IsUnique(); - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + entity.HasIndex(e => e.CommandName) + .HasDatabaseName("commandname_UNIQUE") + .IsUnique(); + + entity.Property(e => e.AwardTypeId) + .HasColumnName("awardtypeid") + .HasColumnType("tinyint(3)"); + + entity.Property(e => e.CommandName) + .IsRequired() + .HasColumnName("commandname") + .HasColumnType("varchar(35)"); + + entity.Property(e => e.Description) + .IsRequired() + .HasColumnName("description") + .HasColumnType("text"); + + entity.Property(e => e.Name) + .IsRequired() + .HasColumnName("name") + .HasColumnType("varchar(32)"); + + entity.Property(e => e.Symbol) + .IsRequired() + .HasColumnName("symbol") + .HasColumnType("varchar(16)"); + }); + + modelBuilder.Entity(entity => { + entity.ToTable("chat"); - } + entity.Property(e => e.ChatId) + .HasColumnName("chatid") + .HasColumnType("bigint(20)"); - protected override void OnModelCreating(ModelBuilder modelBuilder) + entity.Property(e => e.Locale) + .IsRequired() + .HasColumnName("locale") + .HasColumnType("varchar(10)") + .HasDefaultValueSql("'ru-RU'"); + + entity.Property(e => e.IsAdministrator) + .HasColumnName("isadministrator") + .HasColumnType("tinyint(1)") + .HasDefaultValueSql("'0'"); + }); + + modelBuilder.Entity(entity => { - modelBuilder.Entity(entity => - { - entity.ToTable("award"); + entity.ToTable("user"); - entity.HasIndex(e => e.AwardId) - .HasDatabaseName("awardid_UNIQUE") - .IsUnique(); + entity.Property(e => e.UserId) + .HasColumnName("userid") + .HasColumnType("bigint(20)"); - entity.HasIndex(e => e.AwardTypeId) - .HasDatabaseName("fk_awardtype_idx"); + entity.Property(e => e.Username) + .HasColumnName("username") + .HasColumnType("varchar(45)"); - entity.HasIndex(e => e.ChatId) - .HasDatabaseName("fk_chat_idx"); - - entity.HasIndex(e => e.FromId) - .HasDatabaseName("fk_from_idx"); - - entity.HasIndex(e => e.ToId) - .HasDatabaseName("fk_to_idx"); - - entity.Property(e => e.AwardId) - .HasColumnName("awardid") - .HasColumnType("int(11)"); - - entity.Property(e => e.Amount) - .HasColumnName("amount") - .HasColumnType("tinyint(3)") - .HasDefaultValueSql("'1'"); - - entity.Property(e => e.AwardTypeId) - .HasColumnName("awardtypeid") - .HasColumnType("tinyint(3)"); - - entity.Property(e => e.ChatId) - .HasColumnName("chatid") - .HasColumnType("bigint(20)"); - - entity.Property(e => e.Date) - .HasColumnName("date") - .HasColumnType("datetime") - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - entity.Property(e => e.FromId) - .HasColumnName("fromid") - .HasColumnType("bigint(20)"); - - entity.Property(e => e.ToId) - .HasColumnName("toid") - .HasColumnType("bigint(20)"); - - entity.HasOne(d => d.AwardType) - .WithMany(p => p.Awards) - .HasForeignKey(d => d.AwardTypeId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fk_awardtype"); - - entity.HasOne(d => d.Chat) - .WithMany(p => p.Awards) - .HasForeignKey(d => d.ChatId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fk_chat"); - - entity.HasOne(d => d.From) - .WithMany(p => p.AwardsFrom) - .HasForeignKey(d => d.FromId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fk_from"); - - entity.HasOne(d => d.To) - .WithMany(p => p.AwardsTo) - .HasForeignKey(d => d.ToId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fk_to"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("awardtype"); - - entity.HasIndex(e => e.AwardTypeId) - .HasDatabaseName("awardtypeid_UNIQUE") - .IsUnique(); - - entity.HasIndex(e => e.CommandName) - .HasDatabaseName("commandname_UNIQUE") - .IsUnique(); - - entity.Property(e => e.AwardTypeId) - .HasColumnName("awardtypeid") - .HasColumnType("tinyint(3)"); - - entity.Property(e => e.CommandName) - .IsRequired() - .HasColumnName("commandname") - .HasColumnType("varchar(35)"); - - entity.Property(e => e.Description) - .IsRequired() - .HasColumnName("description") - .HasColumnType("text"); - - entity.Property(e => e.Name) - .IsRequired() - .HasColumnName("name") - .HasColumnType("varchar(32)"); - - entity.Property(e => e.Symbol) - .IsRequired() - .HasColumnName("symbol") - .HasColumnType("varchar(16)"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("chat"); - - entity.Property(e => e.ChatId) - .HasColumnName("chatid") - .HasColumnType("bigint(20)"); - - entity.Property(e => e.Locale) - .IsRequired() - .HasColumnName("locale") - .HasColumnType("varchar(10)") - .HasDefaultValueSql("'ru-RU'"); - - entity.Property(e => e.IsAdministrator) - .HasColumnName("isadministrator") - .HasColumnType("tinyint(1)") - .HasDefaultValueSql("'0'"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("user"); - - entity.Property(e => e.UserId) - .HasColumnName("userid") - .HasColumnType("bigint(20)"); - - entity.Property(e => e.Username) - .HasColumnName("username") - .HasColumnType("varchar(45)"); - - entity.Property(e => e.CooldownDate) - .HasColumnName("cooldowndate") - .HasColumnType("timestamp") - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - }); - } + entity.Property(e => e.CooldownDate) + .HasColumnName("cooldowndate") + .HasColumnType("timestamp") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + }); } } diff --git a/JetKarmaBot/Models/User.cs b/JetKarmaBot/Models/User.cs index a80c74e..618235a 100644 --- a/JetKarmaBot/Models/User.cs +++ b/JetKarmaBot/Models/User.cs @@ -1,23 +1,20 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; -namespace JetKarmaBot.Models +namespace JetKarmaBot.Models; + +public partial class User { - public partial class User + public User() { - public User() - { - AwardsFrom = new HashSet(); - AwardsTo = new HashSet(); - } - - public long UserId { get; set; } - public string Username { get; set; } - public DateTime CooldownDate { get; set; } - [InverseProperty("From")] - public virtual ICollection AwardsFrom { get; set; } - [InverseProperty("To")] - public virtual ICollection AwardsTo { get; set; } + AwardsFrom = new HashSet(); + AwardsTo = new HashSet(); } + + public long UserId { get; set; } + public string Username { get; set; } + public DateTime CooldownDate { get; set; } + [InverseProperty("From")] + public virtual ICollection AwardsFrom { get; set; } + [InverseProperty("To")] + public virtual ICollection AwardsTo { get; set; } } diff --git a/JetKarmaBot/Program.cs b/JetKarmaBot/Program.cs index afb6cde..effe5a2 100644 --- a/JetKarmaBot/Program.cs +++ b/JetKarmaBot/Program.cs @@ -1,79 +1,73 @@ -using System; -using System.Reflection; -using System.Runtime.Loader; -using System.Threading; -using JetKarmaBot.Models; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using NLog; -using Perfusion; +using JetKarmaBot.Models; -namespace JetKarmaBot +namespace JetKarmaBot; + +public static class Program { - public static class Program + private static Logger log = LogManager.GetCurrentClassLogger(); + public enum ExitCode : int { - private static Logger log = LogManager.GetCurrentClassLogger(); - public enum ExitCode : int + Ok = 0, + ErrorNotStarted = 0x80, + ErrorRunning = 0x81, + ErrorException = 0x82, + ErrorInvalidCommandLine = 0x100 + }; + + public static int Main(string[] args) + { + log.Info("Starting JetKarmaBot."); + Container c = new Container(); + var cfg = new Config("karma.cfg.json"); + c.AddInstance(cfg); + + var connStr = cfg.ConnectionString + (cfg.ConnectionString.EndsWith(";") ? "" : ";") + "TreatTinyAsBoolean=false"; + var serverVersion = ServerVersion.AutoDetect(connStr); + + var dbOptions = new DbContextOptionsBuilder() + .UseMySql(connStr, serverVersion); + c.AddInfo(new LogInfo()); + if (cfg.SqlDebug) { - Ok = 0, - ErrorNotStarted = 0x80, - ErrorRunning = 0x81, - ErrorException = 0x82, - ErrorInvalidCommandLine = 0x100 - }; - - public static int Main(string[] args) - { - log.Info("Starting JetKarmaBot."); - Container c = new Container(); - var cfg = new Config("karma.cfg.json"); - c.AddInstance(cfg); - - var connStr = cfg.ConnectionString + (cfg.ConnectionString.EndsWith(";") ? "" : ";") + "TreatTinyAsBoolean=false"; - var serverVersion = ServerVersion.AutoDetect(connStr); - - var dbOptions = new DbContextOptionsBuilder() - .UseMySql(connStr, serverVersion); - c.AddInfo(new LogInfo()); - if (cfg.SqlDebug) - { - dbOptions = dbOptions.UseLoggerFactory(c.GetInstance()); - } - c.AddTransient(() => new KarmaContext(dbOptions.Options)); - c.Add(); - - var bot = c.GetInstance(); - - try - { - bot.Init().GetAwaiter().GetResult(); - log.Info("JetKarmaBot started. Press Ctrl-C to exit..."); - } - catch (Exception ex) - { - log.Error(ex); - return (int)ExitCode.ErrorException; - } - ManualResetEvent quitEvent = new ManualResetEvent(false); - try - { - Console.CancelKeyPress += (sender, eArgs) => // ctrl-c - { - eArgs.Cancel = true; - quitEvent.Set(); - }; - AppDomain.CurrentDomain.ProcessExit += (sender, args) => - { - log.Info("Received stop request, waiting for exit..."); - bot?.Stop()?.GetAwaiter().GetResult(); - }; - } - catch { } - - quitEvent.WaitOne(Timeout.Infinite); - log.Info("Waiting for exit..."); - bot?.Stop()?.GetAwaiter().GetResult(); - - return (int)ExitCode.Ok; + dbOptions = dbOptions.UseLoggerFactory(c.GetInstance()); } + c.AddTransient(() => new KarmaContext(dbOptions.Options)); + c.Add(); + + var bot = c.GetInstance(); + + try + { + bot.Init().GetAwaiter().GetResult(); + log.Info("JetKarmaBot started. Press Ctrl-C to exit..."); + } + catch (Exception ex) + { + log.Error(ex); + return (int)ExitCode.ErrorException; + } + ManualResetEvent quitEvent = new ManualResetEvent(false); + try + { + Console.CancelKeyPress += (sender, eArgs) => // ctrl-c + { + eArgs.Cancel = true; + quitEvent.Set(); + }; + AppDomain.CurrentDomain.ProcessExit += (sender, args) => + { + log.Info("Received stop request, waiting for exit..."); + bot?.Stop()?.GetAwaiter().GetResult(); + }; + } + catch { } + + quitEvent.WaitOne(Timeout.Infinite); + log.Info("Waiting for exit..."); + bot?.Stop()?.GetAwaiter().GetResult(); + + return (int)ExitCode.Ok; } } diff --git a/JetKarmaBot/Services/Handling/CommandRouter.cs b/JetKarmaBot/Services/Handling/CommandRouter.cs index c5ef18d..4903dc4 100644 --- a/JetKarmaBot/Services/Handling/CommandRouter.cs +++ b/JetKarmaBot/Services/Handling/CommandRouter.cs @@ -1,111 +1,105 @@ -using JetKarmaBot.Commands; -using NLog; -using Perfusion; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using NLog; using Telegram.Bot; +using JetKarmaBot.Commands; -namespace JetKarmaBot.Services.Handling +namespace JetKarmaBot.Services.Handling; + +public class ChatCommandRouter : IRequestHandler { - public class ChatCommandRouter : IRequestHandler + public class Feature { - 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; } + 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; } - public async Task Start() - { - Me = await Client.GetMeAsync(); - } + public async Task Start() + { + Me = await Client.GetMeAsync(); + } - public Task Handle(RequestContext ctx, Func next) - { - log.Debug("Message received"); - CommandString cmd = ctx.Command; - Feature feature = new Feature() { Router = this }; - ctx.AddFeature(feature); + public Task Handle(RequestContext ctx, Func next) + { + log.Debug("Message received"); + CommandString cmd = ctx.Command; + Feature feature = new Feature() { Router = this }; + ctx.AddFeature(feature); - try + try + { + if (commands.ContainsKey(cmd.Command)) { - if (commands.ContainsKey(cmd.Command)) - { - 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) - { - log.Error($"Error while handling command {cmd.Command}!"); - log.Error(e); - } - - return next(ctx); - } - - public void Add(IChatCommand c) - { - log.ConditionalTrace($"Adding command {c.GetType().Name}"); - foreach (var name in c.Names) - { - log.ConditionalTrace($"Mounting {c.GetType().Name} to {name}"); - if (commands.ContainsKey(name)) - throw new Exception($"command collision for name {name}, commands {commands[name].GetType()} and {c.GetType()}"); - commands[name] = c; + 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(); } } - - internal string GetHelpText(Locale loc) + catch (Exception e) { - List pieces = new List(); - foreach (IChatCommand c in commands.Values.Distinct()) - { - string build = ""; - List names = c.Names.ToList(); - for (int i = 0; i < names.Count - 1; i++) - { - build = build + "/" + names[i] + "\n"; - } - build += "/" + names[names.Count - 1] + " " + string.Join(" ", c.Arguments.Select(x => (!x.Required ? "[" : "") + x.Name + (!x.Required ? "]" : ""))) + " " + getLocalizedCMDDesc(c, loc) + ""; - pieces.Add(build); - } - return string.Join("\n", pieces); + log.Error($"Error while handling command {cmd.Command}!"); + log.Error(e); } - internal string GetHelpTextFor(string commandname, Locale loc) + return next(ctx); + } + + public void Add(IChatCommand c) + { + log.ConditionalTrace($"Adding command {c.GetType().Name}"); + foreach (var name in c.Names) + { + log.ConditionalTrace($"Mounting {c.GetType().Name} to {name}"); + if (commands.ContainsKey(name)) + throw new Exception($"command collision for name {name}, commands {commands[name].GetType()} and {c.GetType()}"); + commands[name] = c; + } + } + + internal string GetHelpText(Locale loc) + { + List pieces = new List(); + foreach (IChatCommand c in commands.Values.Distinct()) { - IChatCommand c = commands[commandname]; string build = ""; List names = c.Names.ToList(); for (int i = 0; i < names.Count - 1; i++) { build = build + "/" + names[i] + "\n"; } - build += "/" + names[names.Count - 1] + " " + string.Join(" ", c.Arguments.Select(x => (!x.Required ? "[" : "") + x.Name + (!x.Required ? "]" : ""))) + " " + getLocalizedCMDDesc(c, loc) + "\n"; - build += string.Join("\n", c.Arguments.Select(ca => (!ca.Required ? "[" : "") + ca.Name + (!ca.Required ? "]" : "") + ": " + getLocalizedCMDArgDesc(ca, loc) + "")); - return build; + build += "/" + names[names.Count - 1] + " " + string.Join(" ", c.Arguments.Select(x => (!x.Required ? "[" : "") + x.Name + (!x.Required ? "]" : ""))) + " " + getLocalizedCMDDesc(c, loc) + ""; + pieces.Add(build); } - - private string getLocalizedCMDDesc(IChatCommand cmd, Locale loc) - { - if (loc.ContainsKey(cmd.DescriptionID)) return loc[cmd.DescriptionID]; - else return cmd.Description; - } - private string getLocalizedCMDArgDesc(ChatCommandArgument arg, Locale loc) - { - if (loc.ContainsKey(arg.DescriptionID)) return loc[arg.DescriptionID]; - else return arg.Description; - } - - Dictionary commands = new Dictionary(); + return string.Join("\n", pieces); } + + internal string GetHelpTextFor(string commandname, Locale loc) + { + IChatCommand c = commands[commandname]; + string build = ""; + List names = c.Names.ToList(); + for (int i = 0; i < names.Count - 1; i++) + { + build = build + "/" + names[i] + "\n"; + } + build += "/" + names[names.Count - 1] + " " + string.Join(" ", c.Arguments.Select(x => (!x.Required ? "[" : "") + x.Name + (!x.Required ? "]" : ""))) + " " + getLocalizedCMDDesc(c, loc) + "\n"; + build += string.Join("\n", c.Arguments.Select(ca => (!ca.Required ? "[" : "") + ca.Name + (!ca.Required ? "]" : "") + ": " + getLocalizedCMDArgDesc(ca, loc) + "")); + return build; + } + + private string getLocalizedCMDDesc(IChatCommand cmd, Locale loc) + { + if (loc.ContainsKey(cmd.DescriptionID)) return loc[cmd.DescriptionID]; + else return cmd.Description; + } + private string getLocalizedCMDArgDesc(ChatCommandArgument arg, Locale loc) + { + if (loc.ContainsKey(arg.DescriptionID)) return loc[arg.DescriptionID]; + else return arg.Description; + } + + Dictionary commands = new Dictionary(); } diff --git a/JetKarmaBot/Services/Handling/DatabaseHandler.cs b/JetKarmaBot/Services/Handling/DatabaseHandler.cs index 73d848d..3f456d6 100644 --- a/JetKarmaBot/Services/Handling/DatabaseHandler.cs +++ b/JetKarmaBot/Services/Handling/DatabaseHandler.cs @@ -1,23 +1,17 @@ -using System; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Perfusion; +namespace JetKarmaBot.Services.Handling; -namespace JetKarmaBot.Services.Handling +public class DatabaseHandler : IRequestHandler { - public class DatabaseHandler : IRequestHandler + [Inject] private KarmaContextFactory Db; + [Inject] private Localization Locale; + public async Task Handle(RequestContext ctx, Func next) { - [Inject] private KarmaContextFactory Db; - [Inject] private Localization Locale; - public async Task Handle(RequestContext ctx, Func next) + using (var db = Db.GetContext()) { - using (var db = Db.GetContext()) - { - ctx.AddFeature(db); // KarmaContext - ctx.AddFeature(Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id))?.Locale ?? "ru-ru"]); // Locale - await next(ctx); - await db.SaveChangesAsync(); - } + ctx.AddFeature(db); // KarmaContext + ctx.AddFeature(Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id))?.Locale ?? "ru-ru"]); // Locale + await next(ctx); + await db.SaveChangesAsync(); } } } \ No newline at end of file diff --git a/JetKarmaBot/Services/Handling/RequestChain.cs b/JetKarmaBot/Services/Handling/RequestChain.cs index 8c5cad3..f35df69 100644 --- a/JetKarmaBot/Services/Handling/RequestChain.cs +++ b/JetKarmaBot/Services/Handling/RequestChain.cs @@ -1,42 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using NLog; -using Perfusion; -namespace JetKarmaBot.Services.Handling +namespace JetKarmaBot.Services.Handling; + +public interface IRequestHandler { - public interface IRequestHandler + Task Handle(RequestContext ctx, Func next); +} +public class RequestChain : IRequestHandler +{ + [Inject] private Logger log; + List handlerStack = new List(); + public async Task Handle(RequestContext ctx, Func next = null) { - Task Handle(RequestContext ctx, Func next); - } - public class RequestChain : IRequestHandler - { - [Inject] private Logger log; - List handlerStack = new List(); - public async Task Handle(RequestContext ctx, Func next = null) + int i = 0; + Func chainNext = null; + chainNext = (newCtx) => { - int i = 0; - Func chainNext = null; - chainNext = (newCtx) => + if (i == handlerStack.Count) { - if (i == handlerStack.Count) - { - log.ConditionalTrace("(next) End of request chain"); - return Task.CompletedTask; - } - IRequestHandler handler = handlerStack[i++]; - log.ConditionalTrace($"(next) Executing handler {handler.GetType().Name}"); - return handler.Handle(newCtx, chainNext); - }; - await chainNext(ctx); - if (next != null) - await next(ctx); - } - public void Add(IRequestHandler handler) - { - log.ConditionalTrace($"Adding {handler.GetType().Name} to reqchain"); - handlerStack.Add(handler); - } + log.ConditionalTrace("(next) End of request chain"); + return Task.CompletedTask; + } + IRequestHandler handler = handlerStack[i++]; + log.ConditionalTrace($"(next) Executing handler {handler.GetType().Name}"); + return handler.Handle(newCtx, chainNext); + }; + await chainNext(ctx); + if (next != null) + await next(ctx); + } + public void Add(IRequestHandler handler) + { + log.ConditionalTrace($"Adding {handler.GetType().Name} to reqchain"); + handlerStack.Add(handler); } } \ No newline at end of file diff --git a/JetKarmaBot/Services/Handling/RequestContext.cs b/JetKarmaBot/Services/Handling/RequestContext.cs index 58297ea..20ac5a2 100644 --- a/JetKarmaBot/Services/Handling/RequestContext.cs +++ b/JetKarmaBot/Services/Handling/RequestContext.cs @@ -1,39 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using JetKarmaBot.Commands; -using JetKarmaBot.Models; using Telegram.Bot; -using Telegram.Bot.Args; using Telegram.Bot.Types; +using JetKarmaBot.Commands; -namespace JetKarmaBot.Services.Handling +namespace JetKarmaBot.Services.Handling; + +public class RequestContext : IServiceProvider { - public class RequestContext : IServiceProvider + public ITelegramBotClient Client { get; } + public Update EventArgs { get; } + public CommandString Command { get; } + public Dictionary Features { get; } = new Dictionary(); + public RequestContext(ITelegramBotClient client, Update args, CommandString cmd) { - public ITelegramBotClient Client { get; } - public Update EventArgs { get; } - public CommandString Command { get; } - public Dictionary Features { get; } = new Dictionary(); - public RequestContext(ITelegramBotClient client, Update args, CommandString cmd) - { - Client = client; - EventArgs = args; - Command = cmd; - } - public object GetService(Type serviceType) => Features[serviceType]; - public T GetFeature() => (T)Features[typeof(T)]; - public void AddFeature(T feat) => Features[typeof(T)] = feat; - - //Method to reduce WET in commands - public Task SendMessage(string text) - => Client.SendMessage( - chatId: EventArgs.Message.Chat.Id, - text: text, - disableNotification: true, - parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, - replyParameters: new ReplyParameters { MessageId = EventArgs.Message.MessageId } - ); + Client = client; + EventArgs = args; + Command = cmd; } + public object GetService(Type serviceType) => Features[serviceType]; + public T GetFeature() => (T)Features[typeof(T)]; + public void AddFeature(T feat) => Features[typeof(T)] = feat; + + //Method to reduce WET in commands + public Task SendMessage(string text) + => Client.SendMessage( + chatId: EventArgs.Message.Chat.Id, + text: text, + disableNotification: true, + parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, + replyParameters: new ReplyParameters { MessageId = EventArgs.Message.MessageId } + ); } \ No newline at end of file diff --git a/JetKarmaBot/Services/Handling/SaveData.cs b/JetKarmaBot/Services/Handling/SaveData.cs index 5c0130c..1a16261 100644 --- a/JetKarmaBot/Services/Handling/SaveData.cs +++ b/JetKarmaBot/Services/Handling/SaveData.cs @@ -1,36 +1,33 @@ -using System; -using System.Threading.Tasks; -using JetKarmaBot.Models; using Microsoft.EntityFrameworkCore; +using JetKarmaBot.Models; -namespace JetKarmaBot.Services.Handling +namespace JetKarmaBot.Services.Handling; + +public class SaveData : IRequestHandler { - public class SaveData : IRequestHandler + public async Task Handle(RequestContext ctx, Func next) { - public async Task Handle(RequestContext ctx, Func next) - { - KarmaContext db = ctx.GetFeature(); - await AddUserToDatabase(db, ctx.EventArgs.Message.From); - if (ctx.EventArgs.Message.ReplyToMessage != null) - await AddUserToDatabase(db, ctx.EventArgs.Message.ReplyToMessage.From); - 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); - } - 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; - } + KarmaContext db = ctx.GetFeature(); + await AddUserToDatabase(db, ctx.EventArgs.Message.From); + if (ctx.EventArgs.Message.ReplyToMessage != null) + await AddUserToDatabase(db, ctx.EventArgs.Message.ReplyToMessage.From); + 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); + } + 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/TimeoutManager.cs b/JetKarmaBot/Services/Handling/TimeoutManager.cs index 7f9fbee..c246552 100644 --- a/JetKarmaBot/Services/Handling/TimeoutManager.cs +++ b/JetKarmaBot/Services/Handling/TimeoutManager.cs @@ -1,133 +1,126 @@ -using Perfusion; -using System.Collections.Generic; -using System; -using System.Threading.Tasks; -using JetKarmaBot.Models; -using System.Threading; -using System.Linq; using NLog; +using JetKarmaBot.Models; -namespace JetKarmaBot.Services.Handling +namespace JetKarmaBot.Services.Handling; + +[Singleton] +public class TimeoutManager : IRequestHandler { - [Singleton] - public class TimeoutManager : IRequestHandler + public class Feature { - public class Feature + public double Multiplier = 1; + } + public class PreDbThrowout : IRequestHandler + { + public TimeoutManager Timeout { get; } + public PreDbThrowout(TimeoutManager timeout) { - public double Multiplier = 1; - } - public class PreDbThrowout : IRequestHandler - { - public TimeoutManager Timeout { get; } - public PreDbThrowout(TimeoutManager timeout) - { - Timeout = timeout; - } - - public async Task Handle(RequestContext ctx, Func next) - { - var uid = ctx.EventArgs.Message.From.Id; - if (Timeout.TimeoutCache.TryGetValue(uid, out var stats)) - { - DateTime debtLimit = DateTime.Now.AddSeconds(Timeout.cfg.Timeout.DebtLimitSeconds); - if (debtLimit < stats.CooldownDate && stats.TimeoutMessaged) - return; - } - await next(ctx); - } - } - public class TimeoutStats - { - public DateTime CooldownDate; - public bool TimeoutMessaged; - public DateTime PreviousAwardDate; - } - [Inject] private KarmaContextFactory Db; - [Inject] private Config cfg; - [Inject] private Localization Locale; - [Inject] private Logger log; - public Dictionary TimeoutCache = new (); - private async Task ApplyCost(string name, bool succeded, long uid, KarmaContext db, Feature feature) - { - if (feature.Multiplier == 0) - return; - 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) - //Programming error - throw new NotImplementedException(); - TimeoutCache[uid].CooldownDate = (TimeoutCache[uid].CooldownDate <= DateTime.Now ? DateTime.Now : TimeoutCache[uid].CooldownDate) - .AddSeconds(feature.Multiplier * costSeconds); - TimeoutCache[uid].TimeoutMessaged = false; - } - - private async Task PopulateStats(long uid, KarmaContext db) - { - if (!TimeoutCache.ContainsKey(uid)) - { - log.ConditionalTrace($"User {uid} not present: saving to cache"); - TimeoutCache[uid] = new TimeoutStats() - { - CooldownDate = (await db.Users.FindAsync(uid))?.CooldownDate ?? DateTime.Now - }; - } - } - public async Task Save(CancellationToken ct = default(CancellationToken)) - { - log.Debug("Saving timeout info to database"); - using (KarmaContext db = Db.GetContext()) - { - foreach (var i in TimeoutCache.Keys) - { - (await db.Users.FindAsync(new object[] { i }, ct)).CooldownDate = TimeoutCache[i].CooldownDate; - } - await db.SaveChangesAsync(ct); - } - log.Debug("Saved timeout info to database"); - } - public async Task SaveLoop(CancellationToken ct = default(CancellationToken)) - { - while (!ct.IsCancellationRequested) - { - await Task.Delay(cfg.Timeout.SaveIntervalSeconds * 1000, ct); - await Save(ct); - } + Timeout = timeout; } public async Task Handle(RequestContext ctx, Func next) { var uid = ctx.EventArgs.Message.From.Id; - KarmaContext db = ctx.GetFeature(); - await PopulateStats(uid, db); - DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds); - if (debtLimit < TimeoutCache[uid].CooldownDate) + if (Timeout.TimeoutCache.TryGetValue(uid, out var stats)) { - if (!TimeoutCache[uid].TimeoutMessaged) - { - Locale currentLocale = ctx.GetFeature(); - await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]); - TimeoutCache[uid].TimeoutMessaged = true; - } - return; + DateTime debtLimit = DateTime.Now.AddSeconds(Timeout.cfg.Timeout.DebtLimitSeconds); + if (debtLimit < stats.CooldownDate && stats.TimeoutMessaged) + return; } - Feature feature = new Feature(); - ctx.AddFeature(feature); - await next(ctx); - - var routerFeature = ctx.GetFeature(); - await ApplyCost(getTypeName(routerFeature.CommandType), routerFeature.Succeded, uid, db, feature); - } - 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)) + ">" : ""); } } + public class TimeoutStats + { + public DateTime CooldownDate; + public bool TimeoutMessaged; + public DateTime PreviousAwardDate; + } + [Inject] private KarmaContextFactory Db; + [Inject] private Config cfg; + [Inject] private Localization Locale; + [Inject] private Logger log; + public Dictionary TimeoutCache = new (); + private async Task ApplyCost(string name, bool succeded, long uid, KarmaContext db, Feature feature) + { + if (feature.Multiplier == 0) + return; + 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) + //Programming error + throw new NotImplementedException(); + TimeoutCache[uid].CooldownDate = (TimeoutCache[uid].CooldownDate <= DateTime.Now ? DateTime.Now : TimeoutCache[uid].CooldownDate) + .AddSeconds(feature.Multiplier * costSeconds); + TimeoutCache[uid].TimeoutMessaged = false; + } + + private async Task PopulateStats(long uid, KarmaContext db) + { + if (!TimeoutCache.ContainsKey(uid)) + { + log.ConditionalTrace($"User {uid} not present: saving to cache"); + TimeoutCache[uid] = new TimeoutStats() + { + CooldownDate = (await db.Users.FindAsync(uid))?.CooldownDate ?? DateTime.Now + }; + } + } + public async Task Save(CancellationToken ct = default(CancellationToken)) + { + log.Debug("Saving timeout info to database"); + using (KarmaContext db = Db.GetContext()) + { + foreach (var i in TimeoutCache.Keys) + { + (await db.Users.FindAsync(new object[] { i }, ct)).CooldownDate = TimeoutCache[i].CooldownDate; + } + await db.SaveChangesAsync(ct); + } + log.Debug("Saved timeout info to database"); + } + public async Task SaveLoop(CancellationToken ct = default(CancellationToken)) + { + while (!ct.IsCancellationRequested) + { + await Task.Delay(cfg.Timeout.SaveIntervalSeconds * 1000, ct); + await Save(ct); + } + } + + public async Task Handle(RequestContext ctx, Func next) + { + var uid = ctx.EventArgs.Message.From.Id; + KarmaContext db = ctx.GetFeature(); + await PopulateStats(uid, db); + DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds); + if (debtLimit < TimeoutCache[uid].CooldownDate) + { + if (!TimeoutCache[uid].TimeoutMessaged) + { + Locale currentLocale = ctx.GetFeature(); + await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]); + TimeoutCache[uid].TimeoutMessaged = true; + } + return; + } + Feature feature = new Feature(); + ctx.AddFeature(feature); + + await next(ctx); + + var routerFeature = ctx.GetFeature(); + await ApplyCost(getTypeName(routerFeature.CommandType), routerFeature.Succeded, uid, db, feature); + } + 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 diff --git a/JetKarmaBot/Services/KarmaContextFactory.cs b/JetKarmaBot/Services/KarmaContextFactory.cs index 63da3b4..a091536 100644 --- a/JetKarmaBot/Services/KarmaContextFactory.cs +++ b/JetKarmaBot/Services/KarmaContextFactory.cs @@ -1,12 +1,10 @@ using JetKarmaBot.Models; -using Perfusion; -namespace JetKarmaBot.Services +namespace JetKarmaBot.Services; + +public class KarmaContextFactory { - public class KarmaContextFactory - { - [Inject] IContainer C { get; set; } + [Inject] IContainer C { get; set; } - public KarmaContext GetContext() => C.GetInstance(); - } + public KarmaContext GetContext() => C.GetInstance(); } diff --git a/JetKarmaBot/Services/Localization.cs b/JetKarmaBot/Services/Localization.cs index 18c703c..5e15653 100644 --- a/JetKarmaBot/Services/Localization.cs +++ b/JetKarmaBot/Services/Localization.cs @@ -1,171 +1,166 @@ -using System; using System.Collections; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json.Linq; using NLog; -using Perfusion; -namespace JetKarmaBot +namespace JetKarmaBot; + +public class Localization : IReadOnlyDictionary { - public class Localization : IReadOnlyDictionary + private Dictionary locales = new Dictionary(); + + public Localization(IContainer c) { - private Dictionary locales = new Dictionary(); + c.ResolveObject(this); + log.Info("Initializing..."); + string langsFolder = "lang"; + if (!Directory.Exists(langsFolder)) + Directory.CreateDirectory(langsFolder); - public Localization(IContainer c) + foreach (string langFilePath in Directory.EnumerateFiles(langsFolder, "*.json")) { - c.ResolveObject(this); - log.Info("Initializing..."); - string langsFolder = "lang"; - if (!Directory.Exists(langsFolder)) - Directory.CreateDirectory(langsFolder); - - foreach (string langFilePath in Directory.EnumerateFiles(langsFolder, "*.json")) + try { - try - { - string langName = Path.GetFileNameWithoutExtension(langFilePath); - string langKey = langName.ToLowerInvariant(); - locales[langKey] = new Locale(JObject.Parse(File.ReadAllText(langFilePath)), langKey); - log.Debug("Found " + langName); - } - catch (Exception e) - { - log.Error($"Error while parsing {langFilePath}!"); - log.Error(e); - } + string langName = Path.GetFileNameWithoutExtension(langFilePath); + string langKey = langName.ToLowerInvariant(); + locales[langKey] = new Locale(JObject.Parse(File.ReadAllText(langFilePath)), langKey); + log.Debug("Found " + langName); } - - if (locales.Any()) - log.Info("Initialized!"); - else - throw new FileNotFoundException($"No locales found in {langsFolder}!"); - } - - public Locale this[string locale] - { - get + catch (Exception e) { - locale = locale.ToLowerInvariant(); - return locales[locale]; + log.Error($"Error while parsing {langFilePath}!"); + log.Error(e); } } - [Inject] - private Logger log; + if (locales.Any()) + log.Info("Initialized!"); + else + throw new FileNotFoundException($"No locales found in {langsFolder}!"); + } - public Locale FindByCommonName(string name) - { - log.ConditionalTrace("Trying to find locale " + name); - foreach (Locale l in locales.Values) - { - if (l.CommonNames.Contains(name)) - { - log.ConditionalTrace("Found locale " + l.Name); - return l; - } - } - // Try to find as locale prefix - IEnumerable matchinglocales = locales.Values.Where(x => x.Name.StartsWith(name + "-")); - if (matchinglocales.Count() > 1) - { - LocalizationException l = new LocalizationException("Too many locales"); - l.Data["LocaleNames"] = matchinglocales.ToArray(); - throw l; - } - else if (matchinglocales.Count() == 1) - return matchinglocales.First(); - log.Warn("Failed to find locale " + name); - return null; - } - public bool ContainsLocale(string locale) + public Locale this[string locale] + { + get { locale = locale.ToLowerInvariant(); - return locales.ContainsKey(locale); + return locales[locale]; } - - void Log(Exception e) => Console.WriteLine(e); - - public IEnumerable Keys => locales.Keys; - - public IEnumerable Values => locales.Values; - - public int Count => locales.Count; - - public bool ContainsKey(string key) => locales.ContainsKey(key.ToLower()); - - public bool TryGetValue(string key, out Locale value) - { - return locales.TryGetValue(key.ToLower(), out value); - } - - public IEnumerator> GetEnumerator() - { - return locales.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - } - public class Locale : IReadOnlyDictionary + + [Inject] + private Logger log; + + public Locale FindByCommonName(string name) { - private Dictionary locale; - private string localeName; - private string[] commonNames; - private string note; - - public Locale(JObject locale, string localeName) + log.ConditionalTrace("Trying to find locale " + name); + foreach (Locale l in locales.Values) { - this.locale = locale.Property("strings").Value.ToObject>(); - this.localeName = localeName; - this.commonNames = locale.Property("names").Value.ToObject(); - if (locale.ContainsKey("note")) this.note = locale.Property("note").Value.ToObject(); + if (l.CommonNames.Contains(name)) + { + log.ConditionalTrace("Found locale " + l.Name); + return l; + } } - public string[] CommonNames => commonNames; - public string Name => localeName; - public bool HasNote => note != null; - - public string Note => note; - - public IEnumerable Keys => ((IReadOnlyDictionary)locale).Keys; - - public IEnumerable Values => ((IReadOnlyDictionary)locale).Values; - - public int Count => ((IReadOnlyDictionary)locale).Count; - - public string this[string name] => locale.ContainsKey(name) ? locale[name] : "Unmapped locale key: " + name; - - public bool ContainsKey(string key) + // Try to find as locale prefix + IEnumerable matchinglocales = locales.Values.Where(x => x.Name.StartsWith(name + "-")); + if (matchinglocales.Count() > 1) { - return ((IReadOnlyDictionary)locale).ContainsKey(key); - } - - public bool TryGetValue(string key, out string value) - { - return ((IReadOnlyDictionary)locale).TryGetValue(key, out value); - } - - public IEnumerator> GetEnumerator() - { - return ((IReadOnlyDictionary)locale).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IReadOnlyDictionary)locale).GetEnumerator(); + LocalizationException l = new LocalizationException("Too many locales"); + l.Data["LocaleNames"] = matchinglocales.ToArray(); + throw l; } + else if (matchinglocales.Count() == 1) + return matchinglocales.First(); + log.Warn("Failed to find locale " + name); + return null; } - [System.Serializable] - public class LocalizationException : Exception + public bool ContainsLocale(string locale) { - public LocalizationException() { } - public LocalizationException(string message) : base(message) { } - public LocalizationException(string message, Exception inner) : base(message, inner) { } - protected LocalizationException( - SerializationInfo info, - StreamingContext context) : base(info, context) { } + locale = locale.ToLowerInvariant(); + return locales.ContainsKey(locale); } + + void Log(Exception e) => Console.WriteLine(e); + + public IEnumerable Keys => locales.Keys; + + public IEnumerable Values => locales.Values; + + public int Count => locales.Count; + + public bool ContainsKey(string key) => locales.ContainsKey(key.ToLower()); + + public bool TryGetValue(string key, out Locale value) + { + return locales.TryGetValue(key.ToLower(), out value); + } + + public IEnumerator> GetEnumerator() + { + return locales.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + +} +public class Locale : IReadOnlyDictionary +{ + private Dictionary locale; + private string localeName; + private string[] commonNames; + private string note; + + public Locale(JObject locale, string localeName) + { + this.locale = locale.Property("strings").Value.ToObject>(); + this.localeName = localeName; + this.commonNames = locale.Property("names").Value.ToObject(); + if (locale.ContainsKey("note")) this.note = locale.Property("note").Value.ToObject(); + } + public string[] CommonNames => commonNames; + public string Name => localeName; + public bool HasNote => note != null; + + public string Note => note; + + public IEnumerable Keys => ((IReadOnlyDictionary)locale).Keys; + + public IEnumerable Values => ((IReadOnlyDictionary)locale).Values; + + public int Count => ((IReadOnlyDictionary)locale).Count; + + public string this[string name] => locale.ContainsKey(name) ? locale[name] : "Unmapped locale key: " + name; + + public bool ContainsKey(string key) + { + return ((IReadOnlyDictionary)locale).ContainsKey(key); + } + + public bool TryGetValue(string key, out string value) + { + return ((IReadOnlyDictionary)locale).TryGetValue(key, out value); + } + + public IEnumerator> GetEnumerator() + { + return ((IReadOnlyDictionary)locale).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IReadOnlyDictionary)locale).GetEnumerator(); + } +} +[System.Serializable] +public class LocalizationException : Exception +{ + public LocalizationException() { } + public LocalizationException(string message) : base(message) { } + public LocalizationException(string message, Exception inner) : base(message, inner) { } + protected LocalizationException( + SerializationInfo info, + StreamingContext context) : base(info, context) { } } \ No newline at end of file diff --git a/JetKarmaBot/Services/LogInfo.cs b/JetKarmaBot/Services/LogInfo.cs index 6a55117..593e098 100644 --- a/JetKarmaBot/Services/LogInfo.cs +++ b/JetKarmaBot/Services/LogInfo.cs @@ -1,22 +1,18 @@ -using System; -using System.Linq; using NLog; -using Perfusion; -namespace JetKarmaBot +namespace JetKarmaBot; + +public class LogInfo : ObjectInfo { - public class LogInfo : ObjectInfo - { - public override ObjectInfo Clone() => new LogInfo(); + public override ObjectInfo Clone() => new LogInfo(); - public override object GetInstance(IContainer c, Type requester = null) - { - return LogManager.GetLogger(requester != null ? getTypeName(requester) : ""); - } - 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)) + ">" : ""); - } + public override object GetInstance(IContainer c, Type requester = null) + { + return LogManager.GetLogger(requester != null ? getTypeName(requester) : ""); + } + 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 diff --git a/JetKarmaBot/Services/LoggerVirtualizer.cs b/JetKarmaBot/Services/LoggerVirtualizer.cs index 85fb301..dff10ee 100644 --- a/JetKarmaBot/Services/LoggerVirtualizer.cs +++ b/JetKarmaBot/Services/LoggerVirtualizer.cs @@ -1,103 +1,100 @@ -using System; using Microsoft.Extensions.Logging; using NLog; -using Perfusion; -namespace JetKarmaBot +namespace JetKarmaBot; + +public class NLoggerFactory : ILoggerFactory { - public class NLoggerFactory : ILoggerFactory + [Inject] + private NLoggerProvider c; + public void AddProvider(ILoggerProvider provider) { - [Inject] - private NLoggerProvider c; - public void AddProvider(ILoggerProvider provider) - { - } - - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) => c.CreateLogger(categoryName); - - public void Dispose() - { - } } - public class NLoggerProvider : ILoggerProvider + + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) => c.CreateLogger(categoryName); + + public void Dispose() { - [Inject] - private Container c; + } +} +public class NLoggerProvider : ILoggerProvider +{ + [Inject] + private Container c; - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) => new LoggerVirtualizer(LogManager.GetLogger(categoryName)); + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) => new LoggerVirtualizer(LogManager.GetLogger(categoryName)); - public void Dispose() + public void Dispose() + { + } +} + +public class LoggerVirtualizer : Microsoft.Extensions.Logging.ILogger +{ + private Logger logger; + + public LoggerVirtualizer(Logger logger) + { + this.logger = logger; + } + + private Microsoft.Extensions.Logging.LogLevel getAppropriate(NLog.LogLevel level) + { + if (level == NLog.LogLevel.Trace) + return Microsoft.Extensions.Logging.LogLevel.Trace; + else if (level == NLog.LogLevel.Debug) + return Microsoft.Extensions.Logging.LogLevel.Debug; + else if (level == NLog.LogLevel.Info) + return Microsoft.Extensions.Logging.LogLevel.Information; + else if (level == NLog.LogLevel.Warn) + return Microsoft.Extensions.Logging.LogLevel.Warning; + else if (level == NLog.LogLevel.Error) + return Microsoft.Extensions.Logging.LogLevel.Error; + else if (level == NLog.LogLevel.Fatal) + return Microsoft.Extensions.Logging.LogLevel.Critical; + else if (level == NLog.LogLevel.Off) + return Microsoft.Extensions.Logging.LogLevel.None; + else + return Microsoft.Extensions.Logging.LogLevel.None; + } + private NLog.LogLevel getAppropriate(Microsoft.Extensions.Logging.LogLevel level) + { + switch (level) { + case Microsoft.Extensions.Logging.LogLevel.Trace: + return NLog.LogLevel.Trace; + case Microsoft.Extensions.Logging.LogLevel.Debug: + return NLog.LogLevel.Debug; + case Microsoft.Extensions.Logging.LogLevel.Information: + return NLog.LogLevel.Info; + case Microsoft.Extensions.Logging.LogLevel.Warning: + return NLog.LogLevel.Warn; + case Microsoft.Extensions.Logging.LogLevel.Error: + return NLog.LogLevel.Error; + case Microsoft.Extensions.Logging.LogLevel.Critical: + return NLog.LogLevel.Fatal; + default: + return NLog.LogLevel.Off; } } - public class LoggerVirtualizer : Microsoft.Extensions.Logging.ILogger + public IDisposable BeginScope(TState state) => new SomeDisposable(); + + public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { - private Logger logger; - - public LoggerVirtualizer(Logger logger) - { - this.logger = logger; - } - - private Microsoft.Extensions.Logging.LogLevel getAppropriate(NLog.LogLevel level) - { - if (level == NLog.LogLevel.Trace) - return Microsoft.Extensions.Logging.LogLevel.Trace; - else if (level == NLog.LogLevel.Debug) - return Microsoft.Extensions.Logging.LogLevel.Debug; - else if (level == NLog.LogLevel.Info) - return Microsoft.Extensions.Logging.LogLevel.Information; - else if (level == NLog.LogLevel.Warn) - return Microsoft.Extensions.Logging.LogLevel.Warning; - else if (level == NLog.LogLevel.Error) - return Microsoft.Extensions.Logging.LogLevel.Error; - else if (level == NLog.LogLevel.Fatal) - return Microsoft.Extensions.Logging.LogLevel.Critical; - else if (level == NLog.LogLevel.Off) - return Microsoft.Extensions.Logging.LogLevel.None; - else - return Microsoft.Extensions.Logging.LogLevel.None; - } - private NLog.LogLevel getAppropriate(Microsoft.Extensions.Logging.LogLevel level) - { - switch (level) - { - case Microsoft.Extensions.Logging.LogLevel.Trace: - return NLog.LogLevel.Trace; - case Microsoft.Extensions.Logging.LogLevel.Debug: - return NLog.LogLevel.Debug; - case Microsoft.Extensions.Logging.LogLevel.Information: - return NLog.LogLevel.Info; - case Microsoft.Extensions.Logging.LogLevel.Warning: - return NLog.LogLevel.Warn; - case Microsoft.Extensions.Logging.LogLevel.Error: - return NLog.LogLevel.Error; - case Microsoft.Extensions.Logging.LogLevel.Critical: - return NLog.LogLevel.Fatal; - default: - return NLog.LogLevel.Off; - } - } - - public IDisposable BeginScope(TState state) => new SomeDisposable(); - - public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) - { - return logger.IsEnabled(getAppropriate(logLevel)); - } - - public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - string message = state.ToString(); - if (exception != null) logger.Log(getAppropriate(logLevel), exception, formatter(state, exception)); - else logger.Log(getAppropriate(logLevel), state); - } + return logger.IsEnabled(getAppropriate(logLevel)); } - public class SomeDisposable : IDisposable + + public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + string message = state.ToString(); + if (exception != null) logger.Log(getAppropriate(logLevel), exception, formatter(state, exception)); + else logger.Log(getAppropriate(logLevel), state); + } +} +public class SomeDisposable : IDisposable +{ + public void Dispose() { - public void Dispose() - { - } } } \ No newline at end of file