Browse Source

Alert settings. Top 5 by exp. Piggy Bank alert. Refactore code

Tigran 3 years ago
parent
commit
11056a141d
56 changed files with 894 additions and 377 deletions
  1. 9 27
      CardCollector/Bot.cs
  2. 24 0
      CardCollector/Commands/CallbackQuery/Alerts.cs
  3. 23 0
      CardCollector/Commands/CallbackQuery/Settings.cs
  4. 2 0
      CardCollector/Commands/CallbackQueryCommand.cs
  5. 4 2
      CardCollector/Commands/ChosenInlineResult/GiveExp.cs
  6. 2 2
      CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs
  7. 1 1
      CardCollector/Commands/InlineQuery/ShowAuctionStickers.cs
  8. 2 2
      CardCollector/Commands/InlineQuery/ShowCollectionStickers.cs
  9. 0 1
      CardCollector/Commands/Message/Collection.cs
  10. 0 1
      CardCollector/Commands/Message/DownloadStickerPack.cs
  11. 5 11
      CardCollector/Commands/Message/GiveExp.cs
  12. 2 1
      CardCollector/Commands/Message/Menu.cs
  13. 1 1
      CardCollector/Commands/Message/Start.cs
  14. 1 1
      CardCollector/Commands/PreCheckoutQuery/BuyGems.cs
  15. 1 1
      CardCollector/Controllers/AuctionController.cs
  16. 30 52
      CardCollector/Controllers/MessageController.cs
  17. 10 4
      CardCollector/DailyTasks/CustomTasks/SendStickers.cs
  18. 3 10
      CardCollector/DailyTasks/DailyTask.cs
  19. 54 37
      CardCollector/DataBase/BotDatabase.cs
  20. 1 1
      CardCollector/DataBase/Entity/AuctionEntity.cs
  21. 27 0
      CardCollector/DataBase/Entity/StickerEntity.cs
  22. 3 2
      CardCollector/DataBase/Entity/UserEntity.cs
  23. 70 0
      CardCollector/DataBase/Entity/UserSettings.cs
  24. 15 13
      CardCollector/DataBase/EntityDao/AuctionDao.cs
  25. 3 11
      CardCollector/DataBase/EntityDao/CashDao.cs
  26. 9 9
      CardCollector/DataBase/EntityDao/DailyTaskDao.cs
  27. 1 8
      CardCollector/DataBase/EntityDao/LevelDao.cs
  28. 5 5
      CardCollector/DataBase/EntityDao/PacksDao.cs
  29. 3 6
      CardCollector/DataBase/EntityDao/SessionTokenDao.cs
  30. 31 0
      CardCollector/DataBase/EntityDao/SettingsDao.cs
  31. 6 13
      CardCollector/DataBase/EntityDao/ShopDao.cs
  32. 3 4
      CardCollector/DataBase/EntityDao/SpecialOfferUsersDao.cs
  33. 14 10
      CardCollector/DataBase/EntityDao/StickerDao.cs
  34. 11 7
      CardCollector/DataBase/EntityDao/UserDao.cs
  35. 9 8
      CardCollector/DataBase/EntityDao/UserLevelDao.cs
  36. 5 5
      CardCollector/DataBase/EntityDao/UserPacksDao.cs
  37. 6 34
      CardCollector/DataBase/EntityDao/UserStickerRelationDao.cs
  38. 1 33
      CardCollector/Extensions.cs
  39. 18 0
      CardCollector/Resources/Command.Designer.cs
  40. 6 0
      CardCollector/Resources/Command.resx
  41. 16 10
      CardCollector/Resources/Constants.cs
  42. 43 1
      CardCollector/Resources/Keyboard.cs
  43. 49 4
      CardCollector/Resources/Messages.Designer.cs
  44. 18 3
      CardCollector/Resources/Messages.resx
  45. 81 0
      CardCollector/Resources/Text.Designer.cs
  46. 27 0
      CardCollector/Resources/Text.resx
  47. 6 5
      CardCollector/Session/Modules/FiltersModule.cs
  48. 2 2
      CardCollector/Session/Session.cs
  49. 30 0
      CardCollector/TimerTasks/DailyTaskAlert.cs
  50. 19 0
      CardCollector/TimerTasks/DailyTaskReset.cs
  51. 42 13
      CardCollector/TimerTasks/ExecuteStickerEffects.cs
  52. 35 0
      CardCollector/TimerTasks/PiggyBankAlert.cs
  53. 17 0
      CardCollector/TimerTasks/ResetChatGiveExp.cs
  54. 50 0
      CardCollector/TimerTasks/TimerTask.cs
  55. 38 0
      CardCollector/TimerTasks/TopExpUsersAlert.cs
  56. 0 16
      CardCollector/Utilities.cs

+ 9 - 27
CardCollector/Bot.cs

@@ -3,16 +3,12 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Timers;
-using CardCollector.Commands;
-using CardCollector.Commands.Message;
-using CardCollector.DailyTasks;
 using CardCollector.DataBase;
 using CardCollector.DataBase.EntityDao;
 using CardCollector.Resources;
-using CardCollector.StickerEffects;
+using CardCollector.TimerTasks;
 using Telegram.Bot;
 using Telegram.Bot.Types;
-using CancellationTokenSource = System.Threading.CancellationTokenSource;
 using Timer = System.Timers.Timer;
 
 namespace CardCollector
@@ -40,22 +36,15 @@ namespace CardCollector
         public static void Main(string[] args)
         {
             Logs.LogOut("Bot started");
-            var cts = new CancellationTokenSource();
-            Client.StartReceiving(HandleUpdateAsync, HandleErrorAsync, cancellationToken: cts.Token);
-            Client.SetMyCommandsAsync(_commands, BotCommandScope.AllPrivateChats(), cancellationToken: cts.Token);
             
             _timer.Elapsed += SavingChanges;
             _timer.Elapsed += UserDao.ClearMemory;
+            TimerTask.SetupAll();
             
-            /* Запускаем механизм уведомления */
-            Utilities.SetUpTimer(Constants.DailyTaskAlert, DailyTaskAlert);
-            /* Запускаем сброс ежедневных заданий */
-            Utilities.SetUpTimer(Constants.DailyTaskReset, DailyTask.ResetTasks);
-            /* Запускаем таймер с эффектами стикеров */
-            Utilities.SetUpTimer(Constants.DailyStickerRewardCheck, EffectFunctions.RunAll);
-            /* Запускаем таймер сброса отправленных стикеров */
-            Utilities.SetUpTimer(Constants.ResetGroupStickersExp, GiveExp.ResetStickersExp);
-            
+            var cts = new CancellationTokenSource();
+            Client.StartReceiving(HandleUpdateAsync, HandleErrorAsync, cancellationToken: cts.Token);
+            Client.SetMyCommandsAsync(_commands, BotCommandScope.AllPrivateChats(), cancellationToken: cts.Token);
+
             _end.WaitOne();
             Logs.LogOut("Stopping program");
             
@@ -64,7 +53,7 @@ namespace CardCollector
 
         public static async Task StopProgram()
         {
-            await CardCollectorDatabase.SaveAllChangesAsync();
+            await BotDatabase.SaveData();
             await UserDao.ClearMemory();
             _end.Set();
         }
@@ -72,15 +61,8 @@ namespace CardCollector
         private static async void SavingChanges(object o, ElapsedEventArgs e)
         {
             try {
-                await CardCollectorDatabase.SaveAllChangesAsync();
-            } catch (Exception) { /*ignored*/ }
-        }
-
-        private static async void DailyTaskAlert(object o, ElapsedEventArgs e)
-        {
-            var users = await UserDao.GetAllWhere(user => Task.FromResult(!user.IsBlocked));
-            foreach (var user in users)
-                await SendMessage(user, Messages.daily_task_alertation);
+                await BotDatabase.SaveData();
+            } catch (Exception) { /**/ }
         }
     }
 }

+ 24 - 0
CardCollector/Commands/CallbackQuery/Alerts.cs

