initial chat options implementation

This commit is contained in:
Andriy
2023-07-20 20:24:16 +03:00
parent 46774bfd93
commit 8de8eac959
17 changed files with 420 additions and 5 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
+38
View File
@@ -0,0 +1,38 @@
#nullable enable
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> /// </summary>
public DbSet<BotUserValue> UserValues { get; set; } public DbSet<BotUserValue> UserValues { get; set; }
public DbSet<ChatOptionValue> ChatOptionValues { get; set; }
public CoreContext(DbContextOptions<CoreContext> ctx) : base(ctx) public CoreContext(DbContextOptions<CoreContext> ctx) : base(ctx)
{ {
Database.Migrate(); Database.Migrate();
@@ -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;
}
}
@@ -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.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Kruzya.TelegramBot.Core.Migrations namespace Kruzya.TelegramBot.Core.Migrations
{ {
[DbContext(typeof(CoreContext))] [DbContext(typeof(CoreContext))]
@@ -14,7 +16,7 @@ namespace Kruzya.TelegramBot.Core.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.1.2") .HasAnnotation("ProductVersion", "7.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUser", b => modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUser", b =>
@@ -44,13 +46,13 @@ namespace Kruzya.TelegramBot.Core.Migrations
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<byte[]>("Value") b.Property<byte[]>("Value")
.HasColumnType("longblob"); .HasColumnType("longblob");
b.Property<string>("ValueType") b.Property<string>("ValueType")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.HasKey("BotUserValueId"); b.HasKey("BotUserValueId");
@@ -59,11 +61,30 @@ namespace Kruzya.TelegramBot.Core.Migrations
b.ToTable("UserValues"); 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 => modelBuilder.Entity("Kruzya.TelegramBot.Core.Data.BotUserValue", b =>
{ {
b.HasOne("Kruzya.TelegramBot.Core.Data.BotUser", "BotUser") b.HasOne("Kruzya.TelegramBot.Core.Data.BotUser", "BotUser")
.WithMany() .WithMany()
.HasForeignKey("BotUserId"); .HasForeignKey("BotUserId");
b.Navigation("BotUser");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
+25
View File
@@ -0,0 +1,25 @@
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Options;
public abstract class AbstractOption<TOption, TValue> : IOption<TValue>
where TOption: AbstractOption<TOption, TValue>
{
private readonly IOptionProvider _optionProvider;
protected AbstractOption(IOptionProvider optionProvider)
{
_optionProvider = optionProvider;
}
public async Task<TValue> GetValueAsync(long chatId)
{
return await _optionProvider.GetValueAsync<TOption, TValue>(chatId);
}
public async Task SetValueAsync(long chatId, TValue value)
{
await _optionProvider.SetValueAsync<TOption, TValue>(chatId, value);
}
}
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Reflection;
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<TOption, TValue>(long chatId)
{
return await GetValueAsync(
chatId,
GetOptionId<TOption>(),
GetDefaultValue<TOption, TValue>()
);
}
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<TOption, TValue>(long chatId, TValue value)
{
var chatOption = await _db.ChatOptionValues.FindOrCreateOption(chatId, GetOptionId<TOption>());
chatOption.SetValue(value);
_db.AddOrUpdate(chatOption);
}
protected OptionAttribute GetOptionAttribute<TOption>()
{
return typeof(TOption).GetCustomAttribute<OptionAttribute>();
}
protected string GetOptionId<TOption>()
{
var optionId = GetOptionAttribute<TOption>()?.OptionId;
if (optionId == null)
{
throw new ArgumentException("Option must implement OptionAttribute");
}
return optionId;
}
protected TValue GetDefaultValue<TOption, TValue>()
{
return (TValue) GetOptionAttribute<TOption>().DefaultValue;
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Options;
public interface IOption<TValue>
{
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<TOption, TValue>(long chatId);
public Task SetValueAsync<TOption, TValue>(long chatId, TValue value);
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Runtime.Serialization;
namespace Kruzya.TelegramBot.Core.Options;
[AttributeUsage(AttributeTargets.Class)]
public class OptionAttribute : Attribute
{
public string OptionId { get; }
public object DefaultValue { get; }
public OptionAttribute(string optionId, object defaultValue = default)
{
OptionId = optionId;
DefaultValue = defaultValue;
}
}
+3
View File
@@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Kruzya.TelegramBot.Core.Options;
using Telegram.Bot.Types; using Telegram.Bot.Types;
namespace Kruzya.TelegramBot.Core namespace Kruzya.TelegramBot.Core
@@ -53,6 +54,8 @@ namespace Kruzya.TelegramBot.Core
services.AddSingleton<ConcurrentBag<DeleteRequest>>(); services.AddSingleton<ConcurrentBag<DeleteRequest>>();
services.AddHostedService<DeleteService>(); services.AddHostedService<DeleteService>();
services.AddScoped<IOptionProvider, DbOptionProvider>();
// Trigger module handlers. // Trigger module handlers.
_core.Modules.ConfigureServices(services); _core.Modules.ConfigureServices(services);
} }
+20
View File
@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using BotFramework;
using BotFramework.Attributes;
using BotFramework.Enums;
using Telegram.Bot;
namespace Kruzya.TelegramBot.Core;
public class Test : BotEventHandler
{
public uint Counter { get; set; } = 5;
[Command(InChat.All, "test_state")]
public async Task TestState()
{
Counter++;
await Bot.SendTextMessageAsync(Chat.Id, Counter.ToString());
}
}
+3
View File
@@ -3,7 +3,9 @@ global using GuessCache = Kruzya.TelegramBot.Core.Cache.MemoryCache<Telegram.Bot
using Kruzya.TelegramBot.Core; using Kruzya.TelegramBot.Core;
using Kruzya.TelegramBot.Core.Options;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using West.Entertainment.Options;
namespace West.Entertainment namespace West.Entertainment
@@ -15,6 +17,7 @@ namespace West.Entertainment
public override void ConfigureServices(IServiceCollection services) public override void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<IGuessCache, GuessCache>(); services.AddSingleton<IGuessCache, GuessCache>();
services.AddScoped<NsfwOption>();
} }
} }
} }
+19 -1
View File
@@ -12,20 +12,23 @@ using Kruzya.TelegramBot.Core.Service;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using West.Entertainment.Options;
namespace West.Entertainment.Handler namespace West.Entertainment.Handler
{ {
public class Common : BotEventHandler public class Common : BotEventHandler
{ {
private readonly UserService _userService; private readonly UserService _userService;
private readonly NsfwOption _nsfwOption;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CoreContext _db; private readonly CoreContext _db;
public Common(CoreContext db, ILogger<Common> logger, UserService userService) public Common(CoreContext db, ILogger<Common> logger, UserService userService, NsfwOption nsfwOption)
{ {
_db = db; _db = db;
_logger = logger; _logger = logger;
_userService = userService; _userService = userService;
_nsfwOption = nsfwOption;
} }
[Command(InChat.Public, "cooldowns", CommandParseMode.Both)] [Command(InChat.Public, "cooldowns", CommandParseMode.Both)]
@@ -59,5 +62,20 @@ namespace West.Entertainment.Handler
await Bot.SendTextMessageAsync(Chat.Id, msg.ToString(), parseMode: ParseMode.Html); await Bot.SendTextMessageAsync(Chat.Id, msg.ToString(), parseMode: ParseMode.Html);
} }
[Command("test_option_get")]
public async Task HandleTestOptionGet()
{
await Bot.SendTextMessageAsync(Chat.Id, $"nsfw option is: {await _nsfwOption.GetValueAsync(Chat.Id)}");
}
[Command("test_option_set")]
public async Task HandleTestOptionSet()
{
var value = await _nsfwOption.GetValueAsync(Chat.Id);
await _nsfwOption.SetValueAsync(Chat.Id, !value);
await Bot.SendTextMessageAsync(Chat.Id, $"nsfw option is: {await _nsfwOption.GetValueAsync(Chat.Id)}");
}
} }
} }
@@ -0,0 +1,11 @@
using Kruzya.TelegramBot.Core.Options;
namespace West.Entertainment.Options;
[Option("allowNSFW", false)]
public class NsfwOption : AbstractOption<NsfwOption, bool>
{
public NsfwOption(IOptionProvider optionProvider) : base(optionProvider)
{
}
}