Compare commits

..

58 Commits

Author SHA1 Message Date
Nikolay Kochulin
6bbc85ebf0 Update dependencies and switch to .netcoreapp2.2 2019-06-14 22:46:38 +03:00
Nikolay Kochulin
178f1ee089 Ensure existence of DB 2019-06-07 18:37:11 +03:00
Nikolay Kochulin
ff63dbe386 Fix db isadministrator + update perfusion
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-05-17 15:31:54 +03:00
Nikolay Kochulin
7bef7255f8 Basic error telegram logging support
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-05-16 20:25:18 +03:00
Nikolay Kochulin
45b94e7561 Add field IsAdministrator
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-05-16 19:32:32 +03:00
Nikolay Kochulin
79e78f41c5 Add autocomplete based on language identifier
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-24 21:42:06 +03:00
Nikolay Kochulin
8322f4f871 Add rate limiting to /award
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-24 18:13:25 +03:00
Nikolay Kochulin
61166ea0c9 Add SqlDebug to config
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-24 18:10:34 +03:00
Nikolay Kochulin
e7c53fa2ad Fix "a a" in en-US locale
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-20 20:16:51 +03:00
Nikolay Kochulin
1ee1abc672 Better formatting for /currencies
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-18 18:09:50 +03:00
Nikolay Kochulin
71a2328eb1 Add accusative forms to award type names
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-18 17:44:05 +03:00
Nikolay Kochulin
4353428d41 Add /currencies
Signed-off-by: Nikolay Kochulin <porez0xfeedface@gmail.com>
2019-04-18 17:28:20 +03:00
Nikolay Kochulin
bf8778c7d9 Update to new Perfusion 2019-04-18 15:43:57 +03:00
Nikolay Kochulin
2cd40cb85d Merge branch 'master' of ssh://lserver1/srv/git/karmabot 2019-03-14 12:17:16 +03:00
Nikolay Kochulin
68c2715c17 Fix grammatical error 2019-03-14 12:17:13 +03:00
bcda3b9282 fix be-by.json build action 2019-02-12 11:41:50 +03:00
e2ee46eba9 build project under dotnet framework for deploy on linux x86 2019-02-09 19:01:38 +03:00
Nikolay Kochulin
d410d7ae47 Translate ru-RU into be-BY 2019-02-07 23:26:38 +03:00
e5d81925f0 ru help 2019-02-07 23:10:59 +03:00
Nikolay Kochulin
6835ba4843 Merge help-command into master 2019-02-07 17:53:52 +03:00
Nikolay Kochulin
d986a59844 Add functionality to localize command help strings. 2019-02-07 17:45:49 +03:00
Nikolay Kochulin
7f9e5918d4 Add all other awardtypes in 2019-02-07 17:20:12 +03:00
Nikolay Kochulin
4be28f3c1b Add functionality for awardtype localized names 2019-02-07 17:14:36 +03:00
Nikolay Kochulin
83d1b458af Add help command mechanisms 2019-02-07 16:44:32 +03:00
Nikolay Kochulin
b93d20e07e Start creating help command 2019-02-06 23:29:12 +03:00
Nikolay Kochulin
fadd970c5d Add joke belarusian locale + add locale notes 2019-02-06 20:38:58 +03:00
Nikolay Kochulin
09d2ce7bb6 Add joke belarusian locale. 2019-02-06 20:33:18 +03:00
Nikolay Kochulin
98361572dc Add /locale list and /locale all 2019-02-06 20:17:57 +03:00
Nikolay Kochulin
08ebdbaa37 Make logging accessible via perfusion 2019-02-06 18:19:09 +03:00
Nikolay Kochulin
0a3d969bec Update perfusion commit id + fix bug 2019-02-05 22:47:15 +03:00
Nikolay Kochulin
fdfea2bbb1 Update to use new perfusion version 2019-01-12 17:25:38 +03:00
Nikolay Kochulin
3e7ace4a7e Add "you have nothing yet" feature to /status 2019-01-11 22:10:28 +03:00
Nikolay Kochulin
4aee536c63 Make JetKarmaBot add chats to db 2019-01-11 21:58:48 +03:00
Nikolay Kochulin
6a4ee6e791 Add a bit of logging to AwardCommand and ChangeLocaleCommand 2019-01-06 23:49:02 +03:00
Nikolay Kochulin
1159af1785 Add logging to Program and CommandRouter 2019-01-06 23:39:31 +03:00
Nikolay Kochulin
a9cca3ee59 Added logging 2019-01-06 23:24:52 +03:00
Nikolay Kochulin
51dce7f588 Allow adding of common names of locales to locale files 2019-01-06 22:29:25 +03:00
d20cc89f28 localization fixes 2019-01-06 19:08:55 +03:00
fd732d8719 awards fix 2019-01-06 18:57:48 +03:00
Nikolay Kochulin
d95dae4ddb WIP entity framework 2019-01-06 18:29:00 +03:00
d3bbfe8c52 transition start 2019-01-02 17:07:31 +03:00
Nikolay Kochulin
23dbd6ca17 Allow getting locales 2018-12-31 23:05:13 +03:00
Nikolay Kochulin
c8adaaa0d4 Rename ChangeLanguageCommand to ChangeLocaleCommand 2018-12-31 21:56:46 +03:00
Nikolay Kochulin
bff68a9748 Add language chooser feature to chats 2018-12-26 20:56:53 +03:00
Nikolay Kochulin
12b90d010f
Make localization not use config 2018-12-21 23:00:32 +03:00
efb1a532bd ru-RU phrasing 2018-12-21 22:50:21 +03:00
Nikolay Kochulin
8918d576c4
Use now perfusion commit 2018-12-21 22:46:23 +03:00
Nikolay Kochulin
6fb4fdf544
Make /award output mentions 2018-12-21 19:53:25 +03:00
Nikolay Kochulin
58c6d52232
Add experimental russian locale 2018-12-21 19:50:35 +03:00
Nikolay Kochulin
8d0d70553c
Add localization 2018-12-21 18:13:36 +03:00
0025be1474 status command 2018-12-19 23:55:58 +03:00
2fbfde551b revoking support 2018-12-19 23:55:45 +03:00
37236d50ff pass through parsed string instead of sender to command 2018-12-19 22:45:57 +03:00
4947351b89 handle commands with mentions
skip commands directed at other bots
2018-12-19 22:43:30 +03:00
d535d2e8f4 Finish moving to Perfusion 2018-12-19 22:42:53 +03:00
fff2c0d3fa fix perfusion usage 2018-12-19 22:21:12 +03:00
a55f7a1d97 Merge remote-tracking branch 'origin/master' 2018-12-19 16:37:01 +03:00
Nikolay Kochulin
1394062930
Convert project to perfusion 2018-12-19 15:18:41 +03:00
36 changed files with 1302 additions and 1636 deletions

View File

@ -0,0 +1,124 @@
using JetKarmaBot.Commands;
using JetKarmaBot.Services;
using NLog;
using Perfusion;
using System;
using System.Collections.Generic;
using System.Linq;
using Telegram.Bot;
using Telegram.Bot.Args;
namespace JetKarmaBot
{
public class ChatCommandRouter
{
Telegram.Bot.Types.User BotUser { get; }
[Inject] private Logger log;
[Inject] private KarmaContextFactory Db;
[Inject] private TelegramBotClient Client { get; set; }
public ChatCommandRouter(Telegram.Bot.Types.User botUser)
{
BotUser = botUser;
}
public bool Execute(object sender, MessageEventArgs args)
{
log.Debug("Message received");
var text = args.Message.Text;
if (CommandString.TryParse(text, out var cmd))
{
if (cmd.UserName != null && cmd.UserName != BotUser.Username)
{
// directed not at us!
log.Debug("Message not directed at us");
return false;
}
try
{
if (commands.ContainsKey(cmd.Command))
{
log.Debug($"Handling message via {commands[cmd.Command].GetType().Name}");
return commands[cmd.Command].Execute(cmd, args);
}
}
catch (Exception e)
{
log.Error($"Error while handling command {cmd.Command}!");
log.Error(e);
ReportToAdministratorChats($"Error while handling command {cmd.Command}!\n{e.ToString()}");
}
}
return false;
}
public void ReportToAdministratorChats(string text)
{
using (var db = Db.GetContext())
{
foreach (long chatid in db.Chats.Where(x => x.IsAdministrator).Select(x => x.ChatId))
{
Client.SendTextMessageAsync(chatid, text);
}
}
}
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<string> pieces = new List<string>();
foreach (IChatCommand c in commands.Values.Distinct())
{
string build = "";
List<string> 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 ? "]" : ""))) + " <i>" + getLocalizedCMDDesc(c, loc) + "</i>";
pieces.Add(build);
}
return string.Join("\n", pieces);
}
internal string GetHelpTextFor(string commandname, Locale loc)
{
IChatCommand c = commands[commandname];
string build = "";
List<string> 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 ? "]" : ""))) + " <i>" + getLocalizedCMDDesc(c, loc) + "</i>\n";
build += string.Join("\n", c.Arguments.Select(ca => (!ca.Required ? "[" : "") + ca.Name + (!ca.Required ? "]" : "") + ": <i>" + getLocalizedCMDArgDesc(ca, loc) + "</i>"));
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<string, IChatCommand> commands = new Dictionary<string, IChatCommand>();
}
}

View File

