diff --git a/Core/Core.csproj b/Core/Core.csproj
index bb41840..afda299 100644
--- a/Core/Core.csproj
+++ b/Core/Core.csproj
@@ -8,7 +8,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Dockerfile b/Dockerfile
index 167e1dc..64ae22a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,6 +15,7 @@ COPY ./modules/Entertainment/*.csproj ./modules/Entertainment/
COPY ./modules/UserGroupTag/*.csproj ./modules/UserGroupTag/
COPY ./modules/ChatQuotes/*.csproj ./modules/ChatQuotes/
COPY ./modules/Reputation/*.csproj ./modules/Reputation/
+COPY ./modules/ContentStore/*.csproj ./modules/ContentStore/
RUN dotnet restore Kruzya.TelegramBot.sln
# Copy all project files
diff --git a/Kruzya.TelegramBot.sln b/Kruzya.TelegramBot.sln
index 0dcdad6..a995a9a 100644
--- a/Kruzya.TelegramBot.sln
+++ b/Kruzya.TelegramBot.sln
@@ -25,7 +25,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entertainment", "modules\En
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatQuotes", "modules\ChatQuotes\ChatQuotes.csproj", "{04F16325-8F44-4250-9B2E-0F77E8F65CBF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reputation", "modules\Reputation\Reputation.csproj", "{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reputation", "modules\Reputation\Reputation.csproj", "{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContentStore", "modules\ContentStore\ContentStore.csproj", "{4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -77,6 +79,10 @@ Global
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -91,6 +97,7 @@ Global
{1D62101B-C2B1-4E79-8F7A-690E44E3EE81} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
{04F16325-8F44-4250-9B2E-0F77E8F65CBF} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
+ {4F1E8FE8-640F-4671-87FC-0FFCCB6EDF5E} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5BA73C3B-D5FC-4942-9091-504325CDC308}
diff --git a/modules/ContentStore/ContentStore.cs b/modules/ContentStore/ContentStore.cs
new file mode 100644
index 0000000..9522d0b
--- /dev/null
+++ b/modules/ContentStore/ContentStore.cs
@@ -0,0 +1,32 @@
+using System.Reflection;
+using Kruzya.TelegramBot.Core;
+using Microsoft.Extensions.DependencyInjection;
+using West.TelegramBot.ContentStore.Service;
+using Module = Kruzya.TelegramBot.Core.Module;
+
+namespace West.TelegramBot.ContentStore;
+
+public class ContentStore : Module
+{
+ public readonly List MediaServiceTypes = new();
+
+ public ContentStore(Core core) : base(core)
+ {
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ foreach (var exportedType in assembly.GetExportedTypes()
+ .Where(t => !t.IsAbstract && t.GetInterface(nameof(IRandomMediaService)) != null))
+ {
+ MediaServiceTypes.Add(exportedType);
+ }
+ }
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ foreach (var serviceType in MediaServiceTypes)
+ {
+ services.AddSingleton(typeof(IRandomMediaService), serviceType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/ContentStore/ContentStore.csproj b/modules/ContentStore/ContentStore.csproj
new file mode 100644
index 0000000..c76005f
--- /dev/null
+++ b/modules/ContentStore/ContentStore.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0
+ enable
+ enable
+ TelegramBot.ContentStore
+ West.TelegramBot.ContentStore
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/ContentStore/Handler/Store.cs b/modules/ContentStore/Handler/Store.cs
new file mode 100644
index 0000000..0d84bb2
--- /dev/null
+++ b/modules/ContentStore/Handler/Store.cs
@@ -0,0 +1,147 @@
+#nullable enable
+
+using BotFramework;
+using BotFramework.Attributes;
+using BotFramework.Enums;
+using Kruzya.TelegramBot.Core.Extensions;
+using Kruzya.TelegramBot.Core.Service;
+using Microsoft.Extensions.DependencyInjection;
+using Telegram.Bot;
+using Telegram.Bot.Types.Enums;
+using Telegram.Bot.Types.InputFiles;
+using Telegram.Bot.Types.ReplyMarkups;
+using West.TelegramBot.ContentStore.Model;
+using West.TelegramBot.ContentStore.Service;
+
+namespace West.TelegramBot.ContentStore.Handler;
+
+public class Store : BotEventHandler
+{
+ private readonly IReputation? _reputation;
+ private readonly IServiceProvider _provider;
+ private readonly ContentStore _store;
+
+ public Store(IServiceProvider provider, ContentStore store)
+ {
+ _reputation = provider.GetService();
+ _provider = provider;
+ _store = store;
+ }
+
+ [Command(InChat.Public, "store", CommandParseMode.Both)]
+ public async Task HandleStore()
+ {
+ if (_reputation == null)
+ {
+ await Bot.SendTextMessageAsync(Chat.Id, "Reputation is unavailable.");
+ return;
+ }
+
+ var rowList = new List>();
+
+ var counter = 0;
+ var list = new List();
+ foreach (var item in _provider.GetServices())
+ {
+ var btn = item.StoreItem.ToButton(From.Id);
+ if ((counter & 1) == 0) // 0 - even, 1 - odd
+ {
+ list = new List
+ {
+ btn
+ };
+ }
+ else
+ {
+ list.Add(btn);
+ rowList.Add(list);
+ }
+
+ counter++;
+ }
+
+ await Bot.SendTextMessageAsync(Chat.Id, "Чего пожелаете?", replyMarkup: new InlineKeyboardMarkup(rowList));
+ }
+
+ [Update(InChat.Public, UpdateFlag.CallbackQuery)]
+ public async Task HandleStoreCallback()
+ {
+ var query = CallbackQuery;
+ var queryData = query.Data;
+ if (queryData == null || !queryData.StartsWith("store|") || _reputation == null)
+ {
+ return;
+ }
+
+ var paramList = queryData.Split('|');
+ if (!long.TryParse(paramList[1], out var senderId))
+ {
+ return;
+ }
+
+ if (senderId != From.Id)
+ {
+ await AnswerQuery(query.Id, "Keyboard called by another user.");
+ return;
+ }
+
+ var answer = "Unknown action.";
+ if (paramList[2] == "purchase")
+ {
+ var purchaseType = paramList[3];
+ answer = "Спасибо за покупку";
+
+ if (!TryGetMediaService(purchaseType, out var mediaService))
+ {
+ await AnswerQuery(query.Id, "Unknown purchasable.");
+ return;
+ }
+
+ var storeItem = mediaService.StoreItem;
+ var messageId = query.Message!.MessageId;
+ if (await _reputation.GetUserReputationAsync(Chat, From) < storeItem.Price)
+ {
+ await Bot.EditMessageTextAsync(
+ Chat.Id,
+ messageId,
+ $"{From.ToHtml()}, продолжай работать и тебе обязательно хватит на покупку.",
+ ParseMode.Html);
+ await AnswerQuery(query.Id);
+ return;
+ }
+
+ await _reputation.IncrementReputationAsync(Chat, From, -storeItem.Price);
+ await Bot.EditMessageTextAsync(Chat.Id,
+ messageId,
+ $"{From.ToHtml()} купил \"{storeItem.Name}\".",
+ ParseMode.Html);
+
+ var randomMedia = await mediaService!.GetRandomMediaAsync();
+ var file = new InputOnlineFile(randomMedia.Url);
+ switch (randomMedia.Type)
+ {
+ case InputMediaType.Photo:
+ await Bot.SendPhotoAsync(Chat.Id, file, randomMedia.Caption, replyToMessageId: messageId);
+ break;
+ case InputMediaType.Video:
+ await Bot.SendVideoAsync(Chat.Id, file, caption: randomMedia.Caption, replyToMessageId: messageId);
+ break;
+ }
+ }
+
+ await AnswerQuery(query.Id, answer);
+ }
+
+ private bool TryGetMediaService(string name, out IRandomMediaService? mediaService)
+ {
+ mediaService = _provider.GetServices()
+ .FirstOrDefault(s => s.StoreItem.Id == name);
+
+ return mediaService != null;
+ }
+
+ private async Task AnswerQuery(string id, string? message = null)
+ {
+ await Bot.AnswerCallbackQueryAsync(id, message);
+ }
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Model/CatImage.cs b/modules/ContentStore/Model/CatImage.cs
new file mode 100644
index 0000000..4f3b619
--- /dev/null
+++ b/modules/ContentStore/Model/CatImage.cs
@@ -0,0 +1,7 @@
+namespace West.TelegramBot.ContentStore.Model;
+
+public class AnimalImage
+{
+ public string Id { get; set; } = null!;
+ public string Url { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Model/OBoobsItem.cs b/modules/ContentStore/Model/OBoobsItem.cs
new file mode 100644
index 0000000..947016f
--- /dev/null
+++ b/modules/ContentStore/Model/OBoobsItem.cs
@@ -0,0 +1,10 @@
+namespace West.TelegramBot.ContentStore.Model;
+
+public class OBoobsItem
+{
+ public int Id { get; set; }
+ public int Rank { get; set; }
+ public string? Author { get; set; }
+ public string? Model { get; set; }
+ public string Preview { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Model/RandomMedia.cs b/modules/ContentStore/Model/RandomMedia.cs
new file mode 100644
index 0000000..dedb55e
--- /dev/null
+++ b/modules/ContentStore/Model/RandomMedia.cs
@@ -0,0 +1,18 @@
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
+
+namespace West.TelegramBot.ContentStore.Model;
+
+public class RandomMedia
+{
+ public RandomMedia(string url, string? caption = null)
+ {
+ Url = url;
+ Caption = caption;
+ }
+
+ public string Url;
+ public string? Caption;
+
+ public InputMediaType Type { get; set; } = InputMediaType.Photo;
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Model/StoreItem.cs b/modules/ContentStore/Model/StoreItem.cs
new file mode 100644
index 0000000..604d1af
--- /dev/null
+++ b/modules/ContentStore/Model/StoreItem.cs
@@ -0,0 +1,25 @@
+using Telegram.Bot.Types.ReplyMarkups;
+
+namespace West.TelegramBot.ContentStore.Model;
+
+public class StoreItem
+{
+ public string Id;
+ public readonly string Name;
+ public readonly double Price;
+
+ public StoreItem(string id, string name, double price)
+ {
+ Id = id;
+ Name = name;
+ Price = price;
+ }
+
+ public InlineKeyboardButton ToButton(long userId)
+ {
+ return new InlineKeyboardButton($"{Name} ({Price})")
+ {
+ CallbackData = $"store|{userId}|purchase|{Id}"
+ };
+ }
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/AnimBoobsService.cs b/modules/ContentStore/Service/AnimBoobsService.cs
new file mode 100644
index 0000000..7fab5bc
--- /dev/null
+++ b/modules/ContentStore/Service/AnimBoobsService.cs
@@ -0,0 +1,27 @@
+using Telegram.Bot.Types.Enums;
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public class AnimBoobsService : IRandomMediaService
+{
+ private readonly HttpClient _httpClient;
+ public StoreItem StoreItem { get; } = new("boobs", "Сиськи движущиеся", 120);
+
+ public AnimBoobsService()
+ {
+ _httpClient = new HttpClient()
+ {
+ BaseAddress = new Uri("https://westdev.me/_boobs/")
+ };
+ }
+
+ public async Task GetRandomMediaAsync()
+ {
+ var fileName = await _httpClient.GetStringAsync("index.php");
+ return new RandomMedia($"https://westdev.me/_boobs/media/{fileName}")
+ {
+ Type = InputMediaType.Video
+ };
+ }
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/AnimalAsAService.cs b/modules/ContentStore/Service/AnimalAsAService.cs
new file mode 100644
index 0000000..eb6933c
--- /dev/null
+++ b/modules/ContentStore/Service/AnimalAsAService.cs
@@ -0,0 +1,26 @@
+using Newtonsoft.Json;
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public abstract class AnimalAsAService : IRandomMediaService
+{
+ private readonly HttpClient _httpClient;
+ public abstract StoreItem StoreItem { get; }
+
+ protected AnimalAsAService(Uri baseAddress)
+ {
+ _httpClient = new HttpClient
+ {
+ BaseAddress = baseAddress
+ };
+ }
+
+
+ public async Task GetRandomMediaAsync()
+ {
+ var resp = await _httpClient.GetAsync("images/search");
+ var catImage = JsonConvert.DeserializeObject>(await resp.Content.ReadAsStringAsync())[0];
+ return new RandomMedia(catImage.Url);
+ }
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/CatApiService.cs b/modules/ContentStore/Service/CatApiService.cs
new file mode 100644
index 0000000..88fa8b7
--- /dev/null
+++ b/modules/ContentStore/Service/CatApiService.cs
@@ -0,0 +1,9 @@
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public class CatApiService : AnimalAsAService
+{
+ public override StoreItem StoreItem { get; } = new("cat", "Котэ", 40);
+ public CatApiService() : base(new Uri("https://api.thecatapi.com/v1/")) {}
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/DogApiService.cs b/modules/ContentStore/Service/DogApiService.cs
new file mode 100644
index 0000000..cf1ab1e
--- /dev/null
+++ b/modules/ContentStore/Service/DogApiService.cs
@@ -0,0 +1,9 @@
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public class DogApiService : AnimalAsAService
+{
+ public override StoreItem StoreItem { get; } = new("dog", "Пёсель", 40);
+ public DogApiService() : base(new Uri("https://api.thedogapi.com/v1/")) {}
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/IRandomMediaService.cs b/modules/ContentStore/Service/IRandomMediaService.cs
new file mode 100644
index 0000000..4d63126
--- /dev/null
+++ b/modules/ContentStore/Service/IRandomMediaService.cs
@@ -0,0 +1,10 @@
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public interface IRandomMediaService
+{
+ public StoreItem StoreItem { get; }
+
+ public Task GetRandomMediaAsync();
+}
\ No newline at end of file
diff --git a/modules/ContentStore/Service/OBoobsService.cs b/modules/ContentStore/Service/OBoobsService.cs
new file mode 100644
index 0000000..2566f97
--- /dev/null
+++ b/modules/ContentStore/Service/OBoobsService.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using West.TelegramBot.ContentStore.Model;
+
+namespace West.TelegramBot.ContentStore.Service;
+
+public class OBoobsService : IRandomMediaService
+{
+ private readonly HttpClient _httpClient;
+ public StoreItem StoreItem { get; } = new("boobs", "Сиськи", 60);
+
+ public OBoobsService()
+ {
+ _httpClient = new HttpClient
+ {
+ BaseAddress = new Uri("http://api.oboobs.ru")
+ };
+ }
+
+ public async Task GetRandomMediaAsync()
+ {
+ // "/boobs/{start=0; sql offset}/{count=1; sql limit}/{order=-id;[id,rank,-rank,interest,-interest,random]}/
+ var httpResp = await _httpClient.GetAsync("boobs/1/1/random");
+ var boobsResponse = JsonConvert.DeserializeObject>(await httpResp.Content.ReadAsStringAsync());
+ var boobsItem = boobsResponse[0];
+ var boobsId = boobsItem.Id.ToString("D5");
+
+ return new RandomMedia($"https://media.oboobs.ru/boobs/{boobsId}.jpg", boobsItem.Model);
+ }
+}
\ No newline at end of file