@@ -0,0 +1,24 @@
+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.CallbackQuery
+{
+    public class Alerts : CallbackQueryCommand
+    {
+        protected override string CommandText => Command.alerts;
+        
+        public override async Task Execute()
+        {
+            var data = CallbackData.Split('=');
+            if (data.Length > 1) User.Settings.SwitchProperty((UserSettingsEnum) int.Parse(data[1]));
+            await MessageController.EditMessage(User, Messages.alerts, Keyboard.Alerts(User.Settings), ParseMode.Html);
+        }
+
+        public Alerts() { }
+        public Alerts(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 23 - 0
CardCollector/Commands/CallbackQuery/Settings.cs

@@ -0,0 +1,23 @@
+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.CallbackQuery
+{
+    public class Settings : CallbackQueryCommand
+    {
+        protected override string CommandText => Command.settings;
+        protected override bool AddToStack => true;
+
+        public override async Task Execute()
+        {
+            await MessageController.EditMessage(User, Messages.settings, Keyboard.Settings, ParseMode.Html);
+        }
+
+        public Settings() { }
+        public Settings(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 2 - 0
CardCollector/Commands/CallbackQueryCommand.cs

@@ -60,6 +60,8 @@ namespace CardCollector.Commands
             new SpecialOffers(),
             new SelectTier(),
             new SelectShopPack(),
+            new Alerts(),
+            new Settings(),
         };
 
         /* Метод, создающий объекты команд исходя из полученного обновления */

+ 4 - 2
CardCollector/Commands/ChosenInlineResult/GiveExp.cs

@@ -16,8 +16,10 @@ namespace CardCollector.Commands.ChosenInlineResult
         {
             if (!User.Session.ChosenResultWithMessage)
             {
-                await MessageController.SendMessage(User, $"{Messages.you_gained} 1 {Text.exp} {Messages.send_sticker}" +
-                                                          $"\n{Messages.you_can_add_bot_to_conversation}", addToList: true);
+                if (User.Settings[UserSettingsEnum.ExpGain])
+                    await MessageController.SendMessage(User, 
+                        $"{Messages.you_gained} 1 {Text.exp} {Messages.send_sticker}" +
+                        $"\n{Messages.you_can_add_bot_to_conversation}");
                 await User.GiveExp(1);
             }
             User.Session.ChosenResultWithMessage = false;

+ 2 - 2
CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs

@@ -14,9 +14,9 @@ namespace CardCollector.Commands.ChosenInlineResult
         public override async Task Execute()
         {
             var dailyTask = DailyTask.List[DailyTaskKeys.SendStickersToUsers];
-            if (await dailyTask.Execute(User.Id))
+            if (await dailyTask.Execute(User))
             {
-                await dailyTask.GiveReward(User.Id);
+                await dailyTask.GiveReward(User);
                 await MessageController.EditMessage(User, Messages.pack_prize);
             }
         }

+ 1 - 1
CardCollector/Commands/InlineQuery/ShowAuctionStickers.cs

@@ -17,7 +17,7 @@ namespace CardCollector.Commands.InlineQuery
             // Получаем список стикеров
             var stickersList = await AuctionController.GetStickers(Query);
             var filters = User.Session.GetModule<FiltersModule>();
-            var results = filters.ApplyTo(stickersList, true).ToTelegramResults(Command.select_sticker);
+            var results = (await filters.ApplyTo(stickersList, true)).ToTelegramResults(Command.select_sticker);
             // Посылаем пользователю ответ на его запрос
             await MessageController.AnswerInlineQuery(InlineQueryId, results);
         }

+ 2 - 2
CardCollector/Commands/InlineQuery/ShowCollectionStickers.cs

@@ -16,8 +16,8 @@ namespace CardCollector.Commands.InlineQuery
         {
             // Получаем список стикеров
             var stickersList = await User.GetStickersList(Query);
-            var results = User.Session.GetModule<FiltersModule>()
-                .ApplyTo(stickersList).ToTelegramResults(Command.select_sticker);
+            var results = (await User.Session.GetModule<FiltersModule>()
+                .ApplyTo(stickersList)).ToTelegramResults(Command.select_sticker);
             // Посылаем пользователю ответ на его запрос
             await MessageController.AnswerInlineQuery(InlineQueryId, results);
         }

+ 0 - 1
CardCollector/Commands/Message/Collection.cs

@@ -1,7 +1,6 @@
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
-using CardCollector.Session.Modules;
 using Telegram.Bot.Types;
 
 namespace CardCollector.Commands.Message

+ 0 - 1
CardCollector/Commands/Message/DownloadStickerPack.cs

@@ -2,7 +2,6 @@
 using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
-using CardCollector.Session.Modules;
 using Telegram.Bot.Types;
 
 namespace CardCollector.Commands.Message

+ 5 - 11
CardCollector/Commands/Message/GiveExp.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Threading.Tasks;
-using System.Timers;
 using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
@@ -14,7 +13,7 @@ namespace CardCollector.Commands.Message
     {
         protected override string CommandText => "";
 
-        private static readonly Dictionary<long, Dictionary<long, int>> GroupStickersExp = new();
+        public static readonly Dictionary<long, Dictionary<long, int>> GroupStickersExp = new();
 
         public override async Task Execute()
         {
@@ -30,17 +29,12 @@ namespace CardCollector.Commands.Message
                 GroupStickersExp[chatId][User.Id]++;
                 var membersCount = await Bot.Client.GetChatMemberCountAsync(chatId) - 1;
                 await User.GiveExp(membersCount < 21 ? membersCount : 20);
-                await MessageController.SendMessage(User, 
-                    $"{Messages.you_gained} {(membersCount < 21 ? membersCount : 20)} {Text.exp} {Messages.send_sticker}" +
-                    $"\n{Messages.count_sends_per_day} \"{Update.Message.Chat.Title}\" {GroupStickersExp[chatId][User.Id]} / 5",
-                    addToList: true);
+                if (User.Settings[UserSettingsEnum.ExpGain])
+                    await MessageController.SendMessage(User, 
+                        $"{Messages.you_gained} {(membersCount < 21 ? membersCount : 20)} {Text.exp} {Messages.send_sticker}" +
+                        $"\n{Messages.count_sends_per_day} \"{Update.Message.Chat.Title}\" {GroupStickersExp[chatId][User.Id]} / 5");
             }
         }
-        
-        public static void ResetStickersExp(object o, ElapsedEventArgs e)
-        {
-            GroupStickersExp.Clear();
-        }
 
         protected internal override bool IsMatches(UserEntity user, Update update)
         {

+ 2 - 1
CardCollector/Commands/Message/Menu.cs

@@ -3,6 +3,7 @@ using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
 using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
 
 namespace CardCollector.Commands.Message
 {
@@ -13,7 +14,7 @@ namespace CardCollector.Commands.Message
         public override async Task Execute()
         {
             /* Отправляем пользователю сообщение со стандартной клавиатурой */
-            await MessageController.SendMessage(User, Messages.menu_message, Keyboard.Menu);
+            await MessageController.SendMessage(User, Messages.main_menu, Keyboard.Menu, ParseMode.Html, false);
         }
 
         public Menu() { }

+ 1 - 1
CardCollector/Commands/Message/Start.cs

@@ -15,7 +15,7 @@ namespace CardCollector.Commands.Message
         public override async Task Execute()
         {
             /* Отправляем пользователю сообщение со стандартной клавиатурой */
-            await MessageController.EditMessage(User, Messages.start_message, Keyboard.Menu);
+            await MessageController.SendMessage(User, Messages.start_message, Keyboard.Menu, addToList: false);
         }
         
         public Start() { }

+ 1 - 1
CardCollector/Commands/PreCheckoutQuery/BuyGems.cs

@@ -18,7 +18,7 @@ namespace CardCollector.Commands.PreCheckoutQuery
             User.Cash.Gems += gemsCount;
             await MessageController.EditMessage(User, Messages.thanks_for_buying_gems);
             await MessageController.SendMessage(User, 
-                $"{Messages.you_gained} {gemsCount * 2} {Text.exp} {Messages.buy_gems}", addToList: true);
+                $"{Messages.you_gained} {gemsCount * 2} {Text.exp} {Messages.buy_gems}");
             await User.GiveExp(gemsCount * 2);
         }
 

+ 1 - 1
CardCollector/Controllers/AuctionController.cs

@@ -50,7 +50,7 @@ namespace CardCollector.Controllers
 
         public static async Task<int> GetStickerCount(int productId)
         {
-            return await AuctionDao.GetQuantity(productId);
+            return await AuctionDao.GetCount(productId);
         }
 
         public static async Task<int> GetStickerCount(string stickerId, FiltersModule sessionFilters)

+ 30 - 52
CardCollector/Controllers/MessageController.cs

@@ -85,50 +85,29 @@ namespace CardCollector.Controllers
          user - пользователь, которому необходимо отправить сообщение
          message - текст сообщения
          keyboard - клавиатура, которую надо добавить к сообщению */
-        public static async Task<Message> EditMessage(UserEntity user, string message, IReplyMarkup keyboard = null,
+        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)
-                            return await Bot.Client.EditMessageTextAsync(user.ChatId, user.Session.Messages.Last(),
-                                message, parseMode, 
-                                replyMarkup: (InlineKeyboardMarkup)keyboard ?? InlineKeyboardMarkup.Empty());
-                        var result = await Bot.Client.SendTextMessageAsync(user.ChatId, message, parseMode,
-                            replyMarkup: keyboard, disableNotification: true);
-                        user.Session?.Messages.Add(result.MessageId);
-                        return result;
-                }
-                catch (Exception e)
-                {
-                    try
-                    {
-                        await user.ClearChat();
-                        var result = await Bot.Client.SendTextMessageAsync(user.ChatId, message, parseMode,
-                            replyMarkup: keyboard, disableNotification: true);
-                        user.Session?.Messages.Add(result.MessageId);
-                        return result;
-                    }
-                    catch (Exception)
-                    {
-                        LogOutWarning("Can't edit text message " + e.Message);
-                    }
-                }
-            return new Message();
+            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 SendMessage(UserEntity user, string message, IReplyMarkup keyboard = null,
-            ParseMode? parseMode = null, bool addToList = false)
+            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) { /**/ }
+            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); }
         }
         
         /* Метод для отправки стикера
@@ -136,18 +115,16 @@ namespace CardCollector.Controllers
          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();
         }
 
@@ -187,11 +164,12 @@ namespace CardCollector.Controllers
         /* Метод для удаления сообщения
          user - пользователь, которому необходимо удалить сообщение
          messageId - Id сообщения */
-        public static async Task DeleteMessage(UserEntity user, int messageId)
+        public static async Task DeleteMessage(UserEntity user, int messageId, bool sticker = false)
         {
             try
             {
-                user.Session.Messages.Remove(messageId);
+                if (sticker) user.Session.StickerMessages.Remove(messageId);
+                else user.Session.Messages.Remove(messageId);
                 if (!user.IsBlocked)
                     await Bot.Client.DeleteMessageAsync(user.ChatId, messageId);
             }

+ 10 - 4
CardCollector/DailyTasks/CustomTasks/SendStickers.cs

@@ -1,5 +1,8 @@
 using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
 
 namespace CardCollector.DailyTasks.CustomTasks
 {
@@ -10,17 +13,20 @@ namespace CardCollector.DailyTasks.CustomTasks
         public override string Title => Titles.send_stickers;
         public override string Description => Descriptions.send_stickers;
 
-        public override async Task<bool> Execute(long userId, object[] args = null)
+        public override async Task<bool> Execute(UserEntity user, object[] args = null)
         {
-            var task = await DailyTaskDao.GetTaskInfo(userId, Id);
+            var task = await DailyTaskDao.GetTaskInfo(user.Id, Id);
             if (task.Progress == 0) return false;
             task.Progress--;
+            if (user.Settings[UserSettingsEnum.DailyTaskProgress])
+                await MessageController.SendMessage(user,
+                    $"{Messages.send_sticker_progress}: {Goal - task.Progress} / {Goal}", addToList: false);
             return task.Progress == 0;
         }
 
-        public override async Task GiveReward(long userId, object[] args = null)
+        public override async Task GiveReward(UserEntity user, object[] args = null)
         {
-            var userPacks = await UserPacksDao.GetOne(userId, 1);
+            var userPacks = await UserPacksDao.GetOne(user.Id, 1);
             userPacks.Count++;
         }
     }

+ 3 - 10
CardCollector/DailyTasks/DailyTask.cs

@@ -1,8 +1,7 @@
 using System.Collections.Generic;
 using System.Threading.Tasks;
-using System.Timers;
 using CardCollector.DailyTasks.CustomTasks;
-using CardCollector.DataBase.EntityDao;
+using CardCollector.DataBase.Entity;
 
 namespace CardCollector.DailyTasks
 {
@@ -23,13 +22,7 @@ namespace CardCollector.DailyTasks
         public abstract string Title { get; }
         public abstract string Description { get; }
 
-        public abstract Task<bool> Execute(long userId, object[] args = null);
-        public abstract Task GiveReward(long userId, object[] args = null);
-
-        public static async void ResetTasks(object o, ElapsedEventArgs e)
-        {
-            await foreach (var item in DailyTaskDao.GetAll())
-                item.Progress = List[(DailyTaskKeys) item.TaskId].Goal;
-        }
+        public abstract Task<bool> Execute(UserEntity user, object[] args = null);
+        public abstract Task GiveReward(UserEntity user, object[] args = null);
     }
 }

