فهرست منبع

shop system and open packs

Tigran 3 سال پیش
والد
کامیت
9d7236efb0
33فایلهای تغییر یافته به همراه862 افزوده شده و 125 حذف شده
  1. 6 8
      CardCollector/Commands/CallbackQuery/AuthorMenuQuery.cs
  2. 84 0
      CardCollector/Commands/CallbackQuery/BuyByCoins.cs
  3. 84 0
      CardCollector/Commands/CallbackQuery/BuyByGems.cs
  4. 24 0
      CardCollector/Commands/CallbackQuery/BuyGemsQuery.cs
  5. 21 0
      CardCollector/Commands/CallbackQuery/BuyPackQuery.cs
  6. 7 4
      CardCollector/Commands/CallbackQuery/BuyStickerQuery.cs
  7. 22 40
      CardCollector/Commands/CallbackQuery/CallbackQuery.cs
  8. 6 7
      CardCollector/Commands/CallbackQuery/MyPacksQuery.cs
  9. 36 10
      CardCollector/Commands/CallbackQuery/OpenPackCallback.cs
  10. 36 0
      CardCollector/Commands/CallbackQuery/OpenSpecificCallback.cs
  11. 20 0
      CardCollector/Commands/CallbackQuery/PackInfo.cs
  12. 6 1
      CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs
  13. 25 0
      CardCollector/Commands/PreCheckoutQuery/Gems50.cs
  14. 2 0
      CardCollector/Commands/PreCheckoutQuery/PreCheckoutQuery.cs
  15. 16 0
      CardCollector/Controllers/MessageController.cs
  16. 1 1
      CardCollector/DailyTasks/CustomTasks/SendStickers.cs
  17. 2 1
      CardCollector/DataBase/CardCollectorDatabase.cs
  18. 2 3
      CardCollector/DataBase/Entity/CashEntity.cs
  19. 2 1
      CardCollector/DataBase/Entity/ShopEntity.cs
  20. 14 0
      CardCollector/DataBase/Entity/SpecificPacksEntity.cs
  21. 3 8
      CardCollector/DataBase/Entity/UsersPacksEntity.cs
  22. 8 2
      CardCollector/DataBase/EntityDao/PacksDao.cs
  23. 40 0
      CardCollector/DataBase/EntityDao/SpecificPackDao.cs
  24. 5 21
      CardCollector/DataBase/EntityDao/UsersPacksDao.cs
  25. 45 0
      CardCollector/Resources/Command.Designer.cs
  26. 15 0
      CardCollector/Resources/Command.resx
  27. 90 0
      CardCollector/Resources/Currency.cs
  28. 55 17
      CardCollector/Resources/Keyboard.cs
  29. 71 0
      CardCollector/Resources/Messages.Designer.cs
  30. 29 0
      CardCollector/Resources/Messages.resx
  31. 63 0
      CardCollector/Resources/Text.Designer.cs
  32. 21 0
      CardCollector/Resources/Text.resx
  33. 1 1
      CardCollector/Session/Session.cs

+ 6 - 8
CardCollector/Commands/CallbackQuery/AuthorMenuQuery.cs

@@ -13,23 +13,21 @@ namespace CardCollector.Commands.CallbackQuery
         protected override string CommandText => Command.author_menu;
         public override async Task Execute()
         {
-            var packs = (await UsersPacksDao.GetUserPacks(User.Id))
-                .Where(item => item.Id != 0 && item.Count > 0)
-                .ToList();
-            if (packs.Count == 0)
+            var packs = await UsersPacksDao.GetUserPacks(User.Id);
+            if (packs.AuthorCount == 0)
                 await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.packs_count_zero, true);
             else
             {
                 var page = int.Parse(CallbackData.Split('=')[1]);
                 var low = page * 10 - 10;
                 var up = page * 10;
-                packs = packs.Where((_, index) => index >= low && index < up).ToList();
-                var info = await Task.WhenAll(packs.Select(item => PacksDao.GetById(item.PackId)));
-                if (packs.Count == 0)
+                var authors = await PacksDao.GetAll();
+                authors = authors.Where((_, index) => index >= low && index < up).ToList();
+                if (authors.Count == 0)
                     await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.page_not_found);
                 else
                     await MessageController.EditMessage(User, CallbackMessageId, Messages.choose_author,
-                        Keyboard.GetAuthorsKeyboard(packs, info.ToList(), page));
+                        Keyboard.GetAuthorsKeyboard(authors, page));
             }
         }
 

+ 84 - 0
CardCollector/Commands/CallbackQuery/BuyByCoins.cs