@ -1,122 +1,99 @@
using Microsoft.EntityFrameworkCore; using System;
using System.Collections.Generic;
using System.Linq;
using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Perfusion;
using JetKarmaBot.Services;
using NLog; using NLog;
using JetKarmaBot.Services.Handling;
using JetKarmaBot.Models;
namespace JetKarmaBot.Commands; namespace JetKarmaBot.Commands
class AwardCommand : IChatCommand
{ {
class AwardCommand : IChatCommand
{
public IReadOnlyCollection<string> Names => new[] { "award", "revoke" }; public IReadOnlyCollection<string> Names => new[] { "award", "revoke" };
[Inject] private Logger log; [Inject]
private Logger log;
public async Task<bool> Execute(RequestContext ctx) public bool Execute(CommandString cmd, MessageEventArgs args)
{ {
var db = ctx.GetFeature<KarmaContext>(); using (var db = Db.GetContext())
var currentLocale = ctx.GetFeature<Locale>();
var awarder = ctx.EventArgs.Message.From;
if (Timeout.TimeoutCache[awarder.Id].PreviousAwardDate.AddSeconds(Config.Timeout.AwardTimeSeconds) > DateTime.Now)
{ {
ctx.GetFeature<TimeoutManager.Feature>().Multiplier = 0; // Doesn't count as success or failure var currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
if (!Timeout.TimeoutCache[awarder.Id].TimeoutMessaged) if (args.Message.ReplyToMessage == null)
await ctx.SendMessage(currentLocale["jetkarmabot.ratelimit"]); {
Timeout.TimeoutCache[awarder.Id].TimeoutMessaged = true; Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]);
return false; return true;
} }
string awardTypeText = null; var awarder = args.Message.From;
long recipientId = default; var recipient = args.Message.ReplyToMessage.From;
foreach (string arg in ctx.Command.Parameters)
bool awarding = cmd.Command == "award";
if (awarder.Id == recipient.Id)
{ {
if (arg.StartsWith('@')) Client.SendTextMessageAsync(
{ args.Message.Chat.Id,
if (recipientId != default(int)) currentLocale["jetkarmabot.award.errawardself"],
{ replyToMessageId: args.Message.MessageId);
await ctx.SendMessage(currentLocale["jetkarmabot.award.errdup"]); return true;
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) if (Me.Id == recipient.Id)
{ {
recipientId = ctx.EventArgs.Message.ReplyToMessage.From.Id; Client.SendTextMessageAsync(
} args.Message.Chat.Id,
awarding
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<ChatCommandRouter.Feature>().Router.Me.Id == recipientId)
{
await ctx.SendMessage(awarding
? currentLocale["jetkarmabot.award.errawardbot"] ? currentLocale["jetkarmabot.award.errawardbot"]
: currentLocale["jetkarmabot.award.errrevokebot"]); : currentLocale["jetkarmabot.award.errrevokebot"],
return false; replyToMessageId: args.Message.MessageId);
return true;
} }
var text = ctx.EventArgs.Message.Text; var text = args.Message.Text;
var awardTypeText = cmd.Parameters.FirstOrDefault();
global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null
? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText) ? db.AwardTypes.First(at => at.CommandName == awardTypeText)
: await db.AwardTypes.FindAsync((sbyte)1); : db.AwardTypes.Find((sbyte)1);
DateTime cutoff = DateTime.Now - TimeSpan.FromMinutes(5);
var prevCount = await db.Awards if (db.Awards.Where(x => x.Date > cutoff && x.FromId == awarder.Id).Count() >= 10)
.Where(aw => aw.ToId == recipientId && aw.AwardTypeId == awardType.AwardTypeId && aw.ChatId == ctx.EventArgs.Message.Chat.Id) {
.SumAsync(aw => aw.Amount); Client.SendTextMessageAsync(
args.Message.Chat.Id,
await db.Awards.AddAsync(new Models.Award() currentLocale["jetkarmabot.award.ratelimit"],
replyToMessageId: args.Message.MessageId);
return true;
}
db.Awards.Add(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 = recipient.Id,
ChatId = ctx.EventArgs.Message.Chat.Id ChatId = args.Message.Chat.Id
}); });
log.Debug($"Awarded {(awarding ? 1 : -1)}{awardType.Symbol} to {recipient.Username}");
var recUserName = (await db.Users.FindAsync(recipientId)).Username; db.SaveChanges();
log.Debug($"Awarded {(awarding ? 1 : -1)}{awardType.Symbol} to {recUserName}");
string message = awarding string message = awarding
? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), recUserName) ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], getLocalizedName(awardType, currentLocale), "@" + recipient.Username)
: string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), recUserName); : string.Format(currentLocale["jetkarmabot.award.revokemessage"], getLocalizedName(awardType, currentLocale), "@" + recipient.Username);
var currentCount = db.Awards
.Where(aw => aw.ToId == recipient.Id && aw.AwardTypeId == awardType.AwardTypeId)
.Sum(aw => aw.Amount);
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"], "@" + recipient.Username, currentCount, awardType.Symbol);
await ctx.SendMessage(response); Client.SendTextMessageAsync(
Timeout.TimeoutCache[awarder.Id].PreviousAwardDate = DateTime.Now; args.Message.Chat.Id,
response,
replyToMessageId: args.Message.MessageId);
return true; return true;
} }
}
private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc) private string getLocalizedName(global::JetKarmaBot.Models.AwardType awardType, Locale loc)
{ {
@ -130,9 +107,10 @@ class AwardCommand : IChatCommand
} }
} }
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
[Inject] TimeoutManager Timeout { get; set; } User Me { get; }
[Inject] Config Config { 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";
@ -144,13 +122,12 @@ class AwardCommand : IChatCommand
Type=ChatCommandArgumentType.String, Type=ChatCommandArgumentType.String,
Description="The award to grant to/strip of the specified user", Description="The award to grant to/strip of the specified user",
DescriptionID="jetkarmabot.award.awardtypehelp" DescriptionID="jetkarmabot.award.awardtypehelp"
},
new ChatCommandArgument() {
Name="to",
Required=false,
Type=ChatCommandArgumentType.String,
Description="The user to award it to.",
DescriptionID="jetkarmabot.award.tohelp"
} }
}; };
public AwardCommand(User me)
{
Me = me;
}
}
} }

View File

@ -1,36 +1,48 @@
using System.Collections.Generic;
using Telegram.Bot;
using Telegram.Bot.Args;
using Perfusion;
using JetKarmaBot.Services;
using NLog; using NLog;
using JetKarmaBot.Services.Handling; using System.Linq;
using JetKarmaBot.Models;
namespace JetKarmaBot.Commands; namespace JetKarmaBot.Commands
class LocaleCommand : IChatCommand
{ {
public IReadOnlyCollection<string> Names => new[] { "changelocale", "locale" }; class LocaleCommand : IChatCommand
[Inject] private Logger log;
public async Task<bool> Execute(RequestContext ctx)
{ {
var db = ctx.GetFeature<KarmaContext>(); public IReadOnlyCollection<string> Names => new[] { "changelocale", "locale" };
var currentLocale = ctx.GetFeature<Locale>(); [Inject]
var cmd = ctx.Command; private Logger log;
var args = ctx.EventArgs;
public bool Execute(CommandString cmd, MessageEventArgs args)
{
using (var db = Db.GetContext())
{
var currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
if (cmd.Parameters.Length < 1) if (cmd.Parameters.Length < 1)
{ {
await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.getlocale"]); Client.SendTextMessageAsync(
return false; args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.getlocale"],
replyToMessageId: args.Message.MessageId);
return true;
} }
else if (cmd.Parameters[0] == "list") else if (cmd.Parameters[0] == "list")
{ {
await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.listalltext"] + "\n" Client.SendTextMessageAsync(
+ string.Join("\n", Locale.Select(a => a.Key))); args.Message.Chat.Id,
return false; currentLocale["jetkarmabot.changelocale.listalltext"] + "\n"
+ string.Join("\n", Locale.Select(a => a.Key)),
replyToMessageId: args.Message.MessageId);
return true;
} }
else if (cmd.Parameters[0] == "all") else if (cmd.Parameters[0] == "all")
{ {
await ctx.SendMessage(currentLocale["jetkarmabot.changelocale.errorall"]); Client.SendTextMessageAsync(
return false; args.Message.Chat.Id,
currentLocale["jetkarmabot.changelocale.errorall"],
replyToMessageId: args.Message.MessageId);
return true;
} }
string localeId; string localeId;
if (Locale.ContainsLocale(cmd.Parameters[0])) if (Locale.ContainsLocale(cmd.Parameters[0]))
@ -42,22 +54,29 @@ class LocaleCommand : IChatCommand
} }
catch (LocalizationException e) catch (LocalizationException e)
{ {
await ctx.SendMessage( Client.SendTextMessageAsync(
currentLocale["jetkarmabot.changelocale.toomany"] + "\n" args.Message.Chat.Id,
+ 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)),
return false; replyToMessageId: args.Message.MessageId);
}
(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; return true;
} }
db.Chats.Find(args.Message.Chat.Id).Locale = localeId;
log.Debug($"Changed language of chat {args.Message.Chat.Id} to {localeId}");
db.SaveChanges();
currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
Client.SendTextMessageAsync(
args.Message.Chat.Id,
(currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "")
+ currentLocale["jetkarmabot.changelocale.justchanged"],
replyToMessageId: args.Message.MessageId);
return true;
}
}
[Inject] KarmaContextFactory Db { get; set; }
[Inject] TelegramBotClient Client { get; set; }
[Inject] Localization Locale { get; set; } [Inject] Localization Locale { get; set; }
public string Description => "Switches current chat locale to [locale]"; public string Description => "Switches current chat locale to [locale]";
@ -72,4 +91,5 @@ class LocaleCommand : IChatCommand
DescriptionID="jetkarmabot.changelocale.localehelp" DescriptionID="jetkarmabot.changelocale.localehelp"
} }
}; };
}
} }

