mirror of
https://github.com/Jetsparrow/karmabot.git
synced 2026-01-21 00:56:09 +03:00
Basic leaky bucket timeout
This commit is contained in:
parent
228c078947
commit
df982d53aa
@ -23,33 +23,23 @@ namespace JetKarmaBot
|
||||
Me = await Client.GetMeAsync();
|
||||
}
|
||||
|
||||
public Task<bool> Execute(object sender, MessageEventArgs args)
|
||||
public Task<bool> Execute(CommandString cmd, MessageEventArgs args)
|
||||
{
|
||||
log.Debug("Message received");
|
||||
var text = args.Message.Text;
|
||||
if (CommandString.TryParse(text, out var cmd))
|
||||
{
|
||||
if (cmd.UserName != null && cmd.UserName != Me.Username)
|
||||
{
|
||||
// directed not at us!
|
||||
log.Debug("Message not directed at us");
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
if (commands.ContainsKey(cmd.Command))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ namespace JetKarmaBot.Commands
|
||||
class AwardCommand : IChatCommand
|
||||
{
|
||||
public IReadOnlyCollection<string> Names => new[] { "award", "revoke" };
|
||||
[Inject]
|
||||
private Logger log;
|
||||
[Inject] private Logger log;
|
||||
[Inject] private TimeoutManager Timeout;
|
||||
|
||||
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args)
|
||||
{
|
||||
@ -24,6 +24,7 @@ namespace JetKarmaBot.Commands
|
||||
{
|
||||
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
|
||||
|
||||
var awarder = args.Message.From;
|
||||
string awardTypeText = null;
|
||||
int recipientId = default(int);
|
||||
foreach (string arg in cmd.Parameters)
|
||||
@ -33,12 +34,14 @@ namespace JetKarmaBot.Commands
|
||||
if (recipientId != default(int))
|
||||
{
|
||||
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
recipientId = await db.Users.Where(x => x.Username == arg).Select(x => x.UserId).FirstOrDefaultAsync();
|
||||
if (recipientId == default(int))
|
||||
{
|
||||
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errbadusername"]);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -49,6 +52,7 @@ namespace JetKarmaBot.Commands
|
||||
else
|
||||
{
|
||||
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errdup"]);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -62,10 +66,10 @@ namespace JetKarmaBot.Commands
|
||||
if (recipientId == default(int))
|
||||
{
|
||||
await Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
|
||||
var awarder = args.Message.From;
|
||||
|
||||
bool awarding = cmd.Command == "award";
|
||||
|
||||
@ -75,6 +79,7 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.award.errawardself"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -86,6 +91,7 @@ namespace JetKarmaBot.Commands
|
||||
? currentLocale["jetkarmabot.award.errawardbot"]
|
||||
: currentLocale["jetkarmabot.award.errrevokebot"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("AwardFailure", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -93,15 +99,6 @@ namespace JetKarmaBot.Commands
|
||||
global::JetKarmaBot.Models.AwardType awardType = awardTypeText != null
|
||||
? await db.AwardTypes.FirstAsync(at => at.CommandName == awardTypeText)
|
||||
: await db.AwardTypes.FindAsync((sbyte)1);
|
||||
DateTime cutoff = DateTime.Now - TimeSpan.FromMinutes(5);
|
||||
if (await db.Awards.Where(x => x.Date > cutoff && x.FromId == awarder.Id).CountAsync() >= 10)
|
||||
{
|
||||
await Client.SendTextMessageAsync(
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.award.ratelimit"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
return true;
|
||||
}
|
||||
await db.Awards.AddAsync(new Models.Award()
|
||||
{
|
||||
AwardTypeId = awardType.AwardTypeId,
|
||||
@ -130,6 +127,7 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
response,
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("AwardSuccess", awarder.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ namespace JetKarmaBot.Commands
|
||||
class LocaleCommand : IChatCommand
|
||||
{
|
||||
public IReadOnlyCollection<string> Names => new[] { "changelocale", "locale" };
|
||||
[Inject]
|
||||
private Logger log;
|
||||
[Inject] private Logger log;
|
||||
[Inject] private TimeoutManager Timeout;
|
||||
|
||||
public async Task<bool> Execute(CommandString cmd, MessageEventArgs args)
|
||||
{
|
||||
@ -26,6 +26,7 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.changelocale.getlocale"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
else if (cmd.Parameters[0] == "list")
|
||||
@ -35,6 +36,7 @@ namespace JetKarmaBot.Commands
|
||||
currentLocale["jetkarmabot.changelocale.listalltext"] + "\n"
|
||||
+ string.Join("\n", Locale.Select(a => a.Key)),
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
else if (cmd.Parameters[0] == "all")
|
||||
@ -43,6 +45,7 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.changelocale.errorall"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
string localeId;
|
||||
@ -56,9 +59,10 @@ namespace JetKarmaBot.Commands
|
||||
catch (LocalizationException e)
|
||||
{
|
||||
await Client.SendTextMessageAsync(
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)),
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.changelocale.toomany"] + "\n" + string.Join("\n", (e.Data["LocaleNames"] as Locale[]).Select(x => x.Name)),
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("LocaleFailure", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale = localeId;
|
||||
@ -72,6 +76,7 @@ namespace JetKarmaBot.Commands
|
||||
(currentLocale.HasNote ? currentLocale["jetkarmabot.changelocale.beforenote"] + currentLocale.Note + "\n" : "")
|
||||
+ currentLocale["jetkarmabot.changelocale.justchanged"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("LocaleSuccess", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ namespace JetKarmaBot.Commands
|
||||
[Inject] KarmaContextFactory Db;
|
||||
[Inject] TelegramBotClient Client { get; set; }
|
||||
[Inject] Localization Locale { get; set; }
|
||||
[Inject] TimeoutManager Timeout { get; set; }
|
||||
public IReadOnlyCollection<string> Names => new[] { "currencies", "awardtypes" };
|
||||
|
||||
public string Description => "Shows all award types";
|
||||
@ -35,6 +36,7 @@ namespace JetKarmaBot.Commands
|
||||
resp,
|
||||
replyToMessageId: args.Message.MessageId,
|
||||
parseMode: ParseMode.Html);
|
||||
await Timeout.ApplyCost("Currencies", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ namespace JetKarmaBot.Commands
|
||||
[Inject] KarmaContextFactory Db;
|
||||
[Inject] TelegramBotClient Client { get; set; }
|
||||
[Inject] Localization Locale { get; set; }
|
||||
[Inject] TimeoutManager Timeout { get; set; }
|
||||
[Inject] ChatCommandRouter Router;
|
||||
public IReadOnlyCollection<string> Names => new[] { "help" };
|
||||
|
||||
@ -34,6 +35,7 @@ namespace JetKarmaBot.Commands
|
||||
using (var db = Db.GetContext())
|
||||
{
|
||||
var currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
|
||||
await Timeout.ApplyCost("Help", args.Message.From.Id, db);
|
||||
if (cmd.Parameters.Length < 1)
|
||||
{
|
||||
await Client.SendTextMessageAsync(
|
||||
|
||||
@ -51,12 +51,14 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
response,
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("Leaderboard", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Inject] KarmaContextFactory Db { get; set; }
|
||||
[Inject] TelegramBotClient Client { get; set; }
|
||||
[Inject] TimeoutManager Timeout { get; set; }
|
||||
[Inject] Localization Locale { get; set; }
|
||||
|
||||
public string Description => "Shows the people with the most of a specific award.";
|
||||
|
||||
@ -71,12 +71,14 @@ namespace JetKarmaBot.Commands
|
||||
args.Message.Chat.Id,
|
||||
response,
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.ApplyCost("Status", args.Message.From.Id, db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Inject] KarmaContextFactory Db { get; set; }
|
||||
[Inject] TelegramBotClient Client { get; set; }
|
||||
[Inject] TimeoutManager Timeout { get; set; }
|
||||
[Inject] Localization Locale { get; set; }
|
||||
|
||||
public string Description => "Shows the amount of awards that you have";
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using JsonNet.PrivateSettersContractResolvers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace JetKarmaBot
|
||||
{
|
||||
@ -21,7 +22,19 @@ namespace JetKarmaBot
|
||||
}
|
||||
|
||||
public ProxySettings Proxy { get; private set; }
|
||||
public bool SqlDebug { get; private set; }
|
||||
public class TimeoutConfig
|
||||
{
|
||||
public int DebtLimitSeconds { get; private set; } = 60 * 60 * 2;
|
||||
public Dictionary<string, int> CommandCostsSeconds { get; private set; } = new Dictionary<string, int>()
|
||||
{
|
||||
{"AwardSuccessful", 60*15},
|
||||
{"AwardFailed", 60*5},
|
||||
{"Default", 60*5}
|
||||
};
|
||||
public int SaveIntervalSeconds { get; private set; } = 60 * 5;
|
||||
}
|
||||
public TimeoutConfig Timeout { get; private set; } = new TimeoutConfig();
|
||||
public bool SqlDebug { get; private set; } = false;
|
||||
}
|
||||
|
||||
public abstract class ConfigBase
|
||||
|
||||
@ -5,6 +5,7 @@ using Perfusion;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Telegram.Bot;
|
||||
@ -19,9 +20,13 @@ namespace JetKarmaBot
|
||||
[Inject] Config Config { get; set; }
|
||||
[Inject] IContainer Container { get; set; }
|
||||
[Inject] KarmaContextFactory Db { get; set; }
|
||||
[Inject] TimeoutManager Timeout { get; set; }
|
||||
[Inject] Localization Locale { get; set; }
|
||||
|
||||
TelegramBotClient Client { get; set; }
|
||||
ChatCommandRouter Commands;
|
||||
Task timeoutWaitTask;
|
||||
CancellationTokenSource timeoutWaitTaskToken;
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
@ -35,6 +40,9 @@ namespace JetKarmaBot
|
||||
Client = new TelegramBotClient(Config.ApiKey, httpProxy);
|
||||
Container.AddInstance(Client);
|
||||
|
||||
timeoutWaitTaskToken = new CancellationTokenSource();
|
||||
timeoutWaitTask = Timeout.SaveLoop(timeoutWaitTaskToken.Token);
|
||||
|
||||
await InitCommands(Container);
|
||||
|
||||
Client.OnMessage += BotOnMessageReceived;
|
||||
@ -43,29 +51,59 @@ namespace JetKarmaBot
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
Client.StopReceiving();
|
||||
timeoutWaitTaskToken.Cancel();
|
||||
try
|
||||
{
|
||||
await timeoutWaitTask;
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
await Timeout.Save();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
#region service
|
||||
|
||||
void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
|
||||
void BotOnMessageReceived(object sender, MessageEventArgs args)
|
||||
{
|
||||
var message = messageEventArgs.Message;
|
||||
var message = args.Message;
|
||||
if (message == null || message.Type != MessageType.Text)
|
||||
return;
|
||||
if (!CommandString.TryParse(args.Message.Text, out var cmd))
|
||||
return;
|
||||
if (cmd.UserName != null && cmd.UserName != Commands.Me.Username)
|
||||
return;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
using (KarmaContext db = Db.GetContext())
|
||||
{
|
||||
await AddUserToDatabase(db, messageEventArgs.Message.From);
|
||||
if (messageEventArgs.Message.ReplyToMessage != null)
|
||||
await AddUserToDatabase(db, messageEventArgs.Message.ReplyToMessage.From);
|
||||
if (!db.Chats.Any(x => x.ChatId == messageEventArgs.Message.Chat.Id))
|
||||
db.Chats.Add(new Models.Chat { ChatId = messageEventArgs.Message.Chat.Id });
|
||||
await AddUserToDatabase(db, args.Message.From);
|
||||
var checkResult = await Timeout.Check(args.Message.From.Id, db);
|
||||
if (checkResult == TimeoutManager.CheckResult.Limited)
|
||||
{
|
||||
Locale currentLocale = Locale[(await db.Chats.FindAsync(args.Message.Chat.Id)).Locale];
|
||||
await Client.SendTextMessageAsync(
|
||||
args.Message.Chat.Id,
|
||||
currentLocale["jetkarmabot.ratelimit"],
|
||||
replyToMessageId: args.Message.MessageId);
|
||||
await Timeout.SetMessaged(args.Message.From.Id, db);
|
||||
return;
|
||||
}
|
||||
else if (checkResult != TimeoutManager.CheckResult.NonLimited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (args.Message.ReplyToMessage != null)
|
||||
await AddUserToDatabase(db, args.Message.ReplyToMessage.From);
|
||||
if (!db.Chats.Any(x => x.ChatId == args.Message.Chat.Id))
|
||||
db.Chats.Add(new Models.Chat
|
||||
{
|
||||
ChatId = args.Message.Chat.Id
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await Commands.Execute(sender, messageEventArgs);
|
||||
await Commands.Execute(cmd, args);
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,7 +142,8 @@ namespace JetKarmaBot
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Client.StopReceiving();
|
||||
timeoutWaitTaskToken.Dispose();
|
||||
timeoutWaitTask.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -172,6 +172,10 @@ namespace JetKarmaBot.Models
|
||||
entity.Property(e => e.Username)
|
||||
.HasColumnName("username")
|
||||
.HasColumnType("varchar(45)");
|
||||
|
||||
entity.Property(e => e.CooldownDate)
|
||||
.HasColumnName("cooldowndate")
|
||||
.HasColumnType("datetime");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ namespace JetKarmaBot.Models
|
||||
|
||||
public int UserId { get; set; }
|
||||
public string Username { get; set; }
|
||||
public DateTime CooldownDate { get; set; }
|
||||
[InverseProperty("From")]
|
||||
public virtual ICollection<Award> AwardsFrom { get; set; }
|
||||
[InverseProperty("To")]
|
||||
|
||||
87
JetKarmaBot/Services/TimeoutManager.cs
Normal file
87
JetKarmaBot/Services/TimeoutManager.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Perfusion;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using JetKarmaBot.Models;
|
||||
using System.Threading;
|
||||
|
||||
namespace JetKarmaBot.Services
|
||||
{
|
||||
[Singleton]
|
||||
public class TimeoutManager
|
||||
{
|
||||
public struct TimeoutStats
|
||||
{
|
||||
public DateTime CooldownDate;
|
||||
public bool TimeoutMessaged;
|
||||
}
|
||||
[Inject] private KarmaContextFactory Db;
|
||||
[Inject] private Config cfg;
|
||||
public Dictionary<int, TimeoutStats> TimeoutCache = new Dictionary<int, TimeoutStats>();
|
||||
public async Task ApplyCost(string name, int uid, KarmaContext db)
|
||||
{
|
||||
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue(name, out var costSeconds))
|
||||
if (!cfg.Timeout.CommandCostsSeconds.TryGetValue("Default", out costSeconds))
|
||||
{
|
||||
throw new LocalizationException("Default key not present");
|
||||
}
|
||||
await PopulateStats(uid, db);
|
||||
DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds);
|
||||
if (TimeoutCache[uid].CooldownDate >= debtLimit)
|
||||
//Programming error
|
||||
throw new NotImplementedException();
|
||||
TimeoutCache[uid] = new TimeoutStats()
|
||||
{
|
||||
CooldownDate = (TimeoutCache[uid].CooldownDate <= DateTime.Now ? DateTime.Now : TimeoutCache[uid].CooldownDate).AddSeconds(costSeconds),
|
||||
TimeoutMessaged = false
|
||||
};
|
||||
TimeoutCache[uid] = TimeoutCache[uid];
|
||||
}
|
||||
public enum CheckResult
|
||||
{
|
||||
NonLimited, Limited, LimitedSent
|
||||
}
|
||||
public async Task<CheckResult> Check(int uid, KarmaContext db)
|
||||
{
|
||||
await PopulateStats(uid, db);
|
||||
DateTime debtLimit = DateTime.Now.AddSeconds(cfg.Timeout.DebtLimitSeconds);
|
||||
return TimeoutCache[uid].CooldownDate < debtLimit
|
||||
? CheckResult.NonLimited
|
||||
: (TimeoutCache[uid].TimeoutMessaged ? CheckResult.LimitedSent : CheckResult.Limited);
|
||||
}
|
||||
public async Task SetMessaged(int uid, KarmaContext db)
|
||||
{
|
||||
await PopulateStats(uid, db);
|
||||
TimeoutCache[uid] = new TimeoutStats() { TimeoutMessaged = true, CooldownDate = TimeoutCache[uid].CooldownDate };
|
||||
}
|
||||
private async Task PopulateStats(int uid, KarmaContext db)
|
||||
{
|
||||
if (!TimeoutCache.ContainsKey(uid))
|
||||
{
|
||||
TimeoutCache[uid] = new TimeoutStats()
|
||||
{
|
||||
CooldownDate = (await db.Users.FindAsync(uid)).CooldownDate
|
||||
};
|
||||
}
|
||||
}
|
||||
public async Task Save(CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
public async Task SaveLoop(CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(cfg.Timeout.SaveIntervalSeconds * 1000, ct);
|
||||
await Save(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
],
|
||||
"note": "This is a joke. And made with google translate.",
|
||||
"strings": {
|
||||
"jetkarmabot.ratelimit": "Павольны, чувак!",
|
||||
"jetkarmabot.award.errawardnoreply": "Пра каго ты кажаш?",
|
||||
"jetkarmabot.award.errbadusername": "Хто гэта?",
|
||||
"jetkarmabot.award.errdup": "Калі ласка, спыніце батчіць ўзнагароды.",
|
||||
@ -15,7 +16,6 @@
|
||||
"jetkarmabot.award.awardmessage": "Ўручыў \"{0}\" {1}!",
|
||||
"jetkarmabot.award.revokemessage": "Адабраў \"{0}\" у {1}!",
|
||||
"jetkarmabot.award.statustext": "У {0} цяпер {1}{2}.",
|
||||
"jetkarmabot.award.ratelimit": "Павольны, чувак!",
|
||||
"jetkarmabot.award.help": "Уручае ачко карыстачу (або адымае)",
|
||||
"jetkarmabot.award.awardtypehelp": "Тып ачкі",
|
||||
"jetkarmabot.award.tohelp": "Карыстальнік, якога ўзнагародзіць",
|
||||
@ -52,4 +52,4 @@
|
||||
"jetkarmabot.awardtypes.accusative.determination": "DETERMINATION",
|
||||
"jetkarmabot.awardtypes.accusative.raisin": "разыначкі"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
"англійская"
|
||||
],
|
||||
"strings": {
|
||||
"jetkarmabot.ratelimit": "Slow down there, turbo!",
|
||||
"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.",
|
||||
@ -14,7 +15,6 @@
|
||||
"jetkarmabot.award.awardmessage": "Awarded a {0} to {1}!",
|
||||
"jetkarmabot.award.revokemessage": "Revoked a {0} from {1}!",
|
||||
"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.awardtypehelp": "The award to grant to/strip of the specified user",
|
||||
"jetkarmabot.award.tohelp": "The user to award",
|
||||
@ -51,4 +51,4 @@
|
||||
"jetkarmabot.awardtypes.accusative.determination": "DETERMINATION",
|
||||
"jetkarmabot.awardtypes.accusative.raisin": "raisin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
"руская"
|
||||
],
|
||||
"strings": {
|
||||
"jetkarmabot.ratelimit": "Помедленней, чувак!",
|
||||
"jetkarmabot.award.errawardnoreply": "О ком ты говоришь?",
|
||||
"jetkarmabot.award.errbadusername": "Кто это?",
|
||||
"jetkarmabot.award.errdup": "Пожалуйста, не батчайте награды.",
|
||||
@ -14,7 +15,6 @@
|
||||
"jetkarmabot.award.awardmessage": "Вручил \"{0}\" {1}!",
|
||||
"jetkarmabot.award.revokemessage": "Отнял \"{0}\" у {1}!",
|
||||
"jetkarmabot.award.statustext": "У {0} теперь {1}{2}.",
|
||||
"jetkarmabot.award.ratelimit": "Помедленней, чувак!",
|
||||
"jetkarmabot.award.help": "Вручает очко пользователю (или отнимает)",
|
||||
"jetkarmabot.award.awardtypehelp": "Тип очка",
|
||||
"jetkarmabot.award.tohelp": "Пользователь, которого наградить",
|
||||
@ -51,4 +51,4 @@
|
||||
"jetkarmabot.awardtypes.accusative.determination": "РЕШИТЕЛЬНОСТЬ",
|
||||
"jetkarmabot.awardtypes.accusative.raisin": "изюм"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user