@@ -0,0 +1,84 @@
+using System;
+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 BuyByCoins : CallbackQuery
+    {
+        protected override string CommandText => Command.buy_by_coins;
+        public override async Task Execute()
+        {
+            var offerInfo = User.Session.GetModule<ShopModule>().SelectedPosition;
+            if (User.Cash.Coins < offerInfo.ResultPriceCoins)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.not_enougth_coins);
+            else if (offerInfo.Expired)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.offer_expired);
+            else if (offerInfo.IsSpecial && !offerInfo.IsInfinite && await SpecialOfferUsersDao.NowUsed(User.Id, offerInfo.Id))
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.you_already_use_this_offer);
+            else
+            {
+                User.Cash.Coins -= offerInfo.ResultPriceCoins;
+                if (offerInfo.IsSpecial && !offerInfo.IsInfinite)
+                    await SpecialOfferUsersDao.AddNew(User.Id, offerInfo.Id);
+                var userPacks = await UsersPacksDao.GetUserPacks(User.Id);
+                switch (offerInfo.PackId)
+                {
+                    case 1:
+                        userPacks.RandomCount += offerInfo.Count;
+                        break;
+                    case 2:
+                        userPacks.AuthorCount += offerInfo.Count;
+                        break;
+                    default:
+                        var info = await SpecificPackDao.GetInfo(User.Id, offerInfo.PackId);
+                        info.Count += offerInfo.Count;
+                        break;
+                }
+                await MessageController.EditMessage(User, CallbackMessageId, Messages.thanks_for_buying);
+                if (offerInfo.AdditionalPrize != "") await GivePrize(offerInfo.AdditionalPrize);
+            }
+        }
+
+        private async Task GivePrize(string prizeInfo)
+        {
+            var data = prizeInfo.Split('=');
+            StickerEntity sticker = null;
+            switch (data[0])
+            {
+                case "tier":
+                    var tier = int.Parse(data[1]);
+                    var stickers = await StickerDao.GetListWhere(item => item.Tier == tier);
+                    var rnd = new Random();
+                    sticker = stickers[rnd.Next(stickers.Count)];
+                    break;
+                case "sticker":
+                    sticker = await StickerDao.GetStickerByHash(data[1]);
+                    break;
+            }
+            if (sticker != null)
+            {
+                if (!User.Stickers.ContainsKey(sticker.Md5Hash))
+                    await UserStickerRelationDao.AddNew(User, sticker, 1);
+                else
+                {
+                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId,
+                        $"{Messages.you_collected} {await User.Cash.Payout(User.Stickers)}{Text.coin}");
+                    User.Stickers[sticker.Md5Hash].Count ++;
+                }
+                var stickerMessage = await MessageController.SendSticker(User, sticker.Id);
+                var message = await MessageController.SendMessage(User, $"{Messages.congratulation}\n{sticker}");
+                User.Session.Messages.Add(stickerMessage.MessageId);
+                User.Session.Messages.Add(message.MessageId);
+            }
+        }
+
+        public BuyByCoins() { }
+        public BuyByCoins(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 84 - 0
CardCollector/Commands/CallbackQuery/BuyByGems.cs

@@ -0,0 +1,84 @@
+using System;
+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 BuyByGems : CallbackQuery
+    {
+        protected override string CommandText => Command.buy_by_gems;
+        public override async Task Execute()
+        {
+            var offerInfo = User.Session.GetModule<ShopModule>().SelectedPosition;
+            if (User.Cash.Gems < offerInfo.ResultPriceGems)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.not_enougth_gems);
+            else if (offerInfo.Expired)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.offer_expired);
+            else if (offerInfo.IsSpecial && !offerInfo.IsInfinite && await SpecialOfferUsersDao.NowUsed(User.Id, offerInfo.Id))
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.you_already_use_this_offer);
+            else
+            {
+                User.Cash.Gems -= offerInfo.ResultPriceGems;
+                if (offerInfo.IsSpecial && !offerInfo.IsInfinite)
+                    await SpecialOfferUsersDao.AddNew(User.Id, offerInfo.Id);
+                var userPacks = await UsersPacksDao.GetUserPacks(User.Id);
+                switch (offerInfo.PackId)
+                {
+                    case 1:
+                        userPacks.RandomCount += offerInfo.Count;
+                        break;
+                    case 2:
+                        userPacks.AuthorCount += offerInfo.Count;
+                        break;
+                    default:
+                        var info = await SpecificPackDao.GetInfo(User.Id, offerInfo.PackId);
+                        info.Count += offerInfo.Count;
+                        break;
+                }
+                await MessageController.EditMessage(User, CallbackMessageId, Messages.thanks_for_buying);
+                if (offerInfo.AdditionalPrize != "") await GivePrize(offerInfo.AdditionalPrize);
+            }
+        }
+
+        private async Task GivePrize(string prizeInfo)
+        {
+            var data = prizeInfo.Split('=');
+            StickerEntity sticker = null;
+            switch (data[0])
+            {
+                case "tier":
+                    var tier = int.Parse(data[1]);
+                    var stickers = await StickerDao.GetListWhere(item => item.Tier == tier);
+                    var rnd = new Random();
+                    sticker = stickers[rnd.Next(stickers.Count)];
+                    break;
+                case "sticker":
+                    sticker = await StickerDao.GetStickerByHash(data[1]);
+                    break;
+            }
+            if (sticker != null)
+            {
+                if (!User.Stickers.ContainsKey(sticker.Md5Hash))
+                    await UserStickerRelationDao.AddNew(User, sticker, 1);
+                else
+                {
+                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId,
+                        $"{Messages.you_collected} {await User.Cash.Payout(User.Stickers)}{Text.coin}");
+                    User.Stickers[sticker.Md5Hash].Count ++;
+                }
+                var stickerMessage = await MessageController.SendSticker(User, sticker.Id);
+                var message = await MessageController.SendMessage(User, $"{Messages.congratulation}\n{sticker}");
+                User.Session.Messages.Add(stickerMessage.MessageId);
+                User.Session.Messages.Add(message.MessageId);
+            }
+        }
+
+        public BuyByGems() { }
+        public BuyByGems(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 24 - 0
CardCollector/Commands/CallbackQuery/BuyGemsQuery.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.Payments;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class BuyGemsQuery : CallbackQuery
+    {
+        protected override string CommandText => Command.buy_gems;
+        public override async Task Execute()
+        {
+            await User.ClearChat();
+            var messages = await MessageController.SendInvoice(User, Text.gems_title, Text.gems_description, 
+                Command.gems50, new[] {new LabeledPrice(Text.gems_label50, 100)});
+            User.Session.Messages.Add(messages.MessageId);
+        }
+
+        public BuyGemsQuery() { }
+        public BuyGemsQuery(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 21 - 0
CardCollector/Commands/CallbackQuery/BuyPackQuery.cs

@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.Resources;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class BuyPackQuery : CallbackQuery
+    {
+        protected override string CommandText => Command.buy_pack;
+        public override async Task Execute()
+        {
+            await MessageController.EditMessage(User, CallbackMessageId, Messages.choose_option,
+                Keyboard.ShopPacksKeyboard);
+        }
+
+        public BuyPackQuery() { }
+        public BuyPackQuery(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 7 - 4
CardCollector/Commands/CallbackQuery/BuyStickerQuery.cs

@@ -25,11 +25,14 @@ namespace CardCollector.Commands.CallbackQuery
             else
             {
                 await auctionModule.SelectedPosition.BuyCard(auctionModule.Count);
-                if (User.Stickers.ContainsKey(auctionModule.SelectedSticker.Md5Hash))
-                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId, 
-                        $"{Messages.you_collected} {await User.Cash.Payout(User.Stickers)}{Text.coin}");
-                else
+                if (!User.Stickers.ContainsKey(auctionModule.SelectedSticker.Md5Hash))
                     await UserStickerRelationDao.AddNew(User, auctionModule.SelectedSticker, auctionModule.Count);
+                else
+                {
+                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId,
+                        $"{Messages.you_collected} {await User.Cash.Payout(User.Stickers)}{Text.coin}");
+                    User.Stickers[auctionModule.SelectedSticker.Md5Hash].Count += auctionModule.Count;
+                }
                 User.Cash.Gems -= auctionModule.Price * auctionModule.Count;
                 User.Session.ResetModule<AuctionModule>();
                 await User.ClearChat();

+ 22 - 40
CardCollector/Commands/CallbackQuery/CallbackQuery.cs

@@ -31,54 +31,36 @@ namespace CardCollector.Commands.CallbackQuery
         /* Список команд, распознаваемых ботом */
         private static readonly List<CallbackQuery> List = new()
         {
-            /* Кнопка "Автор" */
             new AuthorCallback(),
-            /* Кнопка "Тир" */
-            new TierCallback(),
-            /* Кнопка "Эмоция" */
-            new EmojiCallback(),
-            /* Кнопка "Цена" */
-            new PriceCallback(),
-            /* Кнопка "Сортировка" */
-            new SortCallback(),
-            /* Установка фильтра */
-            new SetFilterCallback(),
-            /* Команда отмены */
+            new AuthorMenuQuery(),
+            new BackToCombine(),
+            new BackToFiltersMenu(),
+            new BuyByCoins(),
+            new BuyByGems(),
+            new BuyGemsQuery(),
+            new BuyPackQuery(),
+            new BuyStickerQuery(),
             new CancelCallback(),
-            /* Команда отмены */
+            new ClearChat(),
             new CollectIncomeQuery(),
-            /* Команда для упралвения количеством стикеров */
-            new CountQuery(),
-            /* Команда для подтверждения покупки */
-            new ConfirmBuyingQuery(),
-            /* команда для подтверждения выставления на аукцион */
-            new PutForAuctionQuery(),
-            /* Команда для покупки стикера */
-            new BuyStickerQuery(),
-            /* Команда возврата к комбинированию */
-            new BackToCombine(),
-            /* Команда добавления к комбинироавнию */
             new CombineCallback(),
-            /* Команда удаления из комбинации */
-            new DeleteCombine(),
-            /* Команда удаления из комбинации */
             new CombineStickers(),
-            /* Команда которая позволяет пользователю просмотреть паки */
+            new ConfirmationSellingQuery(),
+            new ConfirmBuyingQuery(),
+            new CountQuery(),
+            new DailyTasksQuery(),
+            new DeleteCombine(),
+            new EmojiCallback(),
             new MyPacksQuery(),
-            /* Показывает меню по автору */
-            new AuthorMenuQuery(),
-            /* Открытие пака автора */
             new OpenPackCallback(),
-            /* Команда которая показывает список ежедневных заданий */
-            new DailyTasksQuery(),
-            //команда подтверждения выставления на аукцион
-            new ConfirmationSellingQuery(),
-            /* Отмена в момент выбора "значения фильтра", не в самом меню */
-            new BackToFiltersMenu(),
-            /* Отмена в момент выбора "значения фильтра", не в самом меню */
+            new OpenSpecificCallback(),
+            new PackInfo(),
+            new PriceCallback(),
+            new PutForAuctionQuery(),
             new SelectOfferCallback(),
-            /* Очистка чата */
-            new ClearChat(),
+            new SetFilterCallback(),
+            new SortCallback(),
+            new TierCallback(),
         };
 
         /* Метод, создающий объекты команд исходя из полученного обновления */

+ 6 - 7
CardCollector/Commands/CallbackQuery/MyPacksQuery.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
 using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
 using CardCollector.DataBase.EntityDao;
@@ -15,13 +14,13 @@ namespace CardCollector.Commands.CallbackQuery
         public override async Task Execute()
         {
             await User.ClearChat();
-            var userPacks = await UsersPacksDao.GetUserPacks(User.Id);
-            var randomCount = (await UsersPacksDao.GetPackInfo(User.Id, 0)).Count;
-            var authorCount = userPacks.Sum(pack => pack.PackId != 0 ? pack.Count : 0);
+            var userPack = await UsersPacksDao.GetUserPacks(User.Id);
+            var specificCount = await SpecificPackDao.GetCount(User.Id);
             var message = await MessageController.SendMessage(User, 
                 $"{Messages.your_packs}" +
-                $"\n{Messages.random_packs} {randomCount}{Text.items}" +
-                $"\n{Messages.author_packs} {authorCount}{Text.items}",
+                $"\n{Messages.random_packs} {userPack.RandomCount}{Text.items}" +
+                $"\n{Messages.author_packs} {userPack.AuthorCount}{Text.items}" +
+                $"\n{Messages.specific_packs} {specificCount}{Text.items}",
                 Keyboard.PackMenu);
             User.Session.Messages.Add(message.MessageId);
         }

+ 36 - 10
CardCollector/Commands/CallbackQuery/OpenPackCallback.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using CardCollector.Controllers;
 using CardCollector.DataBase.Entity;
@@ -15,18 +16,29 @@ namespace CardCollector.Commands.CallbackQuery
         public override async Task Execute()
         {
             var packId = int.Parse(CallbackData.Split("=")[1]);
-            var pack = await UsersPacksDao.GetPackInfo(User.Id, packId);
-            var packInfo = await PacksDao.GetById(pack.PackId);
-            if (pack.Count < 1)
-                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.packs_count_zero, true);
-            else
+            var userPack = await UsersPacksDao.GetUserPacks(User.Id);
+            var rnd = new Random();
+            var tier = GetTier(rnd.NextDouble() * 100);
+            switch (packId)
+            {
+                case 1 when userPack.RandomCount < 1:
+                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.packs_count_zero, true);
+                    break;
+                case 1:
+                    userPack.RandomCount--;
+                    await OpenPack(await StickerDao.GetListWhere(item => item.Tier == tier));
+                    break;
+                default:
+                    if (!await TryOpen()) 
+                        await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.packs_count_zero, true);
+                    var packInfo = await PacksDao.GetById(packId);
+                    await OpenPack(await StickerDao.GetListWhere(item => item.Tier == tier && item.Author == packInfo.Author));
+                    break;
+            }
+
+            async Task OpenPack(List<StickerEntity> stickers)
             {
                 await User.ClearChat();
-                pack.Count--;
-                var rnd = new Random();
-                var tier = GetTier(rnd.NextDouble() * 100);
-                var stickers = await StickerDao.GetListWhere(item => 
-                    item.Tier == tier && (packId == 0 || item.Author == packInfo.Author));
                 var sticker = stickers[rnd.Next(stickers.Count)];
                 if (User.Stickers.ContainsKey(sticker.Md5Hash))
                 {
@@ -41,6 +53,20 @@ namespace CardCollector.Commands.CallbackQuery
                 User.Session.Messages.Add(stickerMessage.MessageId);
                 User.Session.Messages.Add(message.MessageId);
             }
+            
+            async Task<bool> TryOpen()
+            {
+                if (packId == 2)
+                {
+                    if (userPack.AuthorCount < 1) return false;
+                    userPack.AuthorCount--;
+                    return true;
+                }
+                var info = await SpecificPackDao.GetInfo(User.Id, packId);
+                if (info.Count < 1) return false;
+                info.Count--;
+                return true;
+            }
         }
 
         private int GetTier(double chance)

+ 36 - 0
CardCollector/Commands/CallbackQuery/OpenSpecificCallback.cs

@@ -0,0 +1,36 @@
+using System.Linq;
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
+using CardCollector.Resources;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class OpenSpecificCallback : CallbackQuery
+    {
+        protected override string CommandText => Command.open_specific;
+        public override async Task Execute()
+        {
+            var packs = await SpecificPackDao.GetUserPacks(User.Id);
+            if (packs.Count == 0)
+                await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.packs_count_zero, true);
+            else
+            {
+                var page = int.Parse(CallbackData.Split('=')[1]);
+                var low = page * 10 - 10;
+                var up = page * 10;
+                packs = packs.Where((_, index) => index >= low && index < up).ToList();
+                if (packs.Count == 0)
+                    await MessageController.AnswerCallbackQuery(User, CallbackQueryId, Messages.page_not_found);
+                else
+                    await MessageController.EditMessage(User, CallbackMessageId, Messages.choose_author,
+                        await Keyboard.GetAuthorsKeyboard(packs, page));
+            }
+        }
+
+        public OpenSpecificCallback() { }
+        public OpenSpecificCallback(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 20 - 0
CardCollector/Commands/CallbackQuery/PackInfo.cs

@@ -0,0 +1,20 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.Resources;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.CallbackQuery
+{
+    public class PackInfo : CallbackQuery
+    {
+        protected override string CommandText => Command.pack_info;
+        public override async Task Execute()
+        {
+            await MessageController.EditMessage(User, CallbackMessageId, Messages.pack_info);
+        }
+
+        public PackInfo() { }
+        public PackInfo(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 6 - 1
CardCollector/Commands/ChosenInlineResult/SendPrivateSticker.cs

@@ -1,6 +1,8 @@
 using System.Threading.Tasks;
+using CardCollector.Controllers;
 using CardCollector.DailyTasks;
 using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
 using CardCollector.Resources;
 using Telegram.Bot.Types;
 
@@ -15,7 +17,10 @@ namespace CardCollector.Commands.ChosenInlineResult
             var dailyTask = DailyTask.List[DailyTaskKeys.SendStickersToUsers];
             if (await dailyTask.Execute(User.Id))
             {
-                // TODO give reward
+                var userPacks = await UsersPacksDao.GetUserPacks(User.Id);
+                userPacks.RandomCount++;
+                var message = await MessageController.SendMessage(User, Messages.pack_prize);
+                User.Session.Messages.Add(message.MessageId);
             }
         }
 

+ 25 - 0
CardCollector/Commands/PreCheckoutQuery/Gems50.cs

@@ -0,0 +1,25 @@
+using System.Threading.Tasks;
+using CardCollector.Controllers;
+using CardCollector.DataBase.Entity;
+using CardCollector.Resources;
+using Telegram.Bot;
+using Telegram.Bot.Types;
+
+namespace CardCollector.Commands.PreCheckoutQuery
+{
+    public class Gems50 : PreCheckoutQuery
+    {
+        protected override string CommandText => Command.gems50;
+        public override async Task Execute()
+        {
+            await Bot.Client.AnswerPreCheckoutQueryAsync(PreCheckoutQueryId);
+            User.Cash.Gems += 50;
+            await User.ClearChat();
+            var message = await MessageController.SendMessage(User, Messages.thanks_for_buying);
+            User.Session.Messages.Add(message.MessageId);
+        }
+
+        public Gems50() { }
+        public Gems50(UserEntity user, Update update) : base(user, update) { }
+    }
+}

+ 2 - 0
CardCollector/Commands/PreCheckoutQuery/PreCheckoutQuery.cs

@@ -16,6 +16,8 @@ namespace CardCollector.Commands.PreCheckoutQuery
             {
                 // Тестовая покупка
                 new TestPreCheckoutQuery(),
+                // Покупка 50 алмазов
+                new Gems50(),
             };
 
         /* Метод, создающий объекты команд исходя из полученного обновления */

+ 16 - 0
CardCollector/Controllers/MessageController.cs

@@ -232,5 +232,21 @@ namespace CardCollector.Controllers
         {
             await Bot.Client.AnswerInlineQueryAsync(queryId, results, isPersonal: true, nextOffset: offset, cacheTime: Constants.INLINE_RESULTS_CACHE_TIME);
         }
+
+        public static async Task<TgMessage> SendInvoice(UserEntity user, string title, string description, 
+            string payload, IEnumerable<LabeledPrice> prices, Currency currency = Currency.USD)
+        {
+            try
+            {
+                if (!user.IsBlocked)
+                    return await Bot.Client.SendInvoiceAsync(user.ChatId, title, description, payload, 
+                        AppSettings.PAYMENT_PROVIDER, currency.ToString(), prices, disableNotification: true);
+            }
+            catch (Exception e)
+            {
+                LogOutWarning("Can't send photo " + e.Message);
+            }
+            return new TgMessage();
+        }
     }
 }

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

@@ -13,7 +13,7 @@ namespace CardCollector.DailyTasks.CustomTasks
         public override async Task<bool> Execute(long userId, object[] args = null)
         {
             var task = await DailyTaskDao.GetTaskInfo(userId, Id);
-            if (task.Progress == 0) return true;
+            if (task.Progress == 0) return false;
             task.Progress--;
             return task.Progress == 0;
         }

+ 2 - 1
CardCollector/DataBase/CardCollectorDatabase.cs

@@ -67,8 +67,9 @@ namespace CardCollector.DataBase
         public DbSet<UsersPacksEntity> UsersPacks { get; set; }
         public DbSet<PackEntity> Packs { get; set; }
         public DbSet<SpecialOfferUsers> SpecialOfferUsers { get; set; }
+        public DbSet<SpecificPacksEntity> SpecificPacks { get; set; }
+
 
-        
         /* Конфигурация подключения к БД */
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {

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

@@ -57,10 +57,9 @@ namespace CardCollector.DataBase.Entity
             var stickerInfo = await StickerDao.GetById(relation.StickerId);
             var payoutInterval = DateTime.Now - relation.Payout;
             var payoutsCount = (int) (payoutInterval.TotalMinutes / stickerInfo.IncomeTime);
+            relation.Payout = DateTime.Now;
             if (payoutsCount < 1) return 0;
-            relation.Payout += new TimeSpan(0, stickerInfo.IncomeTime, 0) * payoutsCount;
-            var result = stickerInfo.Income * payoutsCount * relation.Count;
-            return result;
+            return stickerInfo.Income * payoutsCount * relation.Count;
         }
     }
 }

+ 2 - 1
CardCollector/DataBase/Entity/ShopEntity.cs

@@ -19,7 +19,8 @@ namespace CardCollector.DataBase.Entity
         [Column("time_limit"), MaxLength(32)] public DateTime TimeLimit { get; set; } = DateTime.Now;
         [Column("additional_prize"), MaxLength(256)] public string AdditionalPrize { get; set; } = "";
 
-        [NotMapped] public bool IsSpecial => Count > 1 || Discount > 0 || TimeLimited || AdditionalPrize != "";
+        [NotMapped] public bool IsSpecial => Count > 1 || Discount > 0 || TimeLimited ||
+                                             AdditionalPrize != "" || PackId is not 1 and not 2;
         [NotMapped] public bool Expired => TimeLimited && TimeLimit < DateTime.Now;
         [NotMapped] public int ResultPriceCoins => PriceCoins < 0 ? -1 : PriceCoins - PriceCoins * Discount / 100;
         [NotMapped] public int ResultPriceGems => PriceGems < 0 ? -1 : PriceGems - PriceGems * Discount / 100;

+ 14 - 0
CardCollector/DataBase/Entity/SpecificPacksEntity.cs

@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace CardCollector.DataBase.Entity
+{
+    [Table("specific_packs")]
+    public class SpecificPacksEntity
+    {
+        [Key][Column("id"), MaxLength(127)] public long Id { get; set; }
+        [Column("user_id"), MaxLength(127)] public long UserId { get; set; }
+        [Column("pack_id"), MaxLength(32)] public int PackId { get; set; }
+        [Column("count"), MaxLength(32)] public int Count { get; set; }
+    }
+}

+ 3 - 8
CardCollector/DataBase/Entity/UsersPacksEntity.cs

@@ -6,16 +6,11 @@ namespace CardCollector.DataBase.Entity
     [Table("users_packs")]
     public class UsersPacksEntity
     {
-        /* Id записи в таблице, роли не играет */
-        [Column("id"), MaxLength(127)] public long Id { get; set; }
-        
-        /* Id комплекта */
-        [Column("pack_id"), MaxLength(32)] public int PackId { get; set; }
-        
         /* Id пользователя */
-        [Column("user_id"), MaxLength(127)] public long UserId { get; set; }
+        [Key] [Column("user_id"), MaxLength(127)] public long UserId { get; set; }
         
         /* Количество паков у пользователя */
-        [Column("count"), MaxLength(32)] public int Count { get; set; }
+        [Column("random_count"), MaxLength(32)] public int RandomCount { get; set; } = 0;
+        [Column("author_count"), MaxLength(32)] public int AuthorCount { get; set; } = 0;
     }
 }