View File

@ -1,9 +1,13 @@
using System.Text.RegularExpressions; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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; Command = command;
@ -36,7 +40,7 @@ public class CommandString
string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null; string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null;
string[] parameters = words.Skip(1).ToArray(); string[] parameters = words.Skip(1).ToArray();
result = new CommandString(cmd, parameters) { UserName = username }; result = new CommandString(cmd, parameters) { UserName = username};
return true; return true;
} }
@ -45,4 +49,5 @@ public class CommandString
if (TryParse(s, out var c)) return c; if (TryParse(s, out var c)) return c;
throw new ArgumentException($"\"{s}\" is not a command"); throw new ArgumentException($"\"{s}\" is not a command");
} }
}
} }

View File

@ -1,11 +1,17 @@
using Microsoft.EntityFrameworkCore; using System.Collections.Generic;
using JetKarmaBot.Services.Handling; using Telegram.Bot.Args;
using JetKarmaBot.Models; using Perfusion;
using JetKarmaBot.Services;
using Telegram.Bot;
using Telegram.Bot.Types.Enums;
using System.Linq;
namespace JetKarmaBot.Commands; 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; }
public IReadOnlyCollection<string> Names => new[] { "currencies", "awardtypes" }; public IReadOnlyCollection<string> Names => new[] { "currencies", "awardtypes" };
@ -15,14 +21,20 @@ public class CurrenciesCommand : IChatCommand
public IReadOnlyCollection<ChatCommandArgument> Arguments => new ChatCommandArgument[] { public IReadOnlyCollection<ChatCommandArgument> Arguments => new ChatCommandArgument[] {
}; };
public async Task<bool> Execute(RequestContext ctx) public bool Execute(CommandString cmd, MessageEventArgs args)
{ {
var db = ctx.GetFeature<KarmaContext>(); using (var db = Db.GetContext())
var currentLocale = ctx.GetFeature<Locale>(); {
await ctx.SendMessage( var currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n", string resp = currentLocale["jetkarmabot.currencies.listtext"] + "\n" + string.Join("\n",
(await db.AwardTypes.ToListAsync()) db.AwardTypes.ToList().Select(x => $"{x.Symbol} ({x.CommandName}) <i>{currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}</i>"));
.Select(x => $"{x.Symbol} ({x.CommandName}) <i>{currentLocale["jetkarmabot.awardtypes.nominative." + x.CommandName]}</i>"))); Client.SendTextMessageAsync(
args.Message.Chat.Id,
resp,
replyToMessageId: args.Message.MessageId,
parseMode: ParseMode.Html);
return true; return true;
} }
}
}
} }

View File

@ -1,10 +1,18 @@
using JetKarmaBot.Services.Handling; using System.Collections.Generic;
using Telegram.Bot.Args;
using Perfusion;
using JetKarmaBot.Services;
using Telegram.Bot;
using Telegram.Bot.Types.Enums;
namespace JetKarmaBot.Commands; 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; }
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)";
@ -20,19 +28,34 @@ public class HelpCommand : IChatCommand
} }
}; };
public async Task<bool> Execute(RequestContext ctx) public bool Execute(CommandString cmd, MessageEventArgs args)
{ {
var currentLocale = ctx.GetFeature<Locale>(); using (var db = Db.GetContext())
var router = ctx.GetFeature<ChatCommandRouter.Feature>().Router;
if (ctx.Command.Parameters.Length < 1)
{ {
await ctx.SendMessage(router.GetHelpText(currentLocale)); var currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
if (cmd.Parameters.Length < 1)
{
Client.SendTextMessageAsync(
args.Message.Chat.Id,
Router.GetHelpText(currentLocale),
replyToMessageId: args.Message.MessageId,
parseMode: ParseMode.Html);
return true; return true;
} }
else else
{ {
await ctx.SendMessage(router.GetHelpTextFor(ctx.Command.Parameters[0], currentLocale)); Client.SendTextMessageAsync(
args.Message.Chat.Id,
Router.GetHelpTextFor(cmd.Parameters[0], currentLocale),
replyToMessageId: args.Message.MessageId,
parseMode: ParseMode.Html);
return true; return true;
} }
} }
}
public HelpCommand(ChatCommandRouter router)
{
Router = router;
}
}
} }

View File

@ -1,29 +1,31 @@
using JetKarmaBot.Services.Handling; using System.Collections.Generic;
using Telegram.Bot.Args;
namespace JetKarmaBot.Commands; namespace JetKarmaBot.Commands
public interface IChatCommand
{ {
public interface IChatCommand
{
IReadOnlyCollection<string> Names { get; } IReadOnlyCollection<string> Names { get; }
string Description { get; } string Description { get; }
string DescriptionID { get; } string DescriptionID { get; }
IReadOnlyCollection<ChatCommandArgument> Arguments { get; } IReadOnlyCollection<ChatCommandArgument> Arguments { get; }
Task<bool> Execute(RequestContext ctx); bool Execute(CommandString cmd, MessageEventArgs messageEventArgs);
} }
public struct ChatCommandArgument public struct ChatCommandArgument
{ {
public string Name; public string Name;
public bool Required; public bool Required;
public ChatCommandArgumentType Type; public ChatCommandArgumentType Type;
public string Description; public string Description;
public string DescriptionID; public string DescriptionID;
} }
public enum ChatCommandArgumentType public enum ChatCommandArgumentType
{ {
Boolean, Boolean,
String, String,
Integer, Integer,
}
} }

View File

@ -1,65 +0,0 @@
using Microsoft.EntityFrameworkCore;
using JetKarmaBot.Services.Handling;
using JetKarmaBot.Models;
namespace JetKarmaBot.Commands;
class LeaderboardCommand : IChatCommand
{
public IReadOnlyCollection<string> Names => new[] { "leaderboard" };
public async Task<bool> Execute(RequestContext ctx)
{
bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private;
var currentLocale = ctx.GetFeature<Locale>();
if (isPrivate)
{
await ctx.SendMessage(currentLocale["jetkarmabot.award.errawardself"]);
return true;
}
var db = ctx.GetFeature<KarmaContext>();
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);
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<ChatCommandArgument> Arguments => new ChatCommandArgument[] {
new ChatCommandArgument() {
Name="awardtype",
Required=true,
Type=ChatCommandArgumentType.String,
Description="The awardtype to show a leaderboard for.",
DescriptionID= "jetkarmabot.leaderboard.awardtypehelp"
}
};
}

View File