+ 54 - 37
CardCollector/DataBase/CardCollectorDatabase.cs → CardCollector/DataBase/BotDatabase.cs

@@ -1,64 +1,41 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using Microsoft.EntityFrameworkCore;
 
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
 namespace CardCollector.DataBase
 {
     using static Resources.AppSettings;
     
     /* Предоставляет доступ к базе данных */
-    public class CardCollectorDatabase : DbContext
+    public class BotDatabase : DbContext
     {
         /* Скрываем конструктор, чтобы его нельзя было использовать извне */
-        private CardCollectorDatabase() { }
+        private BotDatabase() { }
+        protected DateTime _lastSave = DateTime.Now;
         
         /* Объект базы данных */
-        private static CardCollectorDatabase _instance;
+        private static List<BotDatabase> _instances = new();
         
         /* Предоставляет доступ к объекту */
-        public static CardCollectorDatabase Instance
+        public static BotDatabase Instance
         {
             get
             {
-                if (_instance != null) return _instance;
-                _instance = new CardCollectorDatabase();
-                _instance.Database.EnsureCreated();
-                return _instance;
+                var instance = new BotDatabase();
+                _instances.Add(instance);
+                return instance;
             }
         }
 
-        private static readonly Dictionary<Type, CardCollectorDatabase> _specificInstances = new ();
-
-        public static CardCollectorDatabase GetSpecificInstance(Type type)
-        {
-            try
-            {
-                return _specificInstances[type];
-            }
-            catch (Exception)
-            {
-                var newInstance = new CardCollectorDatabase();
-                _specificInstances.Add(type, newInstance);
-                newInstance.Database.EnsureCreated();
-                return newInstance;
-            }
-        }
-
-        public static async Task SaveAllChangesAsync()
-        {
-            try
-            {
-                await Instance.SaveChangesAsync();
-                foreach (var instance in _specificInstances.Values)
-                    await instance.SaveChangesAsync();
-            } catch (Exception) { /* Ignored */ }
-        }
-
         /* Таблицы базы данных, представленные Entity объектами */
         public DbSet<UserEntity> Users { get; set; }
-        public DbSet<CashEntity> CashTable { get; set; }
+        public DbSet<CashEntity> Cash { get; set; }
         public DbSet<UserStickerRelationEntity> UserStickerRelations { get; set; }
         public DbSet<StickerEntity> Stickers { get; set; }
         public DbSet<AuctionEntity> Auction { get; set; }
@@ -70,7 +47,7 @@ namespace CardCollector.DataBase
         public DbSet<SessionToken> SessionTokens { get; set; }
         public DbSet<UserLevel> UserLevel { get; set; }
         public DbSet<Level> Levels { get; set; }
-
+        public DbSet<UserSettings> Settings { get; set; }
 
         /* Конфигурация подключения к БД */
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -83,5 +60,45 @@ namespace CardCollector.DataBase
                 $"pwd={DB_PWD}"
             );
         }
+
+        public static async Task SaveData()
+        {
+            foreach (var instance in _instances.ToList())
+            {
+                await instance.SaveChangesAsync();
+                var activityInterval = instance._lastSave - DateTime.Now;
+                if (activityInterval.TotalMinutes > 120)
+                {
+                    _instances.Remove(instance);
+                    await instance.DisposeAsync();
+                }
+            }
+        }
+
+        public override void Dispose()
+        {
+            SaveChanges();
+            base.Dispose();
+        }
+
+        public override async ValueTask DisposeAsync()
+        {
+            await SaveChangesAsync();
+            await base.DisposeAsync();
+        }
+
+        public override int SaveChanges()
+        { 
+            var count = base.SaveChanges();
+            if (count > 0) _lastSave = DateTime.Now;
+            return count;
+        }
+
+        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
+        {
+            var count = await base.SaveChangesAsync(cancellationToken);
+            if (count > 0) _lastSave = DateTime.Now;
+            return count;
+        }
     }
 }

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

@@ -34,7 +34,7 @@ namespace CardCollector.DataBase.Entity
             await MessageController.EditMessage(user, $"{Messages.you_sold} {sticker.Title} {count}{Text.items}" +
                                                       $"\n{Messages.you_collected} {gemsSum}{Text.gem}");
             user.Cash.Gems += gemsSum;
-            await CashDao.Save();
+            await BotDatabase.SaveData();
             if (Count == 0) await AuctionDao.DeleteRow(Id);
         }
     }

+ 27 - 0
CardCollector/DataBase/Entity/StickerEntity.cs

@@ -1,7 +1,10 @@
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
+using System.Globalization;
 using System.Linq;
+using System.Threading.Tasks;
+using CardCollector.Controllers;
 using CardCollector.Resources;
 using CardCollector.StickerEffects;
 
@@ -78,5 +81,29 @@ namespace CardCollector.DataBase.Entity
                   Emoji.Equals(value)
                 : true;
         }
+
+        public async Task ApplyEffect(UserEntity user, UserStickerRelationEntity relation)
+        {
+            switch ((Effect)Effect)
+            {
+                case StickerEffects.Effect.PiggyBank200:
+                    user.Cash.MaxCapacity += 200;
+                    await MessageController.EditMessage(user, Messages.effect_PiggyBank200);
+                    break;
+                case StickerEffects.Effect.Diamonds25Percent:
+                    user.Cash.Gems += (int)(user.Cash.Gems * 0.25);
+                    await MessageController.EditMessage(user, Messages.effect_Diamonds25Percent);
+                    break;
+                case StickerEffects.Effect.Random1Pack5Day:
+                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
+                    break;
+                case StickerEffects.Effect.RandomSticker1Tier3Day:
+                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
+                    break;
+                case StickerEffects.Effect.RandomSticker2Tier3Day:
+                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
+                    break;
+            }
+        }
     }
 }

+ 3 - 2
CardCollector/DataBase/Entity/UserEntity.cs

@@ -35,10 +35,11 @@ namespace CardCollector.DataBase.Entity
         [NotMapped] public CashEntity Cash { get; set; }
         /*Уровень пользователя*/
         [NotMapped] public UserLevel CurrentLevel { get; set; }
+        [NotMapped] public UserSettings Settings { get; set; }
         
         /* Стикеры пользователя */
         [NotMapped] public Dictionary<string, UserStickerRelationEntity> Stickers { get; set; }
-        
+
         /* Данные, хранящиеся в рамках одной сессии */
         [NotMapped] public UserSession Session;
 
@@ -76,7 +77,7 @@ namespace CardCollector.DataBase.Entity
                 var levelReward = levelInfo.GetRewardInstance();
                 var message = $"{Messages.congratulation_new_level} {CurrentLevel.Level}" +
                               $"\n{await levelReward.GetReward(this)}";
-                await MessageController.SendMessage(this, message);
+                await MessageController.SendMessage(this, message, addToList: false);
                 levelInfo = await LevelDao.GetLevel(CurrentLevel.Level + 1);
             }
         }

+ 70 - 0
CardCollector/DataBase/Entity/UserSettings.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+
+namespace CardCollector.DataBase.Entity
+{
+    [Table("user_settings")]
+    public class UserSettings
+    {
+        [Key, Column("id"), MaxLength(127)] public long UserId { get; set; }
+
+        [Column("settings"), MaxLength(512)] public string Settings {
+            get {
+                return Utilities.ToJson(settings.ToDictionary(item => (int) item.Key, item => Convert.ToInt32(item.Value)));
+            }
+            set
+            {
+                var dict = Utilities.FromJson<Dictionary<int, bool>>(value);
+                settings = dict.ToDictionary(item => (UserSettingsEnum) item.Key, item => Convert.ToBoolean(item.Value));
+            }
+        }
+
+        [NotMapped] private Dictionary<UserSettingsEnum, bool> settings = new ();
+
+        private bool GetProperty(UserSettingsEnum property)
+        {
+            if (!settings.ContainsKey(property))
+                settings.Add(property, true);
+            return settings[property];
+        }
+
+        private void SetProperty(UserSettingsEnum property, bool value)
+        {
+            if (!settings.ContainsKey(property))
+                settings.Add(property, true);
+            settings[property] = value;
+        }
+
+        public void SwitchProperty(UserSettingsEnum property)
+        {
+            if (!settings.ContainsKey(property))
+                settings.Add(property, true);
+            settings[property] = !settings[property];
+        }
+
+        public void InitProperties()
+        {
+            var props = Enum.GetValues<UserSettingsEnum>();
+            foreach (var prop in props) settings.Add(prop, true);
+        }
+
+        public bool this[UserSettingsEnum key]
+        {
+            get => GetProperty(key);
+            set => SetProperty(key, value);
+        }
+    }
+    
+    public enum UserSettingsEnum
+    {
+        DailyTasks,
+        ExpGain,
+        StickerEffects,
+        DailyTaskProgress,
+        PiggyBankCapacity,
+        DailyExpTop,
+    }
+}

