Merge branch 'request-chain'

This commit is contained in:
Nikolay Kochulin 2019-12-08 19:34:56 +00:00
commit fdbeec4ea4
16 changed files with 438 additions and 409 deletions

View File

@ -1,11 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Perfusion; using Perfusion;
using JetKarmaBot.Services; using JetKarmaBot.Services.Handling;
using NLog; using NLog;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -16,33 +13,29 @@ namespace JetKarmaBot.Commands
{ {
public IReadOnlyCollection<string> Names => new[] { "award", "revoke" }; public IReadOnlyCollection<string> Names => new[] { "award", "revoke" };
[Inject] private Logger log; [Inject] private Logger log;
[Inject] private TimeoutManager Timeout;
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
{ var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
var awarder = args.Message.From; var awarder = ctx.EventArgs.Message.From;
string awardTypeText = null; string awardTypeText = null;
int recipientId = default(int); int recipientId = default(int);
foreach (string arg in cmd.Parameters) foreach (string arg in ctx.Command.Parameters)
{ {
if (arg.StartsWith('@')) if (arg.StartsWith('@'))
{ {
if (recipientId != default(int)) if (recipientId != default(int))
{ {
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
recipientId = await db.Users.Where(x => x.Username == arg).Select(x => x.UserId).FirstOrDefaultAsync(); recipientId = await db.Users.Where(x => x.Username == arg).Select(x => x.UserId).FirstOrDefaultAsync();
if (recipientId == default(int)) if (recipientId == default(int))
{ {
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errbadusername"]); await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errbadusername"]);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
} }
else else
@ -51,63 +44,63 @@ namespace JetKarmaBot.Commands
awardTypeText = arg; awardTypeText = arg;
else else
{ {
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]); await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
} }
} }
if (args.Message.ReplyToMessage != null && recipientId == default(int)) if (ctx.EventArgs.Message.ReplyToMessage != null && recipientId == default(int))
{ {
recipientId = args.Message.ReplyToMessage.From.Id; recipientId = ctx.EventArgs.Message.ReplyToMessage.From.Id;
} }
if (recipientId == default(int)) if (recipientId == default(int))
{ {
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]); await ctx.Client.SendTextMessageAsync(ctx.EventArgs.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
bool awarding = cmd.Command == "award"; bool awarding = ctx.Command.Command == "award";
if (awarder.Id == recipientId) if (awarder.Id == recipientId)
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
currentLocale["jetkarmabot.award.errawardself"], currentLocale["jetkarmabot.award.errawardself"],
replyToMessageId: args.Message.MessageId); replyToMessageId: ctx.EventArgs.Message.MessageId);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
if (CommandRouter.Me.Id == recipientId) if (ctx.GetFeature<ChatCommandRouter.Feature>().Router.Me.Id == recipientId)
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
awarding awarding
? currentLocale["jetkarmabot.award.errawardbot"] ? currentLocale["jetkarmabot.award.errawardbot"]
: currentLocale["jetkarmabot.award.errrevokebot"], : currentLocale["jetkarmabot.award.errrevokebot"],
replyToMessageId: args.Message.MessageId); replyToMessageId: ctx.EventArgs.Message.MessageId);
await Timeout.ApplyCost("AwardFailure", awarder.Id, db); return false;
return true;
} }
var text = args.Message.Text; var text = ctx.EventArgs.Message.Text;
global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null
? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText) ? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText)
: await db.AwardTypes.FindAsync((sbyte)1); : 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() await db.Awards.AddAsync(new Models.Award()
{ {
AwardTypeId = awardType.AwardTypeId, AwardTypeId = awardType.AwardTypeId,
Amount = (sbyte)(awarding ? 1 : -1), Amount = (sbyte)(awarding ? 1 : -1),
FromId = awarder.Id, FromId = awarder.Id,
ToId = recipientId, ToId = recipientId,
ChatId = args.Message.Chat.Id ChatId = ctx.EventArgs.Message.Chat.Id
}); });
await db.SaveChangesAsync();
var recUserName = (await db.Users.FindAsync(recipientId)).Username; var recUserName = (await db.Users.FindAsync(recipientId)).Username;
@ -117,20 +110,15 @@ namespace JetKarmaBot.Commands
? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), recUserName) ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), recUserName)
: string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), recUserName); : string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), recUserName);
var currentCount = await db.Awards
.Where(aw => aw.ToId == recipientId && aw.AwardTypeId == awardType.AwardTypeId && aw.ChatId == args.Message.Chat.Id)
.SumAsync(aw => aw.Amount);
var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, currentCount, awardType.Symbol); var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, prevCount + (awarding ? 1 : -1), awardType.Symbol);
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
response, response,
replyToMessageId: args.Message.MessageId); replyToMessageId: ctx.EventArgs.Message.MessageId);
await Timeout.ApplyCost("AwardSuccess", awarder.Id, db);
return true; return true;
} }
}
private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc) private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc)
{ {
@ -144,10 +132,7 @@ namespace JetKarmaBot.Commands
} }
} }
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
[Inject] ChatCommandRouter CommandRouter { get; set; }
public string Description => "Awards/revokes an award to a user."; public string Description => "Awards/revokes an award to a user.";
public string DescriptionID => "jetkarmabot.award.help"; public string DescriptionID => "jetkarmabot.award.help";