@ -1,51 +1,83 @@
using Microsoft.EntityFrameworkCore; using System;
using JetKarmaBot.Services.Handling; using System.Linq;
using System.Collections.Generic;
using System.Text;
using Perfusion;
using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using JetKarmaBot.Models; using JetKarmaBot.Models;
using JetKarmaBot.Services;
namespace JetKarmaBot.Commands; namespace JetKarmaBot.Commands
class StatusCommand : IChatCommand
{ {
public IReadOnlyCollection<string> Names => ["status"]; class StatusCommand : IChatCommand
public async Task<bool> Execute(RequestContext ctx)
{ {
var db = ctx.GetFeature<KarmaContext>(); public IReadOnlyCollection<string> Names => new[] { "status" };
var currentLocale = ctx.GetFeature<Locale>();
var asker = ctx.EventArgs.Message.From; public bool Execute(CommandString cmd, MessageEventArgs args)
bool isPrivate = ctx.EventArgs.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Private; {
using (var db = Db.GetContext())
{
var currentLocale = Locale[db.Chats.Find(args.Message.Chat.Id).Locale];
var asker = args.Message.From;
var awardTypeName = cmd.Parameters.FirstOrDefault();
string response; string response;
var awards = db.Awards.Where(x => x.ToId == asker.Id); if (string.IsNullOrWhiteSpace(awardTypeName))
if (!isPrivate) {
awards = awards.Where(x => x.ChatId == ctx.EventArgs.Message.Chat.Id); // var awards = db.Awards.Where(x => x.ToId == asker.Id)
// .GroupBy(x => x.AwardTypeId)
if (!awards.Any()) // .Select(x => new { AwardTypeId = x.Key, Amount = x.Sum(y => y.Amount) });
if (!db.Awards.Any(x => x.ToId == asker.Id))
response = currentLocale["jetkarmabot.status.havenothing"]; response = currentLocale["jetkarmabot.status.havenothing"];
else else
{ {
var aq = db.AwardTypes.GroupJoin( var awardsQuery = from award in db.Awards
awards, where award.ToId == asker.Id
at => at.AwardTypeId, group award by award.AwardTypeId into g
aw => aw.AwardTypeId, select new { AwardTypeId = g.Key, Amount = g.Sum(x => x.Amount) };
(at, aws) => new {at.Symbol, Amount = aws.Sum(aw => aw.Amount) }); var awardsByType = awardsQuery.ToList();
response = currentLocale["jetkarmabot.status.listalltext"] + "\n"
+ string.Join("\n", awardsByType.Select(a => $" - {db.AwardTypes.Find(a.AwardTypeId).Symbol} {a.Amount}"));
var awardsByType = await aq.ToListAsync(); }
}
else
{
var awardTypeIdQuery = from awt in db.AwardTypes
where awt.CommandName == awardTypeName
select awt.AwardTypeId;
var awardTypeId = awardTypeIdQuery.First();
var awardType = db.AwardTypes.Find(awardTypeId);
response = response = string.Format(currentLocale["jetkarmabot.status.listspecifictext"], db.Awards.Where(x => x.AwardTypeId == awardTypeId && x.ToId == asker.Id).Sum(x => x.Amount), awardType.Symbol);
currentLocale["jetkarmabot.status.listalltext"] + "\n"
+ string.Join("\n", awardsByType.Select(a => $" - {a.Symbol} {a.Amount}"));
} }
await ctx.SendMessage(response); Client.SendTextMessageAsync(
args.Message.Chat.Id,
response,
replyToMessageId: args.Message.MessageId);
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 => "Shows the amount of awards that you have"; public string Description => "Shows the amount of awards that you have";
public string DescriptionID => "jetkarmabot.status.help"; public string DescriptionID => "jetkarmabot.status.help";
public IReadOnlyCollection<ChatCommandArgument> Arguments => []; public IReadOnlyCollection<ChatCommandArgument> Arguments => new ChatCommandArgument[] {
new ChatCommandArgument(){
Name="awardtype",
Required=false,
Type=ChatCommandArgumentType.String,
Description="The awardtype to show. If empty shows everything.",
DescriptionID= "jetkarmabot.status.awardtypehelp"
}
};
}
} }

View File

@ -3,10 +3,10 @@ using Newtonsoft.Json;
using JsonNet.PrivateSettersContractResolvers; using JsonNet.PrivateSettersContractResolvers;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace JetKarmaBot; namespace JetKarmaBot
public class Config : ConfigBase
{ {
public class Config : ConfigBase
{
public Config(string path) : base(path) { } public Config(string path) : base(path) { }
public string ApiKey { get; private set; } public string ApiKey { get; private set; }
@ -21,24 +21,11 @@ public class Config : ConfigBase
} }
public ProxySettings Proxy { get; private set; } public ProxySettings Proxy { get; private set; }
public class TimeoutConfig public bool SqlDebug { get; private set; }
{
public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2;
public Dictionary<string, int> CommandCostsSeconds { get; private set; } = new Dictionary<string, int>()
{
{"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 abstract class ConfigBase public abstract class ConfigBase
{ {
public ConfigBase(string path) public ConfigBase(string path)
{ {
JObject configJson; JObject configJson;
@ -72,5 +59,6 @@ public abstract class ConfigBase
System.Diagnostics.Debug.WriteLine(e); System.Diagnostics.Debug.WriteLine(e);
} }
} }
}
} }

View File

@ -1,12 +1,15 @@
namespace JetKarmaBot; using System.Collections.Generic;
public static class IReadOnlyDictionaryExtensions namespace JetKarmaBot
{ {
public static class IReadOnlyDictionaryExtensions
{
public static TValue GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, TKey key) public static TValue GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, TKey key)
{ {
TValue res = default; TValue res = default(TValue);
if (key != null) if (key != null)
dict.TryGetValue(key, out res); dict.TryGetValue(key, out res);
return res; return res;
} }
}
} }

View File

@ -3,10 +3,10 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
// ReSharper disable once CheckNamespace // 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); var jProperty = base.CreateProperty(member, memberSerialization);
@ -17,10 +17,10 @@ public class PrivateSetterContractResolver : DefaultContractResolver
return jProperty; 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); var jProperty = base.CreateProperty(member, memberSerialization);
@ -31,14 +31,15 @@ public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCaseProp
return jProperty; 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;
} }
}
} }

View File

@ -1,6 +0,0 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Perfusion;

View File

@ -1,126 +1,85 @@
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 NLog; using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Polling; using Telegram.Bot.Args;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
namespace JetKarmaBot; namespace JetKarmaBot
public class JetKarmaBot : IDisposable
{ {
public class JetKarmaBot : IDisposable
{
[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] Localization Locale { get; set; }
[Inject] Logger Log { get; set; }
TelegramBotClient Client { get; set; } TelegramBotClient Client { get; set; }
ChatCommandRouter Commands; ChatCommandRouter Commands;
RequestChain Chain; Telegram.Bot.Types.User Me { get; set; }
Task timeoutWaitTask;
CancellationTokenSource timeoutWaitTaskToken;
private bool stopped = false;
public async Task Init() public async Task Init()
{ {
using (KarmaContext db = Db.GetContext()) using (KarmaContext db = Db.GetContext())
await db.Database.EnsureCreatedAsync(); await db.Database.EnsureCreatedAsync();
var httpProxy = new WebProxy($"{Config.Proxy.Url}:{Config.Proxy.Port}")
{
Credentials = new NetworkCredential(Config.Proxy.Login, Config.Proxy.Password)
};
Client = new TelegramBotClient(Config.ApiKey); Client = new TelegramBotClient(Config.ApiKey, httpProxy);
Container.AddInstance(Client); Container.AddInstance(Client);
Me = await Client.GetMeAsync();
timeoutWaitTaskToken = new CancellationTokenSource(); InitCommands(Container);
timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token);
await InitCommands(Container); Client.OnMessage += BotOnMessageReceived;
InitChain(Container); Client.StartReceiving();
var receiverOptions = new ReceiverOptions { AllowedUpdates = new[] { UpdateType.Message } };
Client.StartReceiving(
HandleUpdateAsync,
HandleErrorAsync,
receiverOptions);
} }
public async Task Stop() public async Task Stop()
{ {
if (stopped) return;
Client?.CloseAsync();
timeoutWaitTaskToken?.Cancel();
try
{
if (timeoutWaitTask != null)
await timeoutWaitTask;
}
catch (OperationCanceledException) { }
await Timeout?.Save();
Dispose(); Dispose();
stopped = true;
} }
#region service #region service
Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
{
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
{ {
var message = messageEventArgs.Message;
if (message == null || message.Type != MessageType.Text) if (message == null || message.Type != MessageType.Text)
return; return;
if (!CommandString.TryParse(message.Text, out var cmd)) using (KarmaContext db = Db.GetContext())
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); if (!db.Users.Any(x => x.UserId == messageEventArgs.Message.From.Id))
db.Users.Add(new Models.User { UserId = messageEventArgs.Message.From.Id });
if (messageEventArgs.Message.ReplyToMessage != null)
if (!db.Users.Any(x => x.UserId == messageEventArgs.Message.ReplyToMessage.From.Id))
db.Users.Add(new Models.User { UserId = messageEventArgs.Message.ReplyToMessage.From.Id });
if (!db.Chats.Any(x => x.ChatId == messageEventArgs.Message.Chat.Id))
db.Chats.Add(new Models.Chat { ChatId = messageEventArgs.Message.Chat.Id });
db.SaveChanges();
} }
string s = message.Text;
long id = message.Chat.Id;
long from = message.From.Id;
Task.Run(() => Commands.Execute(sender, messageEventArgs));
} }
async Task InitCommands(IContainer c) void InitCommands(IContainer c)
{ {
c.Add<HelpCommand>(); Commands = c.ResolveObject(new ChatCommandRouter(Me));
c.Add<AwardCommand>(); Commands.Add(c.ResolveObject(new HelpCommand(Commands)));
c.Add<StatusCommand>(); Commands.Add(c.ResolveObject(new AwardCommand(Me)));
c.Add<LocaleCommand>(); Commands.Add(c.ResolveObject(new StatusCommand()));
c.Add<CurrenciesCommand>(); Commands.Add(c.ResolveObject(new LocaleCommand()));
c.Add<LeaderboardCommand>(); Commands.Add(c.ResolveObject(new CurrenciesCommand()));
Commands = c.GetInstance<ChatCommandRouter>();
await Commands.Start();
foreach (IChatCommand cmd in c.GetInstances<IChatCommand>())
{
Commands.Add(cmd);
}
}
void InitChain(IContainer c)
{
Chain = c.ResolveObject(new RequestChain());
Chain.Add(c.GetInstance<TimeoutManager.PreDbThrowout>());
Chain.Add(c.GetInstance<DatabaseHandler>());
Chain.Add(Timeout);
Chain.Add(c.GetInstance<SaveData>());
Chain.Add(Commands);
} }
#endregion #endregion
@ -129,9 +88,9 @@ public class JetKarmaBot : IDisposable
public void Dispose() public void Dispose()
{ {
timeoutWaitTaskToken?.Dispose(); Client.StopReceiving();
timeoutWaitTask?.Dispose();
} }
#endregion #endregion
}
} }

View File

@ -1,20 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>netcoreapp2.2;</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.4">
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="Telegram.Bot" Version="22.0.2" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PackageReference Include="NLog" Version="5.3.4" /> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
<PackageReference Include="Telegram.Bot" Version="14.12.0" />
<PackageReference Include="NLog" Version="4.6.5" />
<PackageReference Include="NLog.Config" Version="4.6.5" />
<ProjectReference Include="..\perfusion\Perfusion\Perfusion.csproj" /> <ProjectReference Include="..\perfusion\Perfusion\Perfusion.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="karma.cfg.json"> <None Update="karma.cfg.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="lang\*"> <None Update="lang\be-BY.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="lang\en-US.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="lang\ru-RU.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="NLog.config"> <None Update="NLog.config">

View File

