Browse Source

Top users by 4-tier stickers count

Tigran 3 years ago
parent
commit
f8e09ce093
33 changed files with 425 additions and 96 deletions
  1. 2 1
      CardCollector.sln.DotSettings.user
  2. 9 0
      CardCollector/CardCollector.csproj
  3. 63 0
      CardCollector/Commands/CallbackQuery/ShowTopBy.cs
  4. 1 0
      CardCollector/Commands/CallbackQueryCommand.cs
  5. 10 3
      CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs
  6. 4 3
      CardCollector/Commands/Message/Menu.cs
  7. 81 33
      CardCollector/Controllers/MessageController.cs
  8. 2 1
      CardCollector/DailyTasks/CustomTasks/SendStickers.cs
  9. 1 1
      CardCollector/DataBase/BotDatabase.cs
  10. 3 3
      CardCollector/DataBase/Entity/CashEntity.cs
  11. 1 1
      CardCollector/DataBase/Entity/StickerEntity.cs
  12. 1 1
      CardCollector/DataBase/Entity/UserEntity.cs
  13. 4 21
      CardCollector/DataBase/Entity/UserMessages.cs
  14. 1 1
      CardCollector/DataBase/Entity/UserStickerRelation.cs
  15. 26 6
      CardCollector/DataBase/EntityDao/UserMessagesDao.cs
  16. 22 6
      CardCollector/DataBase/EntityDao/UserStickerRelationDao.cs
  17. 9 0
      CardCollector/Resources/Command.Designer.cs
  18. 3 0
      CardCollector/Resources/Command.resx
  19. 10 0
      CardCollector/Resources/Keyboard.cs
  20. 9 0
      CardCollector/Resources/Messages.Designer.cs
  21. 3 0
      CardCollector/Resources/Messages.resx
  22. 9 0
      CardCollector/Resources/Text.Designer.cs
  23. 3 0
      CardCollector/Resources/Text.resx
  24. 8 0
      CardCollector/Resources/TopBy.cs
  25. 81 0
      CardCollector/Resources/TopByTexts.Designer.cs
  26. 27 0
      CardCollector/Resources/TopByTexts.resx
  27. 1 1
      CardCollector/StickerEffects/AuctionDiscount5.cs
  28. 1 1
      CardCollector/StickerEffects/Random1Pack5Day.cs
  29. 1 1
      CardCollector/StickerEffects/RandomSticker1Tier2Day.cs
  30. 1 1
      CardCollector/StickerEffects/RandomSticker2Tier3Day.cs
  31. 10 3
      CardCollector/TimerTasks/DailyTaskAlert.cs
  32. 8 5
      CardCollector/TimerTasks/PiggyBankAlert.cs
  33. 10 3
      CardCollector/TimerTasks/TopExpUsersAlert.cs

+ 2 - 1
CardCollector.sln.DotSettings.user

@@ -21,7 +21,8 @@
 	
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FResources_002FMessages/@EntryIndexedValue">False</s:Boolean>
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FResources_002FSortingTypes/@EntryIndexedValue">False</s:Boolean>
-	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FResources_002FText/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FResources_002FText/@EntryIndexedValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FResources_002FTopByTexts/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=CardCollector_002FStickerEffects_002FEffectTranslations/@EntryIndexedValue">True</s:Boolean>
 	
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>

+ 9 - 0
CardCollector/CardCollector.csproj

@@ -51,6 +51,10 @@
         <Generator>ResXFileCodeGenerator</Generator>
         <LastGenOutput>LogsTranslations.Designer.cs</LastGenOutput>
       </EmbeddedResource>
+      <EmbeddedResource Update="Resources\TopByTexts.resx">
+        <Generator>ResXFileCodeGenerator</Generator>
+        <LastGenOutput>TopByTexts.Designer.cs</LastGenOutput>
+      </EmbeddedResource>
     </ItemGroup>
 
     <ItemGroup>
@@ -99,6 +103,11 @@
         <AutoGen>True</AutoGen>
         <DependentUpon>LogsStranslations.resx</DependentUpon>
       </Compile>
+      <Compile Update="Resources\TopByTexts.Designer.cs">
+        <DesignTime>True</DesignTime>
+        <AutoGen>True</AutoGen>
+        <DependentUpon>TopByTexts.resx</DependentUpon>
+      </Compile>
     </ItemGroup>
 
 </Project>

+ 63 - 0
CardCollector/Commands/CallbackQuery/ShowTopBy.cs

