transition start

This commit is contained in:
jetsparrow 2019-01-02 17:07:31 +03:00
parent 23dbd6ca17
commit d3bbfe8c52
17 changed files with 366 additions and 232 deletions

View File

@ -5,6 +5,7 @@ using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Perfusion;
using JetKarmaBot.Services;
namespace JetKarmaBot.Commands
{
@ -14,7 +15,9 @@ namespace JetKarmaBot.Commands
public bool Execute(CommandString cmd, MessageEventArgs args)
{
var currentLocale = Locale[Db.Chats[args.Message.Chat.Id].Locale];
using (var db = Db.GetContext())
{
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"]);
@ -47,16 +50,31 @@ namespace JetKarmaBot.Commands
}
var text = args.Message.Text;
var awardTypeId = Db.GetAwardTypeId(cmd.Parameters.FirstOrDefault());
var awardType = Db.AwardTypes[awardTypeId];
var awardTypeText = cmd.Parameters.FirstOrDefault();
var awardType = awardTypeText != null
? db.Awardtype.First(at => at.Commandname == awardTypeText)
: db.Awardtype.Find(1);
Db.AddAward(awardTypeId, awarder.Id, recipient.Id, args.Message.Chat.Id, awarding ? 1 : -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 response = message + "\n" + String.Format(currentLocale["jetkarmabot.award.statustext"], "@" + recipient.Username, Db.CountUserAwards(recipient.Id, awardTypeId), awardType.Symbol);
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);
Client.SendTextMessageAsync(
args.Message.Chat.Id,
@ -64,10 +82,11 @@ namespace JetKarmaBot.Commands
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)

View File

@ -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; }
}
}

View File

@ -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<string> 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 });
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;
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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;

View File

@ -6,9 +6,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.5" />
<PackageReference Include="MySql.Data" Version="8.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.4" />
<PackageReference Include="Telegram.Bot" Version="14.10.0" />
<ProjectReference Include="..\perfusion\Perfusion\Perfusion.csproj" />
</ItemGroup>

View File

@ -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; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace JetKarmaBot.Models
{
public partial class Awardtype
{
public Awardtype()
{
Award = new HashSet<Award>();
}
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> Award { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace JetKarmaBot.Models
{
public partial class Chat
{
public Chat()
{
Award = new HashSet<Award>();
}
public long Chatid { get; set; }
public string Locale { get; set; }
public ICollection<Award> Award { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace JetKarmaBot.Models
{
public partial class User
{
public User()
{
AwardFrom = new HashSet<Award>();
AwardTo = new HashSet<Award>();
}
public long Userid { get; set; }
public string Username { get; set; }
public ICollection<Award> AwardFrom { get; set; }
public ICollection<Award> AwardTo { get; set; }
}
}

View File

@ -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<KarmaContext> options)
: base(options)
{
}
public virtual DbSet<Award> Award { get; set; }
public virtual DbSet<Awardtype> Awardtype { get; set; }
public virtual DbSet<Chat> Chat { get; set; }
public virtual DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Award>(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<Awardtype>(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<Chat>(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<User>(entity =>
{
entity.ToTable("user");
entity.Property(e => e.Userid)
.HasColumnName("userid")
.HasColumnType("bigint(20)");
entity.Property(e => e.Username)
.HasColumnName("username")
.HasColumnType("varchar(45)");
});
}
}
}

View File

@ -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<Db>();
var cfg = new Config("karma.cfg.json");
c.AddInstance(cfg);
var dbOptions = new DbContextOptionsBuilder<KarmaContext>()
.UseMySql(cfg.ConnectionString);
c.Add(() => new KarmaContext(dbOptions.Options));
c.Add<JetKarmaBot>();
var bot = c.GetInstance<JetKarmaBot>();

View File

@ -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<long, Chat> m_Chats;
public IReadOnlyDictionary<long, Chat> 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<long, User> m_Users;
public IReadOnlyDictionary<long, User> 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<byte, AwardType> m_AwardTypes;
public byte DefaultAwardTypeId { get; } = 1;
public IReadOnlyDictionary<byte, AwardType> AwardTypes => m_AwardTypes;
public IReadOnlyDictionary<string, AwardType> AwardTypesByCommandName { get; private set; }
public int CountUserAwards(long userId, byte awardTypeId)
{
return Conn.QuerySingle<int?>
(
"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<UserAwardsReport> CountAllUserAwards(long userId)
{
return Conn.Query<UserAwardsReport>
(
@"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<int>(
@"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<Chat>("SELECT * FROM chat").ToDictionary(u => u.ChatId);
m_Users = Conn.Query<User>("SELECT * FROM user").ToDictionary(s => s.UserId);
m_AwardTypes = Conn.Query<AwardType>("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}");
}
}

View File

@ -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<KarmaContext>();
}
}

View File

@ -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;

View File

@ -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."
}

View File

@ -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": "Я сейчас говорю по-русски."
}