@ -1,13 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema; using System;
using System.Collections.Generic;
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 int AwardId { get; set; }
public long ChatId { get; set; } public long ChatId { get; set; }
public long FromId { get; set; } public int FromId { get; set; }
public long ToId { get; set; } public int ToId { get; set; }
public sbyte AwardTypeId { get; set; } public sbyte AwardTypeId { get; set; }
public sbyte Amount { get; set; } public sbyte Amount { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
@ -19,4 +21,5 @@ public partial class Award
public virtual User From { get; set; } public virtual User From { get; set; }
[ForeignKey("ToId")] [ForeignKey("ToId")]
public virtual User To { get; set; } public virtual User To { get; set; }
}
} }

View File

@ -1,7 +1,10 @@
namespace JetKarmaBot.Models; using System;
using System.Collections.Generic;
public partial class AwardType namespace JetKarmaBot.Models
{ {
public partial class AwardType
{
public AwardType() public AwardType()
{ {
Awards = new HashSet<Award>(); Awards = new HashSet<Award>();
@ -14,4 +17,5 @@ public partial class AwardType
public string Description { get; set; } public string Description { get; set; }
public virtual ICollection<Award> Awards { get; set; } public virtual ICollection<Award> Awards { get; set; }
}
} }

View File

@ -1,15 +1,19 @@
namespace JetKarmaBot.Models; using System;
using System.Collections.Generic;
public partial class Chat namespace JetKarmaBot.Models
{ {
public partial class Chat
{
public Chat() public Chat()
{ {
Awards = new HashSet<Award>(); Awards = new HashSet<Award>();
} }
public long ChatId { get; set; } public long ChatId { get; set; }
public string Locale { get; set; } = "ru-RU"; public string Locale { get; set; }
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,10 +1,13 @@
using Microsoft.EntityFrameworkCore; using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Perfusion;
namespace JetKarmaBot.Models; namespace JetKarmaBot.Models
[Transient]
public partial class KarmaContext : DbContext
{ {
[Transient]
public partial class KarmaContext : DbContext
{
public KarmaContext() public KarmaContext()
{ {
} }
@ -31,20 +34,20 @@ public partial class KarmaContext : DbContext
entity.ToTable("award"); entity.ToTable("award");
entity.HasIndex(e => e.AwardId) entity.HasIndex(e => e.AwardId)
.HasDatabaseName("awardid_UNIQUE") .HasName("awardid_UNIQUE")
.IsUnique(); .IsUnique();
entity.HasIndex(e => e.AwardTypeId) entity.HasIndex(e => e.AwardTypeId)
.HasDatabaseName("fk_awardtype_idx"); .HasName("fk_awardtype_idx");
entity.HasIndex(e => e.ChatId) entity.HasIndex(e => e.ChatId)
.HasDatabaseName("fk_chat_idx"); .HasName("fk_chat_idx");
entity.HasIndex(e => e.FromId) entity.HasIndex(e => e.FromId)
.HasDatabaseName("fk_from_idx"); .HasName("fk_from_idx");
entity.HasIndex(e => e.ToId) entity.HasIndex(e => e.ToId)
.HasDatabaseName("fk_to_idx"); .HasName("fk_to_idx");
entity.Property(e => e.AwardId) entity.Property(e => e.AwardId)
.HasColumnName("awardid") .HasColumnName("awardid")
@ -106,11 +109,11 @@ public partial class KarmaContext : DbContext
entity.ToTable("awardtype"); entity.ToTable("awardtype");
entity.HasIndex(e => e.AwardTypeId) entity.HasIndex(e => e.AwardTypeId)
.HasDatabaseName("awardtypeid_UNIQUE") .HasName("awardtypeid_UNIQUE")
.IsUnique(); .IsUnique();
entity.HasIndex(e => e.CommandName) entity.HasIndex(e => e.CommandName)
.HasDatabaseName("commandname_UNIQUE") .HasName("commandname_UNIQUE")
.IsUnique(); .IsUnique();
entity.Property(e => e.AwardTypeId) entity.Property(e => e.AwardTypeId)
@ -169,11 +172,7 @@ public partial class KarmaContext : DbContext
entity.Property(e => e.Username) entity.Property(e => e.Username)
.HasColumnName("username") .HasColumnName("username")
.HasColumnType("varchar(45)"); .HasColumnType("varchar(45)");
entity.Property(e => e.CooldownDate)
.HasColumnName("cooldowndate")
.HasColumnType("timestamp")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
}); });
} }
}
} }

View File

@ -1,20 +1,22 @@
using System.ComponentModel.DataAnnotations.Schema; using System;
using System.Collections.Generic;
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<Award>(); AwardsFrom = new HashSet<Award>();
AwardsTo = new HashSet<Award>(); AwardsTo = new HashSet<Award>();
} }
public long UserId { get; set; } public int UserId { get; set; }
public string Username { get; set; } public string Username { get; set; }
public DateTime CooldownDate { get; set; }
[InverseProperty("From")] [InverseProperty("From")]
public virtual ICollection<Award> AwardsFrom { get; set; } public virtual ICollection<Award> AwardsFrom { get; set; }
[InverseProperty("To")] [InverseProperty("To")]
public virtual ICollection<Award> AwardsTo { get; set; } public virtual ICollection<Award> AwardsTo { get; set; }
}
} }

View File

@ -1,11 +1,14 @@
using Microsoft.EntityFrameworkCore; using System;
using NLog; using System.Threading;
using JetKarmaBot.Models; using JetKarmaBot.Models;
using Microsoft.EntityFrameworkCore;
using NLog;
using Perfusion;
namespace JetKarmaBot; namespace JetKarmaBot
public static class Program
{ {
public static class Program
{
private static Logger log = LogManager.GetCurrentClassLogger(); private static Logger log = LogManager.GetCurrentClassLogger();
public enum ExitCode : int public enum ExitCode : int
{ {
@ -23,11 +26,8 @@ public static class Program
var cfg = new Config("karma.cfg.json"); var cfg = new Config("karma.cfg.json");
c.AddInstance(cfg); c.AddInstance(cfg);
var connStr = cfg.ConnectionString + (cfg.ConnectionString.EndsWith(";") ? "" : ";") + "TreatTinyAsBoolean=false";
var serverVersion = ServerVersion.AutoDetect(connStr);
var dbOptions = new DbContextOptionsBuilder<KarmaContext>() var dbOptions = new DbContextOptionsBuilder<KarmaContext>()
.UseMySql(connStr, serverVersion); .UseMySql(cfg.ConnectionString + (cfg.ConnectionString.EndsWith(";") ? "" : ";") + "TreatTinyAsBoolean=false");
c.AddInfo<Logger>(new LogInfo()); c.AddInfo<Logger>(new LogInfo());
if (cfg.SqlDebug) if (cfg.SqlDebug)
{ {
@ -40,13 +40,14 @@ public static class Program
try try
{ {
bot.Init().GetAwaiter().GetResult(); bot.Init().Wait();
log.Info("JetKarmaBot started. Press Ctrl-C to exit..."); log.Info("JetKarmaBot started. Press Ctrl-C to exit...");
Environment.ExitCode = (int)ExitCode.ErrorRunning;
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error(ex); log.Error(ex);
return (int)ExitCode.ErrorException; Environment.ExitCode = (int)ExitCode.ErrorException;
} }
ManualResetEvent quitEvent = new ManualResetEvent(false); ManualResetEvent quitEvent = new ManualResetEvent(false);
try try
@ -56,18 +57,14 @@ public static class Program
eArgs.Cancel = true; eArgs.Cancel = true;
quitEvent.Set(); quitEvent.Set();
}; };
AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
{
log.Info("Received stop request, waiting for exit...");
bot?.Stop()?.GetAwaiter().GetResult();
};
} }
catch { } catch { }
quitEvent.WaitOne(Timeout.Infinite); quitEvent.WaitOne(Timeout.Infinite);
log.Info("Waiting for exit..."); log.Info("Waiting for exit...");
bot?.Stop()?.GetAwaiter().GetResult(); bot?.Stop()?.Wait();
return (int)ExitCode.Ok; return (int)ExitCode.Ok;
} }
}
} }

View File

@ -1,105 +0,0 @@
using NLog;
using Telegram.Bot;
using JetKarmaBot.Commands;
namespace JetKarmaBot.Services.Handling;
public class ChatCommandRouter : IRequestHandler
{
public class Feature
{
public Type CommandType;
public ChatCommandRouter Router;
public bool Succeded;
}
public Telegram.Bot.Types.User Me { get; private set; }
[Inject] private Logger log;
[Inject] private TelegramBotClient Client { get; set; }
public async Task Start()
{
Me = await Client.GetMeAsync();
}
public Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
{
log.Debug("Message received");
CommandString cmd = ctx.Command;
Feature feature = new Feature() { Router = this };
ctx.AddFeature(feature);
try
{
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;
}
}
internal string GetHelpText(Locale loc)
{
List<string> pieces = new List<string>();
foreach (IChatCommand c in commands.Values.Distinct())
{
string build = "";
List<string> 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 ? "]" : ""))) + " <i>" + getLocalizedCMDDesc(c, loc) + "</i>";
pieces.Add(build);
}
return string.Join("\n", pieces);
}
internal string GetHelpTextFor(string commandname, Locale loc)
{
IChatCommand c = commands[commandname];
string build = "";
List<string> 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 ? "]" : ""))) + " <i>" + getLocalizedCMDDesc(c, loc) + "</i>\n";
build += string.Join("\n", c.Arguments.Select(ca => (!ca.Required ? "[" : "") + ca.Name + (!ca.Required ? "]" : "") + ": <i>" + getLocalizedCMDArgDesc(ca, loc) + "</i>"));
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<string, IChatCommand> commands = new Dictionary<string, IChatCommand>();
}

View File