+ 8 - 2
CardCollector/DataBase/EntityDao/PacksDao.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;
 
@@ -24,6 +26,10 @@ namespace CardCollector.DataBase.EntityDao
             await Instance.SaveChangesAsync();
             return result.Entity;
         }
-        
+
+        public static async Task<List<PackEntity>> GetAll()
+        {
+            return (await Table.WhereAsync(item => item.Id is not 1 or 2)).ToList();
+        }
     }
 }

+ 40 - 0
CardCollector/DataBase/EntityDao/SpecificPackDao.cs

@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using CardCollector.DataBase.Entity;
+using Microsoft.EntityFrameworkCore;
+
+namespace CardCollector.DataBase.EntityDao
+{
+    public static class SpecificPackDao
+    {
+        private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(SpecificPackDao));
+        private static readonly DbSet<SpecificPacksEntity> Table = Instance.SpecificPacks;
+
+        public static async Task<SpecificPacksEntity> GetInfo(long userId, int packId)
+        {
+            return await Table.FirstOrDefaultAsync(item => item.PackId == packId && item.UserId == userId)
+                   ?? await AddNew(userId, packId, 0);
+        }
+
+        public static async Task<SpecificPacksEntity> AddNew(long userId, int packId, int count)
+        {
+            return (await Table.AddAsync(new SpecificPacksEntity
+            {
+                UserId = userId,
+                PackId = packId,
+                Count = count
+            })).Entity;
+        }
+
+        public static async Task<int> GetCount(long userId)
+        {
+            return await Table.SumAsync(item => Task.FromResult(item.Count));
+        }
+
+        public static async Task<List<SpecificPacksEntity>> GetUserPacks(long userId)
+        {
+            return (await Table.WhereAsync(item => item.UserId == userId && item.Count > 0)).ToList();
+        }
+    }
+}