View File

@ -1,8 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Telegram.Bot;
using Telegram.Bot.Args;
using Perfusion; using Perfusion;
using JetKarmaBot.Services; using JetKarmaBot.Services.Handling;
using NLog; using NLog;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,40 +11,38 @@ namespace JetKarmaBot.Commands
{ {
public IReadOnlyCollection<string> Names => new[] { "changelocale", "locale" }; public IReadOnlyCollection<string> Names => new[] { "changelocale", "locale" };
[Inject] private Logger log; [Inject] private Logger log;
[Inject] private TimeoutManager Timeout;
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
{ var cmd = ctx.Command;
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; var args = ctx.EventArgs;
var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
if (cmd.Parameters.Length < 1) if (cmd.Parameters.Length < 1)
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.getlocale"], currentLocale["jetkarmabot.changelocale.getlocale"],
replyToMessageId: args.Message.MessageId); replyToMessageId: args.Message.MessageId);
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); return false;
return true;
} }
else if (cmd.Parameters[0] == "list") else if (cmd.Parameters[0] == "list")
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" currentLocale["jetkarmabot.changelocale.listalltext"] + "\n"
+ string.Join("\n", Locale.Select(a => a.Key)), + string.Join("\n", Locale.Select(a => a.Key)),
replyToMessageId: args.Message.MessageId); replyToMessageId: args.Message.MessageId);
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); return false;
return true;
} }
else if (cmd.Parameters[0] == "all") else if (cmd.Parameters[0] == "all")
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.errorall"], currentLocale["jetkarmabot.changelocale.errorall"],
replyToMessageId: args.Message.MessageId); replyToMessageId: args.Message.MessageId);
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); return false;
return true;
} }
string localeId; string localeId;
if (Locale.ContainsLocale(cmd.Parameters[0])) if (Locale.ContainsLocale(cmd.Parameters[0]))
@ -58,31 +54,25 @@ namespace JetKarmaBot.Commands
} }
catch (LocalizationException e) catch (LocalizationException e)
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)), currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)),
replyToMessageId: args.Message.MessageId); replyToMessageId: args.Message.MessageId);
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db); return false;
return true;
} }
(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale = localeId; (await db.Chats.FindAsync(args.Message.Chat.Id)).Locale = localeId;
log.Debug($"Changed language of chat {args.Message.Chat.Id} to {localeId}"); log.Debug($"Changed language of chat {args.Message.Chat.Id} to {localeId}");
await db.SaveChangesAsync();
currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; currentLocale = Locale[localeId];
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, args.Message.Chat.Id,
(currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "") (currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "")
+ currentLocale["jetkarmabot.changelocale.justchanged"], + currentLocale["jetkarmabot.changelocale.justchanged"],
replyToMessageId: args.Message.MessageId); replyToMessageId: args.Message.MessageId);
await Timeout.ApplyCost("LocaleSuccess", args.Message.From.Id, db);
return true; return true;
} }
}
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
public string Description => "Switches current chat locale to [locale]"; public string Description => "Switches current chat locale to [locale]";

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace JetKarmaBot.Commands namespace JetKarmaBot.Commands

