浏览代码

Uploading sticker with watermark, developer control panel

Tigran 3 年之前
父节点
当前提交
323fb67b25
共有 44 个文件被更改,包括 735 次插入232 次删除
  1. 2 2
      CardCollector.sln.DotSettings.user
  2. 1 1
      CardCollector/Bot.cs
  3. 31 0
      CardCollector/Commands/CallbackQuery/AddForSaleSticker.cs
  4. 5 1
      CardCollector/Commands/CallbackQuery/Back.cs
  5. 2 1
      CardCollector/Commands/CallbackQuery/BuyAuthorPackMenu.cs
  6. 4 0
      CardCollector/Commands/CallbackQuery/BuySticker.cs
  7. 1 1
      CardCollector/Commands/CallbackQuery/CollectIncome.cs
  8. 28 0
      CardCollector/Commands/CallbackQuery/SelectForSalePack.cs
  9. 3 1
      CardCollector/Commands/CallbackQuery/SelectShopPack.cs
  10. 2 1
      CardCollector/Commands/CallbackQuery/Settings.cs
  11. 5 5
      CardCollector/Commands/CallbackQuery/ShowSample.cs
  12. 4 4
      CardCollector/Commands/CallbackQuery/StopBot.cs
  13. 5 5
      CardCollector/Commands/CallbackQuery/UploadStickerPack.cs
  14. 15 6
      CardCollector/Commands/CallbackQueryCommand.cs
  15. 27 0
      CardCollector/Commands/ChosenInlineResult/SelectForSaleSticker.cs
  16. 7 4
      CardCollector/Commands/ChosenInlineResult/SelectSticker.cs
  17. 4 1
      CardCollector/Commands/ChosenInlineResult/StickerInfo.cs
  18. 4 2
      CardCollector/Commands/ChosenInlineResultCommand.cs
  19. 0 2
      CardCollector/Commands/InlineQuery/ShowAuctionStickers.cs
  20. 0 2
      CardCollector/Commands/InlineQuery/ShowCollectionStickers.cs
  21. 0 2
      CardCollector/Commands/InlineQuery/ShowCombineStickers.cs
  22. 42 0
      CardCollector/Commands/InlineQuery/ShowPackStickers.cs
  23. 0 4
      CardCollector/Commands/InlineQuery/ShowStickersInBotChat.cs
  24. 0 4
      CardCollector/Commands/InlineQuery/ShowStickersInGroup.cs
  25. 0 4
      CardCollector/Commands/InlineQuery/ShowStickersInPrivate.cs
  26. 0 2
      CardCollector/Commands/InlineQuery/ShowStickersInShopPack.cs
  27. 0 2
      CardCollector/Commands/InlineQuery/ShowTradersInBotChat.cs
  28. 4 1
      CardCollector/Commands/InlineQueryCommand.cs
  29. 0 1
      CardCollector/Commands/Message/Auction.cs
  30. 1 1
      CardCollector/Commands/Message/Profile.cs
  31. 34 0
      CardCollector/Commands/Message/UploadForSaleSticker.cs
  32. 3 7
      CardCollector/Commands/MessageCommand.cs
  33. 5 0
      CardCollector/DataBase/Entity/StickerEntity.cs
  34. 54 0
      CardCollector/Resources/Command.Designer.cs
  35. 18 0
      CardCollector/Resources/Command.resx
  36. 1 1
      CardCollector/Resources/Constants.cs
  37. 302 151
      CardCollector/Resources/Keyboard.cs
  38. 45 0
      CardCollector/Resources/Messages.Designer.cs
  39. 15 0
      CardCollector/Resources/Messages.resx
  40. 28 10
      CardCollector/Resources/Text.Designer.cs
  41. 8 2
      CardCollector/Resources/Text.resx
  42. 2 1
      CardCollector/Resources/UserState.cs
  43. 16 0
      CardCollector/Session/Modules/AdminModule.cs
  44. 7 0
      global.json

+ 2 - 2
CardCollector.sln.DotSettings.user

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

+ 1 - 1
CardCollector/Bot.cs

@@ -32,7 +32,7 @@ namespace CardCollector
 
         private static readonly IEnumerable<BotCommand> _commands = new[]
         {
-            new BotCommand {Command = Text.start, Description = "Запуск бота"},
+            /*new BotCommand {Command = Text.start, Description = "Запуск бота"},*/
             new BotCommand {Command = Text.menu, Description = "Показать меню"},
             new BotCommand {Command = Text.help, Description = "Показать информацию"},
             /*new BotCommand {Command = "/error", Description = "Сообщить об ошибке"},*/

+ 31 - 0
CardCollector/Commands/CallbackQuery/AddForSaleSticker.cs

@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class AddForSaleSticker : CallbackQueryCommand
+    {
+        protected override string CommandText => Command.add_for_sale_sticker;
+        public override async Task Execute()
+        {
+            User.Session.State = UserState.LoadForSaleSticker;
+            var page = int.Parse(CallbackData.Split('=')[1]);
+            var packs = await PacksDao.GetAll();
+            var totalCount = packs.Count;
+            packs = packs.GetRange((page - 1) * 10, packs.Count >= page * 10 ? 10 : packs.Count % 10);
+            if (packs.Count == 0)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.page_not_found);
+            else
+                await MessageController.EditMessage(User, Messages.select_pack,
+                    Keyboard.GetPacksKeyboard(packs, Command.select_for_sale_pack, 
+                        Keyboard.GetPagePanel(page, totalCount, CommandText)));
+        }
+
+        public AddForSaleSticker() { }
+        public AddForSaleSticker(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 5 - 1
CardCollector/Commands/CallbackQuery/Back.cs

@@ -17,7 +17,11 @@ namespace CardCollector.Commands.CallbackQuery
             EnterGemsPrice.RemoveFromQueue(User.Id);
             if (User.Session.TryGetPreviousMenu(out var menu))
                 await menu.BackToThis(User.Session);
-            else await User.Session.EndSession();
+            else
+            {
+                await User.Session.EndSession();
+                await new Menu(User, Update).Execute();
+            }
         }
         
         public Back() { }

+ 2 - 1
CardCollector/Commands/CallbackQuery/BuyAuthorPackMenu.cs

@@ -23,7 +23,8 @@ namespace CardCollector.Commands.CallbackQuery
                 await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.page_not_found);
             else
                 await MessageController.EditMessage(User, Messages.choose_author,
-                    Keyboard.GetShopPacksKeyboard(packs, Keyboard.GetPagePanel(page, totalCount, CommandText)));
+                    Keyboard.GetPacksKeyboard(packs, Command.select_shop_pack,
+                        Keyboard.GetPagePanel(page, totalCount, CommandText)));
         }
 
         public BuyAuthorPackMenu() { }

+ 4 - 0
CardCollector/Commands/CallbackQuery/BuySticker.cs

@@ -26,10 +26,14 @@ namespace CardCollector.Commands.CallbackQuery
             else
             {
                 await auctionModule.SelectedPosition.BuyCard(auctionModule.Count);
+                var trader = await UserDao.GetById(auctionModule.SelectedPosition.Trader);
+                var traderName = trader.Username != "" ? trader.Username : Text.trader;
                 var discount = 1.0 - await User.AuctionDiscount() / 100.0;
                 User.Cash.Gems -= (int)(auctionModule.Price * auctionModule.Count * discount);
                 await UserStickerRelationDao.AddSticker(User, auctionModule.SelectedSticker, auctionModule.Count);
                 User.Session.ResetModule<AuctionModule>();
+                await MessageController.EditMessage(User, 
+                    string.Format(Messages.thanks_for_buying_sticker, traderName), Keyboard.BackKeyboard);
             }
         }
 

+ 1 - 1
CardCollector/Commands/CallbackQuery/CollectIncome.cs

@@ -31,7 +31,7 @@ namespace CardCollector.Commands.CallbackQuery
                 $"\n{Messages.level}: {User.CurrentLevel.Level}" +
                 $"\n{Messages.current_exp}: {User.CurrentLevel.CurrentExp} / {expGoal}" +
                 $"\n{Messages.cash_capacity}: {User.Cash.MaxCapacity}{Text.coin}",
-                Keyboard.GetProfileKeyboard(User.PrivilegeLevel, packsCount));
+                Keyboard.GetProfileKeyboard(packsCount));
         }
 
         public override async Task AfterExecute()

+ 28 - 0
CardCollector/Commands/CallbackQuery/SelectForSalePack.cs