@@ -0,0 +1,63 @@
+using System.Linq;
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class ShowTopBy : CallbackQueryCommand
+    {
+        protected override string CommandText => Command.show_top_by;
+        public override async Task Execute()
+        {
+            var topBy = (TopBy) int.Parse(CallbackData.Split('=')[1]);
+            switch (topBy)
+            {
+                case TopBy.Exp:
+                    var usersExp = await UserLevelDao.GetTop(5);
+                    var topByExp = Messages.users_top_exp;
+                    foreach (var (userLevel, index) in usersExp.WithIndex())
+                    {
+                        var user = await UserDao.GetById(userLevel.UserId);
+                        topByExp += $"\n{index+1}.{user.Username}: {userLevel.TotalExp} {Text.exp}";
+                    }
+                    User.MessagesId.TopUsersId = 
+                        await MessageController.DeleteAndSend(User, User.MessagesId.TopUsersId,
+                            topByExp, Keyboard.GetTopButton(TopBy.Tier4Stickers), ParseMode.Html);
+                    break;
+                case TopBy.Tier4Stickers:
+                    var tier4Stickers = (await StickerDao.GetListWhere(i => i.Tier == 4))
+                        .Select(s => s.Md5Hash);
+                    var userTier4Stickers = await UserStickerRelationDao
+                        .GetListWhere(us => tier4Stickers.Contains(us.ShortHash));
+                    var usersTier4StickersCount = userTier4Stickers
+                        .GroupBy(i => i.UserId)
+                        .Select(i => new {userId = i.Key, count = i.Sum(j=>j.Count)})
+                        .OrderByDescending(i => i.count)
+                        .Take(5);
+                    var topByTier4 = Messages.users_top_tier_4_stickers_count;
+                    foreach (var (idAndCount, index) in usersTier4StickersCount.WithIndex())
+                    {
+                        var user = await UserDao.GetById(idAndCount.userId);
+                        topByTier4 += $"\n{index+1}.{user.Username}: {idAndCount.count} {Text.stickers}";
+                    }
+                    User.MessagesId.TopUsersId = 
+                        await MessageController.DeleteAndSend(User, User.MessagesId.TopUsersId,
+                            topByTier4, Keyboard.GetTopButton(TopBy.Exp), ParseMode.Html);
+                    break;
+            }
+        }
+
+        public ShowTopBy()
+        {
+        }
+
+        public ShowTopBy(UserEntity user, Update update) : base(user, update)
+        {
+        }
+    }
+}

+ 1 - 0
CardCollector/Commands/CallbackQueryCommand.cs

@@ -66,6 +66,7 @@ namespace CardCollector.Commands
             new AddForSaleSticker(),
             new SelectForSalePack(),
             new UploadStickerPack(),
+            new ShowTopBy(),
         };
 
         /* Данные, поступившие после нажатия на кнокпку */

+ 10 - 3
CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs

@@ -17,11 +17,18 @@ namespace CardCollector.Commands.ChosenInlineResult
             if (await dailyTask.Execute(User))
             {
                 await dailyTask.GiveReward(User);
-                await MessageController.EditMessage(User, Messages.pack_prize, Keyboard.MyPacks);
+                User.MessagesId.DailyTaskProgressId =
+                    await MessageController.DeleteAndSend(User, User.MessagesId.DailyTaskProgressId,
+                        Messages.pack_prize, Keyboard.MyPacks);
             }
         }
 
-        public SendPrivateSticker() { }
-        public SendPrivateSticker(UserEntity user, Update update) : base(user, update) { }
+        public SendPrivateSticker()
+        {
+        }
+
+        public SendPrivateSticker(UserEntity user, Update update) : base(user, update)
+        {
+        }
     }
 }

+ 4 - 3
CardCollector/Commands/Message/Menu.cs

@@ -1,7 +1,9 @@
 using System.Threading.Tasks;
+using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
 using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
 
 namespace CardCollector.Commands.Message
 {
@@ -12,9 +14,8 @@ namespace CardCollector.Commands.Message
         public override async Task Execute()
         {
             await User.ClearChat();
-            await User.MessagesId.SendMenu();/*
-            /* Отправляем пользователю сообщение со стандартной клавиатурой #1#
-            await MessageController.SendMessage(User, Messages.main_menu, Keyboard.Menu, ParseMode.Html);*/
+            User.MessagesId.MenuId = await MessageController.DeleteAndSend(User, User.MessagesId.MenuId,
+                Messages.main_menu, Keyboard.Menu, ParseMode.Html);
         }
 
         public Menu() { }

+ 81 - 33
CardCollector/Controllers/MessageController.cs

@@ -78,6 +78,7 @@ namespace CardCollector.Controllers
                     LogOutError(e);
                     break;
             }
+
             return Task.CompletedTask;
         }
 