View File

@ -1,8 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Telegram.Bot.Args;
using Perfusion; using Perfusion;
using JetKarmaBot.Services; using JetKarmaBot.Services.Handling;
using Telegram.Bot;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,10 +10,7 @@ namespace JetKarmaBot.Commands
{ {
public class CurrenciesCommand : IChatCommand public class CurrenciesCommand : IChatCommand
{ {
[Inject] KarmaContextFactory Db;
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
[Inject] TimeoutManager Timeout { get; set; }
public IReadOnlyCollection<string> Names => new[] { "currencies", "awardtypes" }; public IReadOnlyCollection<string> Names => new[] { "currencies", "awardtypes" };
public string Description => "Shows all award types"; public string Description => "Shows all award types";
@ -24,21 +19,18 @@ namespace JetKarmaBot.Commands
public IReadOnlyCollection<ChatCommandArgument> Arguments => new ChatCommandArgument[] { public IReadOnlyCollection<ChatCommandArgument> Arguments => new ChatCommandArgument[] {
}; };
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
{ var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
string resp = currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", string resp = currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n",
(await db.AwardTypes.ToListAsync()).Select(x => $"{x.Symbol} ({x.CommandName}) <i>{currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}</i>")); (await db.AwardTypes.ToListAsync()).Select(x => $"{x.Symbol} ({x.CommandName}) <i>{currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}</i>"));
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
resp, resp,
replyToMessageId: args.Message.MessageId, replyToMessageId: ctx.EventArgs.Message.MessageId,
parseMode: ParseMode.Html); parseMode: ParseMode.Html);
await Timeout.ApplyCost("Currencies", args.Message.From.Id, db);
return true; return true;
} }
} }
} }
}

View File

@ -1,8 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Telegram.Bot.Args;
using Perfusion; using Perfusion;
using JetKarmaBot.Services; using JetKarmaBot.Services.Handling;
using Telegram.Bot;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,11 +8,7 @@ namespace JetKarmaBot.Commands
{ {
public class HelpCommand : IChatCommand public class HelpCommand : IChatCommand
{ {
[Inject] KarmaContextFactory Db;
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
[Inject] TimeoutManager Timeout { get; set; }
[Inject] ChatCommandRouter Router;
public IReadOnlyCollection<string> Names => new[] { "help" }; public IReadOnlyCollection<string> Names => new[] { "help" };
public string Description => "Displays help text for all(one) command(s)"; public string Description => "Displays help text for all(one) command(s)";
@ -30,31 +24,29 @@ namespace JetKarmaBot.Commands
} }
}; };
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
var router = ctx.GetFeature<ChatCommandRouter.Feature>().Router;
if (ctx.Command.Parameters.Length < 1)
{ {
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; await ctx.Client.SendTextMessageAsync(
await Timeout.ApplyCost("Help", args.Message.From.Id, db); ctx.EventArgs.Message.Chat.Id,
if (cmd.Parameters.Length < 1) router.GetHelpText(currentLocale),
{ replyToMessageId: ctx.EventArgs.Message.MessageId,
await Client.SendTextMessageAsync(
args.Message.Chat.Id,
Router.GetHelpText(currentLocale),
replyToMessageId: args.Message.MessageId,
parseMode: ParseMode.Html); parseMode: ParseMode.Html);
return true; return true;
} }
else else
{ {
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
Router.GetHelpTextFor(cmd.Parameters[0], currentLocale), router.GetHelpTextFor(ctx.Command.Parameters[0], currentLocale),
replyToMessageId: args.Message.MessageId, replyToMessageId: ctx.EventArgs.Message.MessageId,
parseMode: ParseMode.Html); parseMode: ParseMode.Html);
return true; return true;
} }
} }
} }
} }
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot.Args; using JetKarmaBot.Services.Handling;
namespace JetKarmaBot.Commands namespace JetKarmaBot.Commands
{ {
@ -11,7 +11,7 @@ namespace JetKarmaBot.Commands
string DescriptionID { get; } string DescriptionID { get; }
IReadOnlyCollection<ChatCommandArgument> Arguments { get; } IReadOnlyCollection<ChatCommandArgument> Arguments { get; }
Task<bool> Execute(CommandString cmd, MessageEventArgs messageEventArgs); Task<bool> Execute(RequestContext ctx);
} }
public struct ChatCommandArgument public struct ChatCommandArgument