+ 15 - 13
CardCollector/DataBase/EntityDao/AuctionDao.cs

@@ -10,15 +10,10 @@ namespace CardCollector.DataBase.EntityDao
 {
     public static class AuctionDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(AuctionDao));
-        /* Таблица auction в представлении Entity Framework */
-        private static readonly DbSet<AuctionEntity> Table = Instance.Auction;
-        
         public static async Task<List<AuctionEntity>> GetProducts(string stickerId)
         {
-            /* Заменил цикл на LINQ выражение и тип возвращаемого значения - список позиций,
-             так как один и тот же стикер может продавать несколько людей */
-            return (await Table.WhereAsync(e => Task.FromResult(e.StickerId == stickerId))).ToList();
+            var Table = BotDatabase.Instance.Auction;
+            return await Table.Where(e => e.StickerId == stickerId).ToListAsync();
         }
 
         public static async Task<int> GetTotalQuantity(string stickerId)
@@ -30,7 +25,9 @@ namespace CardCollector.DataBase.EntityDao
 
         public static async Task<IEnumerable<StickerEntity>> GetStickers(string filter)
         {
-            var entityList = (await Table.ToListAsync()).Select(async e => await StickerDao.GetById(e.StickerId));
+            var Table = BotDatabase.Instance.Auction;
+            var entityList = (await Table.ToListAsync())
+                .Select(async e => await StickerDao.GetById(e.StickerId));
             var stickersList = await Task.WhenAll(entityList);
             return stickersList
                 .Where(item => item.Contains(filter))
@@ -40,30 +37,35 @@ namespace CardCollector.DataBase.EntityDao
         //добавляем объект в аукцион
         public static async void AddNew(AuctionEntity product)
         {
+            var Table = BotDatabase.Instance.Auction;
             await Table.AddAsync(product);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
         }
         //удаляем проданный объект
         public static async Task DeleteRow(int productId)
         {
+            var Table = BotDatabase.Instance.Auction;
             if (await Table.FirstOrDefaultAsync(c => c.Id == productId) is not { } item) return;
             Table.Attach(item);
             Table.Remove(item);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
         }
 
-        public static bool HaveAny(string stickerId, Expression<Func<AuctionEntity, bool>> source)
+        public static async Task<bool> HaveAny(string stickerId, Expression<Func<AuctionEntity, bool>> source)
         {
-            return Table.Where(i => i.StickerId == stickerId).AnyAsync(source).Result;
+            var Table = BotDatabase.Instance.Auction;
+            return await Table.Where(i => i.StickerId == stickerId).AnyAsync(source);
         }
 
-        public static async Task<int> GetQuantity(int productId)
+        public static async Task<int> GetCount(int productId)
         {
+            var Table = BotDatabase.Instance.Auction;
             return (await Table.FirstAsync(item => item.Id == productId)).Count;
         }
 
         public static async Task<AuctionEntity> GetProduct(int productId)
         {
+            var Table = BotDatabase.Instance.Auction;
             return await Table.FirstAsync(item => item.Id == productId);
         }
     }

+ 3 - 11
CardCollector/DataBase/EntityDao/CashDao.cs

@@ -1,19 +1,15 @@
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
-using Microsoft.EntityFrameworkCore;
 
 namespace CardCollector.DataBase.EntityDao
 {
     /* Класс, позволяющий получить доступ к объектам таблицы Cash*/
     public static class CashDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(CashDao));
-        /* Таблица cash в представлении EntityFramework */
-        private static readonly DbSet<CashEntity> Table = Instance.CashTable;
-        
         /* Получение объекта по Id */
         public static async Task<CashEntity> GetById(long userId)
         {
+            var Table = BotDatabase.Instance.Cash;
             var user = await Table.FindAsync(userId);
             return user ?? await AddNew(userId);
         }
@@ -21,15 +17,11 @@ namespace CardCollector.DataBase.EntityDao
         /* Добавление нового объекта в систему */
         private static async Task<CashEntity> AddNew(long userId)
         {
+            var Table = BotDatabase.Instance.Cash;
             var cash = new CashEntity { UserId = userId };
             var result = await Table.AddAsync(cash);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
-
-        public static async Task Save()
-        {
-            await Instance.SaveChangesAsync();
-        }
     }
 }

+ 9 - 9
CardCollector/DataBase/EntityDao/DailyTaskDao.cs

@@ -9,17 +9,16 @@ namespace CardCollector.DataBase.EntityDao
 {
     public static class DailyTaskDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(DailyTaskDao));
-        private static readonly DbSet<DailyTaskEntity> Table = Instance.DailyTasks;
-        
-        public static IAsyncEnumerable<DailyTaskEntity> GetAll()
+        public static async Task<List<DailyTaskEntity>> GetAll()
         {
-            return Table;
+            var Table = BotDatabase.Instance.DailyTasks;
+            return await Table.ToListAsync();
         }
 
         /* Добавляет новое отношение в таблицу */
         public static async Task<DailyTaskEntity> AddNew(long userId, int taskId)
         {
+            var Table = BotDatabase.Instance.DailyTasks;
             if (await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.TaskId == taskId) is { } obj)
                 return obj;
             var newTask = new DailyTaskEntity()
@@ -29,20 +28,21 @@ namespace CardCollector.DataBase.EntityDao
                 Progress = DailyTask.List[(DailyTaskKeys)taskId].Goal
             };
             var result = await Table.AddAsync(newTask);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
 
         public static async Task<DailyTaskEntity> GetTaskInfo(long userId, int taskId)
         {
-            return await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.TaskId == taskId) 
+            var Table = BotDatabase.Instance.DailyTasks;
+            return await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.TaskId == taskId)
                    ?? await AddNew(userId, taskId);
         }
 
         public static async Task<Dictionary<int, DailyTaskEntity>> GetUserTasks(long userId)
         {
-            return (await Table.WhereAsync(item => Task.FromResult(item.UserId == userId)))
-                .ToDictionary(p => p.TaskId, p => p);
+            var Table = BotDatabase.Instance.DailyTasks;
+            return await Table.Where(item => item.UserId == userId).ToDictionaryAsync(p => p.TaskId, p => p);
         }
     }
 }

+ 1 - 8
CardCollector/DataBase/EntityDao/LevelDao.cs

@@ -7,13 +7,11 @@ namespace CardCollector.DataBase.EntityDao
 {
     public class LevelDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UserLevelDao));
-        /* Таблица cash в представлении EntityFramework */
-        private static readonly DbSet<Level> Table = Instance.Levels;
         
         /* Получение объекта по Id */
         public static async Task<Level?> GetLevel(int level)
         {
+            var Table = BotDatabase.Instance.Levels;
             return await Table.FirstOrDefaultAsync(item => item.LevelValue == level);
         }
 
@@ -25,10 +23,5 @@ namespace CardCollector.DataBase.EntityDao
             await Instance.SaveChangesAsync();
             return result.Entity;
         }*/
-
-        public static async Task Save()
-        {
-            await Instance.SaveChangesAsync();
-        }
     }
 }

+ 5 - 5
CardCollector/DataBase/EntityDao/PacksDao.cs

@@ -10,28 +10,28 @@ namespace CardCollector.DataBase.EntityDao
 {
     public static class PacksDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(PacksDao));
-        private static readonly DbSet<PackEntity> Table = Instance.Packs;
-
         public static async Task<PackEntity> GetById(int id)
         {
+            var Table = BotDatabase.Instance.Packs;
             return await Table.FirstOrDefaultAsync(item => item.Id == id);
         }
         
         public static async Task<PackEntity> AddNew(string author, string description = "")
         {
+            var Table = BotDatabase.Instance.Packs;
             var result = await Table.AddAsync(new PackEntity
             {
                 Author = author,
                 Description = description
             });
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
 
         public static async Task<List<PackEntity>> GetAll()
         {
-            var list = (await Table.WhereAsync(item => item.Id is not 1)).ToList();
+            var Table = BotDatabase.Instance.Packs;
+            var list = await Table.Where(item => item.Id != 1).ToListAsync();
             list.Sort(new AuthorComparer());
             return list;
         }

+ 3 - 6
CardCollector/DataBase/EntityDao/SessionTokenDao.cs

@@ -1,21 +1,18 @@
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
-using Microsoft.EntityFrameworkCore;
 
 namespace CardCollector.DataBase.EntityDao
 {
     public static class SessionTokenDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(SessionTokenDao));
-        /* Таблица auction в представлении Entity Framework */
-        private static readonly DbSet<SessionToken> Table = Instance.SessionTokens;
 
         public static async Task AddNew(long userId, string token)
         {
+            var Table = BotDatabase.Instance.SessionTokens;
             var result = await Table.AddAsync(new SessionToken {UserId = userId, Token = token});
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             Table.Attach(result.Entity);
-            Instance.ChangeTracker.Clear();
+            BotDatabase.Instance.ChangeTracker.Clear();
         }
     }
 }

+ 31 - 0
CardCollector/DataBase/EntityDao/SettingsDao.cs

@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using CardCollector.DataBase.Entity;
+using Microsoft.EntityFrameworkCore;
+
+namespace CardCollector.DataBase.EntityDao
+{
+    public static class SettingsDao
+    {
+        public static async Task<UserSettings> GetById(long userId)
+        {
+            var Table = BotDatabase.Instance.Settings;
+            return await Table.FirstOrDefaultAsync(item => item.UserId == userId) ?? await AddNew(userId);
+        }
+        
+        public static async Task<UserSettings> AddNew(long userId)
+        {
+            var Table = BotDatabase.Instance.Settings;
+            var entry = new UserSettings {UserId = userId};
+            entry.InitProperties();
+            var result = await Table.AddAsync(entry);
+            return result.Entity;
+        }
+
+        public static async Task<Dictionary<long, UserSettings>> GetAll()
+        {
+            var Table = BotDatabase.Instance.Settings;
+            return await Table.ToDictionaryAsync(item => item.UserId, item => item);
+        }
+    }
+}