+ 5 - 21
CardCollector/DataBase/EntityDao/UsersPacksDao.cs

@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
 using Microsoft.EntityFrameworkCore;
 
@@ -11,29 +9,15 @@ namespace CardCollector.DataBase.EntityDao
         private static readonly CardCollectorDatabase Instance = CardCollectorDatabase.GetSpecificInstance(typeof(UsersPacksDao));
         private static readonly DbSet<UsersPacksEntity> Table = Instance.UsersPacks;
 
-        public static async Task<List<UsersPacksEntity>> GetUserPacks(long userId)
+        public static async Task<UsersPacksEntity> GetUserPacks(long userId)
         {
-            return (await Table.WhereAsync(item => Task.FromResult(item.UserId == userId))).ToList();
+            return await Table.FirstOrDefaultAsync(item => item.UserId == userId) ?? await AddNew(userId);
         }
 
-        public static async Task<UsersPacksEntity> GetPackInfo(long userId, int packId)
+        public static async Task<UsersPacksEntity> AddNew(long userId)
         {
-            return await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.PackId == packId)
-                   ?? await AddNew(userId, packId);
-        }
-
-        public static async Task<UsersPacksEntity> AddNew(long userId, int packId, int count = 0)
-        {
-            if (await Table.FirstOrDefaultAsync(item => item.UserId == userId && item.PackId == packId) is { } obj)
-                return obj;
-            var newPack = new UsersPacksEntity()
-            {
-                PackId = packId,
-                UserId = userId,
-                Count = count
-            };
+            var newPack = new UsersPacksEntity(){UserId = userId};
             var result = await Table.AddAsync(newPack);
-            await Instance.SaveChangesAsync();
             return result.Entity;
         }
     }

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

