diff --git a/KfChatDotNetBot/ApplicationDbContext.cs b/KfChatDotNetBot/ApplicationDbContext.cs index 6b8e6ff..efb4f40 100644 --- a/KfChatDotNetBot/ApplicationDbContext.cs +++ b/KfChatDotNetBot/ApplicationDbContext.cs @@ -14,6 +14,8 @@ public class ApplicationDbContext : DbContext { //modelBuilder.Entity() // .OwnsOne(p => p.StateData, b => b.ToJson()); + modelBuilder.Entity(). + OwnsOne(p => p.Metadata, b => b.ToJson()); } public DbSet Users { get; set; } diff --git a/KfChatDotNetBot/Commands/ImageCommands.cs b/KfChatDotNetBot/Commands/ImageCommands.cs index bd48a6b..4a6302b 100644 --- a/KfChatDotNetBot/Commands/ImageCommands.cs +++ b/KfChatDotNetBot/Commands/ImageCommands.cs @@ -7,21 +7,18 @@ using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Services; using KfChatDotNetBot.Settings; -using KfChatDotNetWsClient.Models.Events; using Microsoft.EntityFrameworkCore; -using NLog; namespace KfChatDotNetBot.Commands; public class AddImageCommand : ICommand { public List Patterns => [ - new Regex(@"^admin image (?\w+) add (?\S+)(?:\s+(?raw))?(?:\s+(?.+))?$"), - new Regex(@"^admin images (?\w+) add (?\S+)(?:\s+(?raw))?(?:\s+(?.+))?$"), - new Regex(@"^admin image (?\w+) add_nigger (?\S+)(?:\s+(?raw))?(?:\s+(?.+))?$"), - new Regex(@"^admin images (?\w+) add_nigger (?\S+)(?:\s+(?raw))?(?:\s+(?.+))?$") + new Regex(@"^admin (image|images) (?\w+) (add|add_nigger) (?\S+) (?raw) (?.+)$", RegexOptions.IgnoreCase), + new Regex(@"^admin (image|images) (?\w+) (add|add_nigger) (?\S+) (?.+)$", RegexOptions.IgnoreCase), + new Regex(@"^admin (image|images) (?\w+) (add|add_nigger) (?\S+)$", RegexOptions.IgnoreCase) ]; - public string? HelpText => "Add an image to the image rotation specified"; + public string HelpText => "Add an image to the image rotation specified"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); public RateLimitOptionsModel? RateLimitOptions => null; @@ -34,13 +31,14 @@ public class AddImageCommand : ICommand if (imageKeys == null) throw new InvalidOperationException($"{BuiltIn.Keys.BotImageAcceptableKeys} was null"); var key = arguments["key"].Value; var url = arguments["url"].Value; - var tags = arguments["tags"].Success ? arguments["tags"].Value.Trim() : null; + var tags = arguments.TryGetValue("tags", out var tagsArg) ? tagsArg.Value.ToLower().Split(" ").ToList() : []; var niggerMode = message.Message.Contains("add_nigger"); - var _rawMode = arguments["raw"].Success; + // TODO: Implement real and raw mode + //var _rawMode = arguments.ContainsKey("raw"); if (!imageKeys.Contains(key)) { await botInstance.SendChatMessageAsync( - $"Key you specified is not supported. Available keys are: {string.Join(' ', imageKeys)}", true); + $"Key you specified is not supported. Available keys are: {imageKeys.Humanize()}", true); return; } @@ -49,32 +47,34 @@ public class AddImageCommand : ICommand await botInstance.SendChatMessageAsync("This image already exists in the database with this key", true); return; } - - if (!Regex.IsMatch(url, @"^https?://\S+$")) + + if (!Uri.TryCreate(url, UriKind.Absolute, out _)) { await botInstance.SendWhisperAsync(user.KfId, $"The URL '{url}' you provided is not valid"); return; } - string result = url; + var result = url; // todo add automatic compression/re-upload and raw mode option - await db.Images.AddAsync(new ImageDbModel { Key = key, Url = result, LastSeen = DateTimeOffset.MinValue, Tags = tags }, ctx); + await db.Images.AddAsync(new ImageDbModel + { + Key = key, Url = result, LastSeen = DateTimeOffset.MinValue, TagList = tags, + Metadata = new ImageMetadataModel { AddedByUserId = user.Id, WhenAdded = DateTimeOffset.UtcNow } + }, ctx); + var count = await db.Images.CountAsync(cancellationToken: ctx); await db.SaveChangesAsync(ctx); await botInstance.SendChatMessageAsync( - $"{user.FormatUsername()}, you added the following media to the {key} carousel\n[img]{url}[/img]", true); + $"{user.FormatUsername()}, you added the following media to the {key} carousel which now has {count:N0} images[spoiler=\"Image\"][img]{url}[/img]", true); } } public class RemoveImageCommand : ICommand { public List Patterns => [ - new Regex(@"^admin image (?\w+) remove (?.+)$"), - new Regex(@"^admin images (?\w+) remove (?.+)$"), - new Regex(@"^admin image (?\w+) delete (?.+)$"), - new Regex(@"^admin images (?\w+) delete (?.+)$") + new Regex(@"^admin (image|images) (?\w+) (remove|delete) (?.+)$"), ]; - public string? HelpText => "Remove an image from the image rotation specified"; + public string HelpText => "Remove an image from the image rotation specified"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); public RateLimitOptionsModel? RateLimitOptions => null; @@ -103,22 +103,25 @@ public class RemoveImageCommand : ICommand db.Images.Remove(image); await db.SaveChangesAsync(ctx); - // await botInstance.SendChatMessageAsync("Removed image from database", true); await botInstance.SendChatMessageAsync( - $"{user.FormatUsername()}, you removed the following media from the {key} carousel\n[img]{url}[/img]", true); + $"{user.FormatUsername()}, you removed the following media from the {key} carousel[spoiler=\"Image\"][img]{url}[/img]", true); } } public class ListImageCommand : ICommand { public List Patterns => [ - new Regex(@"^admin image (?\w+) list$"), - new Regex(@"^admin images (?\w+) list$") + new Regex(@"^admin (image|images) (?\w+) list$"), ]; - public string? HelpText => "Remove an image from the image rotation specified"; - public UserRight RequiredRight => UserRight.TrueAndHonest; + public string HelpText => "List images for a given carousel"; + public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - public RateLimitOptionsModel? RateLimitOptions => null; + public RateLimitOptionsModel RateLimitOptions => new() + { + Flags = RateLimitFlags.None, + MaxInvocations = 2, + Window = TimeSpan.FromSeconds(15) + }; public bool WhisperCanInvoke => false; public async Task RunCommand(ChatBot botInstance, BotCommandMessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) @@ -131,19 +134,19 @@ public class ListImageCommand : ICommand if (!imageKeys.Contains(key)) { await botInstance.SendChatMessageAsync( - $"Key you specified is not supported. Available keys are: {string.Join(' ', imageKeys)}", true); + $"Key you specified is not supported. Available keys are: {imageKeys.Humanize()}", true); return; } var images = db.Images.Where(i => i.Key == key); - if (await images.CountAsync(cancellationToken: ctx) > 20 && await Zipline.IsZiplineEnabled()) + if (await images.CountAsync(cancellationToken: ctx) > 10 && await Zipline.IsZiplineEnabled()) { var content = string.Empty; foreach (var image in images) { var ts = DateTimeOffset.UtcNow - image.LastSeen; var time = $"{ts.TotalDays:N0}d{ts.Hours:N0}h{ts.Minutes:N0}m{ts.Seconds:N0}s"; - content += $"{image.Url} - {time} - {image.Tags}" + Environment.NewLine; + content += $"{image.Url} - {time} - {image.TagList.Humanize(" ")}" + Environment.NewLine; } var paste = await Zipline.Upload(content, new MediaTypeHeaderValue("text/plain"), "1d", ctx); @@ -168,51 +171,51 @@ public class ListImageCommand : ICommand public class ManageImageKeyCommand : ICommand { public List Patterns => [ - new Regex(@"^admin imagekey add (?\w+)$"), - new Regex(@"^admin imagekey remove (?\w+)$"), - new Regex(@"^admin imagekey delete (?\w+)$"), - new Regex(@"^admin imageskey add (?\w+)$"), - new Regex(@"^admin imageskey remove (?\w+)$"), - new Regex(@"^admin imageskey delete (?\w+)$") + new Regex(@"^admin (imagekey|imageskey) (?add|remove|delete) (?\w+)$"), ]; - public string? HelpText => "Add or remove an acceptable image key from the BotImageAcceptableKeys setting"; + public string HelpText => "Add or remove an acceptable image key from the BotImageAcceptableKeys setting"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); public RateLimitOptionsModel? RateLimitOptions => null; - public bool WhisperCanInvoke => false; + public bool WhisperCanInvoke => true; public async Task RunCommand(ChatBot botInstance, BotCommandMessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var imageKeys = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotImageAcceptableKeys)).JsonDeserialize>(); if (imageKeys == null) throw new InvalidOperationException($"{BuiltIn.Keys.BotImageAcceptableKeys} was null"); var key = arguments["key"].Value.ToLower(); - var isAdd = message.Message.Contains(" add "); + var operation = arguments["operation"].Value.ToLower(); - if (isAdd) + if (operation is "add") { if (imageKeys.Contains(key)) { - await botInstance.SendChatMessageAsync($"Key \"{key}\" is already in the acceptable keys list", true); + await botInstance.ReplyToUser(message, $"Key \"{key}\" is already in the acceptable keys list", true); return; } imageKeys.Add(key); await SettingsProvider.SetValueAsync(BuiltIn.Keys.BotImageAcceptableKeys, JsonSerializer.Serialize(imageKeys)); - await botInstance.SendChatMessageAsync( - $"Added key \"{key}\" to acceptable image keys. Current keys: {string.Join(' ', imageKeys)}", true); + await botInstance.ReplyToUser(message, + $"Added key \"{key}\" to acceptable image keys. Current keys: {imageKeys.Humanize()}", true); + return; } - else + + if (operation is "remove" or "delete") { if (!imageKeys.Contains(key)) { - await botInstance.SendChatMessageAsync( - $"Key \"{key}\" is not in the acceptable keys list. Current keys: {string.Join(' ', imageKeys)}", true); + await botInstance.ReplyToUser(message, + $"Key \"{key}\" is not in the acceptable keys list. Current keys: {imageKeys.Humanize()}", true); return; } imageKeys.Remove(key); await SettingsProvider.SetValueAsync(BuiltIn.Keys.BotImageAcceptableKeys, JsonSerializer.Serialize(imageKeys)); - await botInstance.SendChatMessageAsync( - $"Removed key \"{key}\" from acceptable image keys. Current keys: {string.Join(' ', imageKeys)}", true); + await botInstance.ReplyToUser(message, + $"Removed key \"{key}\" from acceptable image keys. Current keys: {imageKeys.Humanize()}", true); + return; } + + await botInstance.ReplyToUser(message, $"Operation '{operation}' not supported", true); } } @@ -220,12 +223,13 @@ public class ManageImageKeyCommand : ICommand public class GetRandomImage : ICommand { public List Patterns => [ - new Regex(@"^(?\w+)(?:\s+(?.+))?") + new Regex(@"^(?\w+)$"), + new Regex(@"^(?\w+) (?.+)") ]; - public string? HelpText => "Get a random image"; + public string HelpText => "Get a random image"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromMinutes(10); - public RateLimitOptionsModel? RateLimitOptions => new() + public RateLimitOptionsModel RateLimitOptions => new() { Window = TimeSpan.FromSeconds(30), MaxInvocations = 7, @@ -237,7 +241,7 @@ public class GetRandomImage : ICommand { await using var db = new ApplicationDbContext(); var key = arguments["key"].Value.ToLower(); - var searchTerm = arguments["search"].Success ? arguments["search"].Value.Trim() : null; + var searchTerm = arguments.TryGetValue("search", out var searchArg) ? searchArg.Value.ToLower().Trim() : null; var images = db.Images.Where(i => i.Key == key); if (!await images.AnyAsync(ctx)) { @@ -257,40 +261,31 @@ public class GetRandomImage : ICommand BuiltIn.Keys.BotImageChinkSelfDestruct, BuiltIn.Keys.BotImageChinkSelfDestructDelay ]); - ImageDbModel image; + var selection = await images.ToListAsync(ctx); if (!string.IsNullOrEmpty(searchTerm)) { - var allImages = await images.ToListAsync(ctx); var searchTokens = searchTerm.ToLower().Split(' ', StringSplitOptions.RemoveEmptyEntries); - var matches = allImages.Where(i => - { - if (i.Tags == null) return false; - var tagTokens = i.Tags.ToLower().Split(' ', StringSplitOptions.RemoveEmptyEntries); - return searchTokens.All(st => tagTokens.Contains(st)); - }).ToList(); - if (matches.Count == 0) + selection = searchTokens.Aggregate(selection, (current, token) => + current.Where(i => i.TagList.Count > 0 && i.TagList.Contains(token)).ToList()); + if (selection.Count == 0) { RateLimitService.RemoveMostRecentEntry(user, this); await botInstance.SendChatMessageAsync($"No image in {key} matched \"{searchTerm}\"", true); return; } - image = matches.OrderBy(i => i.LastSeen).First(); } - else + var divideBy = settings[BuiltIn.Keys.BotImageRandomSliceDivideBy].ToType(); + var limit = 1; + var count = await images.CountAsync(ctx); + if (count > divideBy) { - var divideBy = settings[BuiltIn.Keys.BotImageRandomSliceDivideBy].ToType(); - var limit = 1; - var count = await images.CountAsync(ctx); - if (count > divideBy) - { - limit = count / divideBy; - } - - // EF with SQLite can't sort on dates as it's just TEXT - var selection = (await images.ToListAsync(ctx)).OrderBy(i => i.LastSeen).Take(limit).ToList(); - // MaxValue is never returned by Next so you don't need to -1 for indexing - image = selection[new Random().Next(0, selection.Count)]; + limit = count / divideBy; } + + // EF with SQLite can't sort on dates as it's just TEXT + selection = selection.OrderBy(i => i.LastSeen).Take(limit).ToList(); + // MaxValue is never returned by Next so you don't need to -1 for indexing + var image = selection[new Random().Next(0, selection.Count)]; image.LastSeen = DateTimeOffset.UtcNow; db.Images.Update(image); await db.SaveChangesAsync(ctx); diff --git a/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.Designer.cs b/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.Designer.cs new file mode 100644 index 0000000..8e80f2c --- /dev/null +++ b/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.Designer.cs @@ -0,0 +1,640 @@ +// +using System; +using KfChatDotNetBot; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KfChatDotNetBot.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260510172529_ImageTagList")] + partial class ImageTagList + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ChipsggBetDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("REAL"); + + b.Property("BetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CurrencyPrice") + .HasColumnType("REAL"); + + b.Property("GameTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Multiplier") + .HasColumnType("REAL"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Win") + .HasColumnType("INTEGER"); + + b.Property("Winnings") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("ChipsggBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("NextVipLevelWagerRequirement") + .HasColumnType("TEXT"); + + b.Property("RandomSeed") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("TotalWagered") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Gamblers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Exclusions"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Payout") + .HasColumnType("TEXT"); + + b.Property("PerkName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PerkTier") + .HasColumnType("INTEGER"); + + b.Property("PerkType") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Perks"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bet") + .HasColumnType("INTEGER"); + + b.Property("BetId") + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Game") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("Profit") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("HowlggBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ImageDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastSeen") + .HasColumnType("TEXT"); + + b.PrimitiveCollection("TagList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tags") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("REAL"); + + b.Property("JuicedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Juicers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Moms"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BetSeenAt") + .HasColumnType("TEXT"); + + b.Property("GameName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Multiplier") + .HasColumnType("REAL"); + + b.Property("Payout") + .HasColumnType("REAL"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RainbetUserId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("RainbetBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CacheDuration") + .HasColumnType("REAL"); + + b.Property("Default") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsSecret") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Regex") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.Property("ValueType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoCapture") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Service") + .HasColumnType("INTEGER"); + + b.Property("StreamUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Streams"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Effect") + .HasColumnType("TEXT"); + + b.Property("EventSource") + .HasColumnType("INTEGER"); + + b.Property("FromId") + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("NewBalance") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("TimeUnixEpochSeconds") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FromId"); + + b.HasIndex("GamblerId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TwitchViewCountDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ServerTime") + .HasColumnType("REAL"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Viewers") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TwitchViewCounts"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Ignored") + .HasColumnType("INTEGER"); + + b.Property("KfId") + .HasColumnType("INTEGER"); + + b.Property("KfUsername") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserRight") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityType") + .HasColumnType("INTEGER"); + + b.Property("FirstOccurence") + .HasColumnType("TEXT"); + + b.Property("LatestOccurence") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UsersWhoWere"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("GameMeta") + .HasColumnType("TEXT"); + + b.Property("IsComplete") + .HasColumnType("INTEGER"); + + b.Property("Multiplier") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("TimeUnixEpochSeconds") + .HasColumnType("INTEGER"); + + b.Property("WagerAmount") + .HasColumnType("TEXT"); + + b.Property("WagerEffect") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Wagers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "From") + .WithMany() + .HasForeignKey("FromId"); + + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("From"); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.cs b/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.cs new file mode 100644 index 0000000..19c0d42 --- /dev/null +++ b/KfChatDotNetBot/Migrations/20260510172529_ImageTagList.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KfChatDotNetBot.Migrations +{ + /// + public partial class ImageTagList : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TagList", + table: "Images", + type: "TEXT", + nullable: false, + defaultValue: "[]"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TagList", + table: "Images"); + } + } +} diff --git a/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.Designer.cs b/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.Designer.cs new file mode 100644 index 0000000..fdaaca5 --- /dev/null +++ b/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.Designer.cs @@ -0,0 +1,665 @@ +// +using System; +using KfChatDotNetBot; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KfChatDotNetBot.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260510194844_ImageMetadata")] + partial class ImageMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ChipsggBetDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("REAL"); + + b.Property("BetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CurrencyPrice") + .HasColumnType("REAL"); + + b.Property("GameTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Multiplier") + .HasColumnType("REAL"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Win") + .HasColumnType("INTEGER"); + + b.Property("Winnings") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("ChipsggBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("NextVipLevelWagerRequirement") + .HasColumnType("TEXT"); + + b.Property("RandomSeed") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("TotalWagered") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Gamblers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Exclusions"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Payout") + .HasColumnType("TEXT"); + + b.Property("PerkName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PerkTier") + .HasColumnType("INTEGER"); + + b.Property("PerkType") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Perks"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bet") + .HasColumnType("INTEGER"); + + b.Property("BetId") + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Game") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("Profit") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("HowlggBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ImageDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastSeen") + .HasColumnType("TEXT"); + + b.PrimitiveCollection("TagList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tags") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("REAL"); + + b.Property("JuicedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Juicers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Moms"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BetSeenAt") + .HasColumnType("TEXT"); + + b.Property("GameName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Multiplier") + .HasColumnType("REAL"); + + b.Property("Payout") + .HasColumnType("REAL"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RainbetUserId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("RainbetBets"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CacheDuration") + .HasColumnType("REAL"); + + b.Property("Default") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsSecret") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Regex") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.Property("ValueType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoCapture") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Service") + .HasColumnType("INTEGER"); + + b.Property("StreamUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Streams"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Effect") + .HasColumnType("TEXT"); + + b.Property("EventSource") + .HasColumnType("INTEGER"); + + b.Property("FromId") + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("NewBalance") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("TimeUnixEpochSeconds") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FromId"); + + b.HasIndex("GamblerId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TwitchViewCountDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ServerTime") + .HasColumnType("REAL"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Viewers") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TwitchViewCounts"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Ignored") + .HasColumnType("INTEGER"); + + b.Property("KfId") + .HasColumnType("INTEGER"); + + b.Property("KfUsername") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserRight") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityType") + .HasColumnType("INTEGER"); + + b.Property("FirstOccurence") + .HasColumnType("TEXT"); + + b.Property("LatestOccurence") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UsersWhoWere"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GamblerId") + .HasColumnType("INTEGER"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("GameMeta") + .HasColumnType("TEXT"); + + b.Property("IsComplete") + .HasColumnType("INTEGER"); + + b.Property("Multiplier") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("TimeUnixEpochSeconds") + .HasColumnType("INTEGER"); + + b.Property("WagerAmount") + .HasColumnType("TEXT"); + + b.Property("WagerEffect") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GamblerId"); + + b.ToTable("Wagers"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ImageDbModel", b => + { + b.OwnsOne("KfChatDotNetBot.Models.DbModels.ImageMetadataModel", "Metadata", b1 => + { + b1.Property("ImageDbModelId"); + + b1.Property("AddedByUserId"); + + b1.Property("WhenAdded"); + + b1.HasKey("ImageDbModelId"); + + b1.ToTable("Images"); + + b1 + .ToJson("Metadata") + .HasColumnType("TEXT"); + + b1.WithOwner() + .HasForeignKey("ImageDbModelId"); + }); + + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "From") + .WithMany() + .HasForeignKey("FromId"); + + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("From"); + + b.Navigation("Gambler"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b => + { + b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler") + .WithMany() + .HasForeignKey("GamblerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Gambler"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.cs b/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.cs new file mode 100644 index 0000000..d12d458 --- /dev/null +++ b/KfChatDotNetBot/Migrations/20260510194844_ImageMetadata.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KfChatDotNetBot.Migrations +{ + /// + public partial class ImageMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Metadata", + table: "Images", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Metadata", + table: "Images"); + } + } +} diff --git a/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs b/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs index f70983a..c1ac434 100644 --- a/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace KfChatDotNetBot.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ChipsggBetDbModel", b => { @@ -212,6 +212,10 @@ namespace KfChatDotNetBot.Migrations b.Property("LastSeen") .HasColumnType("TEXT"); + b.PrimitiveCollection("TagList") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("Tags") .HasColumnType("TEXT"); @@ -558,6 +562,31 @@ namespace KfChatDotNetBot.Migrations b.Navigation("Gambler"); }); + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ImageDbModel", b => + { + b.OwnsOne("KfChatDotNetBot.Models.DbModels.ImageMetadataModel", "Metadata", b1 => + { + b1.Property("ImageDbModelId"); + + b1.Property("AddedByUserId"); + + b1.Property("WhenAdded"); + + b1.HasKey("ImageDbModelId"); + + b1.ToTable("Images"); + + b1 + .ToJson("Metadata") + .HasColumnType("TEXT"); + + b1.WithOwner() + .HasForeignKey("ImageDbModelId"); + }); + + b.Navigation("Metadata"); + }); + modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b => { b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User") diff --git a/KfChatDotNetBot/Models/DbModels/ImageDbModel.cs b/KfChatDotNetBot/Models/DbModels/ImageDbModel.cs index 8a330f7..6f3b41d 100644 --- a/KfChatDotNetBot/Models/DbModels/ImageDbModel.cs +++ b/KfChatDotNetBot/Models/DbModels/ImageDbModel.cs @@ -6,5 +6,29 @@ public class ImageDbModel public required string Key { get; set; } public required string Url { get; set; } public required DateTimeOffset LastSeen { get; set; } + [Obsolete("Use TagList instead")] public string? Tags { get; set; } + /// + /// List of image tags for recalling specific images + /// + public required List TagList { get; set; } = []; + + /// + /// JSON object containing whatever bullshit metadata we want to attach to this image + /// Value will be null for images that were added prior to metadata being introduced + /// + public required ImageMetadataModel? Metadata { get; set; } +} + +public class ImageMetadataModel +{ + /// + /// User ID (IN THE BOT, NOT KIWI FARMS USER ID) of whoever added this image + /// + public required int AddedByUserId { get; set; } + + /// + /// When the image was added to the database + /// + public required DateTimeOffset WhenAdded { get; set; } } \ No newline at end of file diff --git a/KfChatDotNetBot/Program.cs b/KfChatDotNetBot/Program.cs index 0f8dde9..75b436f 100644 --- a/KfChatDotNetBot/Program.cs +++ b/KfChatDotNetBot/Program.cs @@ -20,6 +20,7 @@ */ using System.Text; +using KfChatDotNetBot.Migrations; using KfChatDotNetBot.Services; using KfChatDotNetBot.Settings; using Microsoft.EntityFrameworkCore; @@ -49,6 +50,21 @@ namespace KfChatDotNetBot logger.Error("Caught an error when attempting to grab the Redis multiplexer"); logger.Error(e); } + + if (await db.Images.AnyAsync()) + { + logger.Info("Checking to see if we need to migrate Tags to TagList"); +#pragma warning disable CS0618 // Type or member is obsolete + var scope = (await db.Images.Where(i => i.Tags != null).ToListAsync()).Where(i => i.TagList.Count == 0).ToList(); + foreach (var item in scope) + { + // Ignoring the null as my query literally filters for != null + item.TagList = item.Tags!.Split(" ").ToList(); +#pragma warning restore CS0618 // Type or member is obsolete + logger.Info($"Migrated tags for image {item.Id}"); + } + await db.SaveChangesAsync(); + } logger.Info("Handing over to bot now"); Console.OutputEncoding = Encoding.UTF8; _ = new ChatBot();