Merge pull request #33 from Bubuni-Team/feature/core/chat-options

[core] New fancy option system
This commit is contained in:
Andriy
2023-07-21 19:29:54 +03:00
committed by GitHub
45 changed files with 705 additions and 93 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
+36
View File
@@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization;
using System.Text.Json;
using System;
namespace Kruzya.TelegramBot.Core.Data;
[PrimaryKey(nameof(ChatId), nameof(OptionId))]
public class ChatOptionValue
{
public long ChatId { get; set; }
public string OptionId { get; set; }
public byte[] Value { get; set; }
public T GetValue<T>(T defVal = default)
{
try
{
return JsonSerializer.Deserialize<T>(new ReadOnlySpan<byte>(Value));
}
catch (Exception)
{
return defVal;
}
}
public void SetValue<T>(T value)
{
Value = JsonSerializer.SerializeToUtf8Bytes(value, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
}
+2
View File
@@ -15,6 +15,8 @@ namespace Kruzya.TelegramBot.Core.Data
/// </summary>
public DbSet<BotUserValue> UserValues { get; set; }
public DbSet<ChatOptionValue> ChatOptionValues { get; set; }
public CoreContext(DbContextOptions<CoreContext> ctx) : base(ctx)
{
Database.Migrate();
+3 -3
View File
@@ -22,14 +22,14 @@ namespace Kruzya.TelegramBot.Core.Extensions
public static async Task<bool> IsUserAdminAsync(this ITelegramBotClient bot, Chat chat, User user)
{
var callerMember = await bot.GetChatMemberAsync(chat, user.Id);
var canUse = callerMember is ChatMemberOwner;
var isAdmin = callerMember is ChatMemberOwner;
if (callerMember is ChatMemberAdministrator callerAdmin)
{
canUse = callerAdmin.CanRestrictMembers;
isAdmin = callerAdmin.CanRestrictMembers;
}
return canUse;
return isAdmin;
}
public static async Task<bool> CanDeleteMessagesAsync(this ITelegramBotClient bot, Chat chat)
@@ -0,0 +1,26 @@
using System.Threading.Tasks;
using Kruzya.TelegramBot.Core.Data;
using Microsoft.EntityFrameworkCore;
namespace Kruzya.TelegramBot.Core.Extensions;
public partial class RepositoryExtension
{
public static async Task<ChatOptionValue> FindOption(this DbSet<ChatOptionValue> repository, long chatId,
string optionId)
{
return await repository.SingleOrDefaultAsync(x => x.ChatId == chatId && x.OptionId == optionId);
}
public static async Task<ChatOptionValue> FindOrCreateOption(this DbSet<ChatOptionValue> repository, long chatId,
string optionId)
{
var opt = await repository.FindOption(chatId, optionId) ?? new ChatOptionValue
{
ChatId = chatId,
OptionId = optionId
};
return opt;
}
}
+9 -4
View File
@@ -1,15 +1,20 @@
using System.Collections.Concurrent;
using Kruzya.TelegramBot.Core.Cache;
using Kruzya.TelegramBot.Core.Options;
using Microsoft.Extensions.DependencyInjection;
namespace Kruzya.TelegramBot.Core.Extensions
namespace Kruzya.TelegramBot.Core.Extensions;
public static class StartupExtension
{
public static class StartupExtension
{
public static IServiceCollection AddQueue<T>(this IServiceCollection collection)
=> collection.AddSingleton<ConcurrentQueue<T>>();
public static IServiceCollection AddMemoryCache<TKey, TValue>(this IServiceCollection collection)
=> collection.AddSingleton<MemoryCache<TKey, TValue>>();
}
public static IServiceCollection AddOption<T>(this IServiceCollection collection) where T : class, IOption
=> collection.AddScoped<T>()
.AddScoped<IOption, T>(s => s.GetService<T>());
}
@@ -9,7 +9,7 @@ using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace Kruzya.TelegramBot.Core
namespace Kruzya.TelegramBot.Core.Handler
{
public abstract class CommonHandler : BotEventHandler
{
@@ -53,5 +53,10 @@ namespace Kruzya.TelegramBot.Core
{
return await Db.UserValues.FindOrCreateOption(Chat.Id, user.Id, "warnCount");
}
protected async Task AnswerQuery(string id, string? message = null)
{
await Bot.AnswerCallbackQueryAsync(id, message);
}
}
}
@@ -1,14 +1,12 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BotFramework;
using BotFramework.Attributes;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Microsoft.EntityFrameworkCore;
using Telegram.Bot.Types;
namespace Kruzya.TelegramBot.Core
namespace Kruzya.TelegramBot.Core.Handler
{
public class GeneralHandler : BotEventHandler
{
@@ -32,7 +30,7 @@ namespace Kruzya.TelegramBot.Core
[Priority(short.MaxValue - 10)]
public async Task<bool> Listener()
{
foreach (var message in new Message[] {RawUpdate.Message, RawUpdate.EditedMessage, RawUpdate.ChannelPost, RawUpdate.EditedChannelPost})
foreach (var message in new Message[] { RawUpdate.Message, RawUpdate.EditedMessage, RawUpdate.ChannelPost, RawUpdate.EditedChannelPost })
{
if (message == null)
{
+184
View File
@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Options;
using Microsoft.Extensions.DependencyInjection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;
namespace Kruzya.TelegramBot.Core.Handler;
public class SettingsHandler : CommonHandler
{
private readonly IEnumerable<IOption> _options;
public SettingsHandler(CoreContext db, IServiceProvider serviceProvider) : base(db)
{
_options = serviceProvider.GetServices<IOption>();
}
[Command(InChat.Public, "settings", CommandParseMode.WithUsername)]
public async Task HandleSettings()
{
if (!await Bot.IsUserAdminAsync(Chat, From))
{
return;
}
await Bot.SendTextMessageAsync(
Chat.Id,
"What do you want to change?",
replyMarkup: GetSettingsMarkup()
);
}
[Update(InChat.Public, UpdateFlag.CallbackQuery)]
public async Task HandleCallback()
{
var query = CallbackQuery;
var queryData = query.Data;
if (queryData == null || !queryData.StartsWith("option") || !await Bot.IsUserAdminAsync(Chat, From))
{
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;
}
if (paramList[2] == "main_menu")
{
await AnswerQuery(query.Id);
await Bot.EditMessageTextAsync(Chat.Id, query.Message!.MessageId, "What do you want to change?",
replyMarkup: GetSettingsMarkup());
return;
}
if (paramList[2] == "edit" || paramList[2] == "select")
{
var option = GetOptionById(paramList[3]);
if (option == null)
{
await AnswerQuery(query.Id, "Invalid option ID");
return;
}
switch (paramList[2])
{
case "edit":
await HandleOptionEdit(option, paramList, query);
break;
case "select":
await HandleOptionSelect(option, paramList, query);
break;
}
}
}
private async Task HandleOptionEdit(IOption option, string[] paramList, CallbackQuery query)
{
var chatId = Chat.Id;
var messageId = query.Message!.MessageId;
switch (option.OptionType)
{
case OptionType.OnOff:
var onOffOpt = (IOption<bool>) option;
await onOffOpt.SetValueAsync(chatId, !await onOffOpt.GetValueAsync(chatId));
await AnswerQuery(query.Id);
await Bot.EditMessageReplyMarkupAsync(chatId, messageId, GetSettingsMarkup());
break;
case OptionType.Select:
var selectOpt = (IOption<string>) option;
await AnswerQuery(query.Id);
await Bot.EditMessageTextAsync(Chat.Id, messageId, "Here's what you can choose:",
replyMarkup: await GetChoiceSelectKeyboard(selectOpt));
break;
default:
await AnswerQuery(query.Id, "Not implemented");
break;
}
}
private async Task HandleOptionSelect(IOption option, string[] paramList, CallbackQuery query)
{
var choice = option.ChoiceList.SingleOrDefault(x => x.Id == paramList[4]);
if (choice == null)
{
await AnswerQuery(query.Id, "Invalid option choice");
return;
}
var selectOption = (IOption<string>) option;
await selectOption.SetValueAsync(Chat.Id, choice.Id);
await AnswerQuery(query.Id);
await Bot.EditMessageReplyMarkupAsync(Chat.Id, query.Message!.MessageId, await GetChoiceSelectKeyboard(selectOption));
}
private async Task<InlineKeyboardButton> GetKeyboardButton(IOption option)
{
var button = new InlineKeyboardButton(option.OptionName)
{
CallbackData = $"option|{From.Id}|edit|{option.OptionId}"
};
if (option.OptionType == OptionType.OnOff)
{
var isEnabled = await ((IOption<bool>) option).GetValueAsync(Chat.Id);
button.Text += isEnabled ? " 🟢" : " 🔴";
}
return button;
}
private async Task<InlineKeyboardMarkup> GetChoiceSelectKeyboard(IOption<string> option)
{
var value = await option.GetValueAsync(Chat.Id);
var choices = option.ChoiceList.Select(x => x.ToButton(From.Id, option.OptionId, x.Id == value))
.ToList();
choices.Add(new []
{
new InlineKeyboardButton("Main menu")
{
CallbackData = $"option|{From.Id}|main_menu"
}
});
return new InlineKeyboardMarkup(choices);
}
private InlineKeyboardMarkup GetSettingsMarkup()
{
var keyboardLayout = _options.ToAsyncEnumerable()
.SelectAwait(async x => await GetKeyboardButton(x))
.ToEnumerable()
.Chunk(3);
return new InlineKeyboardMarkup(keyboardLayout);
}
private IOption GetOptionById(string optionId)
{
return _options.SingleOrDefault(x => x.OptionId == optionId);
}
}
@@ -0,0 +1,95 @@
// <auto-generated />
using System;
using Kruzya.TelegramBot.Core.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Kruzya.TelegramBot.Core.Migrations
{
[DbContext(typeof(CoreContext))]
[Migration("20230720171645_AddChatOptions")]
partial class AddChatOptions
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUser", b =>
{
b.Property<Guid>("BotUserId")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<long>("ChatId")
.HasColumnType("bigint");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("BotUserId");
b.ToTable("Users");
});
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUserValue", b =>
{
b.Property<Guid>("BotUserValueId")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid?>("BotUserId")
.HasColumnType("char(36)");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<byte[]>("Value")
.HasColumnType("longblob");
b.Property<string>("ValueType")
.HasColumnType("longtext");
b.HasKey("BotUserValueId");
b.HasIndex("BotUserId");
b.ToTable("UserValues");
});
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.ChatOptionValue", b =>
{
b.Property<long>("ChatId")
.HasColumnType("bigint");
b.Property<string>("OptionId")
.HasColumnType("varchar(255)");
b.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("longblob");
b.HasKey("ChatId", "OptionId");
b.ToTable("ChatOptionValues");
});
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUserValue", b =>
{
b.HasOne("Kruzya.TelegramBot.Core.Data.BotUser", "BotUser")
.WithMany()
.HasForeignKey("BotUserId");
b.Navigation("BotUser");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,58 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Kruzya.TelegramBot.Core.Migrations
{
/// <inheritdoc />
public partial class AddChatOptions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ChatOptionValues",
columns: table => new
{
ChatId = table.Column<long>(type: "bigint", nullable: false),
OptionId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<byte[]>(type: "longblob", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChatOptionValues", x => new { x.ChatId, x.OptionId });
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChatOptionValues");
migrationBuilder.AlterColumn<string>(
name: "ValueType",
table: "UserValues",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "UserValues",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}
+24 -3
View File
@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Kruzya.TelegramBot.Core.Migrations
{
[DbContext(typeof(CoreContext))]
@@ -14,7 +16,7 @@ namespace Kruzya.TelegramBot.Core.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.2")
.HasAnnotation("ProductVersion", "7.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUser", b =>
@@ -44,13 +46,13 @@ namespace Kruzya.TelegramBot.Core.Migrations
.HasColumnType("char(36)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<byte[]>("Value")
.HasColumnType("longblob");
b.Property<string>("ValueType")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.HasKey("BotUserValueId");
@@ -59,11 +61,30 @@ namespace Kruzya.TelegramBot.Core.Migrations
b.ToTable("UserValues");
});
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.ChatOptionValue", b =>
{
b.Property<long>("ChatId")
.HasColumnType("bigint");
b.Property<string>("OptionId")
.HasColumnType("varchar(255)");
b.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("longblob");
b.HasKey("ChatId", "OptionId");
b.ToTable("ChatOptionValues");
});
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUserValue", b =>
{
b.HasOne("Kruzya.TelegramBot.Core.Data.BotUser", "BotUser")
.WithMany()
.HasForeignKey("BotUserId");
b.Navigation("BotUser");
});
#pragma warning restore 612, 618
}
+31
View File
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Options;
public abstract class AbstractOption<TOption, TValue> : IOption<TValue>
where TOption: AbstractOption<TOption, TValue>
{
public abstract string OptionId { get; }
public abstract string OptionName { get; }
public abstract OptionType OptionType { get; }
public abstract TValue DefaultValue { get; }
public List<SelectChoice> ChoiceList { get; } = new();
private readonly IOptionProvider _optionProvider;
protected AbstractOption(IOptionProvider optionProvider)
{
_optionProvider = optionProvider;
}
public async Task<TValue> GetValueAsync(long chatId)
{
return await _optionProvider.GetValueAsync(chatId, OptionId, DefaultValue);
}
public async Task SetValueAsync(long chatId, TValue value)
{
await _optionProvider.SetValueAsync(chatId, OptionId, value);
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.Threading.Tasks;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
namespace Kruzya.TelegramBot.Core.Options;
public class DbOptionProvider : IOptionProvider
{
private readonly CoreContext _db;
public DbOptionProvider(CoreContext db)
{
_db = db;
}
public async Task<TValue> GetValueAsync<TValue>(long chatId, string optionId, TValue defVal)
{
return (await _db.ChatOptionValues.FindOrCreateOption(chatId, optionId)).GetValue(defVal);
}
public async Task SetValueAsync<TValue>(long chatId, string optionId, TValue value)
{
var chatOption = await _db.ChatOptionValues.FindOrCreateOption(chatId, optionId);
chatOption.SetValue(value);
_db.AddOrUpdate(chatOption);
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Options;
public interface IOption
{
public string OptionId { get; }
public string OptionName { get; }
public OptionType OptionType { get; }
public List<SelectChoice> ChoiceList { get; }
}
public interface IOption<TValue> : IOption
{
public TValue DefaultValue { get; }
public Task<TValue> GetValueAsync(long chatId);
public Task SetValueAsync(long chatId, TValue value);
}
+9
View File
@@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Options;
public interface IOptionProvider
{
public Task<TValue> GetValueAsync<TValue>(long chatId, string optionId, TValue defVal);
public Task SetValueAsync<TValue>(long chatId, string optionId, TValue value);
}
+7
View File
@@ -0,0 +1,7 @@
namespace Kruzya.TelegramBot.Core.Options;
public enum OptionType
{
OnOff,
Select
}
+29
View File
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using Telegram.Bot.Types.ReplyMarkups;
namespace Kruzya.TelegramBot.Core.Options;
public class SelectChoice
{
public string Id { get; }
public string FullName { get; }
public string ShortName { get; }
public SelectChoice(string id, string fullName, string shortName)
{
Id = id;
FullName = fullName;
ShortName = shortName;
}
public IEnumerable<InlineKeyboardButton> ToButton(long userId, string optionId, bool isActive = false)
{
return new[]
{
new InlineKeyboardButton((isActive ? "🟢 " : "🔴 ") + FullName)
{
CallbackData = $"option|{userId}|select|{optionId}|{Id}"
}
};
}
}
+3
View File
@@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using Kruzya.TelegramBot.Core.Options;
using Telegram.Bot.Types;
namespace Kruzya.TelegramBot.Core
@@ -53,6 +54,8 @@ namespace Kruzya.TelegramBot.Core
services.AddSingleton<ConcurrentBag<DeleteRequest>>();
services.AddHostedService<DeleteService>();
services.AddScoped<IOptionProvider, DbOptionProvider>();
// Trigger module handlers.
_core.Modules.ConfigureServices(services);
}
+1 -1
View File
@@ -4,9 +4,9 @@ using System;
using System.Threading.Tasks;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
@@ -1,5 +1,5 @@
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Handler;
namespace West.TelegramBot.ChatManagement.Handler;
+1 -1
View File
@@ -1,9 +1,9 @@
using System.Threading.Tasks;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Telegram.Bot;
using Telegram.Bot.Exceptions;
using West.TelegramBot.ChatManagement.Service;
+1 -1
View File
@@ -3,8 +3,8 @@
using System.Threading.Tasks;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Handler;
using West.TelegramBot.ChatManagement.Service;
namespace West.TelegramBot.ChatManagement.Handler;
+1 -1
View File
@@ -3,9 +3,9 @@ using System;
using System.Threading.Tasks;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Kruzya.TelegramBot.Core.Service;
using Microsoft.Extensions.DependencyInjection;
using Telegram.Bot;
+5
View File
@@ -1,5 +1,8 @@
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Options;
using Microsoft.Extensions.DependencyInjection;
using West.TelegramBot.ContentStore.Option;
using West.TelegramBot.ContentStore.Service;
namespace West.TelegramBot.ContentStore;
@@ -22,6 +25,8 @@ public class ContentStore : Module
public override void ConfigureServices(IServiceCollection services)
{
services.AddOption<AllowNsfw>();
foreach (var serviceType in MediaServiceTypes)
{
services.AddScoped(typeof(IRandomMediaService), serviceType);
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
+5 -10
View File
@@ -1,26 +1,26 @@
#nullable enable
using BotFramework;
using BotFramework.Attributes;
using BotFramework.Enums;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Kruzya.TelegramBot.Core.Service;
using Microsoft.Extensions.DependencyInjection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
using West.TelegramBot.ContentStore.Service;
namespace West.TelegramBot.ContentStore.Handler;
public class Store : BotEventHandler
public class Store : CommonHandler
{
private readonly IReputation? _reputation;
private readonly IServiceProvider _provider;
private readonly ContentStore _store;
public Store(IServiceProvider provider, ContentStore store)
public Store(CoreContext db, IServiceProvider provider, ContentStore store) : base(db)
{
_reputation = provider.GetService<IReputation>();
_provider = provider;
@@ -110,7 +110,7 @@ public class Store : BotEventHandler
{
await PerformPurchase(mediaService, messageId);
}
catch (Exception e)
catch (Exception)
{
answer = "Товар недоступен";
await Bot.EditMessageTextAsync(Chat.Id,
@@ -164,9 +164,4 @@ public class Store : BotEventHandler
return mediaService != null;
}
private async Task AnswerQuery(string id, string? message = null)
{
await Bot.AnswerCallbackQueryAsync(id, message);
}
}
+15
View File
@@ -0,0 +1,15 @@
using Kruzya.TelegramBot.Core.Options;
namespace West.TelegramBot.ContentStore.Option;
public class AllowNsfw : AbstractOption<AllowNsfw, bool>
{
public override string OptionId => "allowNSFW";
public override string OptionName => "NSFW";
public override OptionType OptionType => OptionType.OnOff;
public override bool DefaultValue => false;
public AllowNsfw(IOptionProvider optionProvider) : base(optionProvider)
{
}
}
@@ -1,25 +1,23 @@
using System.Net.Http.Headers;
using System.Text;
using BotFramework.Utils;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Microsoft.Extensions.Configuration;
using Telegram.Bot.Types;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
public abstract class AbstractKittiesService : IRandomMediaService
{
private readonly CoreContext _db;
private readonly AllowNsfw _allowNsfw;
protected abstract string VideoType { get; }
public abstract StoreItem StoreItem { get; }
private readonly HttpClient _httpClient;
protected AbstractKittiesService(IConfiguration configuration, CoreContext db)
protected AbstractKittiesService(IConfiguration configuration, AllowNsfw allowNsfw)
{
_db = db;
var section = configuration.GetSection("KittiesService");
var uri = section.GetValue<Uri>("Uri");
@@ -33,6 +31,7 @@ public abstract class AbstractKittiesService : IRandomMediaService
Authorization = new AuthenticationHeaderValue("Bearer", token)
}
};
_allowNsfw = allowNsfw;
}
public async Task<RandomMedia> GetRandomMediaAsync()
@@ -52,8 +51,6 @@ public abstract class AbstractKittiesService : IRandomMediaService
public async Task<bool> CanView(Chat chat, User user)
{
var option = await _db.UserValues.FindOrCreateOption(chat.Id, "AllowNSFW");
return option.GetValue(false);
return await _allowNsfw.GetValueAsync(chat.Id);
}
}
@@ -0,0 +1,19 @@
using Telegram.Bot.Types;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
public abstract class AbstractNsfwService : IRandomMediaService
{
private readonly AllowNsfw _allowNsfw;
public abstract StoreItem StoreItem { get; }
public abstract Task<RandomMedia> GetRandomMediaAsync();
protected AbstractNsfwService(AllowNsfw allowNsfw)
{
_allowNsfw = allowNsfw;
}
public async Task<bool> CanView(Chat chat, User user) => await _allowNsfw.GetValueAsync(chat.Id);
}
@@ -1,27 +1,24 @@
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Telegram.Bot.Types;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
public class AnimBoobsService : IRandomMediaService
public class AnimBoobsService : AbstractNsfwService
{
private readonly CoreContext _db;
private readonly HttpClient _httpClient;
public StoreItem StoreItem { get; } = new("anim_boobs", "Сиськи движущиеся", 120, 40);
public override StoreItem StoreItem { get; } = new("anim_boobs", "Сиськи движущиеся", 120, 40);
public AnimBoobsService(CoreContext db)
public AnimBoobsService(AllowNsfw allowNsfw) : base(allowNsfw)
{
_db = db;
_httpClient = new HttpClient()
_httpClient = new HttpClient
{
BaseAddress = new Uri("https://westdev.me/_boobs/")
};
}
public async Task<RandomMedia> GetRandomMediaAsync()
public override async Task<RandomMedia> GetRandomMediaAsync()
{
var fileName = await _httpClient.GetStringAsync("index.php");
// https://bugs.telegram.org/c/23674 workaround
@@ -33,11 +30,4 @@ public class AnimBoobsService : IRandomMediaService
Nsfw = true
};
}
public async Task<bool> CanView(Chat chat, User user)
{
var option = await _db.UserValues.FindOrCreateOption(chat.Id, "AllowNSFW");
return option.GetValue(false);
}
}
@@ -6,8 +6,8 @@ namespace West.TelegramBot.ContentStore.Service;
public class CapyApiService : IRandomMediaService
{
public StoreItem StoreItem { get; } = new("capybara", "Капибара", 60, 25);
public async Task<RandomMedia> GetRandomMediaAsync()
public Task<RandomMedia> GetRandomMediaAsync()
{
return new RandomMedia(new InputFileUrl($"https://api.capy.lol/v1/capybara?{new Random().Next(0, 10000)}"));
return Task.FromResult(new RandomMedia(new InputFileUrl($"https://api.capy.lol/v1/capybara?{new Random().Next(0, 10000)}")));
}
}
@@ -1,12 +1,13 @@
using Kruzya.TelegramBot.Core.Data;
using Microsoft.Extensions.Configuration;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
public class GayKittiesService : AbstractKittiesService
{
public GayKittiesService(IConfiguration configuration, CoreContext db) : base(configuration, db)
public GayKittiesService(IConfiguration configuration, AllowNsfw allowNsfw) : base(configuration, allowNsfw)
{
}
@@ -8,5 +8,5 @@ public interface IRandomMediaService
public StoreItem StoreItem { get; }
public Task<RandomMedia> GetRandomMediaAsync();
public async Task<bool> CanView(Chat chat, User user) => true;
public Task<bool> CanView(Chat chat, User user) => Task.FromResult(true);
}
+5 -13
View File
@@ -3,25 +3,24 @@ using Kruzya.TelegramBot.Core.Extensions;
using Newtonsoft.Json;
using Telegram.Bot.Types;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
public class OBoobsService : IRandomMediaService
public class OBoobsService : AbstractNsfwService
{
private readonly CoreContext _db;
private readonly HttpClient _httpClient;
public StoreItem StoreItem { get; } = new("boobs", "Сиськи", 60, 30);
public override StoreItem StoreItem { get; } = new("boobs", "Сиськи", 60, 30);
public OBoobsService(CoreContext db)
public OBoobsService(AllowNsfw allowNsfw) : base(allowNsfw)
{
_db = db;
_httpClient = new HttpClient
{
BaseAddress = new Uri("http://api.oboobs.ru")
};
}
public async Task<RandomMedia> GetRandomMediaAsync()
public override async Task<RandomMedia> 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");
@@ -31,11 +30,4 @@ public class OBoobsService : IRandomMediaService
return new RandomMedia(new InputFileUrl($"https://media.oboobs.ru/boobs/{boobsId}.jpg"), boobsItem.Model, true);
}
public async Task<bool> CanView(Chat chat, User user)
{
var option = await _db.UserValues.FindOrCreateOption(chat.Id, "AllowNSFW");
return option.GetValue(false);
}
}
@@ -22,7 +22,7 @@ public abstract class SomeRandomApiAbstractService : IRandomMediaService
public async Task<RandomMedia> GetRandomMediaAsync()
{
var response = await _httpClient.GetStringAsync($"https://some-random-api.com/animal/{ApiId}");
var image = JsonConvert.DeserializeObject<SomeRandomApiAnimalResponse>(response);
var image = JsonConvert.DeserializeObject<SomeRandomApiAnimalResponse>(response)!;
return new RandomMedia(new InputFileUrl(image.Image), image.Fact);
}
@@ -1,6 +1,7 @@
using Kruzya.TelegramBot.Core.Data;
using Microsoft.Extensions.Configuration;
using West.TelegramBot.ContentStore.Model;
using West.TelegramBot.ContentStore.Option;
namespace West.TelegramBot.ContentStore.Service;
@@ -9,7 +10,7 @@ public class StraightKittiesService : AbstractKittiesService
protected override string VideoType => "straight";
public override StoreItem StoreItem { get; } = new("kitties_straight", "Прон", 40, 100);
public StraightKittiesService(IConfiguration configuration, CoreContext db) : base(configuration, db)
public StraightKittiesService(IConfiguration configuration, AllowNsfw allowNsfw) : base(configuration, allowNsfw)
{
}
}
+1
View File
@@ -3,6 +3,7 @@ global using GuessCache = Kruzya.TelegramBot.Core.Cache.MemoryCache<Telegram.Bot
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Options;
using Microsoft.Extensions.DependencyInjection;
+1
View File
@@ -9,6 +9,7 @@ using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.AutoDelete;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Kruzya.TelegramBot.Core.Service;
using Telegram.Bot;
using Telegram.Bot.Types;
+1
View File
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types;
using Microsoft.AspNetCore.Components.Forms;
using Kruzya.TelegramBot.Core.Handler;
namespace West.Entertainment.Handler;
+1 -1
View File
@@ -3,9 +3,9 @@
using BotFramework.Attributes;
using BotFramework.Enums;
using BotFramework.Utils;
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Handler;
using Kruzya.TelegramBot.Core.Service;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
+21
View File
@@ -0,0 +1,21 @@
using Kruzya.TelegramBot.Core.Options;
namespace West.TelegramBot.Reputation.Options;
public class KarmaMode : AbstractOption<KarmaMode, string>
{
public override string OptionId => "karmaMode";
public override string OptionName => "Karma Mode";
public override OptionType OptionType => OptionType.Select;
public override string DefaultValue => "geometric";
public KarmaMode(IOptionProvider optionProvider) : base(optionProvider)
{
ChoiceList.AddRange(new []
{
new SelectChoice("geometric", "Geometric", "G"),
new SelectChoice("linear", "Linear", "L")
});
}
}
+3
View File
@@ -1,9 +1,11 @@
using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Extensions;
using Kruzya.TelegramBot.Core.Service;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using West.TelegramBot.Reputation.Data;
using West.TelegramBot.Reputation.Options;
using West.TelegramBot.Reputation.Service;
namespace West.TelegramBot.Reputation;
@@ -19,6 +21,7 @@ public class Reputation : Module
var dsn = Configuration.GetConnectionString("Reputation");
services.AddDbContext<ReputationContext>(options => options.UseMySql(dsn, ServerVersion.AutoDetect(dsn)));
services.AddOption<KarmaMode>();
services.AddScoped<IReputation, ReputationService>();
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@@ -4,16 +4,19 @@ using Kruzya.TelegramBot.Core.Service;
using Microsoft.EntityFrameworkCore;
using Telegram.Bot.Types;
using West.TelegramBot.Reputation.Data;
using West.TelegramBot.Reputation.Options;
namespace West.TelegramBot.Reputation.Service;
public class ReputationService : IReputation
{
private readonly ReputationContext _reputationContext;
private readonly KarmaMode _karmaMode;
public ReputationService(ReputationContext reputationContext)
public ReputationService(ReputationContext reputationContext, KarmaMode karmaMode)
{
_reputationContext = reputationContext;
_karmaMode = karmaMode;
}
public async Task<double> GetUserReputationAsync(Chat chat, User user)
@@ -45,14 +48,25 @@ public class ReputationService : IReputation
public async Task<double> GetReputationDiffAsync(Chat chat, User sender, User receiver)
{
var senderRep = await FindUserReputation(chat, sender);
if (senderRep == null)
{
return 1;
}
var karmaMode = await _karmaMode.GetValueAsync(chat.Id);
var senderValue = senderRep.Value;
return Math.Round(senderValue == 0 ? 1 : Math.Sqrt(senderValue), 2);
double diff;
if (senderValue == 0 || karmaMode == "linear")
{
diff = 1;
}
else
{
diff = Math.Round(Math.Sqrt(senderValue), 2);
}
return diff;
}
public async Task<IEnumerable<IReputationEntity>> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0)