@ -1,17 +0,0 @@
namespace JetKarmaBot.Services.Handling;
public class DatabaseHandler : IRequestHandler
{
[Inject] private KarmaContextFactory Db;
[Inject] private Localization Locale;
public async Task Handle(RequestContext ctx, Func<RequestContext, Task> next)
{
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();
}
}
}

View File

@ -1,37 +0,0 @@
using NLog;
namespace JetKarmaBot.Services.Handling;
public interface IRequestHandler
{
Task Handle(RequestContext ctx, Func<RequestContext, Task> next);
}
public class RequestChain : IRequestHandler
{
[Inject] private Logger log;
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)
{
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);
}
}

View File

@ -1,32 +0,0 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using JetKarmaBot.Commands;
namespace JetKarmaBot.Services.Handling;
public class RequestContext : IServiceProvider
{
public ITelegramBotClient Client { get; }
public Update EventArgs { get; }
public CommandString Command { get; }
public Dictionary<Type, object> Features { get; } = new Dictionary<Type, object>();
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>() => (T)Features[typeof(T)];
public void AddFeature<T>(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 }
);
}

View File

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

@ -1,126 +0,0 @@
using NLog;
using JetKarmaBot.Models;
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)
{
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<long, TimeoutStats> 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<RequestContext, Task> next)
{
var 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.AddFeature(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)) + ">" : "");
}
}

View File

@ -1,10 +1,12 @@
using JetKarmaBot.Models; 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<KarmaContext>(); public KarmaContext GetContext() => C.GetInstance<KarmaContext>();
}
} }

View File

@ -1,15 +1,20 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NLog; using NLog;
using Perfusion;
namespace JetKarmaBot; namespace JetKarmaBot
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>();
[Inject]
public Localization(IContainer c) public Localization(IContainer c)
{ {
c.ResolveObject(this); c.ResolveObject(this);
@ -105,9 +110,9 @@ public class Localization : IReadOnlyDictionary<string, Locale>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
public class Locale : IReadOnlyDictionary<string, string> public class Locale : IReadOnlyDictionary<string, string>
{ {
private Dictionary<string, string> locale; private Dictionary<string, string> locale;
private string localeName; private string localeName;
private string[] commonNames; private string[] commonNames;
@ -132,7 +137,7 @@ public class Locale : IReadOnlyDictionary<string, string>
public int Count => ((IReadOnlyDictionary<string, string>)locale).Count; public int Count => ((IReadOnlyDictionary<string, string>)locale).Count;
public string this[string name] => locale.ContainsKey(name) ? locale[name] : "Unmapped locale key: " + name; public string this[string name] => locale.ContainsKey(name) ? locale[name] : "unknown";
public bool ContainsKey(string key) public bool ContainsKey(string key)
{ {
@ -153,14 +158,15 @@ public class Locale : IReadOnlyDictionary<string, string>
{ {
return ((IReadOnlyDictionary<string, string>)locale).GetEnumerator(); return ((IReadOnlyDictionary<string, string>)locale).GetEnumerator();
} }
} }
[System.Serializable] [System.Serializable]
public class LocalizationException : Exception public class LocalizationException : Exception
{ {
public LocalizationException() { } public LocalizationException() { }
public LocalizationException(string message) : base(message) { } public LocalizationException(string message) : base(message) { }
public LocalizationException(string message, Exception inner) : base(message, inner) { } public LocalizationException(string message, Exception inner) : base(message, inner) { }
protected LocalizationException( protected LocalizationException(
SerializationInfo info, SerializationInfo info,
StreamingContext context) : base(info, context) { } StreamingContext context) : base(info, context) { }
}
} }

View File

@ -1,11 +1,12 @@
using System;
using System.Linq;
using NLog; using NLog;
using Perfusion;
namespace JetKarmaBot; namespace JetKarmaBot
public class LogInfo : ObjectInfo
{ {
public override ObjectInfo Clone() => new LogInfo(); public class LogInfo : ObjectInfo
{
public override object GetInstance(IContainer c, Type requester = null) public override object GetInstance(IContainer c, Type requester = null)
{ {
return LogManager.GetLogger(requester != null ? getTypeName(requester) : "<type unspecified>"); return LogManager.GetLogger(requester != null ? getTypeName(requester) : "<type unspecified>");
@ -15,4 +16,5 @@ public class LogInfo : ObjectInfo
return (t.DeclaringType == null ? t.Namespace + "." + t.Name : getTypeName(t.DeclaringType) + "." + t.Name) return (t.DeclaringType == null ? t.Namespace + "." + t.Name : getTypeName(t.DeclaringType) + "." + t.Name)
+ (t.GenericTypeArguments.Length > 0 ? "<" + string.Join(",", t.GenericTypeArguments.Select(getTypeName)) + ">" : ""); + (t.GenericTypeArguments.Length > 0 ? "<" + string.Join(",", t.GenericTypeArguments.Select(getTypeName)) + ">" : "");
} }
}
} }

View File

@ -1,10 +1,12 @@
using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NLog; using NLog;
using Perfusion;
namespace JetKarmaBot; namespace JetKarmaBot
public class NLoggerFactory : ILoggerFactory
{ {
public class NLoggerFactory : ILoggerFactory
{
[Inject] [Inject]
private NLoggerProvider c; private NLoggerProvider c;
public void AddProvider(ILoggerProvider provider) public void AddProvider(ILoggerProvider provider)
@ -16,9 +18,9 @@ public class NLoggerFactory : ILoggerFactory
public void Dispose() public void Dispose()
{ {
} }
} }
public class NLoggerProvider : ILoggerProvider public class NLoggerProvider : ILoggerProvider
{ {
[Inject] [Inject]
private Container c; private Container c;
@ -27,10 +29,10 @@ public class NLoggerProvider : ILoggerProvider
public void Dispose() public void Dispose()
{ {
} }
} }
public class LoggerVirtualizer : Microsoft.Extensions.Logging.ILogger public class LoggerVirtualizer : Microsoft.Extensions.Logging.ILogger
{ {
private Logger logger; private Logger logger;
public LoggerVirtualizer(Logger logger) public LoggerVirtualizer(Logger logger)
@ -91,10 +93,11 @@ public class LoggerVirtualizer : Microsoft.Extensions.Logging.ILogger
if (exception != null) logger.Log(getAppropriate(logLevel), exception, formatter(state, exception)); if (exception != null) logger.Log(getAppropriate(logLevel), exception, formatter(state, exception));
else logger.Log(getAppropriate(logLevel), state); else logger.Log(getAppropriate(logLevel), state);
} }
} }
public class SomeDisposable : IDisposable public class SomeDisposable : IDisposable
{ {
public void Dispose() public void Dispose()
{ {
} }
}
} }

View File

@ -6,23 +6,21 @@
], ],
"note": "This is a joke. And made with google translate.", "note": "This is a joke. And made with google translate.",
"strings": { "strings": {
"jetkarmabot.ratelimit": "Павольны, чувак!", "jetkarmabot.award.errawardnoreply": "Калі ласка выкарыстоўвайце гэтую каманду ў адказе іншаму карыстальніку.",
"jetkarmabot.award.errawardnoreply": "Пра каго ты кажаш?",
"jetkarmabot.award.errbadusername": "Хто гэта?",
"jetkarmabot.award.errdup": "Калі ласка, спыніце батчіць ўзнагароды.",
"jetkarmabot.award.errawardself": "Хопіць з сабой гуляцца.", "jetkarmabot.award.errawardself": "Хопіць з сабой гуляцца.",
"jetkarmabot.award.errawardbot": "Я бот, і мяне вашы нікчэмныя пузамеркі не вабяць.", "jetkarmabot.award.errawardbot": "Я бот, і мяне вашы нікчэмныя пузамеркі не вабяць.",
"jetkarmabot.award.errrevokebot": "ಠ_ಠ", "jetkarmabot.award.errrevokebot": "ಠ_ಠ",
"jetkarmabot.award.awardmessage": "Ўручыў \"{0}\" {1}!", "jetkarmabot.award.awardmessage": "Ўручыў \"{0}\" {1}!",
"jetkarmabot.award.revokemessage": "Адабраў \"{0}\" у {1}!", "jetkarmabot.award.revokemessage": "Адабраў \"{0}\" у {1}!",
"jetkarmabot.award.statustext": "У {0} цяпер {1}{2}.", "jetkarmabot.award.statustext": "У {0} цяпер {1}{2}.",
"jetkarmabot.award.ratelimit": "Павольны, чувак!",
"jetkarmabot.award.help": "Уручае ачко карыстачу (або адымае)", "jetkarmabot.award.help": "Уручае ачко карыстачу (або адымае)",
"jetkarmabot.award.awardtypehelp": "Тып ачкі", "jetkarmabot.award.awardtypehelp": "Тып ачкі",
"jetkarmabot.award.tohelp": "Карыстальнік, якога ўзнагародзіць",
"jetkarmabot.status.listalltext": "У вас :", "jetkarmabot.status.listalltext": "У вас :",
"jetkarmabot.status.listspecifictext": "У вас зараз {0}{1}.", "jetkarmabot.status.listspecifictext": "У вас зараз {0}{1}.",
"jetkarmabot.status.havenothing": "У вас пакуль нічога няма.", "jetkarmabot.status.havenothing": "У вас пакуль нічога няма.",
"jetkarmabot.status.help": "Паказвае справаздачу па ўзнагародах.", "jetkarmabot.status.help": "Паказвае справаздачу па ўзнагародах.",
"jetkarmabot.status.awardtypehelp": "Тып ўзнагароды, па якой падасца справаздачу. Калі пуста, то падасца зводны справаздачу.",
"jetkarmabot.changelocale.justchanged": "Так дакладна.", "jetkarmabot.changelocale.justchanged": "Так дакладна.",
"jetkarmabot.changelocale.getlocale": "Я зараз кажу па-беларускай.", "jetkarmabot.changelocale.getlocale": "Я зараз кажу па-беларускай.",
"jetkarmabot.changelocale.listalltext": "Я ведаю:", "jetkarmabot.changelocale.listalltext": "Я ведаю:",
@ -35,32 +33,17 @@
"jetkarmabot.help.commandhelp": "Каманда, да якой трэба паказаць інструкцыю. Калі пуста, то паказваецца спіс каманд.", "jetkarmabot.help.commandhelp": "Каманда, да якой трэба паказаць інструкцыю. Калі пуста, то паказваецца спіс каманд.",
"jetkarmabot.currencies.help": "Паказвае ўсе тыпы узнагарод", "jetkarmabot.currencies.help": "Паказвае ўсе тыпы узнагарод",
"jetkarmabot.currencies.listtext": "Тыпы узнагарод:", "jetkarmabot.currencies.listtext": "Тыпы узнагарод:",
"jetkarmabot.leaderboard.help": "Табліца лідэраў па колькасці ўзнагарод",
"jetkarmabot.leaderboard.awardtypehelp": "Тып ўзнагароды, па якой падасца справаздачу.",
"jetkarmabot.leaderboard.specifictext": "Перадавікі ў намінацыі \"{0}\":",
"jetkarmabot.awardtypes.nominative.star": "зорачка", "jetkarmabot.awardtypes.nominative.star": "зорачка",
"jetkarmabot.awardtypes.nominative.pie": "з паліцы піражок", "jetkarmabot.awardtypes.nominative.pie": "з паліцы піражок",
"jetkarmabot.awardtypes.nominative.dream": "мара", "jetkarmabot.awardtypes.nominative.dream": "мара",
"jetkarmabot.awardtypes.nominative.banana": "стыкер-бананчык", "jetkarmabot.awardtypes.nominative.banana": "стыкер-бананчык",
"jetkarmabot.awardtypes.nominative.determination": "DETERMINATION", "jetkarmabot.awardtypes.nominative.determination": "DETERMINATION",
"jetkarmabot.awardtypes.nominative.raisin": "разынка", "jetkarmabot.awardtypes.nominative.raisin": "разынка",
"jetkarmabot.awardtypes.nominative.touche": "touché",
"jetkarmabot.awardtypes.nominative.covid": "каронавірус",
"jetkarmabot.awardtypes.nominative.pony": "поніс",
"jetkarmabot.awardtypes.nominative.bellissimo": "беліссімо",
"jetkarmabot.awardtypes.nominative.based": "сярдзіта",
"jetkarmabot.awardtypes.nominative.redpill": "пілюля",
"jetkarmabot.awardtypes.accusative.star": "зорачку", "jetkarmabot.awardtypes.accusative.star": "зорачку",
"jetkarmabot.awardtypes.accusative.pie": "з паліцы піражкі", "jetkarmabot.awardtypes.accusative.pie": "з паліцы піражкі",
"jetkarmabot.awardtypes.accusative.dream": "мары", "jetkarmabot.awardtypes.accusative.dream": "мары",
"jetkarmabot.awardtypes.accusative.banana": "стыкера-бананчыка", "jetkarmabot.awardtypes.accusative.banana": "стыкера-бананчыка",
"jetkarmabot.awardtypes.accusative.determination": "DETERMINATION", "jetkarmabot.awardtypes.accusative.determination": "DETERMINATION",
"jetkarmabot.awardtypes.accusative.raisin": "разыначкі", "jetkarmabot.awardtypes.accusative.raisin": "разыначкі"
"jetkarmabot.awardtypes.accusative.touche": "touché",
"jetkarmabot.awardtypes.accusative.covid": "каронавірус",
"jetkarmabot.awardtypes.accusative.pony": "поніса",
"jetkarmabot.awardtypes.accusative.bellissimo": "беліссімо",
"jetkarmabot.awardtypes.accusative.based": "сярдзіта",
"jetkarmabot.awardtypes.accusative.redpill": "пілюлю"
} }
} }