@@ -0,0 +1,28 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using CardCollector.Session.Modules;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class SelectForSalePack : CallbackQueryCommand
+    {
+        protected override string CommandText => Command.select_for_sale_pack;
+        protected override bool AddToStack => true;
+        protected override bool ClearStickers => true;
+        public override async Task Execute()
+        {
+            var packId = int.Parse(CallbackData.Split('=')[1]);
+            var packInfo = await PacksDao.GetById(packId);
+            var module = User.Session.GetModule<AdminModule>();
+            module.SelectedPack = packInfo;
+            await MessageController.EditMessage(User, Messages.choose_sticker, Keyboard.ShowStickers);
+        }
+
+        public SelectForSalePack() { }
+        public SelectForSalePack(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 3 - 1
CardCollector/Commands/CallbackQuery/SelectShopPack.cs

@@ -26,7 +26,9 @@ namespace CardCollector.Commands.CallbackQuery
             if (packId != 1)
             {
                 var stickers = await StickerDao.GetListWhere(item => item.PackId == packId);
-                stickerId = stickers[Utilities.rnd.Next(stickers.Count)].Id;
+                stickerId = User.Session.State is UserState.AuctionMenu or UserState.ShopMenu
+                    ? stickers[Utilities.rnd.Next(stickers.Count)].IdWithWatermark
+                    : stickers[Utilities.rnd.Next(stickers.Count)].Id;
             }
             await MessageController.SendSticker(User, stickerId, Keyboard.OfferKeyboard(module));
         }

+ 2 - 1
CardCollector/Commands/CallbackQuery/Settings.cs

@@ -14,7 +14,8 @@ namespace CardCollector.Commands.CallbackQuery
 
         public override async Task Execute()
         {
-            await MessageController.EditMessage(User, Messages.settings, Keyboard.Settings, ParseMode.Html);
+            await MessageController.EditMessage(User, Messages.settings,
+                Keyboard.Settings(User.PrivilegeLevel), ParseMode.Html);
         }
 
         public Settings() { }

+ 5 - 5
CardCollector/Commands/Message/ShowSample.cs → CardCollector/Commands/CallbackQuery/ShowSample.cs

@@ -5,14 +5,14 @@ using Telegram.Bot;
 using Telegram.Bot.Types;
 using Telegram.Bot.Types.ReplyMarkups;
 
-namespace CardCollector.Commands.Message
+namespace CardCollector.Commands.CallbackQuery
 {
     /* Этот класс можно использовать для тестирования или наброски эскизов
      Команда "Показать пример" доступна только пользователям с уровнем доступа "Разработчик" и выше
      PrivilegeLevel = 7 */
-    public class ShowSample : MessageCommand
+    public class ShowSample : CallbackQueryCommand
     {
-        protected override string CommandText => Text.show_sample;
+        protected override string CommandText => Command.show_sample;
 
         public override async Task Execute()
         {
@@ -32,8 +32,8 @@ namespace CardCollector.Commands.Message
         {
             return base.IsMatches(user, update) && user.PrivilegeLevel >= PrivilegeLevel.Programmer;
         }
-        
-        public ShowSample(UserEntity user, Update update) : base(user, update) { }
+
         public ShowSample() { }
+        public ShowSample(UserEntity user, Update update) : base(user, update) { }
     }
 }

+ 4 - 4
CardCollector/Commands/Message/StopBot.cs → CardCollector/Commands/CallbackQuery/StopBot.cs

@@ -5,11 +5,11 @@ using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
 using Telegram.Bot.Types;
 
-namespace CardCollector.Commands.Message
+namespace CardCollector.Commands.CallbackQuery
 {
-    public class StopBot : MessageCommand
+    public class StopBot : CallbackQueryCommand
     {
-        protected override string CommandText => Text.stop_bot;
+        protected override string CommandText => Command.stop_bot;
         public static bool ConfirmStop = false;
 
         public override async Task Execute()
@@ -17,7 +17,7 @@ namespace CardCollector.Commands.Message
             if (ConfirmStop) await Bot.StopProgram();
             else
             {
-                await MessageController.EditMessage(User, Messages.confirm_stopping);
+                await MessageController.EditMessage(User, Messages.confirm_stopping, Keyboard.StopKeyboard);
                 ConfirmStop = true;
                 var timer = new Timer
                 {

+ 5 - 5
CardCollector/Commands/Message/DownloadStickerPack.cs → CardCollector/Commands/CallbackQuery/UploadStickerPack.cs

@@ -4,11 +4,11 @@ using CardCollector.DataBase.Entity;
 using CardCollector.Resources;
 using Telegram.Bot.Types;
 
-namespace CardCollector.Commands.Message
+namespace CardCollector.Commands.CallbackQuery
 {
-    public class DownloadStickerPack : MessageCommand
+    public class UploadStickerPack : CallbackQueryCommand
     {
-        protected override string CommandText => Text.download_stickerpack;
+        protected override string CommandText => Command.upload_stickerpack;
         protected override bool ClearMenu => true;
         protected override bool AddToStack => true;
 
@@ -22,8 +22,8 @@ namespace CardCollector.Commands.Message
             return base.IsMatches(user, update) && user.PrivilegeLevel >= PrivilegeLevel.Artist;
         }
 
-        public DownloadStickerPack() { }
-        public DownloadStickerPack(UserEntity user, Update update) : base(user, update) 
+        public UploadStickerPack() { }
+        public UploadStickerPack(UserEntity user, Update update) : base(user, update) 
         {
             User.Session.State = UserState.UploadSticker;
         }

+ 15 - 6
CardCollector/Commands/CallbackQueryCommand.cs

@@ -20,12 +20,6 @@ namespace CardCollector.Commands
      Update - обновление, полученное от сервера Телеграм */
     public abstract class CallbackQueryCommand : UpdateModel
     {
-        /* Данные, поступившие после нажатия на кнокпку */
-        protected string CallbackData;
-        /* Id запроса */
-        protected string CallbackQueryId;
-
-        /* Список команд, распознаваемых ботом */
         private static readonly List<CallbackQueryCommand> List = new()
         {
             new AuthorsMenu(),
@@ -67,8 +61,23 @@ namespace CardCollector.Commands
             new ReturnFromAuction(),
             new ControlPanel(),
             new LogsMenu(),
+            new ShowSample(),
+            new StopBot(),
+            new AddForSaleSticker(),
+            new SelectForSalePack(),
+            new UploadStickerPack(),
         };
 
+        /* Данные, поступившие после нажатия на кнокпку */
+
+        protected string CallbackData;
+
+        /* Id запроса */
+
+        protected string CallbackQueryId;
+
+        /* Список команд, распознаваемых ботом */
+
         /* Метод, создающий объекты команд исходя из полученного обновления */
         public static async Task<UpdateModel> Factory(Update update)
         {

+ 27 - 0
CardCollector/Commands/ChosenInlineResult/SelectForSaleSticker.cs

@@ -0,0 +1,27 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using CardCollector.Session.Modules;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.ChosenInlineResult
+{
+    public class SelectForSaleSticker : ChosenInlineResultCommand
+    {
+        protected override string CommandText => Command.select_for_sale_sticker;
+        public override async Task Execute()
+        {
+            var hash = InlineResult.Split('=')[1];
+            var sticker = await StickerDao.GetByHash(hash);
+            var module = User.Session.GetModule<AdminModule>();
+            module.SelectedSticker = sticker;
+            await MessageController.SendSticker(User, sticker.Id);
+            await MessageController.EditMessage(User, Messages.upload_file_with_watermark, Keyboard.BackKeyboard);
+        }
+
+        public SelectForSaleSticker() { }
+        public SelectForSaleSticker(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 7 - 4
CardCollector/Commands/ChosenInlineResult/SelectStickerInline.cs → CardCollector/Commands/ChosenInlineResult/SelectSticker.cs

@@ -8,7 +8,7 @@ using Telegram.Bot.Types;
 
 namespace CardCollector.Commands.ChosenInlineResult
 {
-    public class SelectStickerInline : ChosenInlineResultCommand
+    public class SelectSticker : ChosenInlineResultCommand
     {
         protected override string CommandText => Command.select_sticker;
 
@@ -39,7 +39,10 @@ namespace CardCollector.Commands.ChosenInlineResult
                     User.Session.GetModule<DefaultModule>().SelectedSticker = sticker;
                     break;
             }
-            await MessageController.SendSticker(User, sticker.Id);
+            var stickerId = User.Session.State is UserState.AuctionMenu or UserState.ShopMenu
+                ? sticker.IdWithWatermark
+                : sticker.Id;
+            await MessageController.SendSticker(User, stickerId);
             await MessageController.EditMessage(User, sticker.ToString(stickerCount), Keyboard.GetStickerKeyboard(User.Session));
             if (User.Session.State == UserState.AuctionMenu) User.Session.State = UserState.ProductMenu;
         }
@@ -50,7 +53,7 @@ namespace CardCollector.Commands.ChosenInlineResult
                    user.Session.State is UserState.CollectionMenu or UserState.AuctionMenu or UserState.CombineMenu or UserState.Default;
         }
 
-        public SelectStickerInline() { }
-        public SelectStickerInline(UserEntity user, Update update) : base(user, update) { }
+        public SelectSticker() { }
+        public SelectSticker(UserEntity user, Update update) : base(user, update) { }
     }
 }

+ 4 - 1
CardCollector/Commands/ChosenInlineResult/StickerInfo.cs

@@ -15,7 +15,10 @@ namespace CardCollector.Commands.ChosenInlineResult
         {
             var hash = InlineResult.Split('=')[1];
             var sticker = await StickerDao.GetByHash(hash);
-            await MessageController.SendSticker(User, sticker.Id);
+            var stickerId = User.Session.State is UserState.AuctionMenu or UserState.ShopMenu
+                ? sticker.IdWithWatermark
+                : sticker.Id;
+            await MessageController.SendSticker(User, stickerId);
             await MessageController.EditMessage(User, sticker.ToString(), Keyboard.StickerInfoKeyboard);
         }
 

+ 4 - 2
CardCollector/Commands/ChosenInlineResultCommand.cs

@@ -3,9 +3,11 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using CardCollector.Commands.ChosenInlineResult;
+using CardCollector.Commands.Message;
 using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
 using Telegram.Bot.Types;
+using GiveExp = CardCollector.Commands.ChosenInlineResult.GiveExp;
 
 namespace CardCollector.Commands
 {
@@ -41,8 +43,8 @@ namespace CardCollector.Commands
             // Обработка результата при выборе продавца
             new SelectTrader(),
             new StickerInfo(),
-            
-            new SelectStickerInline(),
+            new SelectSticker(),
+            new SelectForSaleSticker(),
         };
 
         /* Метод, создающий объекты команд исходя из полученного обновления */

+ 0 - 2
CardCollector/Commands/InlineQuery/ShowAuctionStickers.cs

@@ -10,8 +10,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowAuctionStickers : InlineQueryCommand
     {
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             // Получаем список стикеров

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

@@ -10,8 +10,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowCollectionStickers : InlineQueryCommand
     {
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             // Получаем список стикеров

+ 0 - 2
CardCollector/Commands/InlineQuery/ShowCombineStickers.cs

@@ -11,8 +11,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowCombineStickers : InlineQueryCommand
     {
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             var module = User.Session.GetModule<CombineModule>();

+ 42 - 0
CardCollector/Commands/InlineQuery/ShowPackStickers.cs

@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using CardCollector.Session.Modules;
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
+
+namespace CardCollector.Commands.InlineQuery
+{
+    public class ShowPackStickers : InlineQueryCommand
+    {
+        public override async Task Execute()
+        {
+            var packId = User.Session.GetModule<AdminModule>().SelectedPack.Id;
+            var stickers = await StickerDao.GetListWhere(item => item.PackId == packId && item.Contains(Query));
+            stickers.Sort(new TierComparer());
+            await MessageController.AnswerInlineQuery(InlineQueryId, stickers.ToTelegramResults(Command.select_for_sale_sticker));
+        }
+
+        private class TierComparer : IComparer<StickerEntity>
+        {
+            public int Compare(StickerEntity? x, StickerEntity? y)
+            {
+                return x?.Tier - y?.Tier ?? 0;
+            }
+        }
+
+        protected internal override bool IsMatches(UserEntity user, Update update)
+        {
+            var adminModule = user.Session.GetModule<AdminModule>();
+            return user.Session.State == UserState.LoadForSaleSticker &&
+                   update.InlineQuery?.ChatType is ChatType.Sender && 
+                   adminModule.SelectedPack != null;
+        }
+
+        public ShowPackStickers() { }
+        public ShowPackStickers(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 0 - 4
CardCollector/Commands/InlineQuery/ShowStickersInBotChat.cs

@@ -10,10 +10,6 @@ namespace CardCollector.Commands.InlineQuery
     /* Отображение стикеров в личной беседt с ботом */
     public class ShowStickersInBotChat : InlineQueryCommand
     {
-        /* Команда - пустая строка, поскольку пользователь может вводить любые слова
-         после @имя_бота, введенная фраза будет использоваться для фильтрации стикеров */
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             // Получаем список стикеров

+ 0 - 4
CardCollector/Commands/InlineQuery/ShowStickersInGroup.cs

@@ -10,10 +10,6 @@ namespace CardCollector.Commands.InlineQuery
     /* Отображение стикеров в чатах, кроме личной беседы с ботом */
     public class ShowStickersInGroup : InlineQueryCommand
     {
-        /* Команда - пустая строка, поскольку пользователь может вводить любые слова
-         после @имя_бота, введенная фраза будет использоваться для фильтрации стикеров */
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             // Получаем список стикеров

+ 0 - 4
CardCollector/Commands/InlineQuery/ShowStickersInPrivate.cs

@@ -9,10 +9,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowStickersInPrivate : InlineQueryCommand
     {
-        /* Команда - пустая строка, поскольку пользователь может вводить любые слова
-         после @имя_бота, введенная фраза будет использоваться для фильтрации стикеров */
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             // Получаем список стикеров

+ 0 - 2
CardCollector/Commands/InlineQuery/ShowStickersInShopPack.cs

@@ -13,8 +13,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowStickersInShopPack : InlineQueryCommand
     {
-        protected override string CommandText => "";
-        
         public override async Task Execute()
         {
             var packId = User.Session.GetModule<ShopModule>().SelectedPack.Id;

+ 0 - 2
CardCollector/Commands/InlineQuery/ShowTradersInBotChat.cs

@@ -10,8 +10,6 @@ namespace CardCollector.Commands.InlineQuery
 {
     public class ShowTradersInBotChat : InlineQueryCommand
     {
-        protected override string CommandText => "";
-
         public override async Task Execute()
         {
             var module = User.Session.GetModule<AuctionModule>();

+ 4 - 1
CardCollector/Commands/InlineQueryCommand.cs

@@ -25,7 +25,9 @@ namespace CardCollector.Commands
         protected readonly string InlineQueryId;
         /* Запрос */
         protected readonly string Query;
-        
+
+        protected override string CommandText => "";
+
         /* Список команд */
         private static readonly List<InlineQueryCommand> List = new()
         {
@@ -43,6 +45,7 @@ namespace CardCollector.Commands
             // Показать стикеры в личных сообщениях с ботом для выбора или просмотра информации
             new ShowStickersInBotChat(),
             new ShowStickersInPrivate(),
+            new ShowPackStickers()
         };
 
         public override async Task PrepareAndExecute()

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

@@ -16,7 +16,6 @@ namespace CardCollector.Commands.Message
 
         public override async Task Execute()
         {
-            User.Session.InitNewModule<AuctionModule>();
             /* Отображаем сообщение с фильтрами */
             await new ShowFiltersMenu(User, Update).Execute();
         }

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

@@ -30,7 +30,7 @@ namespace CardCollector.Commands.Message
                 $"\n{Messages.level}: {User.CurrentLevel.Level}" +
                 $"\n{Messages.current_exp}: {User.CurrentLevel.CurrentExp} / {expGoal}" +
                 $"\n{Messages.cash_capacity}: {User.Cash.MaxCapacity}{Text.coin}",
-                Keyboard.GetProfileKeyboard(User.PrivilegeLevel, packsCount, income));
+                Keyboard.GetProfileKeyboard(packsCount, income));
         }
         
         public Profile() { }

+ 34 - 0
CardCollector/Commands/Message/UploadForSaleSticker.cs

@@ -0,0 +1,34 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.Resources;
+using CardCollector.Session.Modules;
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
+
+namespace CardCollector.Commands.Message
+{
+    public class UploadForSaleSticker : MessageCommand
+    {
+        protected override string CommandText => "";
+        protected override bool ClearStickers => true;
+
+        public override async Task Execute()
+        {
+            var stickerId = Update.Message!.Sticker!.FileId;
+            User.Session.GetModule<AdminModule>().SelectedSticker.ForSaleId = stickerId;
+            await MessageController.EditMessage(User, Messages.add_watermark_success, Keyboard.BackKeyboard);
+        }
+
+        protected internal override bool IsMatches(UserEntity user, Update update)
+        {
+            if (update.Message!.Type is not MessageType.Sticker) return false;
+            if (user.Session.State is not UserState.LoadForSaleSticker) return false;
+            if (user.Session.GetModule<AdminModule>().SelectedSticker == null) return false;
+            return true;
+        }
+
+        public UploadForSaleSticker() { }
+        public UploadForSaleSticker(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 3 - 7
CardCollector/Commands/MessageCommand.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using CardCollector.Commands.CallbackQuery;
 using CardCollector.Commands.Message;
 using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
@@ -42,21 +43,16 @@ namespace CardCollector.Commands
             // Ожидание ввода эмоджи
             new EnterEmoji(),
             new EnterGemsExchange(),
-            // Загрузка стикерпака
-            new DownloadStickerPack(),
             //команда ввода цены
             new EnterGemsPrice(),
             new CreateToken(),
-            // Команда "Показать пример"
-            new ShowSample(),
-            // Команда "Остановить"
-            new StopBot(),
             /* Выгрузка файлов к боту */
             new UploadFile(),
             new UploadSticker(),
             new GiveExp(),
+            new UploadForSaleSticker(),
         };
-
+        
         /* Метод, создающий объекты команд исходя из полученного обновления */
         public static async Task<UpdateModel> Factory(Update update)
         {

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

@@ -22,6 +22,9 @@ namespace CardCollector.DataBase.Entity
         /* Id стикера на сервере Телеграм */
         [Column("id"), MaxLength(127)] public string Id { get; set; }
         
+        /* Id стикера с вотермаркой */
+        [Column("for_sale_id"), MaxLength(127)] public string ForSaleId { get; set; }
+        
         /* Название стикера */
         [Column("title"), MaxLength(256)] public string Title { get; set; }
         
@@ -50,6 +53,8 @@ namespace CardCollector.DataBase.Entity
         
         [Column("pack_id"), MaxLength(32)] public int PackId { get; set; }
 
+        [NotMapped] public string IdWithWatermark => ForSaleId ?? Id;
+
         public override string ToString()
         {
             var str = $"\n{Title} {string.Concat(Enumerable.Repeat(Text.star, Tier))}" +

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

@@ -60,6 +60,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ACE.
+        /// </summary>
+        internal static string add_for_sale_sticker {
+            get {
+                return ResourceManager.GetString("add_for_sale_sticker", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABX.
         /// </summary>
@@ -357,6 +366,24 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ACF.
+        /// </summary>
+        internal static string select_for_sale_pack {
+            get {
+                return ResourceManager.GetString("select_for_sale_pack", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ACG.
+        /// </summary>
+        internal static string select_for_sale_sticker {
+            get {
+                return ResourceManager.GetString("select_for_sale_sticker", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABF.
         /// </summary>
@@ -456,6 +483,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ACD.
+        /// </summary>
+        internal static string show_sample {
+            get {
+                return ResourceManager.GetString("show_sample", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABA.
         /// </summary>
@@ -483,6 +519,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ACC.
+        /// </summary>
+        internal static string stop_bot {
+            get {
+                return ResourceManager.GetString("stop_bot", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABC.
         /// </summary>
@@ -500,5 +545,14 @@ namespace CardCollector.Resources {
                 return ResourceManager.GetString("unlimited_stickers", resourceCulture);
             }
         }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ACH.
+        /// </summary>
+        internal static string upload_stickerpack {
+            get {
+                return ResourceManager.GetString("upload_stickerpack", resourceCulture);
+            }
+        }
     }
 }

+ 18 - 0
CardCollector/Resources/Command.resx

@@ -165,4 +165,22 @@
     <data name="logs_menu" xml:space="preserve">
         <value>ACB</value>
     </data>
+    <data name="stop_bot" xml:space="preserve">
+        <value>ACC</value>
+    </data>
+    <data name="show_sample" xml:space="preserve">
+        <value>ACD</value>
+    </data>
+    <data name="add_for_sale_sticker" xml:space="preserve">
+        <value>ACE</value>
+    </data>
+    <data name="select_for_sale_pack" xml:space="preserve">
+        <value>ACF</value>
+    </data>
+    <data name="select_for_sale_sticker" xml:space="preserve">
+        <value>ACG</value>
+    </data>
+    <data name="upload_stickerpack" xml:space="preserve">
+        <value>ACH</value>
+    </data>
 </root>

+ 1 - 1
CardCollector/Resources/Constants.cs

@@ -5,7 +5,7 @@ namespace CardCollector.Resources
     public static class Constants
     {
         /* Переключить данный флаг при сборке на сервер */
-        public const bool DEBUG = false;
+        public const bool DEBUG = true;
 
         /* Интервал сохранения изменений */
         public const double SAVING_CHANGES_INTERVAL = DEBUG ? 10 * 1000 : 5 * 60 * 1000;

+ 302 - 151
CardCollector/Resources/Keyboard.cs

@@ -12,7 +12,6 @@ namespace CardCollector.Resources
     /* В данном классе содержатся все клавиатуры, используемые в проекте */
     public static class Keyboard
     {
-
         /* Клавиатура, отображаемая с первым сообщением пользователя */
         public static readonly ReplyKeyboardMarkup Menu = new(new[]
         {
@@ -27,6 +26,18 @@ namespace CardCollector.Resources
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
+        public static InlineKeyboardMarkup StopKeyboard = new(new[]
+        {
+            new[] {InlineKeyboardButton.WithCallbackData(Text.stop_bot, Command.stop_bot)},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)}
+        });
+
+        public static InlineKeyboardMarkup ShowStickers = new(new[]
+        {
+            new[] {InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_stickers)},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)}
+        });
+
         public static InlineKeyboardMarkup BuyCoinsKeyboard(bool confirmButton = false)
         {
             var keyboard = new List<InlineKeyboardButton[]>
@@ -38,8 +49,12 @@ namespace CardCollector.Resources
                     InlineKeyboardButton.WithCallbackData($"900{Text.coin}", $"{Command.set_exchange_sum}=90")
                 }
             };
-            if (confirmButton) keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.confirm_exchange,
-                Command.confirm_exchange)});
+            if (confirmButton)
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(Text.confirm_exchange,
+                        Command.confirm_exchange)
+                });
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
@@ -61,44 +76,54 @@ namespace CardCollector.Resources
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
-        public static InlineKeyboardMarkup Settings = new(new[]
+        public static InlineKeyboardMarkup Settings(PrivilegeLevel privilegeLevel)
         {
-            new[] {InlineKeyboardButton.WithCallbackData(Text.alerts, Command.alerts)},
-            new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
-        });
+            var keyboard = new List<InlineKeyboardButton[]>
+            {
+                new[] {InlineKeyboardButton.WithCallbackData(Text.alerts, Command.alerts)},
+            };
+            if (privilegeLevel > PrivilegeLevel.Vip)
+                keyboard.Add(
+                    new[] {InlineKeyboardButton.WithCallbackData(Text.control_panel, Command.control_panel)});
+            keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
+            return new InlineKeyboardMarkup(keyboard);
+        }
 
         public static InlineKeyboardMarkup MyPacks = new(new[]
         {
             InlineKeyboardButton.WithCallbackData(Text.open_packs, Command.my_packs)
         });
-        
+
         public static InlineKeyboardMarkup Alerts(UserSettings settings)
         {
             var keyboard = new List<InlineKeyboardButton[]>
             {
-                new[] {
+                new[]
+                {
                     InlineKeyboardButton.WithCallbackData(
-                        $"{Text.daily_tasks} {(settings[UserSettingsEnum.DailyTasks] ? Text.alert_on : Text.alert_off)}", 
-                        $"{Command.alerts}={(int)UserSettingsEnum.DailyTasks}"),
+                        $"{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}")
+                        $"{Text.sticker_effects} {(settings[UserSettingsEnum.StickerEffects] ? Text.alert_on : Text.alert_off)}",
+                        $"{Command.alerts}={(int) UserSettingsEnum.StickerEffects}")
                 },
-                new[] {
+                new[]
+                {
                     InlineKeyboardButton.WithCallbackData(
-                        $"{Text.exp_gain} {(settings[UserSettingsEnum.ExpGain] ? Text.alert_on : Text.alert_off)}", 
-                        $"{Command.alerts}={(int)UserSettingsEnum.ExpGain}"),
+                        $"{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}")
+                        $"{Text.daily_task_progress} {(settings[UserSettingsEnum.DailyTaskProgress] ? Text.alert_on : Text.alert_off)}",
+                        $"{Command.alerts}={(int) UserSettingsEnum.DailyTaskProgress}")
                 },
-                new[] {
+                new[]
+                {
                     InlineKeyboardButton.WithCallbackData(
-                        $"{Text.piggy_bank_capacity} {(settings[UserSettingsEnum.PiggyBankCapacity] ? Text.alert_on : Text.alert_off)}", 
-                        $"{Command.alerts}={(int)UserSettingsEnum.PiggyBankCapacity}"),
+                        $"{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}")
+                        $"{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)},
             };
@@ -122,25 +147,43 @@ namespace CardCollector.Resources
                 new[] {InlineKeyboardButton.WithCallbackData(Text.tier, Command.tier)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.emoji, Command.emoji)}
             };
-            if (state != UserState.CollectionMenu) keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.price, Command.select_price)});
+            if (state != UserState.CollectionMenu)
+                keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.price, Command.select_price)});
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.sort, Command.sort)});
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             keyboard.Add(new[] {InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_stickers)});
             return new InlineKeyboardMarkup(keyboard);
         }
+
         /* Клавиатура меню сортировки */
         public static readonly InlineKeyboardMarkup SortOptions = new(new[]
         {
             new[] {InlineKeyboardButton.WithCallbackData(Text.no, $"{Command.set}={Command.sort}={SortingTypes.None}")},
-            new[] {InlineKeyboardButton.WithCallbackData(SortingTypes.ByTierIncrease, $"{Command.set}={Command.sort}={SortingTypes.ByTierIncrease}")},
-            new[] {InlineKeyboardButton.WithCallbackData(SortingTypes.ByTierDecrease, $"{Command.set}={Command.sort}={SortingTypes.ByTierDecrease}")},
-            new[] {InlineKeyboardButton.WithCallbackData(SortingTypes.ByAuthor, $"{Command.set}={Command.sort}={SortingTypes.ByAuthor}")},
-            new[] {InlineKeyboardButton.WithCallbackData(SortingTypes.ByTitle, $"{Command.set}={Command.sort}={SortingTypes.ByTitle}")},
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData(SortingTypes.ByTierIncrease,
+                    $"{Command.set}={Command.sort}={SortingTypes.ByTierIncrease}")
+            },
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData(SortingTypes.ByTierDecrease,
+                    $"{Command.set}={Command.sort}={SortingTypes.ByTierDecrease}")
+            },
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData(SortingTypes.ByAuthor,
+                    $"{Command.set}={Command.sort}={SortingTypes.ByAuthor}")
+            },
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData(SortingTypes.ByTitle,
+                    $"{Command.set}={Command.sort}={SortingTypes.ByTitle}")
+            },
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
         /* Клавиатура меню выбора тира */
-        public static readonly InlineKeyboardMarkup TierOptions = new (new[]
+        public static readonly InlineKeyboardMarkup TierOptions = new(new[]
         {
             new[] {InlineKeyboardButton.WithCallbackData(Text.all, $"{Command.set}={Command.tier}=-1")},
             new[] {InlineKeyboardButton.WithCallbackData("1", $"{Command.set}={Command.tier}=1")},
@@ -151,21 +194,21 @@ namespace CardCollector.Resources
         });
 
         /* Клавиатура меню ввода эмоджи */
-        public static readonly InlineKeyboardMarkup EmojiOptions = new (new[]
+        public static readonly InlineKeyboardMarkup EmojiOptions = new(new[]
         {
             new[] {InlineKeyboardButton.WithCallbackData(Text.all, $"{Command.set}={Command.emoji}=")},
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
         /* Клавиатура с одной кнопкой отмены */
-        public static readonly InlineKeyboardMarkup BackKeyboard = new (new[]
+        public static readonly InlineKeyboardMarkup BackKeyboard = new(new[]
         {
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
 
         /* Клавиатура с одной кнопкой отмены */
-        public static readonly InlineKeyboardMarkup StickerInfoKeyboard = new (new[]
+        public static readonly InlineKeyboardMarkup StickerInfoKeyboard = new(new[]
         {
             new[] {InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_stickers)},
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
@@ -176,57 +219,80 @@ namespace CardCollector.Resources
         {
             return new InlineKeyboardMarkup(new[]
             {
-                new[] {InlineKeyboardButton.WithPayment($"{Text.buy} {count}{Text.gem} {Text.per} ₽{count * 69 / 100}")},
+                new[]
+                {
+                    InlineKeyboardButton.WithPayment($"{Text.buy} {count}{Text.gem} {Text.per} ₽{count * 69 / 100}")
+                },
                 new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
         }
 
         /* Клавиатура с отменой и выставлением */
-        public static readonly InlineKeyboardMarkup AuctionPutCancelKeyboard = new (new[]
+        public static readonly InlineKeyboardMarkup AuctionPutCancelKeyboard = new(new[]
         {
             new[] {InlineKeyboardButton.WithCallbackData(Text.sell_on_auction, Command.confirm_selling)},
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
         /* Клавиатура меню выбора цен */
-        public static readonly InlineKeyboardMarkup CoinsPriceOptions = new (new[]
+        public static readonly InlineKeyboardMarkup CoinsPriceOptions = new(new[]
         {
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 0", $"{Command.set}={Command.price_coins_from}=0"),
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 100", $"{Command.set}={Command.price_coins_to}=100"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 0",
+                    $"{Command.set}={Command.price_coins_from}=0"),
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 100",
+                    $"{Command.set}={Command.price_coins_to}=100"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 100", $"{Command.set}={Command.price_coins_from}=100"),
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 500", $"{Command.set}={Command.price_coins_to}=500"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 100",
+                    $"{Command.set}={Command.price_coins_from}=100"),
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 500",
+                    $"{Command.set}={Command.price_coins_to}=500"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 500", $"{Command.set}={Command.price_coins_from}=500"),
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 1000", $"{Command.set}={Command.price_coins_to}=1000"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 500",
+                    $"{Command.set}={Command.price_coins_from}=500"),
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.to} 1000",
+                    $"{Command.set}={Command.price_coins_to}=1000"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 1000", $"{Command.set}={Command.price_coins_from}=1000"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💰 {Text.from} 1000",
+                    $"{Command.set}={Command.price_coins_from}=1000"),
                 InlineKeyboardButton.WithCallbackData($"💰 {Text.to} ∞", $"{Command.set}={Command.price_coins_to}=0"),
             },
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
         });
 
         /* Клавиатура меню выбора цен */
-        public static readonly InlineKeyboardMarkup GemsPriceOptions = new (new[]
+        public static readonly InlineKeyboardMarkup GemsPriceOptions = new(new[]
         {
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 0", $"{Command.set}={Command.price_gems_from}=0"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 0",
+                    $"{Command.set}={Command.price_gems_from}=0"),
                 InlineKeyboardButton.WithCallbackData($"💎 {Text.to} 10", $"{Command.set}={Command.price_gems_to}=10"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 10", $"{Command.set}={Command.price_gems_from}=10"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 10",
+                    $"{Command.set}={Command.price_gems_from}=10"),
                 InlineKeyboardButton.WithCallbackData($"💎 {Text.to} 50", $"{Command.set}={Command.price_gems_to}=50"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 50", $"{Command.set}={Command.price_gems_from}=50"),
-                InlineKeyboardButton.WithCallbackData($"💎 {Text.to} 100", $"{Command.set}={Command.price_gems_to}=100"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 50",
+                    $"{Command.set}={Command.price_gems_from}=50"),
+                InlineKeyboardButton.WithCallbackData($"💎 {Text.to} 100",
+                    $"{Command.set}={Command.price_gems_to}=100"),
             },
-            new[] {
-                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 100", $"{Command.set}={Command.price_gems_from}=100"),
+            new[]
+            {
+                InlineKeyboardButton.WithCallbackData($"💎 {Text.from} 100",
+                    $"{Command.set}={Command.price_gems_from}=100"),
                 InlineKeyboardButton.WithCallbackData($"💎 {Text.to} ∞", $"{Command.set}={Command.price_gems_to}=0"),
             },
             new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
@@ -234,20 +300,26 @@ namespace CardCollector.Resources
 
         public static InlineKeyboardMarkup GetAuthorsKeyboard(List<string> list, InlineKeyboardButton[] pagePanel)
         {
-            var keyboardList = new List<InlineKeyboardButton[]> {
+            var keyboardList = new List<InlineKeyboardButton[]>
+            {
                 /* Добавляем в список кнопку "Все" */
                 new[] {InlineKeyboardButton.WithCallbackData(Text.all, $"{Command.set}={Command.authors_menu}=")}
             };
             foreach (var (author, i) in list.WithIndex())
             {
-                if (i % 2 == 0) keyboardList.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData(author, $"{Command.set}={Command.authors_menu}={author}")
-                });
-                else keyboardList[keyboardList.Count - 1] = new [] {
-                    keyboardList[keyboardList.Count - 1][0],
-                    InlineKeyboardButton.WithCallbackData(author, $"{Command.set}={Command.authors_menu}={author}")
-                };
+                if (i % 2 == 0)
+                    keyboardList.Add(new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(author, $"{Command.set}={Command.authors_menu}={author}")
+                    });
+                else
+                    keyboardList[keyboardList.Count - 1] = new[]
+                    {
+                        keyboardList[keyboardList.Count - 1][0],
+                        InlineKeyboardButton.WithCallbackData(author, $"{Command.set}={Command.authors_menu}={author}")
+                    };
             }
+
             keyboardList.Add(pagePanel);
             return new InlineKeyboardMarkup(keyboardList);
         }
@@ -255,47 +327,61 @@ namespace CardCollector.Resources
         public static InlineKeyboardButton[] GetPagePanel(int page, int totalCount, string callback)
         {
             var arrows = new List<InlineKeyboardButton>();
-            if (page > 1) arrows.Add(InlineKeyboardButton
-                .WithCallbackData(Text.previous, $"{callback}={page - 1}"));
+            if (page > 1)
+                arrows.Add(InlineKeyboardButton
+                    .WithCallbackData(Text.previous, $"{callback}={page - 1}"));
             arrows.Add(InlineKeyboardButton.WithCallbackData(Text.back, Command.back));
-            if (totalCount > page * 10) arrows.Add(InlineKeyboardButton
-                .WithCallbackData(Text.next, $"{callback}={page + 1}"));
+            if (totalCount > page * 10)
+                arrows.Add(InlineKeyboardButton
+                    .WithCallbackData(Text.next, $"{callback}={page + 1}"));
             return arrows.ToArray();
         }
 
-        public static InlineKeyboardMarkup GetShopPacksKeyboard(List<PackEntity> infoList, InlineKeyboardButton[] pagePanel)
+        public static InlineKeyboardMarkup GetPacksKeyboard(List<PackEntity> infoList, string command,
+            InlineKeyboardButton[] pagePanel)
         {
             var keyboardList = new List<InlineKeyboardButton[]>();
             foreach (var (item, i) in infoList.WithIndex())
             {
-                if (i % 2 == 0) keyboardList.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData(item.Author, $"{Command.select_shop_pack}={item.Id}")
-                });
-                else keyboardList[keyboardList.Count - 1] = new [] {
-                    keyboardList[keyboardList.Count - 1][0],
-                    InlineKeyboardButton.WithCallbackData(item.Author, $"{Command.select_shop_pack}={item.Id}")
-                };
+                if (i % 2 == 0)
+                    keyboardList.Add(new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(item.Author, $"{command}={item.Id}")
+                    });
+                else
+                    keyboardList[keyboardList.Count - 1] = new[]
+                    {
+                        keyboardList[keyboardList.Count - 1][0],
+                        InlineKeyboardButton.WithCallbackData(item.Author, $"{command}={item.Id}")
+                    };
             }
+
             keyboardList.Add(pagePanel);
             return new InlineKeyboardMarkup(keyboardList);
         }
 
-        public static async Task<InlineKeyboardMarkup> GetUserPacksKeyboard(List<UserPacks> infoList, InlineKeyboardButton[] pagePanel)
+        public static async Task<InlineKeyboardMarkup> GetUserPacksKeyboard(List<UserPacks> infoList,
+            InlineKeyboardButton[] pagePanel)
         {
             var keyboardList = new List<InlineKeyboardButton[]>();
             foreach (var (item, i) in infoList.WithIndex())
             {
                 var author = await PacksDao.GetById(item.PackId);
-                if (i % 2 == 0) keyboardList.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData($"{author.Author} ({item.Count}{Text.items})", 
-                        $"{Command.open_pack}={item.PackId}")
-                });
-                else keyboardList[keyboardList.Count - 1] = new [] {
-                    keyboardList[keyboardList.Count - 1][0],
-                    InlineKeyboardButton.WithCallbackData($"{author.Author} ({item.Count}{Text.items})",
-                        $"{Command.open_pack}={item.PackId}")
-                };
+                if (i % 2 == 0)
+                    keyboardList.Add(new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData($"{author.Author} ({item.Count}{Text.items})",
+                            $"{Command.open_pack}={item.PackId}")
+                    });
+                else
+                    keyboardList[keyboardList.Count - 1] = new[]
+                    {
+                        keyboardList[keyboardList.Count - 1][0],
+                        InlineKeyboardButton.WithCallbackData($"{author.Author} ({item.Count}{Text.items})",
+                            $"{Command.open_pack}={item.PackId}")
+                    };
             }
+
             keyboardList.Add(pagePanel);
             return new InlineKeyboardMarkup(keyboardList);
         }
@@ -307,21 +393,27 @@ namespace CardCollector.Resources
             var keyboard = new List<InlineKeyboardButton[]>
             {
                 new[] {InlineKeyboardButton.WithSwitchInlineQuery(Text.send_sticker, sticker.Title)},
-                new[] {InlineKeyboardButton.WithCallbackData($"{Text.sell_on_auction} ({count})", Command.sell_on_auction)},
+                new[]
+                {
+                    InlineKeyboardButton.WithCallbackData($"{Text.sell_on_auction} ({count})", Command.sell_on_auction)
+                },
                 new[]
                 {
                     InlineKeyboardButton.WithCallbackData(Text.minus, $"{Command.count}={Text.minus}"),
                     InlineKeyboardButton.WithCallbackData(Text.plus, $"{Command.count}={Text.plus}"),
                 }
             };
-            if (sticker.Tier != 4) keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData($"{Text.combine} ({count})", Command.combine)});
+            if (sticker.Tier != 4)
+                keyboard.Add(
+                    new[] {InlineKeyboardButton.WithCallbackData($"{Text.combine} ({count})", Command.combine)});
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
         public static InlineKeyboardMarkup GetAuctionStickerKeyboard()
         {
-            return new InlineKeyboardMarkup(new[] {
+            return new InlineKeyboardMarkup(new[]
+            {
                 new[] {InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_traders)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
@@ -329,17 +421,23 @@ namespace CardCollector.Resources
 
         public static InlineKeyboardMarkup GetAuctionProductKeyboard(AuctionModule module, double discount, bool owner)
         {
-            var price = (int)(module.Price * module.Count * discount);
+            var price = (int) (module.Price * module.Count * discount);
             var keyboard = new List<InlineKeyboardButton[]>();
             if (owner)
-                keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.return_from_auction, 
-                    Command.return_from_auction)});
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(Text.return_from_auction,
+                        Command.return_from_auction)
+                });
             else
             {
-                keyboard.AddRange(new []
+                keyboard.AddRange(new[]
                 {
-                    new[] {InlineKeyboardButton.WithCallbackData($"{Text.buy} ({module.Count}) {price}{Text.gem}", 
-                        Command.confirm_buying)},
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData($"{Text.buy} ({module.Count}) {price}{Text.gem}",
+                            Command.confirm_buying)
+                    },
                     new[]
                     {
                         InlineKeyboardButton.WithCallbackData(Text.minus, $"{Command.count}={Text.minus}"),
@@ -347,13 +445,15 @@ namespace CardCollector.Resources
                     },
                 });
             }
+
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
         public static InlineKeyboardMarkup GetStickerKeyboard(StickerEntity stickerInfo)
         {
-            return new InlineKeyboardMarkup(new[] {
+            return new InlineKeyboardMarkup(new[]
+            {
                 new[] {InlineKeyboardButton.WithSwitchInlineQuery(Text.send_sticker, stickerInfo.Title)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
@@ -361,7 +461,8 @@ namespace CardCollector.Resources
 
         public static InlineKeyboardMarkup GetConfirmationKeyboard(string command)
         {
-            return new InlineKeyboardMarkup(new[] {
+            return new InlineKeyboardMarkup(new[]
+            {
                 new[]
                 {
                     InlineKeyboardButton.WithCallbackData(Text.no, Command.back),
@@ -383,7 +484,8 @@ namespace CardCollector.Resources
 
         public static InlineKeyboardMarkup GetCombineStickerKeyboard(CombineModule module)
         {
-            return new InlineKeyboardMarkup(new[] {
+            return new InlineKeyboardMarkup(new[]
+            {
                 new[] {InlineKeyboardButton.WithCallbackData($"{Text.add} ({module.Count})", Command.combine)},
                 new[]
                 {
@@ -400,42 +502,57 @@ namespace CardCollector.Resources
             var keyboard = new List<InlineKeyboardButton[]>();
             foreach (var (sticker, _) in module.CombineList)
             {
-                keyboard.Add(new []{InlineKeyboardButton.WithCallbackData($"{Text.delete} {Text.sticker} {keyboard.Count + 1}",
-                    $"{Command.delete_combine}={sticker.Md5Hash}")});
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData($"{Text.delete} {Text.sticker} {keyboard.Count + 1}",
+                        $"{Command.delete_combine}={sticker.Md5Hash}")
+                });
             }
+
             if (module.CombineCount == Constants.COMBINE_COUNT)
-                keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(
-                    $"{Text.combine} {module.CalculateCombinePrice()}{Text.coin}", Command.combine_stickers)});
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{Text.combine} {module.CalculateCombinePrice()}{Text.coin}", Command.combine_stickers)
+                });
             else keyboard.Add(new[] {InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.add_sticker)});
             keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
         /* Клавиатура, отображаемая вместе с сообщением профиля */
-        public static InlineKeyboardMarkup GetProfileKeyboard(PrivilegeLevel level, int packsCount, int income = 0)
+        public static InlineKeyboardMarkup GetProfileKeyboard(int packsCount, int income = 0)
         {
             var keyboard = new List<InlineKeyboardButton[]>();
             if (income > 0)
-                keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData($"{Text.collect} {income}{Text.coin}",
-                    Command.collect_income)});
-            keyboard.AddRange(new []
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData($"{Text.collect} {income}{Text.coin}",
+                        Command.collect_income)
+                });
+            keyboard.AddRange(new[]
             {
                 new[] {InlineKeyboardButton.WithCallbackData(Text.daily_tasks, Command.daily_tasks)},
-                new[] {
+                new[]
+                {
                     InlineKeyboardButton.WithCallbackData(Text.settings, Command.settings),
                     InlineKeyboardButton.WithCallbackData($"{Text.my_packs} {(packsCount > 0 ? Text.gift : "")}",
                         Command.my_packs)
-                }
+                },
+                new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
-            if (level > PrivilegeLevel.Vip) keyboard.Add(
-                new[] {InlineKeyboardButton.WithCallbackData(Text.control_panel, Command.control_panel)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
         public static InlineKeyboardMarkup ShopKeyboard(bool haveOffers)
         {
-            return new InlineKeyboardMarkup(new[] {
-                new[] {InlineKeyboardButton.WithCallbackData(Text.special_offers + (haveOffers ? Text.gift : ""), Command.special_offers)},
+            return new InlineKeyboardMarkup(new[]
+            {
+                new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(Text.special_offers + (haveOffers ? Text.gift : ""),
+                        Command.special_offers)
+                },
                 new[] {InlineKeyboardButton.WithCallbackData(Text.buy_pack, Command.buy_pack)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.buy_coins, Command.buy_coins)},
                 new[] {InlineKeyboardButton.WithCallbackData(Text.buy_gems, Command.buy_gems)},
@@ -447,46 +564,57 @@ namespace CardCollector.Resources
         {
             var keyboard = new List<InlineKeyboardButton[]>();
             foreach (var offer in specialOffers)
-                keyboard.Add(new []{InlineKeyboardButton.WithCallbackData(offer.Title,
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(offer.Title,
                         $"{Command.select_offer}={offer.Id}")
                 });
-            keyboard.Add(new []{InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
+            keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
-        public static InlineKeyboardMarkup ShopPacksKeyboard = new( new[]
-            {
-                new[] {InlineKeyboardButton.WithCallbackData(Text.buy_random, $"{Command.select_shop_pack}=1")},
-                new[] {InlineKeyboardButton.WithCallbackData(Text.buy_author, $"{Command.buy_author_pack_menu}=1")},
-                new[] {InlineKeyboardButton.WithCallbackData(Text.info, Command.pack_info)},
-                new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
-            });
+        public static InlineKeyboardMarkup ShopPacksKeyboard = new(new[]
+        {
+            new[] {InlineKeyboardButton.WithCallbackData(Text.buy_random, $"{Command.select_shop_pack}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.buy_author, $"{Command.buy_author_pack_menu}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.info, Command.pack_info)},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
+        });
 
         public static InlineKeyboardMarkup OfferKeyboard(ShopModule module)
         {
-            var resultPriceCoins = module.SelectedPosition?.ResultPriceCoins 
-                                   ?? module.SelectedPack?.PriceCoins * module.Count ?? -1; 
-            var resultPriceGems = module.SelectedPosition?.ResultPriceGems 
-                                  ?? module.SelectedPack?.PriceGems * module.Count ?? -1; 
+            var resultPriceCoins = module.SelectedPosition?.ResultPriceCoins
+                                   ?? module.SelectedPack?.PriceCoins * module.Count ?? -1;
+            var resultPriceGems = module.SelectedPosition?.ResultPriceGems
+                                  ?? module.SelectedPack?.PriceGems * module.Count ?? -1;
             var keyboard = new List<InlineKeyboardButton[]>();
             if (resultPriceCoins >= 0)
-                keyboard.Add(new [] {InlineKeyboardButton.WithCallbackData(
-                    $"{resultPriceCoins}{Text.coin}", $"{Command.buy_shop_item}=coins")
+                keyboard.Add(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(
+                        $"{resultPriceCoins}{Text.coin}", $"{Command.buy_shop_item}=coins")
                 });
             if (resultPriceGems >= 0)
-                if (keyboard.Count > 0) keyboard[0] = new [] {
-                    keyboard[0][0], 
-                    InlineKeyboardButton.WithCallbackData($"{resultPriceGems}{Text.gem}",
-                        $"{Command.buy_shop_item}=gems")
-                };
-                else keyboard.Add(new [] {InlineKeyboardButton.WithCallbackData($"{resultPriceGems}{Text.gem}",
-                    $"{Command.buy_shop_item}=gems")
+                if (keyboard.Count > 0)
+                    keyboard[0] = new[]
+                    {
+                        keyboard[0][0],
+                        InlineKeyboardButton.WithCallbackData($"{resultPriceGems}{Text.gem}",
+                            $"{Command.buy_shop_item}=gems")
+                    };
+                else
+                    keyboard.Add(new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData($"{resultPriceGems}{Text.gem}",
+                            $"{Command.buy_shop_item}=gems")
+                    });
+            keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.info, Command.show_offer_info)});
+            keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
+            if (module.SelectedPack is { } a && a.Id != 1)
+                keyboard.Insert(0, new[]
+                {
+                    InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_stickers)
                 });
-            keyboard.Add(new [] {InlineKeyboardButton.WithCallbackData(Text.info, Command.show_offer_info)});
-            keyboard.Add(new []{InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
-            if (module.SelectedPack is { } a && a.Id != 1) keyboard.Insert(0, new [] {
-                InlineKeyboardButton.WithSwitchInlineQueryCurrentChat(Text.show_stickers)
-            });
             return new InlineKeyboardMarkup(keyboard);
         }
 
@@ -499,8 +627,8 @@ namespace CardCollector.Resources
         {
             return new InlineKeyboardMarkup(new[]
             {
-                new []{InlineKeyboardButton.WithCallbackData(buttonText, callbackData)},
-                new []{InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
+                new[] {InlineKeyboardButton.WithCallbackData(buttonText, callbackData)},
+                new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
         }
 
@@ -508,9 +636,9 @@ namespace CardCollector.Resources
         {
             return new InlineKeyboardMarkup(new[]
             {
-                new []{InlineKeyboardButton.WithCallbackData(Text.buy_more, callbackData)},
-                new []{InlineKeyboardButton.WithCallbackData(Text.open_packs, Command.my_packs)},
-                new []{InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
+                new[] {InlineKeyboardButton.WithCallbackData(Text.buy_more, callbackData)},
+                new[] {InlineKeyboardButton.WithCallbackData(Text.open_packs, Command.my_packs)},
+                new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)},
             });
         }
 
@@ -518,21 +646,44 @@ namespace CardCollector.Resources
         {
             var keyboard = new List<InlineKeyboardButton[]>();
             if (level >= PrivilegeLevel.Programmer)
-                keyboard.AddRange(new []
+                keyboard.AddRange(new[]
                 {
-                    new []{InlineKeyboardButton.WithCallbackData(Text.logs_menu, $"{Command.logs_menu}={DateTime.Today}")}
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(Text.logs_menu, $"{Command.logs_menu}={DateTime.Today}")
+                    }
                 });
-            keyboard.Add(new []{InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
+            if (level == PrivilegeLevel.Programmer)
+                keyboard.AddRange(new[]
+                {
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(Text.stop_bot, Command.stop_bot)
+                    },
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(Text.upload_stickerpack, Command.upload_stickerpack)
+                    },
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(Text.show_sample, Command.show_sample)
+                    },
+                    new[]
+                    {
+                        InlineKeyboardButton.WithCallbackData(Text.add_for_sale_sticker, $"{Command.add_for_sale_sticker}=1")
+                    },
+                });
+            keyboard.Add(new[] {InlineKeyboardButton.WithCallbackData(Text.back, Command.back)});
             return new InlineKeyboardMarkup(keyboard);
         }
 
         public static InlineKeyboardMarkup LogsMenu(DateTime date)
         {
-            return new InlineKeyboardMarkup(new []
+            return new InlineKeyboardMarkup(new[]
             {
-                InlineKeyboardButton.WithCallbackData(Text.arrow_left, $"{Command.logs_menu}={date.AddDays(1)}"), 
-                InlineKeyboardButton.WithCallbackData(Text.back, Command.back), 
-                InlineKeyboardButton.WithCallbackData(Text.arrow_right, $"{Command.logs_menu}={date.AddDays(-1)}"), 
+                InlineKeyboardButton.WithCallbackData(Text.arrow_left, $"{Command.logs_menu}={date.AddDays(1)}"),
+                InlineKeyboardButton.WithCallbackData(Text.back, Command.back),
+                InlineKeyboardButton.WithCallbackData(Text.arrow_right, $"{Command.logs_menu}={date.AddDays(-1)}"),
             });
         }
     }

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

@@ -60,6 +60,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Стикер с вотеркой добавлен успешно!.
+        /// </summary>
+        internal static string add_watermark_success {
+            get {
+                return ResourceManager.GetString("add_watermark_success", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to 🔔&lt;b&gt;Уведомления&lt;/b&gt;🔔.
         /// </summary>
@@ -215,6 +224,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Выберите стикер:.
+        /// </summary>
+        internal static string choose_sticker {
+            get {
+                return ResourceManager.GetString("choose_sticker", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Выберите тир из списка ниже:.
         /// </summary>
@@ -728,6 +746,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Выберите пак:.
+        /// </summary>
+        internal static string select_pack {
+            get {
+                return ResourceManager.GetString("select_pack", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to за отправку стикера в беседу..
         /// </summary>
@@ -833,6 +860,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to {0}: спасибо, что купил у меня стикер!.
+        /// </summary>
+        internal static string thanks_for_buying_sticker {
+            get {
+                return ResourceManager.GetString("thanks_for_buying_sticker", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Тир.
         /// </summary>
@@ -860,6 +896,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Загрузите файл с вотермаркой для этого стикера:.
+        /// </summary>
+        internal static string upload_file_with_watermark {
+            get {
+                return ResourceManager.GetString("upload_file_with_watermark", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Пожалуйста, загрузите ваш xlsx файл с описанием стикеров, по одной строке на стикер. Соблюдайте порядок, в котором вы загружали стикеры.
         ///Структура таблицы должна сожержать столбцы в следующем порядке: Название, Автор, Тир, Эмоции, Эффект, Описание.

+ 15 - 0
CardCollector/Resources/Messages.resx

@@ -351,4 +351,19 @@ https://telegra.ph/help-11-04-4</value>
     <data name="first_reward" xml:space="preserve">
         <value>Спасибо за регистрацию! Вы получили 7 паков, используйте эту кнопку, чтобы открыть их.</value>
     </data>
+    <data name="select_pack" xml:space="preserve">
+        <value>Выберите пак:</value>
+    </data>
+    <data name="choose_sticker" xml:space="preserve">
+        <value>Выберите стикер:</value>
+    </data>
+    <data name="upload_file_with_watermark" xml:space="preserve">
+        <value>Загрузите файл с вотермаркой для этого стикера:</value>
+    </data>
+    <data name="add_watermark_success" xml:space="preserve">
+        <value>Стикер с вотеркой добавлен успешно!</value>
+    </data>
+    <data name="thanks_for_buying_sticker" xml:space="preserve">
+        <value>{0}: спасибо, что купил у меня стикер!</value>
+    </data>
 </root>

+ 28 - 10
CardCollector/Resources/Text.Designer.cs

@@ -69,6 +69,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Добавить стикер с вотеркой.
+        /// </summary>
+        internal static string add_for_sale_sticker {
+            get {
+                return ResourceManager.GetString("add_for_sale_sticker", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Добавить стикер.
         /// </summary>
@@ -366,15 +375,6 @@ namespace CardCollector.Resources {
             }
         }
         
-        /// <summary>
-        ///   Looks up a localized string similar to Загрузить стикерпак.
-        /// </summary>
-        internal static string download_stickerpack {
-            get {
-                return ResourceManager.GetString("download_stickerpack", resourceCulture);
-            }
-        }
-        
         /// <summary>
         ///   Looks up a localized string similar to Эффект.
         /// </summary>
@@ -781,7 +781,7 @@ namespace CardCollector.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Пример.
+        ///   Looks up a localized string similar to Показать пример.
         /// </summary>
         internal static string show_sample {
             get {
@@ -924,6 +924,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Продавец.
+        /// </summary>
+        internal static string trader {
+            get {
+                return ResourceManager.GetString("trader", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Бессрочно.
         /// </summary>
@@ -933,6 +942,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Загрузить стикерпак.
+        /// </summary>
+        internal static string upload_stickerpack {
+            get {
+                return ResourceManager.GetString("upload_stickerpack", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Да.
         /// </summary>

+ 8 - 2
CardCollector/Resources/Text.resx

@@ -55,7 +55,7 @@
         <value>Аукцион</value>
     </data>
     <data name="show_sample" xml:space="preserve">
-        <value>Пример</value>
+        <value>Показать пример</value>
     </data>
     <data name="start" xml:space="preserve">
         <value>/start</value>
@@ -72,7 +72,7 @@
     <data name="no" xml:space="preserve">
         <value>Нет</value>
     </data>
-    <data name="download_stickerpack" xml:space="preserve">
+    <data name="upload_stickerpack" xml:space="preserve">
         <value>Загрузить стикерпак</value>
     </data>
     <data name="star" xml:space="preserve">
@@ -312,4 +312,10 @@
     <data name="arrow_right" xml:space="preserve">
         <value>▶</value>
     </data>
+    <data name="add_for_sale_sticker" xml:space="preserve">
+        <value>Добавить стикер с вотеркой</value>
+    </data>
+    <data name="trader" xml:space="preserve">
+        <value>Продавец</value>
+    </data>
 </root>

+ 2 - 1
CardCollector/Resources/UserState.cs

@@ -17,6 +17,7 @@
         /* Пользователь в базовом состоянии (по умолчанию) */
         Default,
 
-        UploadSticker
+        UploadSticker,
+        LoadForSaleSticker
     }
 }

+ 16 - 0
CardCollector/Session/Modules/AdminModule.cs

@@ -0,0 +1,16 @@
+using CardCollector.DataBase.Entity;
+
+namespace CardCollector.Session.Modules
+{
+    public class AdminModule : Module
+    {
+        public PackEntity SelectedPack;
+        public StickerEntity SelectedSticker;
+
+        public void Reset()
+        {
+            SelectedPack = null;
+            SelectedSticker = null;
+        }
+    }
+}

+ 7 - 0
global.json

@@ -0,0 +1,7 @@
+{
+  "sdk": {
+    "version": "5.0",
+    "rollForward": "latestMajor",
+    "allowPrerelease": false
+  }
+}