View File

@ -1,15 +1,9 @@
using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Perfusion; using Perfusion;
using Telegram.Bot; using JetKarmaBot.Services.Handling;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using JetKarmaBot.Models;
using JetKarmaBot.Services;
namespace JetKarmaBot.Commands namespace JetKarmaBot.Commands
{ {
@ -17,13 +11,12 @@ namespace JetKarmaBot.Commands
{ {
public IReadOnlyCollection<string> Names => new[] { "leaderboard" }; public IReadOnlyCollection<string> Names => new[] { "leaderboard" };
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
{ var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; var asker = ctx.EventArgs.Message.From;
var asker = args.Message.From; var awardTypeName = ctx.Command.Parameters.FirstOrDefault();
var awardTypeName = cmd.Parameters.FirstOrDefault();
string response; string response;
@ -38,7 +31,7 @@ namespace JetKarmaBot.Commands
response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" + string.Join('\n', response = string.Format(currentLocale["jetkarmabot.leaderboard.specifictext"], awardType.Symbol) + "\n" + string.Join('\n',
await Task.WhenAll((await db.Awards await Task.WhenAll((await db.Awards
.Where(x => x.ChatId == args.Message.Chat.Id && x.AwardTypeId == awardTypeId) .Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id && x.AwardTypeId == awardTypeId)
.GroupBy(x => x.ToId) .GroupBy(x => x.ToId)
.Select(x => new { UserId = x.Key, Amount = x.Sum(y => y.Amount) }) .Select(x => new { UserId = x.Key, Amount = x.Sum(y => y.Amount) })
.OrderByDescending(x => x.Amount) .OrderByDescending(x => x.Amount)
@ -47,18 +40,13 @@ namespace JetKarmaBot.Commands
.Select(async (x, index) => $"{index + 1}. {(await db.Users.FindAsync(x.UserId)).Username} - {x.Amount}")) .Select(async (x, index) => $"{index + 1}. {(await db.Users.FindAsync(x.UserId)).Username} - {x.Amount}"))
); );
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
response, response,
replyToMessageId: args.Message.MessageId); replyToMessageId: ctx.EventArgs.Message.MessageId);
await Timeout.ApplyCost("Leaderboard", args.Message.From.Id, db);
return true; return true;
} }
}
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] TimeoutManager Timeout { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
public string Description => "Shows the people with the most of a specific award."; public string Description => "Shows the people with the most of a specific award.";

View File

@ -1,13 +1,7 @@
using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using Perfusion; using Perfusion;
using Telegram.Bot; using JetKarmaBot.Services.Handling;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using JetKarmaBot.Models;
using JetKarmaBot.Services;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -17,14 +11,13 @@ namespace JetKarmaBot.Commands
{ {
public IReadOnlyCollection<string> Names => new[] { "status" }; public IReadOnlyCollection<string> Names => new[] { "status" };
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args) public async Task<bool> Execute(RequestContext ctx)
{ {
using (var db = Db.GetContext()) var db = ctx.Database;
{ var currentLocale = Locale[(await db.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale]; var asker = ctx.EventArgs.Message.From;
var asker = args.Message.From; var awardTypeName = ctx.Command.Parameters.FirstOrDefault();
var awardTypeName = cmd.Parameters.FirstOrDefault(); bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private;
bool isPrivate = args.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private;
string response; string response;
@ -33,12 +26,12 @@ namespace JetKarmaBot.Commands
// var awards = db.Awards.Where(x => x.ToId == asker.Id) // var awards = db.Awards.Where(x => x.ToId == asker.Id)
// .GroupBy(x => x.AwardTypeId) // .GroupBy(x => x.AwardTypeId)
// .Select(x => new { AwardTypeId = x.Key, Amount = x.Sum(y => y.Amount) }); // .Select(x => new { AwardTypeId = x.Key, Amount = x.Sum(y => y.Amount) });
if (!db.Awards.Any(x => x.ToId == asker.Id && (x.ChatId == args.Message.Chat.Id || isPrivate))) if (!db.Awards.Any(x => x.ToId == asker.Id && (x.ChatId == ctx.EventArgs.Message.Chat.Id || isPrivate)))
response = currentLocale["jetkarmabot.status.havenothing"]; response = currentLocale["jetkarmabot.status.havenothing"];
else else
{ {
var awardsQuery = from award in db.Awards var awardsQuery = from award in db.Awards
where award.ToId == asker.Id && (award.ChatId == args.Message.Chat.Id || isPrivate) where award.ToId == asker.Id && (award.ChatId == ctx.EventArgs.Message.Chat.Id || isPrivate)
group award by award.AwardTypeId into g group award by award.AwardTypeId into g
join awardType in db.AwardTypes join awardType in db.AwardTypes
on g.Key equals awardType.AwardTypeId on g.Key equals awardType.AwardTypeId
@ -63,22 +56,17 @@ namespace JetKarmaBot.Commands
await db.Awards.Where( await db.Awards.Where(
x => x.AwardTypeId == awardTypeId x => x.AwardTypeId == awardTypeId
&& x.ToId == asker.Id && x.ToId == asker.Id
&& x.ChatId == args.Message.Chat.Id) && x.ChatId == ctx.EventArgs.Message.Chat.Id)
.SumAsync(x => x.Amount), awardType.Symbol); .SumAsync(x => x.Amount), awardType.Symbol);
} }
await Client.SendTextMessageAsync( await ctx.Client.SendTextMessageAsync(
args.Message.Chat.Id, ctx.EventArgs.Message.Chat.Id,
response, response,
replyToMessageId: args.Message.MessageId); replyToMessageId: ctx.EventArgs.Message.MessageId);
await Timeout.ApplyCost("Status", args.Message.From.Id, db);
return true; return true;
} }
}
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] TimeoutManager Timeout { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
public string Description => "Shows the amount of awards that you have"; public string Description => "Shows the amount of awards that you have";

View File

@ -27,8 +27,8 @@ namespace JetKarmaBot
public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2; public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2;
public Dictionary<string, int> CommandCostsSeconds { get; private set; } = new Dictionary<string, int>() public Dictionary<string, int> CommandCostsSeconds { get; private set; } = new Dictionary<string, int>()
{ {
{"AwardSuccessful", 60*15}, {"JetKarmaBot.Commands.AwardCommand (OK)", 60*15},
{"AwardFailed", 60*5}, {"JetKarmaBot.Commands.AwardCommand (ERR)", 60*5},
{"Default", 60*5} {"Default", 60*5}
}; };
public int SaveIntervalSeconds { get; private set; } = 60 * 5; public int SaveIntervalSeconds { get; private set; } = 60 * 5;

View File

@ -1,16 +1,15 @@
using JetKarmaBot.Commands; using JetKarmaBot.Commands;
using JetKarmaBot.Models; using JetKarmaBot.Models;
using JetKarmaBot.Services; using JetKarmaBot.Services;
using JetKarmaBot.Services.Handling;
using Perfusion; using Perfusion;
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Args; using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
namespace JetKarmaBot namespace JetKarmaBot
@ -25,6 +24,7 @@ namespace JetKarmaBot
TelegramBotClient Client { get; set; } TelegramBotClient Client { get; set; }
ChatCommandRouter Commands; ChatCommandRouter Commands;
RequestChain Chain;
Task timeoutWaitTask; Task timeoutWaitTask;
CancellationTokenSource timeoutWaitTaskToken; CancellationTokenSource timeoutWaitTaskToken;
@ -44,6 +44,7 @@ namespace JetKarmaBot
timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token); timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token);
await InitCommands(Container); await InitCommands(Container);
InitChain(Container);
Client.OnMessage += BotOnMessageReceived; Client.OnMessage += BotOnMessageReceived;
Client.StartReceiving(); Client.StartReceiving();
@ -78,48 +79,13 @@ namespace JetKarmaBot
{ {
using (KarmaContext db = Db.GetContext()) using (KarmaContext db = Db.GetContext())
{ {
await AddUserToDatabase(db, args.Message.From); RequestContext ctx = new RequestContext(Client, args, cmd, db);
var checkResult = await Timeout.Check(args.Message.From.Id, db); await Chain.Handle(ctx);
if (checkResult == TimeoutManager.CheckResult.Limited)
{
Locale currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
await Client.SendTextMessageAsync(
args.Message.Chat.Id,
currentLocale["jetkarmabot.ratelimit"],
replyToMessageId: args.Message.MessageId);
await Timeout.SetMessaged(args.Message.From.Id, db);
return;
}
else if (checkResult != TimeoutManager.CheckResult.NonLimited)
{
return;
}
if (args.Message.ReplyToMessage != null)
await AddUserToDatabase(db, args.Message.ReplyToMessage.From);
if (!db.Chats.Any(x => x.ChatId == args.Message.Chat.Id))
db.Chats.Add(new Models.Chat
{
ChatId = args.Message.Chat.Id
});
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
await Commands.Execute(cmd, args);
}); });
} }
private async Task AddUserToDatabase(KarmaContext db, Telegram.Bot.Types.User u)
{
string un;
if (u.Username == null)
un = u.FirstName + (u.LastName != null ? " " + u.LastName : "");
else
un = "@" + u.Username;
if (!db.Users.Any(x => x.UserId == u.Id))
await db.Users.AddAsync(new Models.User { UserId = u.Id, Username = un });
else
(await db.Users.FindAsync(u.Id)).Username = un;
}
async Task InitCommands(IContainer c) async Task InitCommands(IContainer c)
{ {
c.Add<HelpCommand>(); c.Add<HelpCommand>();
@ -136,6 +102,14 @@ namespace JetKarmaBot
} }
} }
void InitChain(IContainer c)
{
Chain = new RequestChain();
Chain.Add(Timeout);
Chain.Add(new SaveData());
Chain.Add(Commands);
}
#endregion #endregion
#region IDisposable #region IDisposable