@@ -88,43 +89,86 @@ namespace CardCollector.Controllers
         public static async Task EditMessage(UserEntity user, string message, IReplyMarkup keyboard = null,
             ParseMode? parseMode = null)
         {
-            if (!user.IsBlocked) try {
-                if (user.Session.Messages.Count > 0 && keyboard is InlineKeyboardMarkup or null)
-                    await Bot.Client.EditMessageTextAsync(user.ChatId, user.Session.Messages.Last(),
-                        message, parseMode, replyMarkup: keyboard != null ? (InlineKeyboardMarkup)keyboard : null);
-                else await SendMessage(user, message, keyboard, parseMode);
-            } catch {
-                await user.ClearChat();
-                await SendMessage(user, message, keyboard, parseMode);
+            if (!user.IsBlocked)
+                try
+                {
+                    if (user.Session.Messages.Count > 0 && keyboard is InlineKeyboardMarkup or null)
+                        await Bot.Client.EditMessageTextAsync(user.ChatId, user.Session.Messages.Last(),
+                            message, parseMode, replyMarkup: keyboard != null ? (InlineKeyboardMarkup) keyboard : null);
+                    else await SendMessage(user, message, keyboard, parseMode);
+                }
+                catch
+                {
+                    await user.ClearChat();
+                    await SendMessage(user, message, keyboard, parseMode);
+                }
+        }
+
+        public static async Task<int> DeleteAndSend(UserEntity user, int prevMsgId, string message,
+            IReplyMarkup keyboard = null, ParseMode? parseMode = null)
+        {
+            if (!user.IsBlocked)
+            {
+                if (prevMsgId != -1)
+                    try
+                    {
+                        await Bot.Client.DeleteMessageAsync(user.ChatId, prevMsgId);
+                    }
+                    catch
+                    {
+                        /**/
+                    }
+
+                try
+                {
+                    var result = await Bot.Client.SendTextMessageAsync(user.ChatId, message, parseMode,
+                        replyMarkup: keyboard, disableNotification: true);
+                    return result.MessageId;
+                }
+                catch
+                {
+                    /**/
+                }
             }
+
+            return -1;
         }
 
         public static async Task SendMessage(UserEntity user, string message, IReplyMarkup keyboard = null,
             ParseMode? parseMode = null, bool addToList = true)
         {
-            if (!user.IsBlocked) try {
-                var result = await Bot.Client.SendTextMessageAsync(user.ChatId, message, parseMode,
-                    replyMarkup: keyboard, disableNotification: true);
-                if (addToList)
-                    user.Session.Messages.Add(result.MessageId);
-            } catch (Exception e) { LogOut(e); }
+            if (!user.IsBlocked)
+                try
+                {
+                    var result = await Bot.Client.SendTextMessageAsync(user.ChatId, message, parseMode,
+                        replyMarkup: keyboard, disableNotification: true);
+                    if (addToList)
+                        user.Session.Messages.Add(result.MessageId);
+                }
+                catch (Exception e)
+                {
+                    LogOut(e);
+                }
         }
-        
+
         /* Метод для отправки стикера
          user - пользователь, которому необходимо отправить сообщение
          fileId - id стикера, расположенного на серверах телеграм */
         public static async Task<Message> SendSticker(UserEntity user, string fileId, IReplyMarkup keyboard = null)
         {
-            if (!user.IsBlocked) try {
-                await user.ClearChat();
-                var result = await Bot.Client.SendStickerAsync(user.ChatId, fileId, true, replyMarkup: keyboard);
-                user.Session.StickerMessages.Add(result.MessageId);
-                return result;
-            }
-            catch (Exception e)
-            {
-                LogOutWarning("Can't send sticker " + e.Message);
-            }
+            if (!user.IsBlocked)
+                try
+                {
+                    await user.ClearChat();
+                    var result = await Bot.Client.SendStickerAsync(user.ChatId, fileId, true, replyMarkup: keyboard);
+                    user.Session.StickerMessages.Add(result.MessageId);
+                    return result;
+                }
+                catch (Exception e)
+                {
+                    LogOutWarning("Can't send sticker " + e.Message);
+                }
+
             return new Message();
         }
 
@@ -132,7 +176,8 @@ namespace CardCollector.Controllers
          user - пользователь, которому необходимо отредактировать сообщение
          messageId - Id сообщения
          keyboard - новая клавиатура, которую надо добавить к сообщению */
-        public static async Task<Message> EditReplyMarkup(UserEntity user, InlineKeyboardMarkup keyboard, int messageId = -1)
+        public static async Task<Message> EditReplyMarkup(UserEntity user, InlineKeyboardMarkup keyboard,
+            int messageId = -1)
         {
             if (!user.IsBlocked)
                 try
@@ -144,10 +189,12 @@ namespace CardCollector.Controllers
                 {
                     LogOutWarning("Can't edit reply markup " + e.Message);
                 }
+
             return new Message();
         }
-        
-        public static async Task AnswerCallbackQuery(UserEntity user, string callbackQueryId, string text, bool showAlert = false)
+
+        public static async Task AnswerCallbackQuery(UserEntity user, string callbackQueryId, string text,
+            bool showAlert = false)
         {
             try
             {
@@ -160,7 +207,7 @@ namespace CardCollector.Controllers
                 LogOutWarning("Can't answer callbackquery " + e.Message);
             }
         }
-        
+
         /* Метод для удаления сообщения
          user - пользователь, которому необходимо удалить сообщение
          messageId - Id сообщения */
@@ -182,15 +229,15 @@ namespace CardCollector.Controllers
         /* Метод для ответа на запрос @имя_бота
          queryId - Id запроса
          results - массив объектов InlineQueryResult */
-        public static async Task AnswerInlineQuery(string queryId, IEnumerable<InlineQueryResult> results, 
+        public static async Task AnswerInlineQuery(string queryId, IEnumerable<InlineQueryResult> results,
             string offset = null)
         {
-            await Bot.Client.AnswerInlineQueryAsync(queryId, results, isPersonal: true, nextOffset: offset, 
+            await Bot.Client.AnswerInlineQueryAsync(queryId, results, isPersonal: true, nextOffset: offset,
                 cacheTime: Constants.INLINE_RESULTS_CACHE_TIME);
         }
 
-        public static async Task<Message> SendInvoice(UserEntity user, string title, string description, 
-            string payload, IEnumerable<LabeledPrice> prices, InlineKeyboardMarkup keyboard = null, 
+        public static async Task<Message> SendInvoice(UserEntity user, string title, string description,
+            string payload, IEnumerable<LabeledPrice> prices, InlineKeyboardMarkup keyboard = null,
             Currency currency = Currency.USD)
         {
             if (!user.IsBlocked)
@@ -207,6 +254,7 @@ namespace CardCollector.Controllers
                 {
                     LogOutWarning("Can't send photo " + e.Message);
                 }
+
             return new Message();
         }
     }

+ 2 - 1
CardCollector/DailyTasks/CustomTasks/SendStickers.cs

@@ -21,7 +21,8 @@ namespace CardCollector.DailyTasks.CustomTasks
             if (task.Progress == 0) return false;
             task.Progress--;
             if (user.Settings[UserSettingsEnum.DailyTaskProgress])
-                await MessageController.EditMessage(user,
+                user.MessagesId.DailyTaskProgressId = 
+                await MessageController.DeleteAndSend(user, user.MessagesId.DailyTaskProgressId,
                     $"{Messages.send_sticker_progress}: {Goal - task.Progress} / {Goal}");
             if (task.Progress == 0)
             {

+ 1 - 1
CardCollector/DataBase/BotDatabase.cs

@@ -34,7 +34,7 @@ namespace CardCollector.DataBase
         /* Таблицы базы данных, представленные Entity объектами */
         public DbSet<UserEntity> Users { get; set; }
         public DbSet<CashEntity> Cash { get; set; }
-        public DbSet<UserStickerRelationEntity> UserStickerRelations { get; set; }
+        public DbSet<UserStickerRelation> UserStickerRelations { get; set; }
         public DbSet<StickerEntity> Stickers { get; set; }
         public DbSet<AuctionEntity> Auction { get; set; }
         public DbSet<ShopEntity> Shop { get; set; }

+ 3 - 3
CardCollector/DataBase/Entity/CashEntity.cs

@@ -27,14 +27,14 @@ namespace CardCollector.DataBase.Entity
         
         [NotMapped] private DateTime LastPayout = DateTime.Now;
         
-        public async Task<int> CalculateIncome(Dictionary<string, UserStickerRelationEntity> stickers)
+        public async Task<int> CalculateIncome(Dictionary<string, UserStickerRelation> stickers)
         {
             LastPayout = DateTime.Now;
             var result = await stickers.Values.SumAsync(async sticker => await Payout(sticker));
             return result > MaxCapacity ? MaxCapacity : result;
         }
         
-        public async Task<int> Payout(Dictionary<string, UserStickerRelationEntity> stickers)
+        public async Task<int> Payout(Dictionary<string, UserStickerRelation> stickers)
         {
             var result = await stickers.Values.SumAsync(async sticker => await Payout(sticker, true));
             result = result > MaxCapacity ? MaxCapacity : result;
@@ -42,7 +42,7 @@ namespace CardCollector.DataBase.Entity
             return result;
         }
         
-        private async Task<int> Payout(UserStickerRelationEntity relation, bool updatePayout = false)
+        private async Task<int> Payout(UserStickerRelation relation, bool updatePayout = false)
         {
             var stickerInfo = await StickerDao.GetById(relation.StickerId);
             var payoutInterval = LastPayout - relation.Payout;

+ 1 - 1
CardCollector/DataBase/Entity/StickerEntity.cs

@@ -87,7 +87,7 @@ namespace CardCollector.DataBase.Entity
                 : true;
         }
 
-        public async Task ApplyEffect(UserEntity user, UserStickerRelationEntity relation)
+        public async Task ApplyEffect(UserEntity user, UserStickerRelation relation)
         {
             switch ((Effect)Effect)
             {

+ 1 - 1
CardCollector/DataBase/Entity/UserEntity.cs

@@ -45,7 +45,7 @@ namespace CardCollector.DataBase.Entity
         [NotMapped] public UserSettings Settings { get; set; }
         
         /* Стикеры пользователя */
-        [NotMapped] public Dictionary<string, UserStickerRelationEntity> Stickers { get; set; }
+        [NotMapped] public Dictionary<string, UserStickerRelation> Stickers { get; set; }
         [NotMapped] public UserMessages MessagesId { get; set; }
 
         /* Данные, хранящиеся в рамках одной сессии */

+ 4 - 21
CardCollector/DataBase/Entity/UserMessages.cs

@@ -1,33 +1,16 @@
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
-using System.Threading.Tasks;
-using CardCollector.Resources;
-using Telegram.Bot;
-using Telegram.Bot.Types.Enums;
 
 namespace CardCollector.DataBase.Entity
 {
     [Table("messages")]
     public class UserMessages
     {
-        /* id записи */
         [Key] [Column("user_id"), MaxLength(127)] public long UserId { get; set; }
-        /* id стикера */
         [Column("menu_id"), MaxLength(32)] public int MenuId { get; set; } = -1;
-
-        public async Task SendMenu()
-        {
-            try
-            {
-                if (MenuId != -1)
-                    await Bot.Client.DeleteMessageAsync(UserId, MenuId);
-            } catch { /**/ }
-            try
-            {
-                var msg = await Bot.Client.SendTextMessageAsync(UserId, Messages.main_menu, replyMarkup: Keyboard.Menu,
-                    parseMode: ParseMode.Html);
-                MenuId = msg.MessageId;
-            } catch { /**/ }
-        }
+        [Column("collect_income_id"), MaxLength(32)] public int CollectIncomeId { get; set; } = -1;
+        [Column("top_users_id"), MaxLength(32)] public int TopUsersId { get; set; } = -1;
+        [Column("daily_task_id"), MaxLength(32)] public int DailyTaskId { get; set; } = -1;
+        [Column("daily_task_progress_id"), MaxLength(32)] public int DailyTaskProgressId { get; set; } = -1;
     }
 }

+ 1 - 1
CardCollector/DataBase/Entity/UserStickerRelationEntity.cs → CardCollector/DataBase/Entity/UserStickerRelation.cs

@@ -6,7 +6,7 @@ namespace CardCollector.DataBase.Entity
 {
     /* Явялется расширенной связью многий ко многим между таблицами пользователей и стикеров */
     [Table("user_to_stickers_relations")]
-    public class UserStickerRelationEntity
+    public class UserStickerRelation
     {
         /* Id записи в таблице, роли не играет */
         [Column("id"), MaxLength(127)] public long Id { get; set; }

+ 26 - 6
CardCollector/DataBase/EntityDao/UserMessagesDao.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
@@ -16,14 +17,17 @@ namespace CardCollector.DataBase.EntityDao
             Instance = BotDatabase.GetClassInstance(typeof(UserLevelDao));
             Table = Instance.UserMessages;
         }
-        
+
         /* Получение объекта по Id */
         public static async Task<UserMessages> GetById(long userId)
         {
-            try {
+            try
+            {
                 var user = await Table.FirstOrDefaultAsync(item => item.UserId == userId);
                 return user ?? await AddNew(userId);
-            } catch (InvalidOperationException) {
+            }
+            catch (InvalidOperationException)
+            {
                 Thread.Sleep(Utilities.rnd.Next(30));
                 return await GetById(userId);
             }
@@ -32,14 +36,30 @@ namespace CardCollector.DataBase.EntityDao
         /* Добавление нового объекта в систему */
         private static async Task<UserMessages> AddNew(long userId)
         {
-            try {
-                var userLevel = new UserMessages { UserId = userId };
+            try
+            {
+                var userLevel = new UserMessages {UserId = userId};
                 var result = await Table.AddAsync(userLevel);
                 return result.Entity;
-            } catch (InvalidOperationException) {
+            }
+            catch (InvalidOperationException)
+            {
                 Thread.Sleep(Utilities.rnd.Next(30));
                 return await AddNew(userId);
             }
         }
+
+        public static async Task<Dictionary<long, UserMessages>> GetAll()
+        {
+            try
+            {
+                return await Table.ToDictionaryAsync(um => um.UserId, um => um);
+            }
+            catch (InvalidOperationException)
+            {
+                Thread.Sleep(Utilities.rnd.Next(30));
+                return await GetAll();
+            }
+        }
     }
 }

+ 22 - 6
CardCollector/DataBase/EntityDao/UserStickerRelationDao.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Linq.Expressions;
 using System.Threading;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
@@ -12,20 +13,20 @@ namespace CardCollector.DataBase.EntityDao
     public static class UserStickerRelationDao
     {
         public static BotDatabase Instance;
-        public static DbSet<UserStickerRelationEntity> Table;
+        public static DbSet<UserStickerRelation> Table;
 
         static UserStickerRelationDao()
         {
             Instance = BotDatabase.GetClassInstance(typeof(UserStickerRelationDao));
             Table = Instance.UserStickerRelations;
         }
-        
+
         /* Возвращает словарь стикеров по Id пользователя */
-        public static async Task<Dictionary<string, UserStickerRelationEntity>> GetListById(long userId)
+        public static async Task<Dictionary<string, UserStickerRelation>> GetListById(long userId)
         {
             try
             {
-                return await Table.Where(i => i.UserId == userId).ToDictionaryAsync(p=> p.ShortHash, p=> p);
+                return await Table.Where(i => i.UserId == userId).ToDictionaryAsync(p => p.ShortHash, p => p);
             }
             catch (InvalidOperationException)
             {
@@ -35,7 +36,7 @@ namespace CardCollector.DataBase.EntityDao
         }
 
         /* Добавляет новое отношение в таблицу */
-        public static async Task<UserStickerRelationEntity> AddSticker(UserEntity user, StickerEntity sticker, int count = 1)
+        public static async Task<UserStickerRelation> AddSticker(UserEntity user, StickerEntity sticker, int count = 1)
         {
             try
             {
@@ -44,7 +45,8 @@ namespace CardCollector.DataBase.EntityDao
                     user.Stickers[sticker.Md5Hash].Count += count;
                     return user.Stickers[sticker.Md5Hash];
                 }
-                var relation = new UserStickerRelationEntity
+
+                var relation = new UserStickerRelation
                 {
                     UserId = user.Id,
                     StickerId = sticker.Id,
@@ -63,5 +65,19 @@ namespace CardCollector.DataBase.EntityDao
                 return await AddSticker(user, sticker, count);
             }
         }
+
+        public static async Task<List<UserStickerRelation>> GetListWhere(
+            Expression<Func<UserStickerRelation, bool>> predicate)
+        {
+            try
+            {
+                return await Table.Where(predicate).ToListAsync();
+            }
+            catch (InvalidOperationException)
+            {
+                Thread.Sleep(Utilities.rnd.Next(30));
+                return await GetListWhere(predicate);
+            }
+        }
     }
 }

+ 9 - 0
CardCollector/Resources/Command.Designer.cs

@@ -492,6 +492,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ACI.
+        /// </summary>
+        internal static string show_top_by {
+            get {
+                return ResourceManager.GetString("show_top_by", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABA.
         /// </summary>

+ 3 - 0
CardCollector/Resources/Command.resx

@@ -183,4 +183,7 @@
     <data name="upload_stickerpack" xml:space="preserve">
         <value>ACH</value>
     </data>
+    <data name="show_top_by" xml:space="preserve">
+        <value>ACI</value>
+    </data>
 </root>

+ 10 - 0
CardCollector/Resources/Keyboard.cs

@@ -686,5 +686,15 @@ namespace CardCollector.Resources
                 InlineKeyboardButton.WithCallbackData(Text.arrow_right, $"{Command.logs_menu}={date.AddDays(-1)}"),
             });
         }
+
+        public static InlineKeyboardMarkup GetTopButton(TopBy topBy)
+        {
+            return new InlineKeyboardMarkup(new[]
+            {
+                InlineKeyboardButton.WithCallbackData(
+                    TopByTexts.ResourceManager.GetString(((int) topBy).ToString()) ?? string.Empty,
+                    $"{Command.show_top_by}={(int) topBy}")
+            });
+        }
     }
 }

+ 9 - 0
CardCollector/Resources/Messages.Designer.cs

@@ -956,6 +956,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ⭐&lt;b&gt;Топ пользователей по количеству 4⭐ стикеров&lt;/b&gt;⭐.
+        /// </summary>
+        internal static string users_top_tier_4_stickers_count {
+            get {
+                return ResourceManager.GetString("users_top_tier_4_stickers_count", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Вы уже воспользовались данным предложением!.
         /// </summary>

+ 3 - 0
CardCollector/Resources/Messages.resx

@@ -366,4 +366,7 @@ https://telegra.ph/help-11-04-4</value>
     <data name="thanks_for_buying_sticker" xml:space="preserve">
         <value>{0}: спасибо, что купил у меня стикер!</value>
     </data>
+    <data name="users_top_tier_4_stickers_count" xml:space="preserve">
+        <value>⭐&lt;b&gt;Топ пользователей по количеству 4⭐ стикеров&lt;/b&gt;⭐</value>
+    </data>
 </root>

+ 9 - 0
CardCollector/Resources/Text.Designer.cs

@@ -861,6 +861,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Стикеров.
+        /// </summary>
+        internal static string stickers {
+            get {
+                return ResourceManager.GetString("stickers", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Остановить.
         /// </summary>

+ 3 - 0
CardCollector/Resources/Text.resx

@@ -318,4 +318,7 @@
     <data name="trader" xml:space="preserve">
         <value>Продавец</value>
     </data>
+    <data name="stickers" xml:space="preserve">
+        <value>Стикеров</value>
+    </data>
 </root>

+ 8 - 0
CardCollector/Resources/TopBy.cs

@@ -0,0 +1,8 @@
+namespace CardCollector.Resources
+{
+    public enum TopBy
+    {
+        Exp = 1,
+        Tier4Stickers = 2
+    }
+}

+ 81 - 0
CardCollector/Resources/TopByTexts.Designer.cs

@@ -0,0 +1,81 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace CardCollector.Resources {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class TopByTexts {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal TopByTexts() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CardCollector.Resources.TopByTexts", typeof(TopByTexts).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Топ по опыту 🧪.
+        /// </summary>
+        internal static string _1 {
+            get {
+                return ResourceManager.GetString("1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Топ по стикерам 4⭐.
+        /// </summary>
+        internal static string _2 {
+            get {
+                return ResourceManager.GetString("2", resourceCulture);
+            }
+        }
+    }
+}

+ 27 - 0
CardCollector/Resources/TopByTexts.resx

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<root>
+    <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+        <xsd:element name="root" msdata:IsDataSet="true">
+            
+        </xsd:element>
+    </xsd:schema>
+    <resheader name="resmimetype">
+        <value>text/microsoft-resx</value>
+    </resheader>
+    <resheader name="version">
+        <value>1.3</value>
+    </resheader>
+    <resheader name="reader">
+        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    </resheader>
+    <resheader name="writer">
+        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    </resheader>
+    <data name="1" xml:space="preserve">
+        <value>Топ по опыту 🧪</value>
+    </data>
+    <data name="2" xml:space="preserve">
+        <value>Топ по стикерам 4⭐</value>
+    </data>
+</root>

+ 1 - 1
CardCollector/StickerEffects/AuctionDiscount5.cs

@@ -7,7 +7,7 @@ namespace CardCollector.StickerEffects
 {
     public static class AuctionDiscount5
     {
-        public static async Task<bool> IsApplied(Dictionary<string, UserStickerRelationEntity> stickers)
+        public static async Task<bool> IsApplied(Dictionary<string, UserStickerRelation> stickers)
         {
             return await stickers.AnyAsync(async item =>
             {

+ 1 - 1
CardCollector/StickerEffects/Random1Pack5Day.cs

@@ -12,7 +12,7 @@ namespace CardCollector.StickerEffects
     {
         public static int Interval = 5;
 
-        public static async Task<int> GetPacksCount(Dictionary<string, UserStickerRelationEntity> stickers)
+        public static async Task<int> GetPacksCount(Dictionary<string, UserStickerRelation> stickers)
         {
             var today = DateTime.Today.ToString(CultureInfo.CurrentCulture);
             var stickersWithEffect = (await StickerDao.GetListWhere(

+ 1 - 1
CardCollector/StickerEffects/RandomSticker1Tier2Day.cs

@@ -12,7 +12,7 @@ namespace CardCollector.StickerEffects
     {
         public static int Interval = 2;
 
-        public static async Task<int> GetStickersCount(Dictionary<string, UserStickerRelationEntity> stickers)
+        public static async Task<int> GetStickersCount(Dictionary<string, UserStickerRelation> stickers)
         {
             var today = DateTime.Today.ToString(CultureInfo.CurrentCulture);
             var stickersWithEffect = (await StickerDao.GetListWhere(

+ 1 - 1
CardCollector/StickerEffects/RandomSticker2Tier3Day.cs

@@ -12,7 +12,7 @@ namespace CardCollector.StickerEffects
     {
         public static int Interval = 3;
 
-        public static async Task<int> GetStickersCount(Dictionary<string, UserStickerRelationEntity> stickers)
+        public static async Task<int> GetStickersCount(Dictionary<string, UserStickerRelation> stickers)
         {
             var today = DateTime.Today.ToString(CultureInfo.CurrentCulture);
             var stickersWithEffect = (await StickerDao.GetListWhere(

+ 10 - 3
CardCollector/TimerTasks/DailyTaskAlert.cs

@@ -9,20 +9,27 @@ namespace CardCollector.TimerTasks
 {
     public class DailyTaskAlert : TimerTask
     {
-        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(12, 24, 0) : new TimeSpan(10, 0, 0);
+        protected override TimeSpan RunAt => Constants.DEBUG 
+            ? new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes + 1, 0)
+            : new TimeSpan(10, 0, 0);
         
         protected override async void TimerCallback(object o, ElapsedEventArgs e)
         {
             var users = await UserDao.GetAllWhere(user => !user.IsBlocked);
             var settings = await SettingsDao.GetAll();
+            var messages = await UserMessagesDao.GetAll();
             foreach (var user in users)
             {
                 try {
                     if (settings[user.Id][UserSettingsEnum.DailyTasks])
-                        await MessageController.SendMessage(user, Messages.daily_task_alertation, addToList: false);
+                        messages[user.Id].DailyTaskId = 
+                            await MessageController.DeleteAndSend(user, messages[user.Id].DailyTaskId,
+                                Messages.daily_task_alertation);
                 }
                 catch {
-                    await MessageController.SendMessage(user, Messages.daily_task_alertation, addToList: false);
+                    messages[user.Id].DailyTaskId = 
+                        await MessageController.DeleteAndSend(user, messages[user.Id].DailyTaskId,
+                            Messages.daily_task_alertation);
                 }
             }
         }

+ 8 - 5
CardCollector/TimerTasks/PiggyBankAlert.cs

@@ -10,13 +10,14 @@ namespace CardCollector.TimerTasks
     public class PiggyBankAlert : TimerTask
     {
         protected override TimeSpan RunAt => Constants.DEBUG
-                ? new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes + 10, 0)
+                ? new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes + 1, 0)
                 : new TimeSpan((DateTime.Now.TimeOfDay.Hours / 4 + 1) * 4, 0, 0);
         
         protected override async void TimerCallback(object o, ElapsedEventArgs e)
         {
             var users = await UserDao.GetAllWhere(user => !user.IsBlocked);
             var settings = await SettingsDao.GetAll();
+            var messages = await UserMessagesDao.GetAll();
             foreach (var user in users)
             {
                 var cash = await CashDao.GetById(user.Id);
@@ -24,12 +25,14 @@ namespace CardCollector.TimerTasks
                 var income = await cash.CalculateIncome(stickers);
                 try {
                     if (settings[user.Id][UserSettingsEnum.PiggyBankCapacity])
-                        await MessageController.SendMessage(user, 
-                            $"{Messages.uncollected_income}: {income} / {cash.MaxCapacity} {Text.coin}", addToList: false);
+                        messages[user.Id].CollectIncomeId = 
+                        await MessageController.DeleteAndSend(user, messages[user.Id].CollectIncomeId,
+                            $"{Messages.uncollected_income}: {income} / {cash.MaxCapacity} {Text.coin}");
                 }
                 catch {
-                    await MessageController.SendMessage(user, 
-                        $"{Messages.uncollected_income}: {income} / {cash.MaxCapacity} {Text.coin}", addToList: false);
+                    messages[user.Id].CollectIncomeId = 
+                        await MessageController.DeleteAndSend(user, messages[user.Id].CollectIncomeId,
+                            $"{Messages.uncollected_income}: {income} / {cash.MaxCapacity} {Text.coin}");
                 }
             }
         }

+ 10 - 3
CardCollector/TimerTasks/TopExpUsersAlert.cs

@@ -10,13 +10,16 @@ namespace CardCollector.TimerTasks
 {
     public class TopExpUsersAlert : TimerTask
     {
-        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(13, 41, 20) : new TimeSpan(10, 30, 0);
+        protected override TimeSpan RunAt => Constants.DEBUG 
+            ? new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes + 1, 0)
+            : new TimeSpan(10, 30, 0);
         
         protected override async void TimerCallback(object o, ElapsedEventArgs e)
         {
             var usersExp = await UserLevelDao.GetTop(5);
             var users = await UserDao.GetAllWhere(item => !item.IsBlocked);
             var settings = await SettingsDao.GetAll();
+            var messages = await UserMessagesDao.GetAll();
             var message = Messages.users_top_exp;
             foreach (var (userLevel, index) in usersExp.WithIndex())
             {
@@ -27,10 +30,14 @@ namespace CardCollector.TimerTasks
             {
                 try {
                     if (settings[user.Id][UserSettingsEnum.DailyExpTop])
-                        await MessageController.SendMessage(user, message, parseMode: ParseMode.Html, addToList: false);
+                        messages[user.Id].TopUsersId = 
+                            await MessageController.DeleteAndSend(user, messages[user.Id].TopUsersId,
+                                message, Keyboard.GetTopButton(TopBy.Tier4Stickers), ParseMode.Html);
                 }
                 catch {
-                    await MessageController.SendMessage(user, message, parseMode: ParseMode.Html, addToList: false);
+                    messages[user.Id].TopUsersId = 
+                        await MessageController.DeleteAndSend(user, messages[user.Id].TopUsersId,
+                            message, Keyboard.GetTopButton(TopBy.Tier4Stickers), ParseMode.Html);
                 }
             }
         }