mirror of
https://github.com/Jetsparrow/karmabot.git
synced 2026-01-21 00:56:09 +03:00
Split out parts into JetBotLib
This commit is contained in:
parent
f510c99f3a
commit
ae39f15fda
@ -1,13 +1,10 @@
|
|||||||
using JetKarmaBot.Commands;
|
using System;
|
||||||
using NLog;
|
|
||||||
using Perfusion;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
|
|
||||||
namespace JetKarmaBot.Services.Handling
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public class ChatCommandRouter : IRequestHandler
|
public class ChatCommandRouter : IRequestHandler
|
||||||
{
|
{
|
||||||
@ -17,54 +14,35 @@ namespace JetKarmaBot.Services.Handling
|
|||||||
public ChatCommandRouter Router;
|
public ChatCommandRouter Router;
|
||||||
public bool Succeded;
|
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 Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
|
public Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
|
||||||
{
|
{
|
||||||
log.Debug("Message received");
|
|
||||||
CommandString cmd = ctx.Command;
|
CommandString cmd = ctx.Command;
|
||||||
Feature feature = new Feature() { Router = this };
|
Feature feature = new Feature() { Router = this };
|
||||||
ctx.Features.Add(feature);
|
ctx.Features.Add(feature);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (commands.ContainsKey(cmd.Command))
|
if (commands.ContainsKey(cmd.Command))
|
||||||
{
|
{
|
||||||
feature.CommandType = commands[cmd.Command].GetType();
|
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);
|
async Task processCommand() => feature.Succeded = await commands[cmd.Command].Execute(ctx);
|
||||||
return processCommand();
|
return processCommand();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.Error($"Error while handling command {cmd.Command}!");
|
|
||||||
log.Error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(ctx);
|
return next(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(IChatCommand c)
|
public void Add(IChatCommand c)
|
||||||
{
|
{
|
||||||
log.ConditionalTrace($"Adding command {c.GetType().Name}");
|
|
||||||
foreach (var name in c.Names)
|
foreach (var name in c.Names)
|
||||||
{
|
{
|
||||||
log.ConditionalTrace($"Mounting {c.GetType().Name} to {name}");
|
|
||||||
if (commands.ContainsKey(name))
|
if (commands.ContainsKey(name))
|
||||||
throw new Exception($"command collision for name {name}, commands {commands[name].GetType()} and {c.GetType()}");
|
throw new Exception($"command collision for name {name}, commands {commands[name].GetType()} and {c.GetType()}");
|
||||||
commands[name] = c;
|
commands[name] = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string GetHelpText(Locale loc)
|
public string GetHelpText(Locale loc)
|
||||||
{
|
{
|
||||||
List<string> pieces = new List<string>();
|
List<string> pieces = new List<string>();
|
||||||
foreach (IChatCommand c in commands.Values.Distinct())
|
foreach (IChatCommand c in commands.Values.Distinct())
|
||||||
@ -81,7 +59,7 @@ namespace JetKarmaBot.Services.Handling
|
|||||||
return string.Join("\n", pieces);
|
return string.Join("\n", pieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string GetHelpTextFor(string commandname, Locale loc)
|
public string GetHelpTextFor(string commandname, Locale loc)
|
||||||
{
|
{
|
||||||
IChatCommand c = commands[commandname];
|
IChatCommand c = commands[commandname];
|
||||||
string build = "";
|
string build = "";
|
||||||
@ -2,7 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public class CommandString
|
public class CommandString
|
||||||
{
|
{
|
||||||
@ -1,8 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetKarmaBot.Services.Handling;
|
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public interface IChatCommand
|
public interface IChatCommand
|
||||||
{
|
{
|
||||||
9
JetBotLib/JetBotLib.csproj
Normal file
9
JetBotLib/JetBotLib.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Telegram.Bot" Version="14.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@ -5,43 +5,25 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
|
||||||
using Perfusion;
|
|
||||||
|
|
||||||
namespace JetKarmaBot
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public class Localization : IReadOnlyDictionary<string, Locale>
|
public class Localization : IReadOnlyDictionary<string, Locale>
|
||||||
{
|
{
|
||||||
private Dictionary<string, Locale> locales = new Dictionary<string, Locale>();
|
private Dictionary<string, Locale> locales = new Dictionary<string, Locale>();
|
||||||
|
|
||||||
public Localization(IContainer c)
|
public Localization()
|
||||||
{
|
{
|
||||||
c.ResolveObject(this);
|
|
||||||
log.Info("Initializing...");
|
|
||||||
string langsFolder = "lang";
|
string langsFolder = "lang";
|
||||||
if (!Directory.Exists(langsFolder))
|
if (!Directory.Exists(langsFolder))
|
||||||
Directory.CreateDirectory(langsFolder);
|
Directory.CreateDirectory(langsFolder);
|
||||||
|
|
||||||
foreach (string langFilePath in Directory.EnumerateFiles(langsFolder, "*.json"))
|
foreach (string langFilePath in Directory.EnumerateFiles(langsFolder, "*.json"))
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
string langName = Path.GetFileNameWithoutExtension(langFilePath);
|
string langName = Path.GetFileNameWithoutExtension(langFilePath);
|
||||||
string langKey = langName.ToLowerInvariant();
|
string langKey = langName.ToLowerInvariant();
|
||||||
locales[langKey] = new Locale(JObject.Parse(File.ReadAllText(langFilePath)), langKey);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locales.Any())
|
|
||||||
log.Info("Initialized!");
|
|
||||||
else
|
|
||||||
throw new FileNotFoundException($"No locales found in {langsFolder}!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Locale this[string locale]
|
public Locale this[string locale]
|
||||||
@ -53,17 +35,15 @@ namespace JetKarmaBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Inject]
|
|
||||||
private Logger log;
|
|
||||||
|
|
||||||
public Locale FindByCommonName(string name)
|
public Locale FindByCommonName(string name)
|
||||||
{
|
{
|
||||||
log.ConditionalTrace("Trying to find locale " + name);
|
// log.ConditionalTrace("Trying to find locale " + name);
|
||||||
foreach (Locale l in locales.Values)
|
foreach (Locale l in locales.Values)
|
||||||
{
|
{
|
||||||
if (l.CommonNames.Contains(name))
|
if (l.CommonNames.Contains(name))
|
||||||
{
|
{
|
||||||
log.ConditionalTrace("Found locale " + l.Name);
|
// log.ConditionalTrace("Found locale " + l.Name);
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +57,7 @@ namespace JetKarmaBot
|
|||||||
}
|
}
|
||||||
else if (matchinglocales.Count() == 1)
|
else if (matchinglocales.Count() == 1)
|
||||||
return matchinglocales.First();
|
return matchinglocales.First();
|
||||||
log.Warn("Failed to find locale " + name);
|
// log.Warn("Failed to find locale " + name);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public bool ContainsLocale(string locale)
|
public bool ContainsLocale(string locale)
|
||||||
@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
|
||||||
using Perfusion;
|
|
||||||
|
|
||||||
namespace JetKarmaBot.Services.Handling
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public interface IRequestHandler
|
public interface IRequestHandler
|
||||||
{
|
{
|
||||||
@ -12,7 +10,6 @@ namespace JetKarmaBot.Services.Handling
|
|||||||
}
|
}
|
||||||
public class RequestChain : IRequestHandler
|
public class RequestChain : IRequestHandler
|
||||||
{
|
{
|
||||||
[Inject] private Logger log;
|
|
||||||
List<IRequestHandler> handlerStack = new List<IRequestHandler>();
|
List<IRequestHandler> handlerStack = new List<IRequestHandler>();
|
||||||
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next = null)
|
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next = null)
|
||||||
{
|
{
|
||||||
@ -22,11 +19,9 @@ namespace JetKarmaBot.Services.Handling
|
|||||||
{
|
{
|
||||||
if (i == handlerStack.Count)
|
if (i == handlerStack.Count)
|
||||||
{
|
{
|
||||||
log.ConditionalTrace("(next) End of request chain");
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
IRequestHandler handler = handlerStack[i++];
|
IRequestHandler handler = handlerStack[i++];
|
||||||
log.ConditionalTrace($"(next) Executing handler {handler.GetType().Name}");
|
|
||||||
return handler.Handle(newCtx, chainNext);
|
return handler.Handle(newCtx, chainNext);
|
||||||
};
|
};
|
||||||
await chainNext(ctx);
|
await chainNext(ctx);
|
||||||
@ -35,7 +30,6 @@ namespace JetKarmaBot.Services.Handling
|
|||||||
}
|
}
|
||||||
public void Add(IRequestHandler handler)
|
public void Add(IRequestHandler handler)
|
||||||
{
|
{
|
||||||
log.ConditionalTrace($"Adding {handler.GetType().Name} to reqchain");
|
|
||||||
handlerStack.Add(handler);
|
handlerStack.Add(handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,12 +2,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetKarmaBot.Commands;
|
|
||||||
using JetKarmaBot.Models;
|
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Args;
|
using Telegram.Bot.Args;
|
||||||
|
|
||||||
namespace JetKarmaBot.Services.Handling
|
namespace JetBotLib
|
||||||
{
|
{
|
||||||
public class RequestContext : IServiceProvider
|
public class RequestContext : IServiceProvider
|
||||||
{
|
{
|
||||||
98
JetBotLib/TimeoutManager.cs
Normal file
98
JetBotLib/TimeoutManager.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace JetBotLib
|
||||||
|
{
|
||||||
|
public abstract class TimeoutManager : IRequestHandler
|
||||||
|
{
|
||||||
|
public class Feature
|
||||||
|
{
|
||||||
|
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<RequestContext, Task> next)
|
||||||
|
{
|
||||||
|
int uid = ctx.EventArgs.Message.From.Id;
|
||||||
|
if (Timeout.TimeoutCache.TryGetValue(uid, out var stats))
|
||||||
|
{
|
||||||
|
DateTime debtLimit = DateTime.Now.AddSeconds(Timeout.DebtLimitSeconds);
|
||||||
|
if (debtLimit < stats.CooldownDate && stats.TimeoutMessaged)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await next(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class TimeoutStats
|
||||||
|
{
|
||||||
|
public DateTime CooldownDate;
|
||||||
|
public bool TimeoutMessaged;
|
||||||
|
protected Dictionary<Type, object> features = new Dictionary<Type, object>();
|
||||||
|
public IReadOnlyDictionary<Type, object> Features => features;
|
||||||
|
public T GetFeature<T>()
|
||||||
|
{
|
||||||
|
if (!Features.ContainsKey(typeof(T)))
|
||||||
|
features.Add(typeof(T), Activator.CreateInstance(typeof(T)));
|
||||||
|
return (T)Features[typeof(T)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Dictionary<int, TimeoutStats> TimeoutCache = new Dictionary<int, TimeoutStats>();
|
||||||
|
|
||||||
|
public abstract Task Save(CancellationToken ct = default(CancellationToken));
|
||||||
|
protected abstract Task LoadUser(int uid, RequestContext ctx = null);
|
||||||
|
public abstract int SaveIntervalSeconds { get; }
|
||||||
|
public abstract int DebtLimitSeconds { get; }
|
||||||
|
public abstract int GetCostSeconds(string name, bool status);
|
||||||
|
|
||||||
|
public async Task SaveLoop(CancellationToken ct = default(CancellationToken))
|
||||||
|
{
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(SaveIntervalSeconds * 1000, ct);
|
||||||
|
await Save(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
|
||||||
|
{
|
||||||
|
int uid = ctx.EventArgs.Message.From.Id;
|
||||||
|
await LoadUser(uid, ctx);
|
||||||
|
DateTime debtLimit = DateTime.Now.AddSeconds(DebtLimitSeconds);
|
||||||
|
if (debtLimit < TimeoutCache[uid].CooldownDate)
|
||||||
|
{
|
||||||
|
if (!TimeoutCache[uid].TimeoutMessaged)
|
||||||
|
{
|
||||||
|
Locale currentLocale = ctx.GetFeature<Locale>();
|
||||||
|
await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]);
|
||||||
|
TimeoutCache[uid].TimeoutMessaged = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Feature feature = new Feature();
|
||||||
|
ctx.Features.Add(feature);
|
||||||
|
|
||||||
|
await next(ctx);
|
||||||
|
|
||||||
|
var routerFeature = ctx.GetFeature<ChatCommandRouter.Feature>();
|
||||||
|
if (feature.Multiplier == 0) return;
|
||||||
|
int cost = GetCostSeconds(getTypeName(routerFeature.CommandType), routerFeature.Succeded);
|
||||||
|
if (TimeoutCache[uid].CooldownDate < DateTime.Now) TimeoutCache[uid].CooldownDate = DateTime.Now;
|
||||||
|
TimeoutCache[uid].CooldownDate = TimeoutCache[uid].CooldownDate.AddSeconds(cost);
|
||||||
|
TimeoutCache[uid].TimeoutMessaged = false;
|
||||||
|
}
|
||||||
|
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)) + ">" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,11 +7,16 @@ using NLog;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
class AwardCommand : IChatCommand
|
class AwardCommand : IChatCommand
|
||||||
{
|
{
|
||||||
|
public class TimeoutFeature
|
||||||
|
{
|
||||||
|
public DateTime PreviousAwardDate;
|
||||||
|
}
|
||||||
public IReadOnlyCollection<string> Names => new[] { "award", "revoke" };
|
public IReadOnlyCollection<string> Names => new[] { "award", "revoke" };
|
||||||
[Inject] private Logger log;
|
[Inject] private Logger log;
|
||||||
|
|
||||||
@ -21,8 +26,9 @@ namespace JetKarmaBot.Commands
|
|||||||
var currentLocale = ctx.GetFeature<Locale>();
|
var currentLocale = ctx.GetFeature<Locale>();
|
||||||
|
|
||||||
var awarder = ctx.EventArgs.Message.From;
|
var awarder = ctx.EventArgs.Message.From;
|
||||||
|
var timeoutFeature = Timeout.TimeoutCache[awarder.Id].GetFeature<TimeoutFeature>();
|
||||||
|
|
||||||
if (Timeout.TimeoutCache[awarder.Id].PreviousAwardDate.AddSeconds(Config.Timeout.AwardTimeSeconds) > DateTime.Now)
|
if (timeoutFeature.PreviousAwardDate.AddSeconds(Config.Timeout.AwardTimeSeconds) > DateTime.Now)
|
||||||
{
|
{
|
||||||
ctx.GetFeature<TimeoutManager.Feature>().Multiplier = 0; // Doesn't count as success or failure
|
ctx.GetFeature<TimeoutManager.Feature>().Multiplier = 0; // Doesn't count as success or failure
|
||||||
if (!Timeout.TimeoutCache[awarder.Id].TimeoutMessaged)
|
if (!Timeout.TimeoutCache[awarder.Id].TimeoutMessaged)
|
||||||
@ -81,7 +87,7 @@ namespace JetKarmaBot.Commands
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.GetFeature<ChatCommandRouter.Feature>().Router.Me.Id == recipientId)
|
if (ctx.GetFeature<JetKarmaBot>().Me.Id == recipientId)
|
||||||
{
|
{
|
||||||
await ctx.SendMessage(awarding
|
await ctx.SendMessage(awarding
|
||||||
? currentLocale["jetkarmabot.award.errawardbot"]
|
? currentLocale["jetkarmabot.award.errawardbot"]
|
||||||
@ -119,7 +125,7 @@ namespace JetKarmaBot.Commands
|
|||||||
var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, prevCount + (awarding ? 1 : -1), awardType.Symbol);
|
var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], recUserName, prevCount + (awarding ? 1 : -1), awardType.Symbol);
|
||||||
|
|
||||||
await ctx.SendMessage(response);
|
await ctx.SendMessage(response);
|
||||||
Timeout.TimeoutCache[awarder.Id].PreviousAwardDate = DateTime.Now;
|
timeoutFeature.PreviousAwardDate = DateTime.Now;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using NLog;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using JetKarmaBot.Services.Handling;
|
|||||||
using Telegram.Bot.Types.Enums;
|
using Telegram.Bot.Types.Enums;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Perfusion;
|
using Perfusion;
|
||||||
using JetKarmaBot.Services.Handling;
|
using JetKarmaBot.Services.Handling;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using JetKarmaBot.Services.Handling;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
|
using JetBotLib;
|
||||||
|
|
||||||
namespace JetKarmaBot.Commands
|
namespace JetKarmaBot.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using JetBotLib;
|
||||||
using JetKarmaBot.Commands;
|
using JetKarmaBot.Commands;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
using JetKarmaBot.Services;
|
using JetKarmaBot.Services;
|
||||||
@ -19,9 +20,10 @@ namespace JetKarmaBot
|
|||||||
[Inject] Config Config { get; set; }
|
[Inject] Config Config { get; set; }
|
||||||
[Inject] IContainer Container { get; set; }
|
[Inject] IContainer Container { get; set; }
|
||||||
[Inject] KarmaContextFactory Db { get; set; }
|
[Inject] KarmaContextFactory Db { get; set; }
|
||||||
[Inject] TimeoutManager Timeout { get; set; }
|
[Inject] KarmaTimeoutManager Timeout { get; set; }
|
||||||
[Inject] Localization Locale { get; set; }
|
[Inject] Localization Locale { get; set; }
|
||||||
|
|
||||||
|
public Telegram.Bot.Types.User Me { get; private set; }
|
||||||
TelegramBotClient Client { get; set; }
|
TelegramBotClient Client { get; set; }
|
||||||
ChatCommandRouter Commands;
|
ChatCommandRouter Commands;
|
||||||
RequestChain Chain;
|
RequestChain Chain;
|
||||||
@ -38,6 +40,7 @@ namespace JetKarmaBot
|
|||||||
};
|
};
|
||||||
|
|
||||||
Client = new TelegramBotClient(Config.ApiKey, httpProxy);
|
Client = new TelegramBotClient(Config.ApiKey, httpProxy);
|
||||||
|
Me = await Client.GetMeAsync();
|
||||||
Container.AddInstance(Client);
|
Container.AddInstance(Client);
|
||||||
|
|
||||||
timeoutWaitTaskToken = new CancellationTokenSource();
|
timeoutWaitTaskToken = new CancellationTokenSource();
|
||||||
@ -72,10 +75,11 @@ namespace JetKarmaBot
|
|||||||
return;
|
return;
|
||||||
if (!CommandString.TryParse(args.Message.Text, out var cmd))
|
if (!CommandString.TryParse(args.Message.Text, out var cmd))
|
||||||
return;
|
return;
|
||||||
if (cmd.UserName != null && cmd.UserName != Commands.Me.Username)
|
if (cmd.UserName != null && cmd.UserName != Me.Username)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RequestContext ctx = new RequestContext(Client, args, cmd);
|
RequestContext ctx = new RequestContext(Client, args, cmd);
|
||||||
|
ctx.Features.Add(this);
|
||||||
_ = Chain.Handle(ctx);
|
_ = Chain.Handle(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +92,6 @@ namespace JetKarmaBot
|
|||||||
c.Add<CurrenciesCommand>();
|
c.Add<CurrenciesCommand>();
|
||||||
c.Add<LeaderboardCommand>();
|
c.Add<LeaderboardCommand>();
|
||||||
Commands = c.GetInstance<ChatCommandRouter>();
|
Commands = c.GetInstance<ChatCommandRouter>();
|
||||||
await Commands.Start();
|
|
||||||
foreach (IChatCommand cmd in c.GetInstances<IChatCommand>())
|
foreach (IChatCommand cmd in c.GetInstances<IChatCommand>())
|
||||||
{
|
{
|
||||||
Commands.Add(cmd);
|
Commands.Add(cmd);
|
||||||
|
|||||||
@ -32,4 +32,7 @@
|
|||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\JetBotLib\JetBotLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using JetBotLib;
|
||||||
using Perfusion;
|
using Perfusion;
|
||||||
|
|
||||||
namespace JetKarmaBot.Services.Handling
|
namespace JetKarmaBot.Services
|
||||||
{
|
{
|
||||||
public class DatabaseHandler : IRequestHandler
|
public class DatabaseHandler : IRequestHandler
|
||||||
{
|
{
|
||||||
@ -1,133 +0,0 @@
|
|||||||
using Perfusion;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using JetKarmaBot.Models;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Linq;
|
|
||||||
using NLog;
|
|
||||||
|
|
||||||
namespace JetKarmaBot.Services.Handling
|
|
||||||
{
|
|
||||||
[Singleton]
|
|
||||||
public class TimeoutManager : IRequestHandler
|
|
||||||
{
|
|
||||||
public class Feature
|
|
||||||
{
|
|
||||||
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<RequestContext, Task> next)
|
|
||||||
{
|
|
||||||
int 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<int, TimeoutStats> TimeoutCache = new Dictionary<int, TimeoutStats>();
|
|
||||||
private async Task ApplyCost(string name, bool succeded, int 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(int 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.Info("Saving timeout info to database");
|
|
||||||
using (KarmaContext db = Db.GetContext())
|
|
||||||
{
|
|
||||||
foreach (int i in TimeoutCache.Keys)
|
|
||||||
{
|
|
||||||
(await db.Users.FindAsync(new object[] { i }, ct)).CooldownDate = TimeoutCache[i].CooldownDate;
|
|
||||||
}
|
|
||||||
await db.SaveChangesAsync(ct);
|
|
||||||
}
|
|
||||||
log.Info("Saved");
|
|
||||||
}
|
|
||||||
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<RequestContext, Task> next)
|
|
||||||
{
|
|
||||||
int uid = ctx.EventArgs.Message.From.Id;
|
|
||||||
KarmaContext db = ctx.GetFeature<KarmaContext>();
|
|
||||||
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<Locale>();
|
|
||||||
await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]);
|
|
||||||
TimeoutCache[uid].TimeoutMessaged = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Feature feature = new Feature();
|
|
||||||
ctx.Features.Add(feature);
|
|
||||||
|
|
||||||
await next(ctx);
|
|
||||||
|
|
||||||
var routerFeature = ctx.GetFeature<ChatCommandRouter.Feature>();
|
|
||||||
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)) + ">" : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
JetKarmaBot/Services/KarmaTimeoutManager.cs
Normal file
53
JetKarmaBot/Services/KarmaTimeoutManager.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using JetBotLib;
|
||||||
|
using JetKarmaBot.Models;
|
||||||
|
using Perfusion;
|
||||||
|
|
||||||
|
namespace JetKarmaBot.Services
|
||||||
|
{
|
||||||
|
public class KarmaTimeoutManager : TimeoutManager
|
||||||
|
{
|
||||||
|
[Inject] Config cfg;
|
||||||
|
[Inject] KarmaContextFactory Db;
|
||||||
|
public override int SaveIntervalSeconds => cfg.Timeout.SaveIntervalSeconds;
|
||||||
|
|
||||||
|
public override int DebtLimitSeconds => cfg.Timeout.DebtLimitSeconds;
|
||||||
|
|
||||||
|
public override int GetCostSeconds(string name, bool status)
|
||||||
|
{
|
||||||
|
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name + (status ? " (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");
|
||||||
|
}
|
||||||
|
return costSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task Save(CancellationToken ct = default(CancellationToken))
|
||||||
|
{
|
||||||
|
using (KarmaContext db = Db.GetContext())
|
||||||
|
{
|
||||||
|
foreach (int uid in TimeoutCache.Keys)
|
||||||
|
{
|
||||||
|
(await db.Users.FindAsync(new object[] { uid }, ct)).CooldownDate = TimeoutCache[uid].CooldownDate;
|
||||||
|
}
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task LoadUser(int uid, RequestContext ctx = null)
|
||||||
|
{
|
||||||
|
KarmaContext db = ctx.GetFeature<KarmaContext>();
|
||||||
|
if (!TimeoutCache.ContainsKey(uid))
|
||||||
|
{
|
||||||
|
TimeoutCache[uid] = new TimeoutStats()
|
||||||
|
{
|
||||||
|
CooldownDate = (await db.Users.FindAsync(uid))?.CooldownDate ?? DateTime.Now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using JetBotLib;
|
||||||
using JetKarmaBot.Models;
|
using JetKarmaBot.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user