View File

@ -5,23 +5,21 @@
"англійская" "англійская"
], ],
"strings": { "strings": {
"jetkarmabot.ratelimit": "Slow down there, turbo!", "jetkarmabot.award.errawardnoreply": "Please use this command in reply to another user.",
"jetkarmabot.award.errawardnoreply": "Who are you talking about?",
"jetkarmabot.award.errbadusername": "I don't know who that is.",
"jetkarmabot.award.errdup": "Please stop batching awards.",
"jetkarmabot.award.errawardself": "Please stop playing with yourself.", "jetkarmabot.award.errawardself": "Please stop playing with yourself.",
"jetkarmabot.award.errawardbot": "I am a bot, and have no use for your foolish fake internet points.", "jetkarmabot.award.errawardbot": "I am a bot, and have no use for your foolish fake internet points.",
"jetkarmabot.award.errrevokebot": "ಠ_ಠ", "jetkarmabot.award.errrevokebot": "ಠ_ಠ",
"jetkarmabot.award.awardmessage": "Awarded a {0} to {1}!", "jetkarmabot.award.awardmessage": "Awarded a {0} to {1}!",
"jetkarmabot.award.revokemessage": "Revoked a {0} from {1}!", "jetkarmabot.award.revokemessage": "Revoked a {0} from {1}!",
"jetkarmabot.award.statustext": "{0} is at {1}{2} now.", "jetkarmabot.award.statustext": "{0} is at {1}{2} now.",
"jetkarmabot.award.ratelimit": "Slow down there, turbo!",
"jetkarmabot.award.help": "Awards/revokes an award to a user.", "jetkarmabot.award.help": "Awards/revokes an award to a user.",
"jetkarmabot.award.awardtypehelp": "The award to grant to/strip of the specified user", "jetkarmabot.award.awardtypehelp": "The award to grant to/strip of the specified user",
"jetkarmabot.award.tohelp": "The user to award",
"jetkarmabot.status.listalltext": "Your badges report:", "jetkarmabot.status.listalltext": "Your badges report:",
"jetkarmabot.status.listspecifictext": "You are at {0}{1} now.", "jetkarmabot.status.listspecifictext": "You are at {0}{1} now.",
"jetkarmabot.status.havenothing": "You don't have anything yet.", "jetkarmabot.status.havenothing": "You don't have anything yet.",
"jetkarmabot.status.help": "Shows the amount of awards that you have", "jetkarmabot.status.help": "Shows the amount of awards that you have",
"jetkarmabot.status.awardtypehelp": "The awardtype to show. If empty shows everything.",
"jetkarmabot.changelocale.justchanged": "Roger that.", "jetkarmabot.changelocale.justchanged": "Roger that.",
"jetkarmabot.changelocale.getlocale": "I'm currently speaking English.", "jetkarmabot.changelocale.getlocale": "I'm currently speaking English.",
"jetkarmabot.changelocale.listalltext": "I know:", "jetkarmabot.changelocale.listalltext": "I know:",
@ -34,32 +32,17 @@
"jetkarmabot.help.commandhelp": "The command to return help text for. If empty shows all commands.", "jetkarmabot.help.commandhelp": "The command to return help text for. If empty shows all commands.",
"jetkarmabot.currencies.help": "Shows all award types", "jetkarmabot.currencies.help": "Shows all award types",
"jetkarmabot.currencies.listtext": "Award types:", "jetkarmabot.currencies.listtext": "Award types:",
"jetkarmabot.leaderboard.help": "Shows the people with the most of a specific award.",
"jetkarmabot.leaderboard.awardtypehelp": "The awardtype to show a leaderboard for.",
"jetkarmabot.leaderboard.specifictext": "Leaderboard for {0}:",
"jetkarmabot.awardtypes.nominative.star": "star", "jetkarmabot.awardtypes.nominative.star": "star",
"jetkarmabot.awardtypes.nominative.pie": "pie from the shelf", "jetkarmabot.awardtypes.nominative.pie": "pie from the shelf",
"jetkarmabot.awardtypes.nominative.dream": "dream", "jetkarmabot.awardtypes.nominative.dream": "dream",
"jetkarmabot.awardtypes.nominative.banana": "banana sticker", "jetkarmabot.awardtypes.nominative.banana": "banana sticker",
"jetkarmabot.awardtypes.nominative.determination": "DETERMINATION", "jetkarmabot.awardtypes.nominative.determination": "DETERMINATION",
"jetkarmabot.awardtypes.nominative.raisin": "raisin", "jetkarmabot.awardtypes.nominative.raisin": "raisin",
"jetkarmabot.awardtypes.nominative.touche": "touché",
"jetkarmabot.awardtypes.nominative.covid": "coronavirus",
"jetkarmabot.awardtypes.nominative.pony": "pony",
"jetkarmabot.awardtypes.nominative.bellissimo": "bellissimo",
"jetkarmabot.awardtypes.nominative.based": "based",
"jetkarmabot.awardtypes.nominative.redpill": "redpill",
"jetkarmabot.awardtypes.accusative.star": "star", "jetkarmabot.awardtypes.accusative.star": "star",
"jetkarmabot.awardtypes.accusative.pie": "pie from the shelf", "jetkarmabot.awardtypes.accusative.pie": "pie from the shelf",
"jetkarmabot.awardtypes.accusative.dream": "dream", "jetkarmabot.awardtypes.accusative.dream": "dream",
"jetkarmabot.awardtypes.accusative.banana": "banana sticker", "jetkarmabot.awardtypes.accusative.banana": "banana sticker",
"jetkarmabot.awardtypes.accusative.determination": "DETERMINATION", "jetkarmabot.awardtypes.accusative.determination": "DETERMINATION",
"jetkarmabot.awardtypes.accusative.raisin": "raisin", "jetkarmabot.awardtypes.accusative.raisin": "raisin"
"jetkarmabot.awardtypes.accusative.touche": "touché",
"jetkarmabot.awardtypes.accusative.covid": "coronavirus",
"jetkarmabot.awardtypes.accusative.pony": "pony",
"jetkarmabot.awardtypes.accusative.bellissimo": "bellissimo",
"jetkarmabot.awardtypes.accusative.based": "based",
"jetkarmabot.awardtypes.accusative.redpill": "redpill"
} }
} }