+ 6 - 13
CardCollector/DataBase/EntityDao/ShopDao.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Linq;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using Microsoft.EntityFrameworkCore;
@@ -7,30 +8,22 @@ namespace CardCollector.DataBase.EntityDao
 {
     public static class ShopDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(ShopDao));
-        private static readonly DbSet<ShopEntity> Table = Instance.Shop;
-
         public static async Task<IEnumerable<ShopEntity>> GetShopPositions()
         {
-            return await Table.WhereAsync(e => !e.IsSpecial);
+            var Table = BotDatabase.Instance.Shop;
+            return (await Table.ToListAsync()).Where(e => !e.IsSpecial);
         }
         
         public static async Task<IEnumerable<ShopEntity>> GetSpecialPositions()
         {
-            return await Table.WhereAsync(e => e.IsSpecial && !e.Expired);
+            var Table = BotDatabase.Instance.Shop;
+            return (await Table.ToListAsync()).Where(e => e.IsSpecial && !e.Expired);
         }
 
         public static async Task<ShopEntity> GetById(int positionId)
         {
+            var Table = BotDatabase.Instance.Shop;
             return await Table.FirstAsync(e => e.Id == positionId);
         }
-
-        public static async void DeleteRow(int productId)
-        {
-            if (await Table.FirstOrDefaultAsync(c => c.Id == productId) is not { } item) return;
-            Table.Attach(item);
-            Table.Remove(item);
-            await Instance.SaveChangesAsync();
-        }
     }
 }

+ 3 - 4
CardCollector/DataBase/EntityDao/SpecialOfferUsersDao.cs

@@ -6,22 +6,21 @@ namespace CardCollector.DataBase.EntityDao
 {
     public class SpecialOfferUsersDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(SpecialOfferUsersDao));
-        private static readonly DbSet<SpecialOfferUsers> Table = Instance.SpecialOfferUsers;
-
         public static async Task<bool> NowUsed(long userId, int offerId)
         {
+            var Table = BotDatabase.Instance.SpecialOfferUsers;
             return await Table.AnyAsync(e => e.UserId == userId && e.OfferId == offerId);
         }
 
         public static async Task AddNew(long userId, int offerId)
         {
+            var Table = BotDatabase.Instance.SpecialOfferUsers;
             await Table.AddAsync(new SpecialOfferUsers
             {
                 UserId = userId,
                 OfferId = offerId
             });
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
         }
     }
 }

+ 14 - 10
CardCollector/DataBase/EntityDao/StickerDao.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Linq.Expressions;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using Microsoft.EntityFrameworkCore;
@@ -10,50 +11,53 @@ namespace CardCollector.DataBase.EntityDao
     /* Класс, предоставляющий доступ к объектам таблицы Stickers*/
     public static class StickerDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(StickerDao));
-        /* Таблица stickers в представлении Entity Framework */
-        private static readonly DbSet<StickerEntity> Table = Instance.Stickers;
-        
         /* Получение информации о стикере по его хешу, возвращает Null, если стикера не существует */
         public static async Task<StickerEntity> GetByHash(string hash)
         {
+            var Table = BotDatabase.Instance.Stickers;
             return await Table.FirstOrDefaultAsync(item => item.Md5Hash == hash);
         }
 
         public static async Task<List<string>> GetAuthorsList()
         {
-            var list = (await Table.ToListAsync()).Select(item => item.Author).Distinct().ToList();
+            var Table = BotDatabase.Instance.Stickers;
+            var list = await Table.Select(item => item.Author).Distinct().ToListAsync();
             list.Sort();
             return list;
         }
 
         public static async Task<List<StickerEntity>> GetAll(string filter = "")
         {
-            return (await Table.WhereAsync(item => item.Contains(filter))).ToList();
+            var Table = BotDatabase.Instance.Stickers;
+            return await Table.Where(item => item.Contains(filter)).ToListAsync();
         }
 
         public static async Task AddNew(StickerEntity sticker)
         {
+            var Table = BotDatabase.Instance.Stickers;
             await Table.AddAsync(sticker);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
         }
 
         public static async Task AddRange(IEnumerable<StickerEntity> stickers, int packId)
         {
+            var Table = BotDatabase.Instance.Stickers;
             await Table.AddRangeAsync(stickers.Select(item => {
                 item.PackId = packId;
                 return item;
             }));
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
         }
 
-        public static async Task<List<StickerEntity>> GetListWhere(Func<StickerEntity, bool> func)
+        public static async Task<List<StickerEntity>> GetListWhere(Expression<Func<StickerEntity, bool>> func)
         {
-            return (await Table.WhereAsync(func)).ToList();
+            var Table = BotDatabase.Instance.Stickers;
+            return await Table.Where(func).ToListAsync();
         }
 
         public static async Task<StickerEntity> GetById(string id)
         {
+            var Table = BotDatabase.Instance.Stickers;
             return await Table.FirstOrDefaultAsync(sticker => sticker.Id == id);
         }
     }

+ 11 - 7
CardCollector/DataBase/EntityDao/UserDao.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Linq.Expressions;
 using System.Threading.Tasks;
 using System.Timers;
 using CardCollector.Controllers;
@@ -15,10 +16,6 @@ namespace CardCollector.DataBase.EntityDao
     /* Класс, предоставляющий доступ к объектам пользователей таблицы Users */
     public static class UserDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UserDao));
-        /* Таблица Users в представлении EntityFramework */
-        private static readonly DbSet<UserEntity> Table = Instance.Users;
-        
         /* Активные пользователи в системе */
         private static readonly Dictionary<long, UserEntity> ActiveUsers = new();
 
@@ -33,12 +30,14 @@ namespace CardCollector.DataBase.EntityDao
             }
             catch
             {
+                var Table = BotDatabase.Instance.Users;
                 /* Ищем пользователя в базе данных или добавляем нового, если не найден*/
                 result = await Table.FindAsync(user.Id) ?? await AddNew(user);
                 
                 /* Собираем объект пользователя */
                 result.Cash = await CashDao.GetById(user.Id);
                 result.Stickers = await UserStickerRelationDao.GetListById(user.Id);
+                result.Settings = await SettingsDao.GetById(user.Id);
                 result.CurrentLevel = await UserLevelDao.GetById(user.Id);
                 result.Session.InitNewModule<FiltersModule>();
                 result.Session.InitNewModule<DefaultModule>();
@@ -53,6 +52,7 @@ namespace CardCollector.DataBase.EntityDao
 
         public static async Task<UserEntity> GetById(long userId)
         {
+            var Table = BotDatabase.Instance.Users;
             var user = await Table.FirstAsync(item => item.Id == userId);
             user.Cash = await CashDao.GetById(user.Id);
             //user.Stickers = await UserStickerRelationDao.GetListById(user.Id);
@@ -62,12 +62,14 @@ namespace CardCollector.DataBase.EntityDao
         /* Получение пользователя по представлению user из Базы данных */
         public static async Task<List<UserEntity>> GetUsersList(string filter)
         {
+            var Table = BotDatabase.Instance.Users;
             return await Table.Where(user => user.Username.Contains(filter)).ToListAsync();
         }
 
         /* Добавление новго пользователя в систему */
         private static async Task<UserEntity> AddNew(User user)
         {
+            var Table = BotDatabase.Instance.Users;
             var userEntity = new UserEntity
             {
                 Id = user.Id,
@@ -76,7 +78,7 @@ namespace CardCollector.DataBase.EntityDao
                 IsBlocked = false
             };
             var result = await Table.AddAsync(userEntity);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
 
@@ -100,13 +102,15 @@ namespace CardCollector.DataBase.EntityDao
             }
         }
 
-        public static async Task<IEnumerable<UserEntity>> GetAllWhere(Func<UserEntity, Task<bool>> callback)
+        public static Task<List<UserEntity>> GetAllWhere(Expression<Func<UserEntity, bool>> callback)
         {
-            return await Table.WhereAsync(callback);
+            var Table = BotDatabase.Instance.Users;
+            return Table.Where(callback).ToListAsync();
         }
 
         public static async Task<IEnumerable<UserEntity>> GetAll()
         {
+            var Table = BotDatabase.Instance.Users;
             return await Table.ToListAsync();
         }
     }

+ 9 - 8
CardCollector/DataBase/EntityDao/UserLevelDao.cs

@@ -1,4 +1,6 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using Microsoft.EntityFrameworkCore;
 
@@ -6,13 +8,10 @@ namespace CardCollector.DataBase.EntityDao
 {
     public class UserLevelDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UserLevelDao));
-        /* Таблица cash в представлении EntityFramework */
-        private static readonly DbSet<UserLevel> Table = Instance.UserLevel;
-        
         /* Получение объекта по Id */
         public static async Task<UserLevel> GetById(long userId)
         {
+            var Table = BotDatabase.Instance.UserLevel;
             var user = await Table.FirstOrDefaultAsync(item => item.UserId == userId);
             return user ?? await AddNew(userId);
         }
@@ -20,15 +19,17 @@ namespace CardCollector.DataBase.EntityDao
         /* Добавление нового объекта в систему */
         private static async Task<UserLevel> AddNew(long userId)
         {
+            var Table = BotDatabase.Instance.UserLevel;
             var userLevel = new UserLevel { UserId = userId };
             var result = await Table.AddAsync(userLevel);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
 
-        public static async Task Save()
+        public static Task<List<UserLevel>> GetTop(int top)
         {
-            await Instance.SaveChangesAsync();
+            var Table = BotDatabase.Instance.UserLevel;
+            return Table.OrderByDescending(item => item.TotalExp).Take(top).ToListAsync();
         }
     }
 }

+ 5 - 5
CardCollector/DataBase/EntityDao/UserPacksDao.cs

@@ -8,24 +8,24 @@ namespace CardCollector.DataBase.EntityDao
 {
     public static class UserPacksDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UserPacksDao));