@@ -132,6 +132,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABK.
+        /// </summary>
+        internal static string buy_offer {
+            get {
+                return ResourceManager.GetString("buy_offer", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABG.
         /// </summary>
@@ -258,6 +267,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABN.
+        /// </summary>
+        internal static string gems50 {
+            get {
+                return ResourceManager.GetString("gems50", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to AAS.
         /// </summary>
@@ -276,6 +294,24 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABO.
+        /// </summary>
+        internal static string open_specific {
+            get {
+                return ResourceManager.GetString("open_specific", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ABM.
+        /// </summary>
+        internal static string pack_info {
+            get {
+                return ResourceManager.GetString("pack_info", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to AAU.
         /// </summary>
@@ -375,6 +411,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to ABL.
+        /// </summary>
+        internal static string shop_authors {
+            get {
+                return ResourceManager.GetString("shop_authors", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to ABA.
         /// </summary>

+ 15 - 0
CardCollector/Resources/Command.resx

@@ -132,4 +132,19 @@
     <data name="buy_by_gems" xml:space="preserve">
         <value>ABJ</value>
     </data>
+    <data name="buy_offer" xml:space="preserve">
+        <value>ABK</value>
+    </data>
+    <data name="shop_authors" xml:space="preserve">
+        <value>ABL</value>
+    </data>
+    <data name="pack_info" xml:space="preserve">
+        <value>ABM</value>
+    </data>
+    <data name="gems50" xml:space="preserve">
+        <value>ABN</value>
+    </data>
+    <data name="open_specific" xml:space="preserve">
+        <value>ABO</value>
+    </data>
 </root>

+ 90 - 0
CardCollector/Resources/Currency.cs

@@ -0,0 +1,90 @@
+namespace CardCollector.Resources
+{
+    public enum Currency
+    {
+        AED, //	United Arab Emirates Dirham	AED 3.67	AED 36,729.80
+        AFN, //	Afghan Afghani	AFN89.95	AFN899,521.53
+        ALL, //	Albanian Lek	104,85ALL	1.048.579,68ALL
+        AMD, //	Armenian Dram	476.58 AMD	4,765,800.79 AMD
+        ARS, //	Argentine Peso	ARS 99,03	ARS 990.379,97
+        AUD, //	Australian Dollar	AU$1.34	AU$13,495.82
+        AZN, //	Azerbaijani Manat	1,70 AZN	17 009,89 AZN
+        BAM, //	Bosnia & Herzegovina Convertible Mark	1,68 BAM	16.861,91 BAM
+        BDT, //	Bangladeshi Taka	BDT 85.37	BDT 853,721.41
+        BGN, //	Bulgarian Lev	1,68 BGN	16 873,90 BGN
+        BND, //	Brunei Dollar	BND1,34	BND13.485,06
+        BOB, //	Bolivian Boliviano	BOB 6,89	BOB 68.929,49
+        BRL, //	Brazilian Real	R$ 5,51	R$ 55.136,98
+        CAD, //	Canadian Dollar	CA$1.23	CA$12,396.40
+        CHF, //	Swiss Franc	0.92 CHF	9'207.06 CHF
+        CLP, //	Chilean Peso	CLP 815	CLP 8.152.098
+        CNY, //	Chinese Renminbi Yuan	CN¥6.43	CN¥64,353.97
+        COP, //	Colombian Peso	COP 3.740,00	COP 37.400.000,00
+        CRC, //	Costa Rican Colón	CRC624,77	CRC6.247.769,30
+        CZK, //	Czech Koruna	21,87 CZK	218 708,98 CZK
+        DKK, //	Danish Krone	6,40 DKK	64089,51 DKK
+        DOP, //	Dominican Peso	DOP56.06	DOP560,687.96
+        DZD, //	Algerian Dinar	DZD 137.39	DZD 1,373,990.11
+        EGP, //	Egyptian Pound	EGP 15.74	EGP 157,457.00
+        EUR, //	Euro	0,86 €	8 612,65 €
+        GBP, //	British Pound	£0.73	£7,300.05
+        GEL, //	Georgian Lari	3,13 GEL	31 300,71 GEL
+        GTQ, //	Guatemalan Quetzal	GTQ7.71	GTQ77,153.40
+        HKD, //	Hong Kong Dollar	HK$7.77	HK$77,788.45
+        HNL, //	Honduran Lempira	HNL 24.00	HNL 240,070.69
+        HRK, //	Croatian Kuna	6,47 HRK	64.751,98 HRK
+        HUF, //	Hungarian Forint	309,72 HUF	3 097 202,56 HUF
+        IDR, //	Indonesian Rupiah	IDR14.115,40	IDR141.154.000,00
+        ILS, //	Israeli New Sheqel	₪ 3.22	₪ 32,228.10
+        INR, //	Indian Rupee	₹75.27	₹752,759.95
+        ISK, //	Icelandic Króna	128 ISK	1.287.193 ISK
+        JMD, //	Jamaican Dollar	JMD149.51	JMD1,495,167.90
+        JPY, //	Japanese Yen	¥113	¥1,133,539
+        KES, //	Kenyan Shilling	KES110.84	KES1,108,497.36
+        KGS, //	Kyrgyzstani Som	84-78 KGS	847 888-36 KGS
+        KRW, //	South Korean Won	₩1,184	₩11,848,850
+        KZT, //	Kazakhstani Tenge	KZT424-64	KZT4 246 450-16
+        LBP, //	Lebanese Pound	LBP 1,520.21	LBP 15,202,105.17
+        LKR, //	Sri Lankan Rupee	LKR 200.35	LKR 2,003,596.83
+        MAD, //	Moroccan Dirham	MAD 9.04	MAD 90,406.15
+        MDL, //	Moldovan Leu	17.27 MDL	172,747.89 MDL
+        MNT, //	Mongolian Tögrög	MNT2 850,92	MNT28 509 244,47
+        MUR, //	Mauritian Rupee	MUR42.85	MUR428,502.40
+        MVR, //	Maldivian Rufiyaa	15.45 MVR	154,502.76 MVR
+        MXN, //	Mexican Peso	MX$20.54	MX$205,472.03
+        MYR, //	Malaysian Ringgit	MYR4.15	MYR41,524.98
+        MZN, //	Mozambican Metical	MZN63.82	MZN638,298.96
+        NGN, //	Nigerian Naira	NGN409.69	NGN4,096,900.19
+        NIO, //	Nicaraguan Córdoba	NIO 35.09	NIO 350,920.30
+        NOK, //	Norwegian Krone	NOK 8,44	NOK 84 409,10
+        NPR, //	Nepalese Rupee	NPR120.21	NPR1,202,138.02
+        NZD, //	New Zealand Dollar	NZ$1.42	NZ$14,258.52
+        PAB, //	Panamanian Balboa	PAB 0.99	PAB 9,968.01
+        PEN, //	Peruvian Nuevo Sol	PEN 4.02	PEN 40,269.50
+        PHP, //	Philippine Peso	PHP50.61	PHP506,154.97
+        PKR, //	Pakistani Rupee	PKR170.29	PKR1,702,998.41
+        PLN, //	Polish Złoty	3,94 PLN	39 426,90 PLN
+        PYG, //	Paraguayan Guaraní	PYG 6.878	PYG 68.789.819
+        QAR, //	Qatari Riyal	QAR 3.64	QAR 36,409.65
+        RON, //	Romanian Leu	4,26 RON	42.646,96 RON
+        RSD, //	Serbian Dinar	101,31 RSD	1.013.138,95 RSD
+        RUB, //	Russian Ruble	71,79 RUB	717 984,98 RUB
+        SAR, //	Saudi Riyal	SAR 3.75	SAR 37,511.54
+        SEK, //	Swedish Krona	8,64 SEK	86.422,15 SEK
+        SGD, //	Singapore Dollar	SGD1.34	SGD13,480.50
+        THB, //	Thai Baht	฿33.17	฿331,769.91
+        TJS, //	Tajikistani Somoni	11;27 TJS	112 739;78 TJS
+        TRY, //	Turkish Lira	9,13 TRY	91.347,75 TRY
+        TTD, //	Trinidad and Tobago Dollar	TTD6.76	TTD67,660.42
+        TWD, //	New Taiwan Dollar	NT$28.09	NT$280,905.04
+        TZS, //	Tanzanian Shilling	TZS2,300.00	TZS23,000,000.52
+        UAH, //	Ukrainian Hryvnia	26,25UAH	262 557,01UAH
+        UGX, //	Ugandan Shilling	UGX3,583	UGX35,835,538
+        USD, //	United States Dollar	$1.00	$10,000.00
+        UYU, //	Uruguayan Peso	UYU 43,29	UYU 432.962,76
+        UZS, //	Uzbekistani Som	10 665,94 UZS	106 659 482,20 UZS
+        VND, //	Vietnamese Đồng	22.758 ₫	227.580.000 ₫
+        YER, //	Yemeni Rial	YER 250.24	YER 2,502,496.39
+        ZAR, //	South African Rand
+    }
+}

+ 55 - 17
CardCollector/Resources/Keyboard.cs

@@ -1,6 +1,8 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using CardCollector.DataBase.Entity;
+using CardCollector.DataBase.EntityDao;
 using CardCollector.Session;
 using CardCollector.Session.Modules;
 using Telegram.Bot.Types.ReplyMarkups;
@@ -20,8 +22,10 @@ namespace CardCollector.Resources
 
         public static readonly InlineKeyboardMarkup PackMenu = new(new[]
         {
-            new[] {InlineKeyboardButton.WithCallbackData(Text.open_random, $"{Command.open_pack}=0")},
-            new[] {InlineKeyboardButton.WithCallbackData(Text.open_author, $"{Command.author_menu}=1")}
+            new[] {InlineKeyboardButton.WithCallbackData(Text.open_random, $"{Command.open_pack}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.open_author, $"{Command.author_menu}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.open_specific, $"{Command.open_specific}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.cancel, Command.cancel)},
         });
 
         public static InlineKeyboardMarkup BackToFilters(string stickerTitle)
@@ -202,24 +206,20 @@ namespace CardCollector.Resources
         }
 
         /* Возвращает клавиатуру со списоком авторов */
-        public static InlineKeyboardMarkup GetAuthorsKeyboard(
-            List<UsersPacksEntity> list, 
-            List<PackEntity> infoList,
-            int page)
+        public static InlineKeyboardMarkup GetAuthorsKeyboard(List<PackEntity> infoList, int page)
         {
             /* Список кнопок на клавиатуре */
             var keyboardList = new List<InlineKeyboardButton[]>();
             /* Копируем список */
-            foreach (var (item, i) in list.WithIndex())
+            foreach (var (item, i) in infoList.WithIndex())
             {
+                Logs.LogOut(i);
                 if (i % 2 == 0) keyboardList.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData($"{infoList[i].Author} {item.Count}{Text.items}",
-                        $"{Command.open_pack}={item.PackId}")
+                    InlineKeyboardButton.WithCallbackData(item.Author, $"{Command.open_pack}={item.Id}")
                 });
                 else keyboardList[keyboardList.Count - 1] = new [] {
                     keyboardList[keyboardList.Count - 1][0],
-                    InlineKeyboardButton.WithCallbackData($"{infoList[i].Author} {item.Count}{Text.items}",
-                        $"{Command.open_pack}={item.PackId}")
+                    InlineKeyboardButton.WithCallbackData(item.Author, $"{Command.open_pack}={item.Id}")
                 };
             }
             keyboardList.Add(new[] {
@@ -233,6 +233,35 @@ namespace CardCollector.Resources
             return new InlineKeyboardMarkup(keyboardList);
         }
 
+        /* Возвращает клавиатуру со списоком авторов */
+        public static async Task<InlineKeyboardMarkup> GetAuthorsKeyboard(List<SpecificPacksEntity> infoList, int page)
+        {
+            /* Список кнопок на клавиатуре */
+            var keyboardList = new List<InlineKeyboardButton[]>();
+            /* Копируем список */
+            foreach (var (item, i) in infoList.WithIndex())
+            {
+                var author = await PacksDao.GetById(item.PackId);
+                Logs.LogOut(i);
+                if (i % 2 == 0) keyboardList.Add(new [] {
+                    InlineKeyboardButton.WithCallbackData($"{author.Author} {item.Count}", $"{Command.open_pack}={item.Id}")
+                });
+                else keyboardList[keyboardList.Count - 1] = new [] {
+                    keyboardList[keyboardList.Count - 1][0],
+                    InlineKeyboardButton.WithCallbackData($"{author.Author} {item.Count}", $"{Command.open_pack}={item.Id}")
+                };
+            }
+            keyboardList.Add(new[] {
+                InlineKeyboardButton.WithCallbackData(Text.previous, $"{Command.open_specific}={page - 1}"),
+                InlineKeyboardButton.WithCallbackData(Text.next, $"{Command.open_specific}={page + 1}")
+            });
+            keyboardList.Add(new[] {
+                InlineKeyboardButton.WithCallbackData(Text.cancel, Command.cancel)
+            });
+            /* Вовзращаем клавиатуру */
+            return new InlineKeyboardMarkup(keyboardList);
+        }
+
         public static InlineKeyboardMarkup GetCollectionStickerKeyboard(CollectionModule module)
         {
             var sticker = module.SelectedSticker;
@@ -361,18 +390,27 @@ namespace CardCollector.Resources
             return new InlineKeyboardMarkup(keyboard);
         }
 
+        public static InlineKeyboardMarkup ShopPacksKeyboard = new (new[]
+        {
+            new[] {InlineKeyboardButton.WithCallbackData(Text.buy_random, $"{Command.select_offer}=1")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.buy_author, $"{Command.select_offer}=2")},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.info, Command.pack_info)},
+            new[] {InlineKeyboardButton.WithCallbackData(Text.cancel, Command.cancel)},
+        });
+
         public static InlineKeyboardMarkup OfferKeyboard(ShopEntity offerInfo)
         {
             var keyboard = new List<InlineKeyboardButton[]>();
             if (offerInfo.PriceCoins >= 0)
-                keyboard.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData($"{Text.buy} {offerInfo.ResultPriceCoins}{Text.coin}", 
-                        Command.buy_by_coins)
+                keyboard.Add(new [] {InlineKeyboardButton.WithCallbackData(
+                    $"{offerInfo.ResultPriceCoins}{Text.coin}", Command.buy_by_coins)
                 });
             if (offerInfo.PriceGems >= 0)
-                keyboard.Add(new [] {
-                    InlineKeyboardButton.WithCallbackData($"{Text.buy} {offerInfo.ResultPriceGems}{Text.gem}", 
-                        Command.buy_by_gems)
+                if (keyboard.Count > 0) keyboard[0] = new [] {keyboard[0][0], InlineKeyboardButton.WithCallbackData(
+                    $"{offerInfo.ResultPriceGems}{Text.gem}", Command.buy_by_gems)
+                };
+                else keyboard.Add(new [] {InlineKeyboardButton.WithCallbackData(
+                    $"{offerInfo.ResultPriceGems}{Text.gem}", Command.buy_by_gems)
                 });
             keyboard.Add(new []{InlineKeyboardButton.WithCallbackData(Text.cancel, Command.cancel)});
             return new InlineKeyboardMarkup(keyboard);

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

@@ -150,6 +150,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Выберите одну из опций ниже.
+        /// </summary>
+        internal static string choose_option {
+            get {
+                return ResourceManager.GetString("choose_option", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Выберите диапазон цен из списка ниже:.
         /// </summary>
@@ -384,6 +393,41 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Время акции истекло!.
+        /// </summary>
+        internal static string offer_expired {
+            get {
+                return ResourceManager.GetString("offer_expired", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to При покупке &quot;Случайного пака&quot; Вам может выпасть ЛЮБОЙ стикер от ЛЮБОГО художника
+        ///
+        ///При покупке &quot;Пака художника&quot; Вам может выпасть ЛЮБОЙ стикер от выбранного Вами художника художника
+        ///
+        ///Вероятности выпадения стикера с тиром:
+        ///1 - 80%
+        ///2 - 16%
+        ///3 - 3.3%
+        ///4 - 0.7%.
+        /// </summary>
+        internal static string pack_info {
+            get {
+                return ResourceManager.GetString("pack_info", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Поздравляем! Вы получили случайный пак! Открыть его можно во вкладке профиля..
+        /// </summary>
+        internal static string pack_prize {
+            get {
+                return ResourceManager.GetString("pack_prize", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to У вас недостаточно паков.
         /// </summary>
@@ -474,6 +518,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Авторских паков:.
+        /// </summary>
+        internal static string specific_packs {
+            get {
+                return ResourceManager.GetString("specific_packs", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Добро пожаловать! Этот бот позволяет вам коллекционировать стикеры. Вы можете выбрать одну из опций ниже. Напишите /help, чтобы получить подробную справку..
         /// </summary>
@@ -501,6 +554,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Спасибо за покупку!.
+        /// </summary>
+        internal static string thanks_for_buying {
+            get {
+                return ResourceManager.GetString("thanks_for_buying", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Тир:.
         /// </summary>
@@ -546,6 +608,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Вы уже воспользовались данным предложением!.
+        /// </summary>
+        internal static string you_already_use_this_offer {
+            get {
+                return ResourceManager.GetString("you_already_use_this_offer", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Получено.
         /// </summary>

+ 29 - 0
CardCollector/Resources/Messages.resx

@@ -195,4 +195,33 @@
     <data name="shop_message" xml:space="preserve">
         <value>Выберите одну из опций ниже:</value>
     </data>
+    <data name="choose_option" xml:space="preserve">
+        <value>Выберите одну из опций ниже</value>
+    </data>
+    <data name="thanks_for_buying" xml:space="preserve">
+        <value>Спасибо за покупку!</value>
+    </data>
+    <data name="pack_info" xml:space="preserve">
+        <value>При покупке "Случайного пака" Вам может выпасть ЛЮБОЙ стикер от ЛЮБОГО художника
+
+При покупке "Пака художника" Вам может выпасть ЛЮБОЙ стикер от выбранного Вами художника художника
+
+Вероятности выпадения стикера с тиром:
+1 - 80%
+2 - 16%
+3 - 3.3%
+4 - 0.7%</value>
+    </data>
+    <data name="pack_prize" xml:space="preserve">
+        <value>Поздравляем! Вы получили случайный пак! Открыть его можно во вкладке профиля.</value>
+    </data>
+    <data name="you_already_use_this_offer" xml:space="preserve">
+        <value>Вы уже воспользовались данным предложением!</value>
+    </data>
+    <data name="offer_expired" xml:space="preserve">
+        <value>Время акции истекло!</value>
+    </data>
+    <data name="specific_packs" xml:space="preserve">
+        <value>Авторских паков:</value>
+    </data>
 </root>

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

@@ -132,6 +132,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Пак художника.
+        /// </summary>
+        internal static string buy_author {
+            get {
+                return ResourceManager.GetString("buy_author", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Купить алмазы.
         /// </summary>
@@ -150,6 +159,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Случайный пак.
+        /// </summary>
+        internal static string buy_random {
+            get {
+                return ResourceManager.GetString("buy_random", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Отмена.
         /// </summary>
@@ -312,6 +330,33 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to После покупки вы получите 50 алмазов на свой счет.
+        /// </summary>
+        internal static string gems_description {
+            get {
+                return ResourceManager.GetString("gems_description", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to 50💎.
+        /// </summary>
+        internal static string gems_label50 {
+            get {
+                return ResourceManager.GetString("gems_label50", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to 50 алмазов.
+        /// </summary>
+        internal static string gems_title {
+            get {
+                return ResourceManager.GetString("gems_title", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to /help.
         /// </summary>
@@ -321,6 +366,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Информация.
+        /// </summary>
+        internal static string info {
+            get {
+                return ResourceManager.GetString("info", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to шт..
         /// </summary>
@@ -402,6 +456,15 @@ namespace CardCollector.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Открыть авторский пак.
+        /// </summary>
+        internal static string open_specific {
+            get {
+                return ResourceManager.GetString("open_specific", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to за.
         /// </summary>

+ 21 - 0
CardCollector/Resources/Text.resx

@@ -210,4 +210,25 @@
     <data name="prize" xml:space="preserve">
         <value>В подарок вы получите</value>
     </data>
+    <data name="buy_random" xml:space="preserve">
+        <value>Случайный пак</value>
+    </data>
+    <data name="buy_author" xml:space="preserve">
+        <value>Пак художника</value>
+    </data>
+    <data name="info" xml:space="preserve">
+        <value>Информация</value>
+    </data>
+    <data name="gems_title" xml:space="preserve">
+        <value>50 алмазов</value>
+    </data>
+    <data name="gems_description" xml:space="preserve">
+        <value>После покупки вы получите 50 алмазов на свой счет</value>
+    </data>
+    <data name="gems_label50" xml:space="preserve">
+        <value>50💎</value>
+    </data>
+    <data name="open_specific" xml:space="preserve">
+        <value>Открыть авторский пак</value>
+    </data>
 </root>

+ 1 - 1
CardCollector/Session/Session.cs

@@ -27,7 +27,7 @@ namespace CardCollector.Session
 
         public T InitNewModule<T>() where T : Module
         {
-            Modules.Add(typeof(T), Activator.CreateInstance<T>());
+            if (!Modules.ContainsKey(typeof(T))) Modules.Add(typeof(T), Activator.CreateInstance<T>());
             return (T) Modules[typeof(T)];
         }