View File

@ -5,23 +5,21 @@
"руская" "руская"
], ],
"strings": { "strings": {
"jetkarmabot.ratelimit": "Помедленней, чувак!", "jetkarmabot.award.errawardnoreply": "Пожалуйста используйте эту команду в ответе другому пользователю.",
"jetkarmabot.award.errawardnoreply": "О ком ты говоришь?",
"jetkarmabot.award.errbadusername": "Кто это?",
"jetkarmabot.award.errdup": "Пожалуйста, не батчайте награды.",
"jetkarmabot.award.errawardself": "Хватит с собой играться.", "jetkarmabot.award.errawardself": "Хватит с собой играться.",
"jetkarmabot.award.errawardbot": "Я бот, и меня ваши жалкие пузомерки не интересуют.", "jetkarmabot.award.errawardbot": "Я бот, и меня ваши жалкие пузомерки не интересуют.",
"jetkarmabot.award.errrevokebot": "ಠ_ಠ", "jetkarmabot.award.errrevokebot": "ಠ_ಠ",
"jetkarmabot.award.awardmessage": "Вручил \"{0}\" {1}!", "jetkarmabot.award.awardmessage": "Вручил \"{0}\" {1}!",
"jetkarmabot.award.revokemessage": "Отнял \"{0}\" у {1}!", "jetkarmabot.award.revokemessage": "Отнял \"{0}\" у {1}!",
"jetkarmabot.award.statustext": "У {0} теперь {1}{2}.", "jetkarmabot.award.statustext": "У {0} теперь {1}{2}.",
"jetkarmabot.award.ratelimit": "Помедленней, чувак!",
"jetkarmabot.award.help": "Вручает очко пользователю (или отнимает)", "jetkarmabot.award.help": "Вручает очко пользователю (или отнимает)",
"jetkarmabot.award.awardtypehelp": "Тип очка", "jetkarmabot.award.awardtypehelp": "Тип очка",
"jetkarmabot.award.tohelp": "Пользователь, которого наградить",
"jetkarmabot.status.listalltext": "У вас :", "jetkarmabot.status.listalltext": "У вас :",
"jetkarmabot.status.listspecifictext": "У вас сейчас {0}{1}.", "jetkarmabot.status.listspecifictext": "У вас сейчас {0}{1}.",
"jetkarmabot.status.havenothing": "У вас пока ничего нет.", "jetkarmabot.status.havenothing": "У вас пока ничего нет.",
"jetkarmabot.status.help": "Показывает отчет по наградам.", "jetkarmabot.status.help": "Показывает отчет по наградам.",
"jetkarmabot.status.awardtypehelp": "Тип награды, по которой покажется отчет. Если пусто, то покажется сводный отчет.",
"jetkarmabot.changelocale.justchanged": "Так точно.", "jetkarmabot.changelocale.justchanged": "Так точно.",
"jetkarmabot.changelocale.getlocale": "Я сейчас говорю по-русски.", "jetkarmabot.changelocale.getlocale": "Я сейчас говорю по-русски.",
"jetkarmabot.changelocale.listalltext": "Я знаю:", "jetkarmabot.changelocale.listalltext": "Я знаю:",
@ -34,32 +32,17 @@
"jetkarmabot.help.commandhelp": "Команда, к которой нужно показать инструкцию. Если пусто, то показывается список команд.", "jetkarmabot.help.commandhelp": "Команда, к которой нужно показать инструкцию. Если пусто, то показывается список команд.",
"jetkarmabot.currencies.help": "Показывает все типы наград", "jetkarmabot.currencies.help": "Показывает все типы наград",
"jetkarmabot.currencies.listtext": "Типы наград:", "jetkarmabot.currencies.listtext": "Типы наград:",
"jetkarmabot.leaderboard.help": "Таблица лидеров по количеству наград",
"jetkarmabot.leaderboard.awardtypehelp": "Тип награды, по которой покажется отчет.",
"jetkarmabot.leaderboard.specifictext": "Передовики в номинации \"{0}\":",
"jetkarmabot.awardtypes.nominative.star": "звездочка", "jetkarmabot.awardtypes.nominative.star": "звездочка",
"jetkarmabot.awardtypes.nominative.pie": "с полки пирожок", "jetkarmabot.awardtypes.nominative.pie": "с полки пирожок",
"jetkarmabot.awardtypes.nominative.dream": "мечта", "jetkarmabot.awardtypes.nominative.dream": "мечта",
"jetkarmabot.awardtypes.nominative.banana": "стикер-бананчик", "jetkarmabot.awardtypes.nominative.banana": "стикер-бананчик",
"jetkarmabot.awardtypes.nominative.determination": "РЕШИТЕЛЬНОСТЬ", "jetkarmabot.awardtypes.nominative.determination": "РЕШИТЕЛЬНОСТЬ",
"jetkarmabot.awardtypes.nominative.raisin": "изюм", "jetkarmabot.awardtypes.nominative.raisin": "изюм",
"jetkarmabot.awardtypes.nominative.touche": "touché",
"jetkarmabot.awardtypes.nominative.covid": "коронавирус",
"jetkarmabot.awardtypes.nominative.pony": "понис",
"jetkarmabot.awardtypes.nominative.bellissimo": "белиссимо",
"jetkarmabot.awardtypes.nominative.based": "сердито",
"jetkarmabot.awardtypes.nominative.redpill": "пилюля",
"jetkarmabot.awardtypes.accusative.star": "звездочку", "jetkarmabot.awardtypes.accusative.star": "звездочку",
"jetkarmabot.awardtypes.accusative.pie": "с полки пирожок", "jetkarmabot.awardtypes.accusative.pie": "с полки пирожок",
"jetkarmabot.awardtypes.accusative.dream": "мечту", "jetkarmabot.awardtypes.accusative.dream": "мечту",
"jetkarmabot.awardtypes.accusative.banana": "стикер-бананчик", "jetkarmabot.awardtypes.accusative.banana": "стикер-бананчик",
"jetkarmabot.awardtypes.accusative.determination": "РЕШИТЕЛЬНОСТЬ", "jetkarmabot.awardtypes.accusative.determination": "РЕШИТЕЛЬНОСТЬ",
"jetkarmabot.awardtypes.accusative.raisin": "изюм", "jetkarmabot.awardtypes.accusative.raisin": "изюм"
"jetkarmabot.awardtypes.accusative.touche": "touché",
"jetkarmabot.awardtypes.accusative.covid": "коронавирус",
"jetkarmabot.awardtypes.accusative.pony": "пониса",
"jetkarmabot.awardtypes.accusative.bellissimo": "белиссимо",
"jetkarmabot.awardtypes.accusative.based": "сердито",
"jetkarmabot.awardtypes.accusative.redpill": "пилюлю"
} }
} }

View File

@ -1,41 +0,0 @@
-- Example JetKarmaBot database
-- (taken from mysqldump)
DROP TABLE IF EXISTS `awardtype`;
CREATE TABLE `awardtype` (
`awardtypeid` tinyint(3) NOT NULL PRIMARY KEY,
`commandname` varchar(35) NOT NULL UNIQUE,
`name` varchar(32) NOT NULL,
`symbol` varchar(16) NOT NULL,
`description` text NOT NULL
);
LOCK TABLES `awardtype` WRITE;
INSERT INTO `awardtype` VALUES (1,'example','Example','Examples','An example');
UNLOCK TABLES;
DROP TABLE IF EXISTS `chat`;
CREATE TABLE `chat` (
`chatid` bigint(20) NOT NULL PRIMARY KEY,
`locale` varchar(10) NOT NULL DEFAULT 'ru-RU',
`isadministrator` tinyint(1) NOT NULL DEFAULT 0
);
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`userid` bigint(20) NOT NULL,
`username` varchar(45) DEFAULT NULL,
`cooldowndate` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`userid`)
);
DROP TABLE IF EXISTS `award`;
CREATE TABLE `award` (
`awardid` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`chatid` bigint(20) NOT NULL REFERENCES `chat` (`chatid`),
`fromid` bigint(20) NOT NULL REFERENCES `user` (`userid`),
`toid` bigint(20) NOT NULL REFERENCES `user` (`userid`),
`awardtypeid` tinyint(3) NOT NULL REFERENCES `awardtype` (`awardtypeid`),
`amount` tinyint(3) NOT NULL DEFAULT 1,
`date` datetime NOT NULL DEFAULT current_timestamp()
);

@ -1 +1 @@
Subproject commit 115f59eecb2a4a74c63770866539502cde28d881 Subproject commit 40e8f8871f42f1f32c29480505cf5ac6ba61c393