-        private static readonly DbSet<UserPacks> Table = Instance.UsersPacks;
-
         public static async Task<List<UserPacks>> GetUserPacks(long userId)
         {
-            return (await Table.WhereAsync(item => item.UserId == userId)).ToList();
+            var Table = BotDatabase.Instance.UsersPacks;
+            return await Table.Where(item => item.UserId == userId).ToListAsync();
         }
 
         public static async Task<UserPacks> AddNew(long userId, int packId)
         {
+            var Table = BotDatabase.Instance.UsersPacks;
             var newPack = new UserPacks() { UserId = userId, PackId = packId };
             var result = await Table.AddAsync(newPack);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
 
         public static async Task<UserPacks> GetOne(long userId, int packId)
         {
+            var Table = BotDatabase.Instance.UsersPacks;
             return await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.PackId == packId)
                    ?? await AddNew(userId, packId);
         }

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

@@ -1,12 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
-using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
-using CardCollector.Resources;
-using CardCollector.StickerEffects;
 using Microsoft.EntityFrameworkCore;
 
 namespace CardCollector.DataBase.EntityDao
@@ -14,22 +9,18 @@ namespace CardCollector.DataBase.EntityDao
     /* Предоставляет доступ к соотношениям таблицы user_to_sticker_relation */
     public static class UserStickerRelationDao
     {
-        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UserStickerRelationDao));
-        /* Таблица user_to_sticker_relation в представлении Entity Framework */
-        private static readonly DbSet<UserStickerRelationEntity> Table = Instance.UserStickerRelations;
-        
         /* Возвращает словарь стикеров по Id пользователя */
         public static async Task<Dictionary<string, UserStickerRelationEntity>> GetListById(long userId)
         {
-            var result = await Table
-                .Where(i => i.UserId == userId)
-                .ToDictionaryAsync(p=> p.ShortHash, p=> p);
+            var Table = BotDatabase.Instance.UserStickerRelations;
+            var result = await Table.Where(i => i.UserId == userId).ToDictionaryAsync(p=> p.ShortHash, p=> p);
             return result;
         }
 
         /* Добавляет новое отношение в таблицу */
         public static async Task<UserStickerRelationEntity> AddSticker(UserEntity user, StickerEntity sticker, int count = 1)
         {
+            var Table = BotDatabase.Instance.UserStickerRelations;
             if (user.Stickers.ContainsKey(sticker.Md5Hash))
             {
                 user.Stickers[sticker.Md5Hash].Count += count;
@@ -42,29 +33,10 @@ namespace CardCollector.DataBase.EntityDao
                 Count = count,
                 ShortHash = sticker.Md5Hash
             };
-            switch ((Effect)sticker.Effect)
-            {
-                case Effect.PiggyBank200:
-                    user.Cash.MaxCapacity += 200;
-                    await MessageController.EditMessage(user, Messages.effect_PiggyBank200);
-                    break;
-                case Effect.Diamonds25Percent:
-                    user.Cash.Gems += (int)(user.Cash.Gems * 0.25);
-                    await MessageController.EditMessage(user, Messages.effect_Diamonds25Percent);
-                    break;
-                case Effect.Random1Pack5Day:
-                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
-                    break;
-                case Effect.RandomSticker1Tier3Day:
-                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
-                    break;
-                case Effect.RandomSticker2Tier3Day:
-                    relation.AdditionalData = DateTime.Today.ToString(CultureInfo.CurrentCulture);
-                    break;
-            }
+            await sticker.ApplyEffect(user, relation);
             var result = await Table.AddAsync(relation);
             user.Stickers.Add(sticker.Md5Hash, result.Entity);
-            await Instance.SaveChangesAsync();
+            await BotDatabase.SaveData();
             return result.Entity;
         }
     }

+ 1 - 33
CardCollector/Extensions.cs

@@ -1,14 +1,11 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using System.Threading;
 using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
 using CardCollector.Resources;
-using Microsoft.EntityFrameworkCore;
 using Telegram.Bot.Types.InlineQueryResults;
 
 namespace CardCollector
@@ -50,19 +47,6 @@ namespace CardCollector
             }
             return result;
         }
-        
-        /* Возвращает все стикеры системы */
-        public static async Task<IEnumerable<StickerEntity>> ToStickers
-            (this Dictionary<string, UserStickerRelationEntity> dict, string filter)
-        {
-            var result = new List<StickerEntity>();
-            foreach (var relation in dict.Values.Where(i => i.Count > 0))
-            {
-                var sticker = await StickerDao.GetByHash(relation.StickerId);
-                if (sticker.Title.Contains(filter, StringComparison.CurrentCultureIgnoreCase)) result.Add(sticker);
-            }
-            return result;
-        }
 
         public static async Task<IEnumerable<T>> WhereAsync<T>(
             this IEnumerable<T> source, Func<T, Task<bool>> predicate)
@@ -85,19 +69,6 @@ namespace CardCollector
                 if (await predicate(element)) return true;
             return false;
         }
-
-        public static async Task<IEnumerable<TSource>> WhereAsync<TSource>(
-            [NotNull] this IQueryable<TSource> source, 
-            [NotNull] Func<TSource, bool> predicate,
-            CancellationToken cancellationToken = default)
-        {
-            var results = new ConcurrentQueue<TSource>();
-            await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
-            {
-                if (predicate(element)) results.Enqueue(element);
-            }
-            return results;
-        }
         
         public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> source)
         {
@@ -107,10 +78,7 @@ namespace CardCollector
         public static async Task<int> SumAsync<TSource>(this IEnumerable<TSource> source, Func<TSource, Task<int>> selector)
         {
             var sum = 0;
-            checked
-            {
-                foreach (var item in source) sum += await selector(item);
-            }
+            foreach (var item in source) sum += await selector(item);
             return sum;
         }
     }

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

