diff --git a/JetKarmaBot/Commands/AwardCommand.cs b/JetKarmaBot/Commands/AwardCommand.cs index 0d45773..40c21e4 100644 --- a/JetKarmaBot/Commands/AwardCommand.cs +++ b/JetKarmaBot/Commands/AwardCommand.cs @@ -5,6 +5,7 @@ using Telegram.Bot; using Telegram.Bot.Args; using Telegram.Bot.Types; using Perfusion; +using JetKarmaBot.Services; namespace JetKarmaBot.Commands { @@ -14,60 +15,78 @@ namespace JetKarmaBot.Commands public bool Execute(CommandString cmd, MessageEventArgs args) { - var currentLocale = Locale[Db.Chats[args.Message.Chat.Id].Locale]; - if (args.Message.ReplyToMessage == null) + using (var db = Db.GetContext()) { - Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]); - return true; - } + var currentLocale = Locale[db.Chat.Find(args.Message.Chat.Id).Locale]; + if (args.Message.ReplyToMessage == null) + { + Client.SendTextMessageAsync(args.Message.Chat.Id, currentLocale["jetkarmabot.award.errawardnoreply"]); + return true; + } - var awarder = args.Message.From; - var recipient = args.Message.ReplyToMessage.From; + var awarder = args.Message.From; + var recipient = args.Message.ReplyToMessage.From; - bool awarding = cmd.Command == "award"; + bool awarding = cmd.Command == "award"; + + if (awarder.Id == recipient.Id) + { + Client.SendTextMessageAsync( + args.Message.Chat.Id, + currentLocale["jetkarmabot.award.errawardself"], + replyToMessageId: args.Message.MessageId); + return true; + } + + if (Me.Id == recipient.Id) + { + Client.SendTextMessageAsync( + args.Message.Chat.Id, + awarding + ? currentLocale["jetkarmabot.award.errawardbot"] + : currentLocale["jetkarmabot.award.errrevokebot"], + replyToMessageId: args.Message.MessageId); + return true; + } + + var text = args.Message.Text; + var awardTypeText = cmd.Parameters.FirstOrDefault(); + var awardType = awardTypeText != null + ? db.Awardtype.First(at => at.Commandname == awardTypeText) + : db.Awardtype.Find(1); + + db.Award.Add(new Models.Award() + { + Awardtypeid = awardType.Awardtypeid, + Amount = (sbyte)(awarding ? 1 : -1), + Fromid = awarder.Id, + Toid = recipient.Id, + Chatid = args.Message.Chat.Id + }); + + db.SaveChanges(); + + string message = awarding + ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], awardType.Name, "@" + recipient.Username) + : string.Format(currentLocale["jetkarmabot.award.revokemessage"], awardType.Name, "@" + recipient.Username); + + var currentCount = db.Award + .Where(aw => aw.Toid == recipient.Id && aw.Awardtypeid == awardType.Awardtypeid) + .Sum(aw => aw.Amount); + + var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], "@" + recipient.Username, currentCount, awardType.Symbol); - if (awarder.Id == recipient.Id) - { Client.SendTextMessageAsync( args.Message.Chat.Id, - currentLocale["jetkarmabot.award.errawardself"], + response, replyToMessageId: args.Message.MessageId); return true; } - - if (Me.Id == recipient.Id) - { - Client.SendTextMessageAsync( - args.Message.Chat.Id, - awarding - ? currentLocale["jetkarmabot.award.errawardbot"] - : currentLocale["jetkarmabot.award.errrevokebot"], - replyToMessageId: args.Message.MessageId); - return true; - } - - var text = args.Message.Text; - var awardTypeId = Db.GetAwardTypeId(cmd.Parameters.FirstOrDefault()); - var awardType = Db.AwardTypes[awardTypeId]; - - Db.AddAward(awardTypeId, awarder.Id, recipient.Id, args.Message.Chat.Id, awarding ? 1 : -1); - - string message = awarding - ? string.Format(currentLocale["jetkarmabot.award.awardmessage"], awardType.Name, "@" + recipient.Username) - : string.Format(currentLocale["jetkarmabot.award.revokemessage"], awardType.Name, "@" + recipient.Username); - - var response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], "@" + recipient.Username, Db.CountUserAwards(recipient.Id, awardTypeId), awardType.Symbol); - - Client.SendTextMessageAsync( - args.Message.Chat.Id, - response, - replyToMessageId: args.Message.MessageId); - return true; } - [Inject(true)] Db Db { get; set; } - [Inject(true)] TelegramBotClient Client { get; set; } - [Inject(true)] Localization Locale { get; set; } + [Inject] KarmaContextFactory Db { get; set; } + [Inject] TelegramBotClient Client { get; set; } + [Inject] Localization Locale { get; set; } User Me { get; } public AwardCommand(User me) diff --git a/JetKarmaBot/Commands/ChangeLocaleCommand.cs b/JetKarmaBot/Commands/ChangeLocaleCommand.cs index 2b59f5c..6d2de33 100644 --- a/JetKarmaBot/Commands/ChangeLocaleCommand.cs +++ b/JetKarmaBot/Commands/ChangeLocaleCommand.cs @@ -5,6 +5,8 @@ using Telegram.Bot; using Telegram.Bot.Args; using Telegram.Bot.Types; using Perfusion; +using JetKarmaBot.Models; +using JetKarmaBot.Services; namespace JetKarmaBot.Commands { @@ -32,8 +34,8 @@ namespace JetKarmaBot.Commands return true; } - [Inject(true)] Db Db { get; set; } - [Inject(true)] TelegramBotClient Client { get; set; } - [Inject(true)] Localization Locale { get; set; } + [Inject] KarmaContextFactory Db { get; set; } + [Inject] TelegramBotClient Client { get; set; } + [Inject] Localization Locale { get; set; } } } diff --git a/JetKarmaBot/Commands/StartCommand.cs b/JetKarmaBot/Commands/StartCommand.cs index d52b3ac..ac1e2f2 100644 --- a/JetKarmaBot/Commands/StartCommand.cs +++ b/JetKarmaBot/Commands/StartCommand.cs @@ -1,20 +1,25 @@ using System.Collections.Generic; using Telegram.Bot.Args; using Perfusion; +using JetKarmaBot.Services; namespace JetKarmaBot.Commands { public class StartCommand : IChatCommand { - [Inject(true)]Db Db; + [Inject] KarmaContextFactory Db; public IReadOnlyCollection Names => new[] { "start" }; public bool Execute(CommandString cmd, MessageEventArgs args) { - Db.AddChat(new Db.Chat { ChatId = args.Message.Chat.Id }); - Db.AddUser(new Db.User { UserId = args.Message.From.Id }); - return true; + using (var db = Db.GetContext()) + { + db.Chat.Add(new Models.Chat { Chatid = args.Message.Chat.Id }); + db.User.Add(new Models.User { Userid = args.Message.From.Id }); + db.SaveChanges(); + return true; + } } } } diff --git a/JetKarmaBot/Commands/StatusCommand.cs b/JetKarmaBot/Commands/StatusCommand.cs index 8899d0b..c8da7ee 100644 --- a/JetKarmaBot/Commands/StatusCommand.cs +++ b/JetKarmaBot/Commands/StatusCommand.cs @@ -6,6 +6,8 @@ using Perfusion; using Telegram.Bot; using Telegram.Bot.Args; using Telegram.Bot.Types; +using JetKarmaBot.Models; +using JetKarmaBot.Services; namespace JetKarmaBot.Commands { @@ -44,9 +46,9 @@ namespace JetKarmaBot.Commands return true; } - [Inject(true)] Db Db { get; set; } - [Inject(true)] TelegramBotClient Client { get; set; } - [Inject(true)] Localization Locale { get; set; } + [Inject] KarmaContextFactory Db { get; set; } + [Inject] TelegramBotClient Client { get; set; } + [Inject] Localization Locale { get; set; } } } diff --git a/JetKarmaBot/JetKarmaBot.cs b/JetKarmaBot/JetKarmaBot.cs index c2d5a8f..7b95dd7 100644 --- a/JetKarmaBot/JetKarmaBot.cs +++ b/JetKarmaBot/JetKarmaBot.cs @@ -1,4 +1,5 @@ using JetKarmaBot.Commands; +using JetKarmaBot.Services; using Perfusion; using System; using System.Net; @@ -13,9 +14,9 @@ namespace JetKarmaBot { public class JetKarmaBot : IDisposable { - [Inject(true)] Config Config { get; set; } - [Inject(true)] Container Container { get; set; } - [Inject(true)] Db Db { get; set; } + [Inject] Config Config { get; set; } + [Inject] Container Container { get; set; } + [Inject] KarmaContextFactory Db { get; set; } TelegramBotClient Client { get; set; } ChatCommandRouter Commands; diff --git a/JetKarmaBot/JetKarmaBot.csproj b/JetKarmaBot/JetKarmaBot.csproj index eeaee13..259233e 100644 --- a/JetKarmaBot/JetKarmaBot.csproj +++ b/JetKarmaBot/JetKarmaBot.csproj @@ -6,9 +6,12 @@ - - + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/JetKarmaBot/Models/Award.cs b/JetKarmaBot/Models/Award.cs new file mode 100644 index 0000000..28178f4 --- /dev/null +++ b/JetKarmaBot/Models/Award.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace JetKarmaBot.Models +{ + public partial class Award + { + public int Awardid { get; set; } + public long Chatid { get; set; } + public long Fromid { get; set; } + public long Toid { get; set; } + public sbyte Awardtypeid { get; set; } + public sbyte Amount { get; set; } + public DateTime Date { get; set; } + + public Awardtype Awardtype { get; set; } + public Chat Chat { get; set; } + public User From { get; set; } + public User To { get; set; } + } +} diff --git a/JetKarmaBot/Models/Awardtype.cs b/JetKarmaBot/Models/Awardtype.cs new file mode 100644 index 0000000..f9b024e --- /dev/null +++ b/JetKarmaBot/Models/Awardtype.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace JetKarmaBot.Models +{ + public partial class Awardtype + { + public Awardtype() + { + Award = new HashSet(); + } + + public sbyte Awardtypeid { get; set; } + public string Commandname { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } + public string Description { get; set; } + + public ICollection Award { get; set; } + } +} diff --git a/JetKarmaBot/Models/Chat.cs b/JetKarmaBot/Models/Chat.cs new file mode 100644 index 0000000..7d7e3ee --- /dev/null +++ b/JetKarmaBot/Models/Chat.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace JetKarmaBot.Models +{ + public partial class Chat + { + public Chat() + { + Award = new HashSet(); + } + + public long Chatid { get; set; } + public string Locale { get; set; } + + public ICollection Award { get; set; } + } +} diff --git a/JetKarmaBot/Models/User.cs b/JetKarmaBot/Models/User.cs new file mode 100644 index 0000000..95d0e7d --- /dev/null +++ b/JetKarmaBot/Models/User.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace JetKarmaBot.Models +{ + public partial class User + { + public User() + { + AwardFrom = new HashSet(); + AwardTo = new HashSet(); + } + + public long Userid { get; set; } + public string Username { get; set; } + + public ICollection AwardFrom { get; set; } + public ICollection AwardTo { get; set; } + } +} diff --git a/JetKarmaBot/Models/karmaContext.cs b/JetKarmaBot/Models/karmaContext.cs new file mode 100644 index 0000000..7d66c09 --- /dev/null +++ b/JetKarmaBot/Models/karmaContext.cs @@ -0,0 +1,171 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace JetKarmaBot.Models +{ + public partial class KarmaContext : DbContext + { + public KarmaContext() + { + } + + public KarmaContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Award { get; set; } + public virtual DbSet Awardtype { get; set; } + public virtual DbSet Chat { get; set; } + public virtual DbSet User { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("award"); + + entity.HasIndex(e => e.Awardid) + .HasName("awardid_UNIQUE") + .IsUnique(); + + entity.HasIndex(e => e.Awardtypeid) + .HasName("fk_awardtype_idx"); + + entity.HasIndex(e => e.Chatid) + .HasName("fk_chat_idx"); + + entity.HasIndex(e => e.Fromid) + .HasName("fk_from_idx"); + + entity.HasIndex(e => e.Toid) + .HasName("fk_to_idx"); + + entity.Property(e => e.Awardid) + .HasColumnName("awardid") + .HasColumnType("int(11)"); + + entity.Property(e => e.Amount) + .HasColumnName("amount") + .HasColumnType("tinyint(3)") + .HasDefaultValueSql("'1'"); + + entity.Property(e => e.Awardtypeid) + .HasColumnName("awardtypeid") + .HasColumnType("tinyint(3)"); + + entity.Property(e => e.Chatid) + .HasColumnName("chatid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.Date) + .HasColumnName("date") + .HasColumnType("datetime") + .HasDefaultValueSql("'CURRENT_TIMESTAMP'"); + + entity.Property(e => e.Fromid) + .HasColumnName("fromid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.Toid) + .HasColumnName("toid") + .HasColumnType("bigint(20)"); + + entity.HasOne(d => d.Awardtype) + .WithMany(p => p.Award) + .HasForeignKey(d => d.Awardtypeid) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_awardtype"); + + entity.HasOne(d => d.Chat) + .WithMany(p => p.Award) + .HasForeignKey(d => d.Chatid) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_chat"); + + entity.HasOne(d => d.From) + .WithMany(p => p.AwardFrom) + .HasForeignKey(d => d.Fromid) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_from"); + + entity.HasOne(d => d.To) + .WithMany(p => p.AwardTo) + .HasForeignKey(d => d.Toid) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("fk_to"); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("awardtype"); + + entity.HasIndex(e => e.Awardtypeid) + .HasName("awardtypeid_UNIQUE") + .IsUnique(); + + entity.HasIndex(e => e.Commandname) + .HasName("commandname_UNIQUE") + .IsUnique(); + + entity.Property(e => e.Awardtypeid) + .HasColumnName("awardtypeid") + .HasColumnType("tinyint(3)"); + + entity.Property(e => e.Commandname) + .IsRequired() + .HasColumnName("commandname") + .HasColumnType("varchar(35)"); + + entity.Property(e => e.Description) + .IsRequired() + .HasColumnName("description") + .HasColumnType("text"); + + entity.Property(e => e.Name) + .IsRequired() + .HasColumnName("name") + .HasColumnType("varchar(32)"); + + entity.Property(e => e.Symbol) + .IsRequired() + .HasColumnName("symbol") + .HasColumnType("varchar(16)"); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("chat"); + + entity.Property(e => e.Chatid) + .HasColumnName("chatid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.Locale) + .IsRequired() + .HasColumnName("locale") + .HasColumnType("varchar(10)") + .HasDefaultValueSql("'ru-RU'"); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("user"); + + entity.Property(e => e.Userid) + .HasColumnName("userid") + .HasColumnType("bigint(20)"); + + entity.Property(e => e.Username) + .HasColumnName("username") + .HasColumnType("varchar(45)"); + }); + } + } +} diff --git a/JetKarmaBot/Program.cs b/JetKarmaBot/Program.cs index 6f0796d..4981083 100644 --- a/JetKarmaBot/Program.cs +++ b/JetKarmaBot/Program.cs @@ -1,5 +1,7 @@ using System; using System.Threading; +using JetKarmaBot.Models; +using Microsoft.EntityFrameworkCore; using Perfusion; namespace JetKarmaBot @@ -23,8 +25,13 @@ namespace JetKarmaBot public static int Main(string[] args) { Container c = new Container(); - c.AddInstance(new Config("karma.cfg.json")); - c.Add(); + var cfg = new Config("karma.cfg.json"); + c.AddInstance(cfg); + + var dbOptions = new DbContextOptionsBuilder() + .UseMySql(cfg.ConnectionString); + + c.Add(() => new KarmaContext(dbOptions.Options)); c.Add(); var bot = c.GetInstance(); diff --git a/JetKarmaBot/Services/Db.cs b/JetKarmaBot/Services/Db.cs deleted file mode 100644 index d329b32..0000000 --- a/JetKarmaBot/Services/Db.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Dapper; -using MySql.Data.MySqlClient; -using Perfusion; - -namespace JetKarmaBot -{ - public class Db - { - Dictionary m_Chats; - public IReadOnlyDictionary Chats => m_Chats; - public void AddChat(Chat chat) - { - lock (m_SyncRoot) - if (!m_Chats.ContainsKey(chat.ChatId)) - { - Conn.Execute(@"INSERT INTO chat - (chatid,locale) - VALUES - (@ChatId,@Locale)", - chat); - m_Chats.Add(chat.ChatId, chat); - } - } - - Dictionary m_Users; - public IReadOnlyDictionary Users => m_Users; - public void AddUser(User user) - { - lock (m_SyncRoot) - if (!m_Users.ContainsKey(user.UserId)) - { - Conn.Execute(@"INSERT INTO user - (userid) - VALUES - (@UserId)", - user); - m_Users.Add(user.UserId, user); - } - } - Dictionary m_AwardTypes; - public byte DefaultAwardTypeId { get; } = 1; - public IReadOnlyDictionary AwardTypes => m_AwardTypes; - public IReadOnlyDictionary AwardTypesByCommandName { get; private set; } - - public int CountUserAwards(long userId, byte awardTypeId) - { - return Conn.QuerySingle - ( - "SELECT SUM(amount) FROM award WHERE toid = @userId AND awardtypeid = @awardTypeId", - new { userId, awardTypeId } - ) ?? 0; - } - - public void ChangeChatLocale(Chat chat, string locale) - { - lock (m_SyncRoot) - { - chat.Locale = locale; - Conn.Execute(@"UPDATE chat - SET locale=@Locale - WHERE chatid=@ChatID", - chat); - } - } - - public struct UserAwardsReport - { - public int Amount { get; private set; } - public byte AwardTypeId { get; private set; } - } - - public IEnumerable CountAllUserAwards(long userId) - { - return Conn.Query - ( - @"SELECT SUM(amount) AS amount, t.awardtypeid - FROM award a - JOIN awardtype t on a.awardtypeid = t.awardtypeid - WHERE toid = @userId - GROUP BY awardtypeid;", - new { userId } - ); - } - - public byte GetAwardTypeId(string name) => AwardTypesByCommandName.GetOrDefault(name)?.AwardTypeId ?? DefaultAwardTypeId; - - public bool AddAward(byte awardTypeId, long fromId, long toId, long chatId, int amount) - { - AddChat(new Chat() { ChatId = chatId }); - AddUser(new User() { UserId = fromId }); - AddUser(new User() { UserId = toId }); - - int affected = Conn.ExecuteScalar( - @"INSERT INTO award - (chatid, fromid, toid, awardtypeid, amount) - VALUES - (@chatId, @fromId, @toId, @awardTypeId, @amount)", - new { awardTypeId, fromId, toId, chatId, amount }); - return affected == 1; - } - - #region types - public class Chat - { - public long ChatId { get; set; } - public string Locale { get; set; } - } - - public class User - { - public long UserId { get; set; } - } - - public class AwardType - { - public byte AwardTypeId { get; set; } - public string CommandName { get; set; } - public string Name { get; set; } - public string Symbol { get; set; } - public string Description { get; set; } - } - - public class Award - { - public int AwardId { get; set; } - public byte AwardTypeId { get; set; } - public long FromId { get; set; } - public long ToId { get; set; } - public long ChatId { get; set; } - public byte Amount { get; set; } - } - - #endregion - - #region service - [Inject] - public Db(Config cfg) - { - Log("Initializing..."); - Conn = new MySqlConnection(cfg.ConnectionString); - Conn.ExecuteScalar("select 1"); - Load(); - Log("Initialized!"); - } - - object m_SyncRoot = new object(); - - IDbConnection Conn { get; } - void Load() - { - Log("Populating cache..."); - m_Chats = Conn.Query("SELECT * FROM chat").ToDictionary(u => u.ChatId); - m_Users = Conn.Query("SELECT * FROM user").ToDictionary(s => s.UserId); - m_AwardTypes = Conn.Query("SELECT * FROM awardtype").ToDictionary(c => c.AwardTypeId); - AwardTypesByCommandName = m_AwardTypes.Values.ToDictionary(kvp => kvp.CommandName); - Log("Cache populated!"); - } - #endregion - - void Log(string Message) => Console.WriteLine($"[{nameof(Db)}]: {Message}"); - } -} \ No newline at end of file diff --git a/JetKarmaBot/Services/KarmaContextFactory.cs b/JetKarmaBot/Services/KarmaContextFactory.cs new file mode 100644 index 0000000..88bade8 --- /dev/null +++ b/JetKarmaBot/Services/KarmaContextFactory.cs @@ -0,0 +1,12 @@ +using JetKarmaBot.Models; +using Perfusion; + +namespace JetKarmaBot.Services +{ + public class KarmaContextFactory + { + [Inject] Container C { get; set; } + + public KarmaContext GetContext() => C.GetInstance(); + } +} diff --git a/JetKarmaBot/Services/Localization.cs b/JetKarmaBot/Services/Localization.cs index 0538e31..cd0987b 100644 --- a/JetKarmaBot/Services/Localization.cs +++ b/JetKarmaBot/Services/Localization.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; -using Dapper; -using MySql.Data.MySqlClient; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Perfusion; diff --git a/JetKarmaBot/lang/en-US.json b/JetKarmaBot/lang/en-US.json index 01ed7ad..46b3ffd 100644 --- a/JetKarmaBot/lang/en-US.json +++ b/JetKarmaBot/lang/en-US.json @@ -8,6 +8,6 @@ "jetkarmabot.award.statustext": "{0} is at {1}{2} now.", "jetkarmabot.status.listalltext": "Your badges report:", "jetkarmabot.status.listspecifictext": "You are at {0}{1} now.", - "jetkarmabot.changelocale.justchanged": "good", - "jetkarmabot.changelocale.getlocale": "I'm currently speaking in English (Simplified)." + "jetkarmabot.changelocale.justchanged": "Roger that.", + "jetkarmabot.changelocale.getlocale": "I'm currently speaking English." } \ No newline at end of file diff --git a/JetKarmaBot/lang/ru-RU.json b/JetKarmaBot/lang/ru-RU.json index 49c161c..32609db 100644 --- a/JetKarmaBot/lang/ru-RU.json +++ b/JetKarmaBot/lang/ru-RU.json @@ -8,6 +8,6 @@ "jetkarmabot.award.statustext": "У {0} теперь {1}{2}.", "jetkarmabot.status.listalltext": "У вас :", "jetkarmabot.status.listspecifictext": "У вас сейчас {0}{1}.", - "jetkarmabot.changelocale.justchanged": "хорошо", - "jetkarmabot.changelocale.getlocale": "Я сейчас говорю на языке \"Русский (Россия)\"." + "jetkarmabot.changelocale.justchanged": "Так точно.", + "jetkarmabot.changelocale.getlocale": "Я сейчас говорю по-русски." } \ No newline at end of file