View File

@ -11,7 +11,7 @@ namespace JetKarmaBot.Models
} }
public long ChatId { get; set; } public long ChatId { get; set; }
public string Locale { get; set; } public string Locale { get; set; } = "ru-RU";
public bool IsAdministrator { get; set; } public bool IsAdministrator { get; set; }
public virtual ICollection<Award> Awards { get; set; } public virtual ICollection<Award> Awards { get; set; }

View File

@ -1,6 +1,4 @@
using JetKarmaBot.Commands; using JetKarmaBot.Commands;
using JetKarmaBot.Services;
using Microsoft.EntityFrameworkCore;
using NLog; using NLog;
using Perfusion; using Perfusion;
using System; using System;
@ -8,12 +6,17 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Args;
namespace JetKarmaBot namespace JetKarmaBot.Services.Handling
{ {
public class ChatCommandRouter public class ChatCommandRouter : IRequestHandler
{ {
public class Feature
{
public Type CommandType;
public ChatCommandRouter Router;
public bool Succeded;
}
public Telegram.Bot.Types.User Me { get; private set; } public Telegram.Bot.Types.User Me { get; private set; }
[Inject] private Logger log; [Inject] private Logger log;
[Inject] private TelegramBotClient Client { get; set; } [Inject] private TelegramBotClient Client { get; set; }
@ -23,16 +26,21 @@ namespace JetKarmaBot
Me = await Client.GetMeAsync(); Me = await Client.GetMeAsync();
} }
public Task<bool> Execute(CommandString cmd, MessageEventArgs args) public Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
{ {
log.Debug("Message received"); log.Debug("Message received");
CommandString cmd = ctx.Command;
Feature feature = new Feature() { Router = this };
ctx.Features.Add(feature);
try try
{ {
if (commands.ContainsKey(cmd.Command)) if (commands.ContainsKey(cmd.Command))
{ {
log.Debug($"Handling message via {commands[cmd.Command].GetType().Name}"); feature.CommandType = commands[cmd.Command].GetType();
return commands[cmd.Command].Execute(cmd, args); log.Debug($"Handling message via {feature.CommandType.Name}");
async Task processCommand() => feature.Succeded = await commands[cmd.Command].Execute(ctx);
return processCommand();
} }
} }
catch (Exception e) catch (Exception e)
@ -41,7 +49,7 @@ namespace JetKarmaBot
log.Error(e); log.Error(e);
} }
return Task.FromResult(false); return next(ctx);
} }
public void Add(IChatCommand c) public void Add(IChatCommand c)

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace JetKarmaBot.Services.Handling
{
public interface IRequestHandler
{
Task Handle(RequestContext ctx, Func<RequestContext, Task> next);
}
public class RequestChain : IRequestHandler
{
List<IRequestHandler> handlerStack = new List<IRequestHandler>();
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next = null)
{
int i = 0;
Func<RequestContext, Task> chainNext = null;
chainNext = (newCtx) =>
{
if (i == handlerStack.Count) return Task.CompletedTask;
IRequestHandler handler = handlerStack[i++];
return handler.Handle(newCtx, chainNext);
};
await chainNext(ctx);
if (next != null)
await next(ctx);
}
public void Add(IRequestHandler handler)
{
handlerStack.Add(handler);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetKarmaBot.Commands;
using JetKarmaBot.Models;
using Telegram.Bot;
using Telegram.Bot.Args;
namespace JetKarmaBot.Services.Handling
{
public class RequestContext : IServiceProvider
{
public ITelegramBotClient Client { get; }
public MessageEventArgs EventArgs { get; }
public CommandString Command { get; }
public KarmaContext Database { get; }
public ICollection<object> Features { get; } = new List<object>();
public RequestContext(ITelegramBotClient client, MessageEventArgs args, CommandString cmd, KarmaContext db)
{
Client = client;
EventArgs = args;
Command = cmd;
Database = db;
}
public object GetService(Type serviceType) => Features.First(x => x.GetType() == serviceType);
public T GetFeature<T>() => (T)Features.First(x => x is T);
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using JetKarmaBot.Models;
using Microsoft.EntityFrameworkCore;
namespace JetKarmaBot.Services.Handling
{
public class SaveData : IRequestHandler
{
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
{
KarmaContext db = ctx.Database;
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;
}
}
}

View File

@ -4,11 +4,12 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetKarmaBot.Models; using JetKarmaBot.Models;
using System.Threading; using System.Threading;
using System.Linq;
namespace JetKarmaBot.Services namespace JetKarmaBot.Services.Handling
{ {
[Singleton] [Singleton]
public class TimeoutManager public class TimeoutManager : IRequestHandler
{ {
public struct TimeoutStats public struct TimeoutStats
{ {
@ -17,10 +18,12 @@ namespace JetKarmaBot.Services
} }
[Inject] private KarmaContextFactory Db; [Inject] private KarmaContextFactory Db;
[Inject] private Config cfg; [Inject] private Config cfg;
[Inject] private Localization Locale;
public Dictionary<int, TimeoutStats> TimeoutCache = new Dictionary<int, TimeoutStats>(); public Dictionary<int, TimeoutStats> TimeoutCache = new Dictionary<int, TimeoutStats>();
public async Task ApplyCost(string name, int uid, KarmaContext db) private async Task ApplyCost(string name, bool succeded, int uid, KarmaContext db)
{ {
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name, out var costSeconds)) if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name + (succeded ? " (OK)" : "(ERR)"), out var costSeconds))
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name, out costSeconds))
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue("Default", out costSeconds)) if (!cfg.Timeout.CommandCostsSeconds.TryGetValue("Default", out costSeconds))
{ {
throw new LocalizationException("Default key not present"); throw new LocalizationException("Default key not present");
@ -37,30 +40,14 @@ namespace JetKarmaBot.Services
}; };
TimeoutCache[uid] = TimeoutCache[uid]; TimeoutCache[uid] = TimeoutCache[uid];
} }
public enum CheckResult
{
NonLimited, Limited, LimitedSent
}
public async Task<CheckResult> Check(int uid, KarmaContext db)
{
await PopulateStats(uid, db);
DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds);
return TimeoutCache[uid].CooldownDate < debtLimit
? CheckResult.NonLimited
: (TimeoutCache[uid].TimeoutMessaged ? CheckResult.LimitedSent : CheckResult.Limited);
}
public async Task SetMessaged(int uid, KarmaContext db)
{
await PopulateStats(uid, db);
TimeoutCache[uid] = new TimeoutStats() { TimeoutMessaged = true, CooldownDate = TimeoutCache[uid].CooldownDate };
}
private async Task PopulateStats(int uid, KarmaContext db) private async Task PopulateStats(int uid, KarmaContext db)
{ {
if (!TimeoutCache.ContainsKey(uid)) if (!TimeoutCache.ContainsKey(uid))
{ {
TimeoutCache[uid] = new TimeoutStats() TimeoutCache[uid] = new TimeoutStats()
{ {
CooldownDate = (await db.Users.FindAsync(uid)).CooldownDate CooldownDate = (await db.Users.FindAsync(uid))?.CooldownDate ?? DateTime.Now
}; };
} }
} }
@ -83,5 +70,35 @@ namespace JetKarmaBot.Services
await Save(ct); await Save(ct);
} }
} }
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
{
int uid = ctx.EventArgs.Message.From.Id;
await PopulateStats(uid, ctx.Database);
DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds);
if (debtLimit < TimeoutCache[uid].CooldownDate)
{
if (!TimeoutCache[uid].TimeoutMessaged)
{
Locale currentLocale = Locale[(await ctx.Database.Chats.FindAsync(ctx.EventArgs.Message.Chat.Id)).Locale];
await ctx.Client.SendTextMessageAsync(
ctx.EventArgs.Message.Chat.Id,
currentLocale["jetkarmabot.ratelimit"],
replyToMessageId: ctx.EventArgs.Message.MessageId);
TimeoutCache[uid] = new TimeoutStats() { TimeoutMessaged = true, CooldownDate = TimeoutCache[uid].CooldownDate };
}
return;
}
await next(ctx);
var routerFeature = ctx.GetFeature<ChatCommandRouter.Feature>();
await ApplyCost(getTypeName(routerFeature.CommandType), routerFeature.Succeded, uid, ctx.Database);
}
private string getTypeName(Type t)
{
return (t.DeclaringType == null ? t.Namespace + "." + t.Name : getTypeName(t.DeclaringType) + "." + t.Name)
+ (t.GenericTypeArguments.Length > 0 ? "<" + string.Join(",", t.GenericTypeArguments.Select(getTypeName)) + ">" : "");
}
} }
} }