@@ -60,6 +60,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABX.
+        /// </summary>
+        internal static string alerts {
+            get {
+                return ResourceManager.GetString("alerts", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to AAA.
         /// </summary>
@@ -393,6 +402,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABW.
+        /// </summary>
+        internal static string settings {
+            get {
+                return ResourceManager.GetString("settings", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABS.
         /// </summary>

+ 6 - 0
CardCollector/Resources/Command.resx

@@ -147,4 +147,10 @@
     <data name="sticker_info" xml:space="preserve">
         <value>ABV</value>
     </data>
+    <data name="settings" xml:space="preserve">
+        <value>ABW</value>
+    </data>
+    <data name="alerts" xml:space="preserve">
+        <value>ABX</value>
+    </data>
 </root>

+ 16 - 10
CardCollector/Resources/Constants.cs

@@ -1,6 +1,4 @@
-using System;
 // ReSharper disable ConditionIsAlwaysTrueOrFalse
-#pragma warning disable 162
 
 namespace CardCollector.Resources
 {
@@ -26,13 +24,21 @@ namespace CardCollector.Resources
         
         /* Количество стикеров для создания комбинации */
         public const int COMBINE_COUNT = 5;
-        
-        /* Время оповещения и сброса ежедневных заданий */
-        public static readonly TimeSpan DailyTaskAlert = DEBUG ? new TimeSpan(20, 30, 0) : new TimeSpan(10, 0, 0);
-        public static readonly TimeSpan DailyTaskReset = DEBUG ? new TimeSpan(20, 30, 0) : new TimeSpan(10, 0, 0);
-        /* Время выдачи наград за пассивные эффекты стикеров */
-        public static readonly TimeSpan DailyStickerRewardCheck = DEBUG ? new TimeSpan(20, 35, 0) : new TimeSpan(11, 0, 0);
-        /* Сброс отправленных в беседы стикеров */
-        public static readonly TimeSpan ResetGroupStickersExp = DEBUG ? new TimeSpan(20, 35, 0) : new TimeSpan(10, 0, 0);
+
+
+        public const int AuctionDao = 1;
+        public const int CashDao = 2;
+        public const int DailyTaskDao = 3;
+        public const int LevelDao = 4;
+        public const int PacksDao = 5;
+        public const int SessionTokenDao = 6;
+        public const int SettingsDao = 7;
+        public const int ShopDao = 8;
+        public const int SpecialOfferUsersDao = 9;
+        public const int StickerDao = 10;
+        public const int UserDao = 11;
+        public const int UserLevelDao = 12;
+        public const int UserPacksDao = 13;
+        public const int UserStickerRelationDao = 14;
     }
 }

+ 43 - 1
CardCollector/Resources/Keyboard.cs

@@ -38,6 +38,45 @@ namespace CardCollector.Resources
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
+        public static InlineKeyboardMarkup Settings = new(new[]
+        {
+            new[] {InlineKeyboardButton.WithCallbackData(Text.alerts, Command.alerts)},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
+        });
+
+        public static InlineKeyboardMarkup Alerts(UserSettings settings)
+        {
+            var keyboard = new List<InlineKeyboardButton[]>
+            {
+                new[] {
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.daily_tasks} {(settings[UserSettingsEnum.DailyTasks] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.DailyTasks}"),
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.sticker_effects} {(settings[UserSettingsEnum.StickerEffects] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.StickerEffects}")
+                },
+                new[] {
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.exp_gain} {(settings[UserSettingsEnum.ExpGain] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.ExpGain}"),
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.daily_task_progress} {(settings[UserSettingsEnum.DailyTaskProgress] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.DailyTaskProgress}")
+                },
+                new[] {
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.piggy_bank_capacity} {(settings[UserSettingsEnum.PiggyBankCapacity] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.PiggyBankCapacity}"),
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.daily_exp_top} {(settings[UserSettingsEnum.DailyExpTop] ? Text.alert_on : Text.alert_off)}", 
+                        $"{Command.alerts}={(int)UserSettingsEnum.DailyExpTop}")
+                },
+                new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
+            };
+            return new InlineKeyboardMarkup(keyboard);
+        }
+
         public static InlineKeyboardMarkup BackToFilters(string stickerTitle)
         {
             return new InlineKeyboardMarkup(new[]
@@ -338,7 +377,10 @@ namespace CardCollector.Resources
             var keyboard = new List<InlineKeyboardButton[]> {
                 new[] {InlineKeyboardButton.WithCallbackData($"{Text.collect} {income}{Text.coin}", Command.collect_income)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.daily_tasks, Command.daily_tasks)},
-                new[] {InlineKeyboardButton.WithCallbackData(Text.my_packs, Command.my_packs)},
+                new[] {
+                    InlineKeyboardButton.WithCallbackData(Text.settings, Command.settings),
+                    InlineKeyboardButton.WithCallbackData(Text.my_packs, Command.my_packs),
+                },
             };
             if (privilegeLevel > 2) keyboard.Add(
                 new[] {InlineKeyboardButton.WithCallbackData(Text.control_panel, Command.control_panel)});

+ 49 - 4
CardCollector/Resources/Messages.Designer.cs

@@ -60,6 +60,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to 🔔&lt;b&gt;Уведомления&lt;/b&gt;🔔.
+        /// </summary>
+        internal static string alerts {
+            get {
+                return ResourceManager.GetString("alerts", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to (все).
         /// </summary>
@@ -493,11 +502,11 @@ namespace CardCollector.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Вы можете выбрать одну из опций ниже.
+        ///   Looks up a localized string similar to ⬇️&lt;b&gt;Главное меню&lt;/b&gt;⬇️.
         /// </summary>
-        internal static string menu_message {
+        internal static string main_menu {
             get {
-                return ResourceManager.GetString("menu_message", resourceCulture);
+                return ResourceManager.GetString("main_menu", resourceCulture);
             }
         }
         
@@ -663,7 +672,25 @@ namespace CardCollector.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Выберите одну из опций ниже:.
+        ///   Looks up a localized string similar to &quot;Отправка стикеров в личные сообщения&quot; прогресс.
+        /// </summary>
+        internal static string send_sticker_progress {
+            get {
+                return ResourceManager.GetString("send_sticker_progress", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ⚙&lt;b&gt;Настройки&lt;/b&gt;⚙.
+        /// </summary>
+        internal static string settings {
+            get {
+                return ResourceManager.GetString("settings", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to 💎&lt;b&gt;Магазин&lt;/b&gt;💎.
         /// </summary>
         internal static string shop_message {
             get {
@@ -743,6 +770,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Не забывайте собирать прибыль во вкладке профиля. Несобранная прибыль составляет.
+        /// </summary>
+        internal static string uncollected_income {
+            get {
+                return ResourceManager.GetString("uncollected_income", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Произошла неожиданная ошибка, попробуйте еще раз..
         /// </summary>
@@ -794,6 +830,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to 🧪&lt;b&gt;Топ пользователей по опыту&lt;/b&gt;🧪.
+        /// </summary>
+        internal static string users_top_exp {
+            get {
+                return ResourceManager.GetString("users_top_exp", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Вы уже воспользовались данным предложением!.
         /// </summary>

+ 18 - 3
CardCollector/Resources/Messages.resx

@@ -90,8 +90,8 @@
     <data name="you_collected" xml:space="preserve">
         <value>Получено прибыли со стикеров:</value>
     </data>
-    <data name="menu_message" xml:space="preserve">
-        <value>Вы можете выбрать одну из опций ниже</value>
+    <data name="main_menu" xml:space="preserve">
+        <value>⬇️&lt;b&gt;Главное меню&lt;/b&gt;⬇️</value>
     </data>
     <data name="your_cash" xml:space="preserve">
         <value>Баланс</value>
@@ -175,7 +175,7 @@
         <value>Страница не найдена</value>
     </data>
     <data name="shop_message" xml:space="preserve">
-        <value>Выберите одну из опций ниже:</value>
+        <value>💎&lt;b&gt;Магазин&lt;/b&gt;💎</value>
     </data>
     <data name="choose_option" xml:space="preserve">
         <value>Выберите одну из опций ниже</value>
@@ -306,4 +306,19 @@
     <data name="thanks_for_buying_gems" xml:space="preserve">
         <value>Спасибо за покупку алмазов!</value>
     </data>
+    <data name="settings" xml:space="preserve">
+        <value>⚙&lt;b&gt;Настройки&lt;/b&gt;⚙</value>
+    </data>
+    <data name="alerts" xml:space="preserve">
+        <value>🔔&lt;b&gt;Уведомления&lt;/b&gt;🔔</value>
+    </data>
+    <data name="send_sticker_progress" xml:space="preserve">
+        <value>"Отправка стикеров в личные сообщения" прогресс</value>
+    </data>
+    <data name="uncollected_income" xml:space="preserve">
+        <value>Не забывайте собирать прибыль во вкладке профиля. Несобранная прибыль составляет</value>
+    </data>
+    <data name="users_top_exp" xml:space="preserve">
+        <value>🧪&lt;b&gt;Топ пользователей по опыту&lt;/b&gt;🧪</value>
+    </data>
 </root>

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

@@ -87,6 +87,33 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to 🔕.
+        /// </summary>
+        internal static string alert_off {
+            get {
+                return ResourceManager.GetString("alert_off", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to 🔔.
+        /// </summary>
+        internal static string alert_on {
+            get {
+                return ResourceManager.GetString("alert_on", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Уведомления 🔔.
+        /// </summary>
+        internal static string alerts {
+            get {
+                return ResourceManager.GetString("alerts", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Все.
         /// </summary>
@@ -276,6 +303,24 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Топ игроков по опыту.
+        /// </summary>
+        internal static string daily_exp_top {
+            get {
+                return ResourceManager.GetString("daily_exp_top", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Прогресс еж. заданий.
+        /// </summary>
+        internal static string daily_task_progress {
+            get {
+                return ResourceManager.GetString("daily_task_progress", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Ежедневные задания.
         /// </summary>
@@ -366,6 +411,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Получение опыта.
+        /// </summary>
+        internal static string exp_gain {
+            get {
+                return ResourceManager.GetString("exp_gain", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to От.
         /// </summary>
@@ -564,6 +618,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Наполнение копилки.
+        /// </summary>
+        internal static string piggy_bank_capacity {
+            get {
+                return ResourceManager.GetString("piggy_bank_capacity", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to +.
         /// </summary>
@@ -663,6 +726,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Настройки.
+        /// </summary>
+        internal static string settings {
+            get {
+                return ResourceManager.GetString("settings", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Магазин.
         /// </summary>
@@ -744,6 +816,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Эффекты стикеров.
+        /// </summary>
+        internal static string sticker_effects {
+            get {
+                return ResourceManager.GetString("sticker_effects", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Остановить.
         /// </summary>

+ 27 - 0
CardCollector/Resources/Text.resx

@@ -273,4 +273,31 @@
     <data name="exp" xml:space="preserve">
         <value>опыта</value>
     </data>
+    <data name="settings" xml:space="preserve">
+        <value>Настройки</value>
+    </data>
+    <data name="alerts" xml:space="preserve">
+        <value>Уведомления 🔔</value>
+    </data>
+    <data name="alert_on" xml:space="preserve">
+        <value>🔔</value>
+    </data>
+    <data name="alert_off" xml:space="preserve">
+        <value>🔕</value>
+    </data>
+    <data name="sticker_effects" xml:space="preserve">
+        <value>Эффекты стикеров</value>
+    </data>
+    <data name="exp_gain" xml:space="preserve">
+        <value>Получение опыта</value>
+    </data>
+    <data name="daily_task_progress" xml:space="preserve">
+        <value>Прогресс еж. заданий</value>
+    </data>
+    <data name="piggy_bank_capacity" xml:space="preserve">
+        <value>Наполнение копилки</value>
+    </data>
+    <data name="daily_exp_top" xml:space="preserve">
+        <value>Топ игроков по опыту</value>
+    </data>
 </root>

+ 6 - 5
CardCollector/Session/Modules/FiltersModule.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
 using CardCollector.Resources;
@@ -43,7 +44,7 @@ namespace CardCollector.Session.Modules
             return text;
         }
         
-        public IEnumerable<StickerEntity> ApplyTo(IEnumerable<StickerEntity> list, bool applyPrice = false)
+        public async Task<IEnumerable<StickerEntity>> ApplyTo(IEnumerable<StickerEntity> list, bool applyPrice = false)
         {
             /* Фильтруем по автору */
             if (Filters[Command.authors_menu] is string author && author != "")
@@ -71,18 +72,18 @@ namespace CardCollector.Session.Modules
                     list = list.OrderByDescending(item => item.Tier);
             }
             return applyPrice 
-                ? ApplyPriceTo(list)
+                ? await ApplyPriceTo(list)
                 : list;
         }
         
-        public IEnumerable<StickerEntity> ApplyPriceTo(IEnumerable<StickerEntity> list)
+        public async Task<IEnumerable<StickerEntity>> ApplyPriceTo(IEnumerable<StickerEntity> list)
         {
             /* Фильтруем по цене алмазов ОТ */
             if (Filters[Command.price_gems_from] is int PGF && PGF != 0)
-                list = list.Where(item => AuctionDao.HaveAny(item.Id, i => i.Price >= PGF));
+                list = await list.WhereAsync(item => AuctionDao.HaveAny(item.Id, i => i.Price >= PGF));
             /* Фильтруем по цене адмазов ДО */
             if (Filters[Command.price_gems_to] is int PGT && PGT != 0)
-                list = list.Where(item => AuctionDao.HaveAny(item.Id, i => i.Price <= PGT));
+                list = await list.WhereAsync(item => AuctionDao.HaveAny(item.Id, i => i.Price <= PGT));
             return list;
         }
         

+ 2 - 2
CardCollector/Session/Session.cs

@@ -77,13 +77,13 @@ namespace CardCollector.Session
             foreach (var messageId in Messages.ToList())
                 await MessageController.DeleteMessage(user, messageId);
             foreach (var messageId in StickerMessages.ToList())
-                await MessageController.DeleteMessage(user, messageId);
+                await MessageController.DeleteMessage(user, messageId, true);
         }
 
         public async Task ClearStickers()
         {
             foreach (var messageId in StickerMessages.ToList())
-                await MessageController.DeleteMessage(user, messageId);
+                await MessageController.DeleteMessage(user, messageId, true);
         }
 
         public async Task EndSession()

+ 30 - 0
CardCollector/TimerTasks/DailyTaskAlert.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Timers;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+
+namespace CardCollector.TimerTasks
+{
+    public class DailyTaskAlert : TimerTask
+    {
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 17, 20) : 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();
+            foreach (var user in users)
+            {
+                try {
+                    if (settings[user.Id][UserSettingsEnum.DailyTasks])
+                        await MessageController.SendMessage(user, Messages.daily_task_alertation, addToList: false);
+                }
+                catch {
+                    await MessageController.SendMessage(user, Messages.daily_task_alertation, addToList: false);
+                }
+            }
+        }
+    }
+}

+ 19 - 0
CardCollector/TimerTasks/DailyTaskReset.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Timers;
+using CardCollector.DailyTasks;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+
+namespace CardCollector.TimerTasks
+{
+    public class DailyTaskReset : TimerTask
+    {
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 17, 20) : new TimeSpan(10, 0, 0);
+
+        protected override async void TimerCallback(object o, ElapsedEventArgs e)
+        {
+            foreach (var item in await DailyTaskDao.GetAll())
+                item.Progress = DailyTask.List[(DailyTaskKeys) item.TaskId].Goal;
+        }
+    }
+}

+ 42 - 13
CardCollector/StickerEffects/EffectFunctions.cs → CardCollector/TimerTasks/ExecuteStickerEffects.cs

@@ -1,23 +1,29 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
 using System.Timers;
 using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
 using CardCollector.Resources;
+using CardCollector.StickerEffects;
 
-namespace CardCollector.StickerEffects
+namespace CardCollector.TimerTasks
 {
-    public static class EffectFunctions
+    public class ExecuteStickerEffects : TimerTask
     {
-        public static async void RunAll(object o, ElapsedEventArgs e)
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 17, 20) : new TimeSpan(11, 0, 0);
+        
+        protected override async void TimerCallback(object o, ElapsedEventArgs e)
         {
             await GivePacks();
             await GiveTier1();
             await GiveTier2();
         }
-
-        public static async Task GivePacks()
+        
+        public async Task GivePacks()
         {
-            var users = await UserDao.GetAll();
+            var users = await UserDao.GetAllWhere(user => !user.IsBlocked);
+            var settings = await SettingsDao.GetAll();
             foreach (var user in users)
             {
                 var stickers = await UserStickerRelationDao.GetListById(user.Id);
@@ -26,8 +32,15 @@ namespace CardCollector.StickerEffects
                 {
                     var userPacks = await UserPacksDao.GetOne(user.Id, 1);
                     userPacks.Count += packsCount;
-                    await MessageController.SendMessage(user,
-                        $"{Messages.effect_Random1Pack5Day} {packsCount}{Text.items}");
+                    try {
+                        if (settings[user.Id][UserSettingsEnum.StickerEffects])
+                            await MessageController.SendMessage(user,
+                                $"{Messages.effect_Random1Pack5Day} {packsCount}{Text.items}", addToList: false);
+                    }
+                    catch {
+                        await MessageController.SendMessage(user,
+                            $"{Messages.effect_Random1Pack5Day} {packsCount}{Text.items}", addToList: false);
+                    }
                 }
             }
         }
@@ -35,6 +48,7 @@ namespace CardCollector.StickerEffects
         public static async Task GiveTier2()
         {
             var users = await UserDao.GetAll();
+            var settings = await SettingsDao.GetAll();
             foreach (var user in users)
             {
                 var stickers = await UserStickerRelationDao.GetListById(user.Id);
@@ -48,8 +62,15 @@ namespace CardCollector.StickerEffects
                         generatedMessage += $"\n{sticker.Title} {count}{Text.items}";
                         await UserStickerRelationDao.AddSticker(user, sticker, count);
                     }
-                    await MessageController.SendMessage(user,
-                        $"{Messages.effect_RandomSticker2Tier3Day}{generatedMessage}");
+                    try {
+                        if (settings[user.Id][UserSettingsEnum.StickerEffects])
+                            await MessageController.SendMessage(user,
+                                $"{Messages.effect_RandomSticker2Tier3Day}{generatedMessage}", addToList: false);
+                    }
+                    catch {
+                        await MessageController.SendMessage(user,
+                            $"{Messages.effect_RandomSticker2Tier3Day}{generatedMessage}", addToList: false);
+                    }
                 }
             }
         }
@@ -57,6 +78,7 @@ namespace CardCollector.StickerEffects
         public static async Task GiveTier1()
         {
             var users = await UserDao.GetAll();
+            var settings = await SettingsDao.GetAll();
             foreach (var user in users)
             {
                 var stickers = await UserStickerRelationDao.GetListById(user.Id);
@@ -70,8 +92,15 @@ namespace CardCollector.StickerEffects
                         generatedMessage += $"\n{sticker.Title} {count}{Text.items}";
                         await UserStickerRelationDao.AddSticker(user, sticker, count);
                     }
-                    await MessageController.SendMessage(user,
-                        $"{Messages.effect_RandomSticker1Tier3Day}{generatedMessage}");
+                    try {
+                        if (settings[user.Id][UserSettingsEnum.StickerEffects])
+                            await MessageController.SendMessage(user,
+                                $"{Messages.effect_RandomSticker1Tier3Day}{generatedMessage}", addToList: false);
+                    }
+                    catch {
+                        await MessageController.SendMessage(user,
+                            $"{Messages.effect_RandomSticker1Tier3Day}{generatedMessage}", addToList: false);
+                    }
                 }
             }
         }

+ 35 - 0
CardCollector/TimerTasks/PiggyBankAlert.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Timers;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+
+namespace CardCollector.TimerTasks
+{
+    public class PiggyBankAlert : TimerTask
+    {
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 34, 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();
+            foreach (var user in users)
+            {
+                var cash = await CashDao.GetById(user.Id);
+                var stickers = await UserStickerRelationDao.GetListById(user.Id);
+                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);
+                }
+                catch {
+                    await MessageController.SendMessage(user, 
+                        $"{Messages.uncollected_income}: {income} / {cash.MaxCapacity} {Text.coin}", addToList: false);
+                }
+            }
+        }
+    }
+}

+ 17 - 0
CardCollector/TimerTasks/ResetChatGiveExp.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Timers;
+using CardCollector.Commands.Message;
+using CardCollector.Resources;
+
+namespace CardCollector.TimerTasks
+{
+    public class ResetChatGiveExp : TimerTask
+    {
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 17, 20) : new TimeSpan(10, 0, 0);
+        
+        protected override void TimerCallback(object o, ElapsedEventArgs e)
+        {
+            GiveExp.GroupStickersExp.Clear();
+        }
+    }
+}

+ 50 - 0
CardCollector/TimerTasks/TimerTask.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Timers;
+
+namespace CardCollector.TimerTasks
+{
+    public abstract class TimerTask
+    {
+        protected Timer Timer = new ();
+        protected abstract TimeSpan RunAt { get; }
+        
+        private static List<TimerTask> TasksList = new() {
+            new DailyTaskAlert(),
+            new DailyTaskReset(),
+            new ResetChatGiveExp(),
+            new ExecuteStickerEffects(),
+            new PiggyBankAlert(),
+            new TopExpUsersAlert()
+        };
+
+        public static void SetupAll()
+        {
+            foreach (var task in TasksList)
+                task.Setup();
+        }
+
+        protected static void SetupTimer(Timer timer, TimeSpan timeToRun, ElapsedEventHandler callback)
+        {
+            var interval = timeToRun - DateTime.Now.TimeOfDay;
+            if (interval < TimeSpan.Zero) interval += new TimeSpan(1, 0, 0, 0);
+            timer.AutoReset = false;
+            timer.Enabled = true;
+            timer.Interval = interval.TotalMilliseconds;
+            timer.Elapsed += callback;
+        }
+
+        protected abstract void TimerCallback(object o, ElapsedEventArgs e);
+
+        protected virtual void Setup()
+        {
+            SetupTimer(Timer, RunAt, TimerCallback);
+            Timer.Elapsed += Setup;
+        }
+
+        protected void Setup(object o, ElapsedEventArgs e)
+        {
+            Setup();
+        }
+    }
+}

+ 38 - 0
CardCollector/TimerTasks/TopExpUsersAlert.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Timers;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using Telegram.Bot.Types.Enums;
+
+namespace CardCollector.TimerTasks
+{
+    public class TopExpUsersAlert : TimerTask
+    {
+        protected override TimeSpan RunAt => Constants.DEBUG ? new TimeSpan(10, 17, 20) : new TimeSpan(24, 0, 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 message = Messages.users_top_exp;
+            foreach (var (userLevel, index) in usersExp.WithIndex())
+            {
+                var user = await UserDao.GetById(userLevel.UserId);
+                message += $"\n{index+1}.{user.Username}: {userLevel.TotalExp} {Text.exp}";
+            }
+            foreach (var user in users)
+            {
+                try {
+                    if (settings[user.Id][UserSettingsEnum.DailyExpTop])
+                        await MessageController.SendMessage(user, message, parseMode: ParseMode.Html, addToList: false);
+                }
+                catch {
+                    await MessageController.SendMessage(user, message, parseMode: ParseMode.Html, addToList: false);
+                }
+            }
+        }
+    }
+}

+ 0 - 16
CardCollector/Utilities.cs

@@ -2,7 +2,6 @@
 using System.Net;
 using System.Text;
 using System.Threading.Tasks;
-using System.Timers;
 using CardCollector.Resources;
 using Telegram.Bot;
 using Telegram.Bot.Types;
@@ -46,20 +45,5 @@ namespace CardCollector
             client.DownloadFile(new Uri(fileUri), file.FileName ?? "file");
             return file.FileName ?? "file";
         }
-        
-        public static void SetUpTimer(TimeSpan timeToRun, ElapsedEventHandler callback, bool cycled = true)
-        {
-            var elapsedInterval = timeToRun - DateTime.Now.TimeOfDay;
-            if (elapsedInterval < TimeSpan.Zero) elapsedInterval += new TimeSpan(1, 0, 0, 0);
-            var timer = new Timer
-            {
-                AutoReset = false,
-                Enabled = true,
-                Interval = elapsedInterval.TotalMilliseconds
-            };
-            timer.Elapsed += callback;
-            if (cycled) timer.Elapsed += (_, _) => SetUpTimer(timeToRun, callback);
-            timer.Elapsed += (_, _) => timer.Dispose();
-        }
     }
 }