diff --git a/CHANGELOG.md b/CHANGELOG.md index b9cbbe171..06089bc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,26 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +## Unreleased + +### Changed + +- New cache abstraction added + - 2 implemenations: redis and memory + - All current bots will stay on redis cache, all new bots will use in-process memory cache + - This change removes bot's hard dependency on redis + - Configurable in `creds.yml` (please read the comments) + - You **MUST** use 'redis' if your bot runs on more than 1 shard (2000+ servers) +- [dev] Using new non-locking ConcurrentDictionary + +### Fixed + +- `.xp` will now show default user avatars too + +### Removed + +- Removed `.imagesreload` as images are now lazily loaded on request and then cached + ## [4.2.6] - 22.06.2022 ### Fixed diff --git a/src/NadekoBot.Tests/BotStringsTests.cs b/src/NadekoBot.Tests/BotStringsTests.cs index df6e3b21b..31b4cbc34 100644 --- a/src/NadekoBot.Tests/BotStringsTests.cs +++ b/src/NadekoBot.Tests/BotStringsTests.cs @@ -21,7 +21,7 @@ namespace NadekoBot.Tests var stringsSource = new LocalFileStringsSource( responsesPath, commandsPath); - var strings = new LocalBotStringsProvider(stringsSource); + var strings = new MemoryBotStringsProvider(stringsSource); var culture = new CultureInfo("en-US"); diff --git a/src/NadekoBot/Bot.cs b/src/NadekoBot/Bot.cs index fe65dc16d..2fdec9538 100644 --- a/src/NadekoBot/Bot.cs +++ b/src/NadekoBot/Bot.cs @@ -102,20 +102,20 @@ public sealed class Bot var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds .AddSingleton(_credsProvider) .AddSingleton(_db) // database - .AddRedis(_creds.RedisOptions) // redis .AddSingleton(Client) // discord socket client .AddSingleton(_commandService) // .AddSingleton(_interactionService) .AddSingleton(this) .AddSingleton() - .AddSingleton() .AddSingleton() - .AddBotStringsServices(_creds.TotalShards) .AddConfigServices() .AddConfigMigrators() .AddMemoryCache() // music - .AddMusic(); + .AddMusic() + // cache + .AddCache(_creds); + // admin #if GLOBAL_NADEKO svcs.AddSingleton(); @@ -143,13 +143,6 @@ public sealed class Bot .AddSingleton(x => x.GetRequiredService()); } - svcs.AddSingleton() - .AddSingleton(x => x.GetRequiredService()) - .AddSingleton() - .AddSingleton(x => x.GetRequiredService()) - .AddSingleton(x => x.GetRequiredService()) - .AddSingleton(); - svcs.Scan(scan => scan.FromAssemblyOf() .AddClasses(classes => classes.AssignableToAny( // services diff --git a/src/NadekoBot/Common/Attributes/Ratelimit.cs b/src/NadekoBot/Common/Attributes/Ratelimit.cs index e33b86ba8..92c411d04 100644 --- a/src/NadekoBot/Common/Attributes/Ratelimit.cs +++ b/src/NadekoBot/Common/Attributes/Ratelimit.cs @@ -15,22 +15,24 @@ public sealed class RatelimitAttribute : PreconditionAttribute Seconds = seconds; } - public override Task CheckPermissionsAsync( + public override async Task CheckPermissionsAsync( ICommandContext context, CommandInfo command, IServiceProvider services) { if (Seconds == 0) - return Task.FromResult(PreconditionResult.FromSuccess()); + return PreconditionResult.FromSuccess(); - var cache = services.GetRequiredService(); - var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds); + var cache = services.GetRequiredService(); + var rem = await cache.GetRatelimitAsync( + new($"precondition:{context.User.Id}:{command.Name}"), + Seconds.Seconds()); if (rem is null) - return Task.FromResult(PreconditionResult.FromSuccess()); + return PreconditionResult.FromSuccess(); var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s."; - return Task.FromResult(PreconditionResult.FromError(msgContent)); + return PreconditionResult.FromError(msgContent); } } \ No newline at end of file diff --git a/src/NadekoBot/Common/Cache/BotCacheExtensions.cs b/src/NadekoBot/Common/Cache/BotCacheExtensions.cs new file mode 100644 index 000000000..875188ee8 --- /dev/null +++ b/src/NadekoBot/Common/Cache/BotCacheExtensions.cs @@ -0,0 +1,46 @@ +using OneOf; +using OneOf.Types; + +namespace NadekoBot.Common; + +public static class BotCacheExtensions +{ + public static async ValueTask GetOrDefaultAsync(this IBotCache cache, TypedKey key) + { + var result = await cache.GetAsync(key); + if (result.TryGetValue(out var val)) + return val; + + return default; + } + + private static TypedKey GetImgKey(Uri uri) + => new($"image:{uri}"); + + public static ValueTask SetImageDataAsync(this IBotCache c, string key, byte[] data) + => c.SetImageDataAsync(new Uri(key), data); + public static async ValueTask SetImageDataAsync(this IBotCache c, Uri key, byte[] data) + => await c.AddAsync(GetImgKey(key), data, expiry: TimeSpan.FromHours(48)); + + public static async ValueTask> GetImageDataAsync(this IBotCache c, Uri key) + => await c.GetAsync(GetImgKey(key)); + + public static async Task GetRatelimitAsync( + this IBotCache c, + TypedKey key, + TimeSpan length) + { + var now = DateTime.UtcNow; + var nowB = now.ToBinary(); + + var cachedValue = await c.GetOrAddAsync(key, + () => Task.FromResult(now.ToBinary()), + expiry: length); + + if (cachedValue == nowB) + return null; + + var diff = now - DateTime.FromBinary(cachedValue); + return length - diff; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Cache/IBotCache.cs b/src/NadekoBot/Common/Cache/IBotCache.cs new file mode 100644 index 000000000..35eee7ed7 --- /dev/null +++ b/src/NadekoBot/Common/Cache/IBotCache.cs @@ -0,0 +1,47 @@ +using OneOf; +using OneOf.Types; + +namespace NadekoBot.Common; + +public interface IBotCache +{ + /// + /// Adds an item to the cache + /// + /// Key to add + /// Value to add to the cache + /// Optional expiry + /// Whether old value should be overwritten + /// Type of the value + /// Returns whether add was sucessful. Always true unless ovewrite = false + ValueTask AddAsync(TypedKey key, T value, TimeSpan? expiry = null, bool overwrite = true); + + /// + /// Get an element from the cache + /// + /// Key + /// Type of the value + /// Either a value or + ValueTask> GetAsync(TypedKey key); + + /// + /// Remove a key from the cache + /// + /// Key to remove + /// Type of the value + /// Whether there was item + ValueTask RemoveAsync(TypedKey key); + + /// + /// Get the key if it exists or add a new one + /// + /// Key to get and potentially add + /// Value creation factory + /// Optional expiry + /// Type of the value + /// The retrieved or newly added value + ValueTask GetOrAddAsync( + TypedKey key, + Func> createFactory, + TimeSpan? expiry = null); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Cache/MemoryBotCache.cs b/src/NadekoBot/Common/Cache/MemoryBotCache.cs new file mode 100644 index 000000000..4d8ebb4c0 --- /dev/null +++ b/src/NadekoBot/Common/Cache/MemoryBotCache.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Caching.Memory; +using OneOf; +using OneOf.Types; + +// ReSharper disable InconsistentlySynchronizedField + +namespace NadekoBot.Common; + +public sealed class MemoryBotCache : IBotCache +{ + // needed for overwrites and Delete return value + private readonly object _cacheLock = new object(); + private readonly MemoryCache _cache; + + public MemoryBotCache() + { + _cache = new MemoryCache(new MemoryCacheOptions()); + } + + public ValueTask AddAsync(TypedKey key, T value, TimeSpan? expiry = null, bool overwrite = true) + { + if (overwrite) + { + using var item = _cache.CreateEntry(key.Key); + item.Value = value; + item.AbsoluteExpirationRelativeToNow = expiry; + return new(true); + } + + lock (_cacheLock) + { + if (_cache.TryGetValue(key.Key, out var old) && old is not null) + return new(false); + + using var item = _cache.CreateEntry(key.Key); + item.Value = value; + item.AbsoluteExpirationRelativeToNow = expiry; + return new(true); + } + } + + public async ValueTask GetOrAddAsync( + TypedKey key, + Func> createFactory, + TimeSpan? expiry = null) + => await _cache.GetOrCreateAsync(key.Key, + async ce => + { + ce.AbsoluteExpirationRelativeToNow = expiry; + var val = await createFactory(); + return val; + }); + + public ValueTask> GetAsync(TypedKey key) + { + if (!_cache.TryGetValue(key.Key, out var val) || val is null) + return new(new None()); + + return new((T)val); + } + + public ValueTask RemoveAsync(TypedKey key) + { + lock (_cacheLock) + { + var toReturn = _cache.TryGetValue(key.Key, out var old ) && old is not null; + _cache.Remove(key.Key); + return new(toReturn); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Cache/RedisBotCache.cs b/src/NadekoBot/Common/Cache/RedisBotCache.cs new file mode 100644 index 000000000..41f84b2d2 --- /dev/null +++ b/src/NadekoBot/Common/Cache/RedisBotCache.cs @@ -0,0 +1,119 @@ +using OneOf; +using OneOf.Types; +using StackExchange.Redis; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NadekoBot.Common; + +public sealed class RedisBotCache : IBotCache +{ + private static readonly Type[] _supportedTypes = new [] + { + typeof(bool), typeof(int), typeof(uint), typeof(long), + typeof(ulong), typeof(float), typeof(double), + typeof(string), typeof(byte[]), typeof(ReadOnlyMemory), typeof(Memory), + typeof(RedisValue), + }; + + private static readonly JsonSerializerOptions _opts = new() + { + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + AllowTrailingCommas = true, + IgnoreReadOnlyProperties = false, + }; + private readonly ConnectionMultiplexer _conn; + + public RedisBotCache(ConnectionMultiplexer conn) + { + _conn = conn; + } + + public async ValueTask AddAsync(TypedKey key, T value, TimeSpan? expiry = null, bool overwrite = true) + { + // if a null value is passed, remove the key + if (value is null) + { + await RemoveAsync(key); + return false; + } + + var db = _conn.GetDatabase(); + RedisValue val = IsSupportedType(typeof(T)) + ? RedisValue.Unbox(value) + : JsonSerializer.Serialize(value, _opts); + + var success = await db.StringSetAsync(key.Key, + val, + expiry: expiry, + when: overwrite ? When.Always : When.NotExists); + + return success; + } + + public bool IsSupportedType(Type type) + { + if (type.IsGenericType) + { + var typeDef = type.GetGenericTypeDefinition(); + if (typeDef == typeof(Nullable<>)) + return IsSupportedType(type.GenericTypeArguments[0]); + } + + foreach (var t in _supportedTypes) + { + if (type == t) + return true; + } + + return false; + } + + public async ValueTask> GetAsync(TypedKey key) + { + var db = _conn.GetDatabase(); + var val = await db.StringGetAsync(key.Key); + if (val == default) + return new None(); + + if (IsSupportedType(typeof(T))) + return (T)((IConvertible)val).ToType(typeof(T), null); + + return JsonSerializer.Deserialize(val.ToString(), _opts)!; + } + + public async ValueTask RemoveAsync(TypedKey key) + { + var db = _conn.GetDatabase(); + + return await db.KeyDeleteAsync(key.Key); + } + + public async ValueTask GetOrAddAsync(TypedKey key, Func> createFactory, TimeSpan? expiry = null) + { + var result = await GetAsync(key); + + return await result.Match>( + v => Task.FromResult(v), + async _ => + { + var factoryValue = await createFactory(); + + if (factoryValue is null) + return default; + + await AddAsync(key, factoryValue, expiry); + + // get again to make sure it's the cached value + // and not the late factory value, in case there's a race condition + + var newResult = await GetAsync(key); + + // it's fine to do this, it should blow up if something went wrong. + return newResult.Match( + v => v, + _ => default); + }); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Configs/BotConfig.cs b/src/NadekoBot/Common/Configs/BotConfig.cs index 364f991b8..059d5d53e 100644 --- a/src/NadekoBot/Common/Configs/BotConfig.cs +++ b/src/NadekoBot/Common/Configs/BotConfig.cs @@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs; public sealed partial class BotConfig : ICloneable { [Comment(@"DO NOT CHANGE")] - public int Version { get; set; } = 2; + public int Version { get; set; } = 3; [Comment(@"Most commands, when executed, have a small colored line next to the response. The color depends whether the command @@ -182,4 +182,4 @@ public enum ConsoleOutputType Normal = 0, Simple = 1, None = 2 -} \ No newline at end of file +} diff --git a/src/NadekoBot/Common/Creds.cs b/src/NadekoBot/Common/Creds.cs index 1bd11fa9e..9b4a48661 100644 --- a/src/NadekoBot/Common/Creds.cs +++ b/src/NadekoBot/Common/Creds.cs @@ -19,7 +19,10 @@ public sealed class Creds : IBotCredentials public bool UsePrivilegedIntents { get; set; } [Comment(@"The number of shards that the bot will be running on. -Leave at 1 if you don't know what you're doing.")] +Leave at 1 if you don't know what you're doing. + +note: If you are planning to have more than one shard, then you must change botCache to 'redis'. + Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.")] public int TotalShards { get; set; } [Comment( @@ -50,8 +53,14 @@ go to https://www.patreon.com/portal -> my clients -> create client")] [Comment(@"Official cleverbot api key.")] public string CleverbotApiKey { get; set; } - - [Comment(@"Redis connection string. Don't change if you don't know what you're doing.")] + + [Comment(@"Which cache implementation should bot use. +'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. +'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml")] + public BotCacheImplemenation BotCache { get; set; } + + [Comment(@"Redis connection string. Don't change if you don't know what you're doing. +Only used if botCache is set to 'redis'")] public string RedisOptions { get; set; } [Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")] @@ -104,12 +113,12 @@ Linux default args: ""NadekoBot.dll -- {0}"" Windows default cmd: NadekoBot.exe - args: {0}")] + args: ""{0}""")] public RestartConfig RestartCommand { get; set; } public Creds() { - Version = 5; + Version = 6; Token = string.Empty; UsePrivilegedIntents = true; OwnerIds = new List(); @@ -119,6 +128,7 @@ Windows default Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty); BotListToken = string.Empty; CleverbotApiKey = string.Empty; + BotCache = BotCacheImplemenation.Memory; RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password="; Db = new() { @@ -217,4 +227,10 @@ public class GoogleApiConfig { public string SearchId { get; init; } public string ImageSearchId { get; init; } +} + +public enum BotCacheImplemenation +{ + Memory, + Redis } \ No newline at end of file diff --git a/src/NadekoBot/Common/IBotCredentials.cs b/src/NadekoBot/Common/IBotCredentials.cs index 8681c76a7..ab26b0fa4 100644 --- a/src/NadekoBot/Common/IBotCredentials.cs +++ b/src/NadekoBot/Common/IBotCredentials.cs @@ -26,6 +26,7 @@ public interface IBotCredentials string TwitchClientId { get; set; } string TwitchClientSecret { get; set; } GoogleApiConfig Google { get; set; } + BotCacheImplemenation BotCache { get; set; } } public class RestartConfig diff --git a/src/NadekoBot/Common/ImageUrls.cs b/src/NadekoBot/Common/ImageUrls.cs index 9cdd8d801..15f7a2263 100644 --- a/src/NadekoBot/Common/ImageUrls.cs +++ b/src/NadekoBot/Common/ImageUrls.cs @@ -1,9 +1,11 @@ #nullable disable using NadekoBot.Common.Yml; +using Cloneable; namespace NadekoBot.Common; -public class ImageUrls +[Cloneable] +public partial class ImageUrls : ICloneable { [Comment("DO NOT CHANGE")] public int Version { get; set; } = 3; diff --git a/src/NadekoBot/Common/OneOfExtensions.cs b/src/NadekoBot/Common/OneOfExtensions.cs new file mode 100644 index 000000000..187a43a7f --- /dev/null +++ b/src/NadekoBot/Common/OneOfExtensions.cs @@ -0,0 +1,10 @@ +using OneOf.Types; +using OneOf; + +namespace NadekoBot.Common; + +public static class OneOfExtensions +{ + public static bool TryGetValue(this OneOf oneOf, out T value) + => oneOf.TryPickT0(out value, out _); +} \ No newline at end of file diff --git a/src/NadekoBot/Common/Pokemon/SearchPokemon.cs b/src/NadekoBot/Common/Pokemon/SearchPokemon.cs index 2cdc88d3a..a5896da56 100644 --- a/src/NadekoBot/Common/Pokemon/SearchPokemon.cs +++ b/src/NadekoBot/Common/Pokemon/SearchPokemon.cs @@ -1,11 +1,12 @@ #nullable disable -using Newtonsoft.Json; + +using System.Text.Json.Serialization; namespace NadekoBot.Common.Pokemon; public class SearchPokemon { - [JsonProperty("num")] + [JsonPropertyName("num")] public int Id { get; set; } public string Species { get; set; } diff --git a/src/NadekoBot/Db/Models/StreamOnlineMessage.cs b/src/NadekoBot/Db/Models/StreamOnlineMessage.cs new file mode 100644 index 000000000..75873d1cc --- /dev/null +++ b/src/NadekoBot/Db/Models/StreamOnlineMessage.cs @@ -0,0 +1,13 @@ +#nullable disable +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Db.Models; + +public class StreamOnlineMessage : DbEntity +{ + public ulong ChannelId { get; set; } + public ulong MessageId { get; set; } + + public FollowedStream.FType Type { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/NadekoBot/Db/NadekoContext.cs b/src/NadekoBot/Db/NadekoContext.cs index 5f1f85e63..2d6205a1b 100644 --- a/src/NadekoBot/Db/NadekoContext.cs +++ b/src/NadekoBot/Db/NadekoContext.cs @@ -58,6 +58,9 @@ public abstract class NadekoContext : DbContext public DbSet Patrons { get; set; } public DbSet PatronQuotas { get; set; } + + public DbSet StreamOnlineMessages { get; set; } + #region Mandatory Provider-Specific Values diff --git a/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.Designer.cs b/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.Designer.cs new file mode 100644 index 000000000..d1198b5d4 --- /dev/null +++ b/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.Designer.cs @@ -0,0 +1,3514 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NadekoBot.Services.Database; + +#nullable disable + +namespace NadekoBot.Migrations.Mysql +{ + [DbContext(typeof(MysqlContext))] + [Migration("20220623090718_stondel-db-cache")] + partial class stondeldbcache + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Balance") + .HasColumnType("bigint") + .HasColumnName("balance"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_bankusers"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("ix_bankusers_userid"); + + b.ToTable("bankusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.Property("ClubId") + .HasColumnType("int") + .HasColumnName("clubid"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("userid"); + + b.HasKey("ClubId", "UserId") + .HasName("pk_clubapplicants"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_clubapplicants_userid"); + + b.ToTable("clubapplicants", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.Property("ClubId") + .HasColumnType("int") + .HasColumnName("clubid"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("userid"); + + b.HasKey("ClubId", "UserId") + .HasName("pk_clubbans"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_clubbans_userid"); + + b.ToTable("clubbans", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Description") + .HasColumnType("longtext") + .HasColumnName("description"); + + b.Property("ImageUrl") + .HasColumnType("longtext") + .HasColumnName("imageurl"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasColumnName("name") + .UseCollation("utf8mb4_bin"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("ownerid"); + + b.Property("Xp") + .HasColumnType("int") + .HasColumnName("xp"); + + b.HasKey("Id") + .HasName("pk_clubs"); + + b.HasAlternateKey("Name") + .HasName("ak_clubs_name"); + + b.HasIndex("OwnerId") + .IsUnique() + .HasDatabaseName("ix_clubs_ownerid"); + + b.ToTable("clubs", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AvatarId") + .HasColumnType("longtext") + .HasColumnName("avatarid"); + + b.Property("ClubId") + .HasColumnType("int") + .HasColumnName("clubid"); + + b.Property("CurrencyAmount") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("currencyamount"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Discriminator") + .HasColumnType("longtext") + .HasColumnName("discriminator"); + + b.Property("IsClubAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("isclubadmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("lastlevelup") + .HasDefaultValueSql("(UTC_TIMESTAMP)"); + + b.Property("LastXpGain") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("lastxpgain") + .HasDefaultValueSql("(UTC_TIMESTAMP - INTERVAL 1 year)"); + + b.Property("NotifyOnLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("notifyonlevelup"); + + b.Property("TotalXp") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("totalxp"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("longtext") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_discorduser"); + + b.HasAlternateKey("UserId") + .HasName("ak_discorduser_userid"); + + b.HasIndex("ClubId") + .HasDatabaseName("ix_discorduser_clubid"); + + b.HasIndex("CurrencyAmount") + .HasDatabaseName("ix_discorduser_currencyamount"); + + b.HasIndex("TotalXp") + .HasDatabaseName("ix_discorduser_totalxp"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_discorduser_userid"); + + b.ToTable("discorduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Message") + .HasColumnType("longtext") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.Property("Username") + .HasColumnType("longtext") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_followedstream"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_followedstream_guildconfigid"); + + b.ToTable("followedstream", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b => + { + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("FeatureType") + .HasColumnType("int") + .HasColumnName("featuretype"); + + b.Property("Feature") + .HasColumnType("varchar(255)") + .HasColumnName("feature"); + + b.Property("DailyCount") + .HasColumnType("int unsigned") + .HasColumnName("dailycount"); + + b.Property("HourlyCount") + .HasColumnType("int unsigned") + .HasColumnName("hourlycount"); + + b.Property("MonthlyCount") + .HasColumnType("int unsigned") + .HasColumnName("monthlycount"); + + b.HasKey("UserId", "FeatureType", "Feature") + .HasName("pk_patronquotas"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_patronquotas_userid"); + + b.ToTable("patronquotas", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("AmountCents") + .HasColumnType("int") + .HasColumnName("amountcents"); + + b.Property("LastCharge") + .HasColumnType("datetime(6)") + .HasColumnName("lastcharge"); + + b.Property("UniquePlatformUserId") + .HasColumnType("varchar(255)") + .HasColumnName("uniqueplatformuserid"); + + b.Property("ValidThru") + .HasColumnType("datetime(6)") + .HasColumnName("validthru"); + + b.HasKey("UserId") + .HasName("pk_patrons"); + + b.HasIndex("UniquePlatformUserId") + .IsUnique() + .HasDatabaseName("ix_patrons_uniqueplatformuserid"); + + b.ToTable("patrons", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("MessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageid"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_streamonlinemessages"); + + b.ToTable("streamonlinemessages", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Action") + .HasColumnType("int") + .HasColumnName("action"); + + b.Property("ActionDurationMinutes") + .HasColumnType("int") + .HasColumnName("actiondurationminutes"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("MinAge") + .HasColumnType("time(6)") + .HasColumnName("minage"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_antialtsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antialtsetting_guildconfigid"); + + b.ToTable("antialtsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Action") + .HasColumnType("int") + .HasColumnName("action"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("PunishDuration") + .HasColumnType("int") + .HasColumnName("punishduration"); + + b.Property("Seconds") + .HasColumnType("int") + .HasColumnName("seconds"); + + b.Property("UserThreshold") + .HasColumnType("int") + .HasColumnName("userthreshold"); + + b.HasKey("Id") + .HasName("pk_antiraidsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antiraidsetting_guildconfigid"); + + b.ToTable("antiraidsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AntiSpamSettingId") + .HasColumnType("int") + .HasColumnName("antispamsettingid"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.HasKey("Id") + .HasName("pk_antispamignore"); + + b.HasIndex("AntiSpamSettingId") + .HasDatabaseName("ix_antispamignore_antispamsettingid"); + + b.ToTable("antispamignore", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Action") + .HasColumnType("int") + .HasColumnName("action"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("MessageThreshold") + .HasColumnType("int") + .HasColumnName("messagethreshold"); + + b.Property("MuteTime") + .HasColumnType("int") + .HasColumnName("mutetime"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_antispamsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antispamsetting_guildconfigid"); + + b.ToTable("antispamsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("ChannelName") + .HasColumnType("longtext") + .HasColumnName("channelname"); + + b.Property("CommandText") + .HasColumnType("longtext") + .HasColumnName("commandtext"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("GuildName") + .HasColumnType("longtext") + .HasColumnName("guildname"); + + b.Property("Interval") + .HasColumnType("int") + .HasColumnName("interval"); + + b.Property("VoiceChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("voicechannelid"); + + b.Property("VoiceChannelName") + .HasColumnType("longtext") + .HasColumnName("voicechannelname"); + + b.HasKey("Id") + .HasName("pk_autocommands"); + + b.ToTable("autocommands", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AutoDelete") + .HasColumnType("tinyint(1)") + .HasColumnName("autodelete"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.HasKey("Id") + .HasName("pk_autotranslatechannels"); + + b.HasIndex("ChannelId") + .IsUnique() + .HasDatabaseName("ix_autotranslatechannels_channelid"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_autotranslatechannels_guildid"); + + b.ToTable("autotranslatechannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("int") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Source") + .HasColumnType("longtext") + .HasColumnName("source"); + + b.Property("Target") + .HasColumnType("longtext") + .HasColumnName("target"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_autotranslateusers"); + + b.HasAlternateKey("ChannelId", "UserId") + .HasName("ak_autotranslateusers_channelid_userid"); + + b.ToTable("autotranslateusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Text") + .HasColumnType("longtext") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_bantemplates"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_bantemplates_guildid"); + + b.ToTable("bantemplates", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("ItemId") + .HasColumnType("bigint unsigned") + .HasColumnName("itemid"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Mapping") + .HasColumnType("longtext") + .HasColumnName("mapping"); + + b.Property("Trigger") + .HasColumnType("longtext") + .HasColumnName("trigger"); + + b.HasKey("Id") + .HasName("pk_commandalias"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_commandalias_guildconfigid"); + + b.ToTable("commandalias", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("CommandName") + .HasColumnType("longtext") + .HasColumnName("commandname"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Seconds") + .HasColumnType("int") + .HasColumnName("seconds"); + + b.HasKey("Id") + .HasName("pk_commandcooldown"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_commandcooldown_guildconfigid"); + + b.ToTable("commandcooldown", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("bigint") + .HasColumnName("amount"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("extra"); + + b.Property("Note") + .HasColumnType("longtext") + .HasColumnName("note"); + + b.Property("OtherId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint unsigned") + .HasColumnName("otherid") + .HasDefaultValueSql("NULL"); + + b.Property("Type") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_currencytransactions"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_currencytransactions_userid"); + + b.ToTable("currencytransactions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("State") + .HasColumnType("tinyint(1)") + .HasColumnName("state"); + + b.HasKey("Id") + .HasName("pk_delmsgoncmdchannel"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_delmsgoncmdchannel_guildconfigid"); + + b.ToTable("delmsgoncmdchannel", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Command") + .HasColumnType("varchar(255)") + .HasColumnName("command"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Perm") + .HasColumnType("bigint unsigned") + .HasColumnName("perm"); + + b.HasKey("Id") + .HasName("pk_discordpermoverrides"); + + b.HasIndex("GuildId", "Command") + .IsUnique() + .HasDatabaseName("ix_discordpermoverrides_guildid_command"); + + b.ToTable("discordpermoverrides", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("ItemId") + .HasColumnType("bigint unsigned") + .HasColumnName("itemid"); + + b.Property("ItemType") + .HasColumnType("int") + .HasColumnName("itemtype"); + + b.Property("XpSettingsId") + .HasColumnType("int") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_excludeditem"); + + b.HasIndex("XpSettingsId") + .HasDatabaseName("ix_excludeditem_xpsettingsid"); + + b.ToTable("excludeditem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Url") + .IsRequired() + .HasColumnType("varchar(255)") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_feedsub"); + + b.HasAlternateKey("GuildConfigId", "Url") + .HasName("ak_feedsub_guildconfigid_url"); + + b.ToTable("feedsub", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterchannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterchannelid_guildconfigid"); + + b.ToTable("filterchannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Word") + .HasColumnType("longtext") + .HasColumnName("word"); + + b.HasKey("Id") + .HasName("pk_filteredword"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filteredword_guildconfigid"); + + b.ToTable("filteredword", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterlinkschannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterlinkschannelid_guildconfigid"); + + b.ToTable("filterlinkschannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterwordschannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterwordschannelid_guildconfigid"); + + b.ToTable("filterwordschannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_gcchannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_gcchannelid_guildconfigid"); + + b.ToTable("gcchannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.Property("Number") + .HasColumnType("int") + .HasColumnName("number"); + + b.HasKey("Id") + .HasName("pk_groupname"); + + b.HasIndex("GuildConfigId", "Number") + .IsUnique() + .HasDatabaseName("ix_groupname_guildconfigid_number"); + + b.ToTable("groupname", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AutoAssignRoleIds") + .HasColumnType("longtext") + .HasColumnName("autoassignroleids"); + + b.Property("AutoDeleteByeMessagesTimer") + .HasColumnType("int") + .HasColumnName("autodeletebyemessagestimer"); + + b.Property("AutoDeleteGreetMessagesTimer") + .HasColumnType("int") + .HasColumnName("autodeletegreetmessagestimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages") + .HasColumnType("tinyint(1)") + .HasColumnName("autodeleteselfassignedrolemessages"); + + b.Property("BoostMessage") + .HasColumnType("longtext") + .HasColumnName("boostmessage"); + + b.Property("BoostMessageChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("boostmessagechannelid"); + + b.Property("BoostMessageDeleteAfter") + .HasColumnType("int") + .HasColumnName("boostmessagedeleteafter"); + + b.Property("ByeMessageChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("byemessagechannelid"); + + b.Property("ChannelByeMessageText") + .HasColumnType("longtext") + .HasColumnName("channelbyemessagetext"); + + b.Property("ChannelGreetMessageText") + .HasColumnType("longtext") + .HasColumnName("channelgreetmessagetext"); + + b.Property("CleverbotEnabled") + .HasColumnType("tinyint(1)") + .HasColumnName("cleverbotenabled"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("DeleteMessageOnCommand") + .HasColumnType("tinyint(1)") + .HasColumnName("deletemessageoncommand"); + + b.Property("DeleteStreamOnlineMessage") + .HasColumnType("tinyint(1)") + .HasColumnName("deletestreamonlinemessage"); + + b.Property("DmGreetMessageText") + .HasColumnType("longtext") + .HasColumnName("dmgreetmessagetext"); + + b.Property("ExclusiveSelfAssignedRoles") + .HasColumnType("tinyint(1)") + .HasColumnName("exclusiveselfassignedroles"); + + b.Property("FilterInvites") + .HasColumnType("tinyint(1)") + .HasColumnName("filterinvites"); + + b.Property("FilterLinks") + .HasColumnType("tinyint(1)") + .HasColumnName("filterlinks"); + + b.Property("FilterWords") + .HasColumnType("tinyint(1)") + .HasColumnName("filterwords"); + + b.Property("GameVoiceChannel") + .HasColumnType("bigint unsigned") + .HasColumnName("gamevoicechannel"); + + b.Property("GreetMessageChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("greetmessagechannelid"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Locale") + .HasColumnType("longtext") + .HasColumnName("locale"); + + b.Property("MuteRoleName") + .HasColumnType("longtext") + .HasColumnName("muterolename"); + + b.Property("NotifyStreamOffline") + .HasColumnType("tinyint(1)") + .HasColumnName("notifystreamoffline"); + + b.Property("PermissionRole") + .HasColumnType("longtext") + .HasColumnName("permissionrole"); + + b.Property("Prefix") + .HasColumnType("longtext") + .HasColumnName("prefix"); + + b.Property("SendBoostMessage") + .HasColumnType("tinyint(1)") + .HasColumnName("sendboostmessage"); + + b.Property("SendChannelByeMessage") + .HasColumnType("tinyint(1)") + .HasColumnName("sendchannelbyemessage"); + + b.Property("SendChannelGreetMessage") + .HasColumnType("tinyint(1)") + .HasColumnName("sendchannelgreetmessage"); + + b.Property("SendDmGreetMessage") + .HasColumnType("tinyint(1)") + .HasColumnName("senddmgreetmessage"); + + b.Property("TimeZoneId") + .HasColumnType("longtext") + .HasColumnName("timezoneid"); + + b.Property("VerboseErrors") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("verboseerrors"); + + b.Property("VerbosePermissions") + .HasColumnType("tinyint(1)") + .HasColumnName("verbosepermissions"); + + b.Property("WarnExpireAction") + .HasColumnType("int") + .HasColumnName("warnexpireaction"); + + b.Property("WarnExpireHours") + .HasColumnType("int") + .HasColumnName("warnexpirehours"); + + b.Property("WarningsInitialized") + .HasColumnType("tinyint(1)") + .HasColumnName("warningsinitialized"); + + b.HasKey("Id") + .HasName("pk_guildconfigs"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_guildconfigs_guildid"); + + b.HasIndex("WarnExpireHours") + .HasDatabaseName("ix_guildconfigs_warnexpirehours"); + + b.ToTable("guildconfigs", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("ItemType") + .HasColumnType("int") + .HasColumnName("itemtype"); + + b.Property("LogItemId") + .HasColumnType("bigint unsigned") + .HasColumnName("logitemid"); + + b.Property("LogSettingId") + .HasColumnType("int") + .HasColumnName("logsettingid"); + + b.HasKey("Id") + .HasName("pk_ignoredlogchannels"); + + b.HasIndex("LogSettingId", "LogItemId", "ItemType") + .IsUnique() + .HasDatabaseName("ix_ignoredlogchannels_logsettingid_logitemid_itemtype"); + + b.ToTable("ignoredlogchannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("LogSettingId") + .HasColumnType("int") + .HasColumnName("logsettingid"); + + b.HasKey("Id") + .HasName("pk_ignoredvoicepresencechannels"); + + b.HasIndex("LogSettingId") + .HasDatabaseName("ix_ignoredvoicepresencechannels_logsettingid"); + + b.ToTable("ignoredvoicepresencechannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.HasKey("Id") + .HasName("pk_imageonlychannels"); + + b.HasIndex("ChannelId") + .IsUnique() + .HasDatabaseName("ix_imageonlychannels_channelid"); + + b.ToTable("imageonlychannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelCreatedId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelcreatedid"); + + b.Property("ChannelDestroyedId") + .HasColumnType("bigint unsigned") + .HasColumnName("channeldestroyedid"); + + b.Property("ChannelUpdatedId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelupdatedid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("LogOtherId") + .HasColumnType("bigint unsigned") + .HasColumnName("logotherid"); + + b.Property("LogUserPresenceId") + .HasColumnType("bigint unsigned") + .HasColumnName("loguserpresenceid"); + + b.Property("LogVoicePresenceId") + .HasColumnType("bigint unsigned") + .HasColumnName("logvoicepresenceid"); + + b.Property("LogVoicePresenceTTSId") + .HasColumnType("bigint unsigned") + .HasColumnName("logvoicepresencettsid"); + + b.Property("MessageDeletedId") + .HasColumnType("bigint unsigned") + .HasColumnName("messagedeletedid"); + + b.Property("MessageUpdatedId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageupdatedid"); + + b.Property("UserBannedId") + .HasColumnType("bigint unsigned") + .HasColumnName("userbannedid"); + + b.Property("UserJoinedId") + .HasColumnType("bigint unsigned") + .HasColumnName("userjoinedid"); + + b.Property("UserLeftId") + .HasColumnType("bigint unsigned") + .HasColumnName("userleftid"); + + b.Property("UserMutedId") + .HasColumnType("bigint unsigned") + .HasColumnName("usermutedid"); + + b.Property("UserUnbannedId") + .HasColumnType("bigint unsigned") + .HasColumnName("userunbannedid"); + + b.Property("UserUpdatedId") + .HasColumnType("bigint unsigned") + .HasColumnName("userupdatedid"); + + b.HasKey("Id") + .HasName("pk_logsettings"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_logsettings_guildid"); + + b.ToTable("logsettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AutoDisconnect") + .HasColumnType("tinyint(1)") + .HasColumnName("autodisconnect"); + + b.Property("AutoPlay") + .HasColumnType("tinyint(1)") + .HasColumnName("autoplay"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("MusicChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("musicchannelid"); + + b.Property("PlayerRepeat") + .HasColumnType("int") + .HasColumnName("playerrepeat"); + + b.Property("QualityPreset") + .HasColumnType("int") + .HasColumnName("qualitypreset"); + + b.Property("Volume") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(100) + .HasColumnName("volume"); + + b.HasKey("Id") + .HasName("pk_musicplayersettings"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_musicplayersettings_guildid"); + + b.ToTable("musicplayersettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Author") + .HasColumnType("longtext") + .HasColumnName("author"); + + b.Property("AuthorId") + .HasColumnType("bigint unsigned") + .HasColumnName("authorid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_musicplaylists"); + + b.ToTable("musicplaylists", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_muteduserid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_muteduserid_guildconfigid"); + + b.ToTable("muteduserid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AllowTarget") + .HasColumnType("tinyint(1)") + .HasColumnName("allowtarget"); + + b.Property("AutoDeleteTrigger") + .HasColumnType("tinyint(1)") + .HasColumnName("autodeletetrigger"); + + b.Property("ContainsAnywhere") + .HasColumnType("tinyint(1)") + .HasColumnName("containsanywhere"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("DmResponse") + .HasColumnType("tinyint(1)") + .HasColumnName("dmresponse"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Reactions") + .HasColumnType("longtext") + .HasColumnName("reactions"); + + b.Property("Response") + .HasColumnType("longtext") + .HasColumnName("response"); + + b.Property("Trigger") + .HasColumnType("longtext") + .HasColumnName("trigger"); + + b.HasKey("Id") + .HasName("pk_expressions"); + + b.ToTable("expressions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Tag") + .HasColumnType("longtext") + .HasColumnName("tag"); + + b.HasKey("Id") + .HasName("pk_nsfwblacklistedtags"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_nsfwblacklistedtags_guildid"); + + b.ToTable("nsfwblacklistedtags", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Index") + .HasColumnType("int") + .HasColumnName("index"); + + b.Property("IsCustomCommand") + .HasColumnType("tinyint(1)") + .HasColumnName("iscustomcommand"); + + b.Property("PrimaryTarget") + .HasColumnType("int") + .HasColumnName("primarytarget"); + + b.Property("PrimaryTargetId") + .HasColumnType("bigint unsigned") + .HasColumnName("primarytargetid"); + + b.Property("SecondaryTarget") + .HasColumnType("int") + .HasColumnName("secondarytarget"); + + b.Property("SecondaryTargetName") + .HasColumnType("longtext") + .HasColumnName("secondarytargetname"); + + b.Property("State") + .HasColumnType("tinyint(1)") + .HasColumnName("state"); + + b.HasKey("Id") + .HasName("pk_permissions"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_permissions_guildconfigid"); + + b.ToTable("permissions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("bigint") + .HasColumnName("amount"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("MessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageid"); + + b.Property("Password") + .HasColumnType("longtext") + .HasColumnName("password"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_plantedcurrency"); + + b.HasIndex("ChannelId") + .HasDatabaseName("ix_plantedcurrency_channelid"); + + b.HasIndex("MessageId") + .IsUnique() + .HasDatabaseName("ix_plantedcurrency_messageid"); + + b.ToTable("plantedcurrency", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("MusicPlaylistId") + .HasColumnType("int") + .HasColumnName("musicplaylistid"); + + b.Property("Provider") + .HasColumnType("longtext") + .HasColumnName("provider"); + + b.Property("ProviderType") + .HasColumnType("int") + .HasColumnName("providertype"); + + b.Property("Query") + .HasColumnType("longtext") + .HasColumnName("query"); + + b.Property("Title") + .HasColumnType("longtext") + .HasColumnName("title"); + + b.Property("Uri") + .HasColumnType("longtext") + .HasColumnName("uri"); + + b.HasKey("Id") + .HasName("pk_playlistsong"); + + b.HasIndex("MusicPlaylistId") + .HasDatabaseName("ix_playlistsong_musicplaylistid"); + + b.ToTable("playlistsong", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Question") + .HasColumnType("longtext") + .HasColumnName("question"); + + b.HasKey("Id") + .HasName("pk_poll"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_poll_guildid"); + + b.ToTable("poll", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Index") + .HasColumnType("int") + .HasColumnName("index"); + + b.Property("PollId") + .HasColumnType("int") + .HasColumnName("pollid"); + + b.Property("Text") + .HasColumnType("longtext") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_pollanswer"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_pollanswer_pollid"); + + b.ToTable("pollanswer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("PollId") + .HasColumnType("int") + .HasColumnName("pollid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("VoteIndex") + .HasColumnType("int") + .HasColumnName("voteindex"); + + b.HasKey("Id") + .HasName("pk_pollvote"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_pollvote_pollid"); + + b.ToTable("pollvote", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("bigint unsigned") + .HasColumnName("authorid"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("authorname"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Keyword") + .IsRequired() + .HasColumnType("varchar(255)") + .HasColumnName("keyword"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_quotes"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_quotes_guildid"); + + b.HasIndex("Keyword") + .HasDatabaseName("ix_quotes_keyword"); + + b.ToTable("quotes", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Emote") + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("emote"); + + b.Property("Group") + .HasColumnType("int") + .HasColumnName("group"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("LevelReq") + .HasColumnType("int") + .HasColumnName("levelreq"); + + b.Property("MessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageid"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_reactionroles"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_reactionroles_guildid"); + + b.HasIndex("MessageId", "Emote") + .IsUnique() + .HasDatabaseName("ix_reactionroles_messageid_emote"); + + b.ToTable("reactionroles", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("IsPrivate") + .HasColumnType("tinyint(1)") + .HasColumnName("isprivate"); + + b.Property("Message") + .HasColumnType("longtext") + .HasColumnName("message"); + + b.Property("ServerId") + .HasColumnType("bigint unsigned") + .HasColumnName("serverid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("When") + .HasColumnType("datetime(6)") + .HasColumnName("when"); + + b.HasKey("Id") + .HasName("pk_reminders"); + + b.HasIndex("When") + .HasDatabaseName("ix_reminders_when"); + + b.ToTable("reminders", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Interval") + .HasColumnType("time(6)") + .HasColumnName("interval"); + + b.Property("LastMessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("lastmessageid"); + + b.Property("Message") + .HasColumnType("longtext") + .HasColumnName("message"); + + b.Property("NoRedundant") + .HasColumnType("tinyint(1)") + .HasColumnName("noredundant"); + + b.Property("StartTimeOfDay") + .HasColumnType("time(6)") + .HasColumnName("starttimeofday"); + + b.HasKey("Id") + .HasName("pk_repeaters"); + + b.ToTable("repeaters", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AmountRewardedThisMonth") + .HasColumnType("bigint") + .HasColumnName("amountrewardedthismonth"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("LastReward") + .HasColumnType("datetime(6)") + .HasColumnName("lastreward"); + + b.Property("PlatformUserId") + .HasColumnType("varchar(255)") + .HasColumnName("platformuserid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_rewardedusers"); + + b.HasIndex("PlatformUserId") + .IsUnique() + .HasDatabaseName("ix_rewardedusers_platformuserid"); + + b.ToTable("rewardedusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Status") + .HasColumnType("longtext") + .HasColumnName("status"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_rotatingstatus"); + + b.ToTable("rotatingstatus", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Group") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("group"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("LevelRequirement") + .HasColumnType("int") + .HasColumnName("levelrequirement"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_selfassignableroles"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique() + .HasDatabaseName("ix_selfassignableroles_guildid_roleid"); + + b.ToTable("selfassignableroles", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("bigint unsigned") + .HasColumnName("authorid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Index") + .HasColumnType("int") + .HasColumnName("index"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("int") + .HasColumnName("price"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.Property("RoleName") + .HasColumnType("longtext") + .HasColumnName("rolename"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_shopentry"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_shopentry_guildconfigid"); + + b.ToTable("shopentry", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("ShopEntryId") + .HasColumnType("int") + .HasColumnName("shopentryid"); + + b.Property("Text") + .HasColumnType("longtext") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_shopentryitem"); + + b.HasIndex("ShopEntryId") + .HasDatabaseName("ix_shopentryitem_shopentryid"); + + b.ToTable("shopentryitem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_slowmodeignoredrole"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_slowmodeignoredrole_guildconfigid"); + + b.ToTable("slowmodeignoredrole", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_slowmodeignoreduser"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_slowmodeignoreduser_guildconfigid"); + + b.ToTable("slowmodeignoreduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("int") + .HasColumnName("streamrolesettingsid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("longtext") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_streamroleblacklisteduser"); + + b.HasIndex("StreamRoleSettingsId") + .HasDatabaseName("ix_streamroleblacklisteduser_streamrolesettingsid"); + + b.ToTable("streamroleblacklisteduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AddRoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("addroleid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)") + .HasColumnName("enabled"); + + b.Property("FromRoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("fromroleid"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Keyword") + .HasColumnType("longtext") + .HasColumnName("keyword"); + + b.HasKey("Id") + .HasName("pk_streamrolesettings"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_streamrolesettings_guildconfigid"); + + b.ToTable("streamrolesettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("int") + .HasColumnName("streamrolesettingsid"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("longtext") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_streamrolewhitelisteduser"); + + b.HasIndex("StreamRoleSettingsId") + .HasDatabaseName("ix_streamrolewhitelisteduser_streamrolesettingsid"); + + b.ToTable("streamrolewhitelisteduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("UnbanAt") + .HasColumnType("datetime(6)") + .HasColumnName("unbanat"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unbantimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unbantimer_guildconfigid"); + + b.ToTable("unbantimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("UnmuteAt") + .HasColumnType("datetime(6)") + .HasColumnName("unmuteat"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unmutetimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unmutetimer_guildconfigid"); + + b.ToTable("unmutetimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.Property("UnbanAt") + .HasColumnType("datetime(6)") + .HasColumnName("unbanat"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unroletimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unroletimer_guildconfigid"); + + b.ToTable("unroletimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AwardedXp") + .HasColumnType("bigint") + .HasColumnName("awardedxp"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("lastlevelup") + .HasDefaultValueSql("(UTC_TIMESTAMP)"); + + b.Property("NotifyOnLevelUp") + .HasColumnType("int") + .HasColumnName("notifyonlevelup"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("Xp") + .HasColumnType("bigint") + .HasColumnName("xp"); + + b.HasKey("Id") + .HasName("pk_userxpstats"); + + b.HasIndex("AwardedXp") + .HasDatabaseName("ix_userxpstats_awardedxp"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_userxpstats_guildid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_userxpstats_userid"); + + b.HasIndex("Xp") + .HasDatabaseName("ix_userxpstats_xp"); + + b.HasIndex("UserId", "GuildId") + .IsUnique() + .HasDatabaseName("ix_userxpstats_userid_guildid"); + + b.ToTable("userxpstats", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.Property("VoiceChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("voicechannelid"); + + b.HasKey("Id") + .HasName("pk_vcroleinfo"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_vcroleinfo_guildconfigid"); + + b.ToTable("vcroleinfo", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("AffinityId") + .HasColumnType("int") + .HasColumnName("affinityid"); + + b.Property("ClaimerId") + .HasColumnType("int") + .HasColumnName("claimerid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Price") + .HasColumnType("bigint") + .HasColumnName("price"); + + b.Property("WaifuId") + .HasColumnType("int") + .HasColumnName("waifuid"); + + b.HasKey("Id") + .HasName("pk_waifuinfo"); + + b.HasIndex("AffinityId") + .HasDatabaseName("ix_waifuinfo_affinityid"); + + b.HasIndex("ClaimerId") + .HasDatabaseName("ix_waifuinfo_claimerid"); + + b.HasIndex("Price") + .HasDatabaseName("ix_waifuinfo_price"); + + b.HasIndex("WaifuId") + .IsUnique() + .HasDatabaseName("ix_waifuinfo_waifuid"); + + b.ToTable("waifuinfo", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("ItemEmoji") + .HasColumnType("longtext") + .HasColumnName("itememoji"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.Property("WaifuInfoId") + .HasColumnType("int") + .HasColumnName("waifuinfoid"); + + b.HasKey("Id") + .HasName("pk_waifuitem"); + + b.HasIndex("WaifuInfoId") + .HasDatabaseName("ix_waifuitem_waifuinfoid"); + + b.ToTable("waifuitem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("NewId") + .HasColumnType("int") + .HasColumnName("newid"); + + b.Property("OldId") + .HasColumnType("int") + .HasColumnName("oldid"); + + b.Property("UpdateType") + .HasColumnType("int") + .HasColumnName("updatetype"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_waifuupdates"); + + b.HasIndex("NewId") + .HasDatabaseName("ix_waifuupdates_newid"); + + b.HasIndex("OldId") + .HasDatabaseName("ix_waifuupdates_oldid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_waifuupdates_userid"); + + b.ToTable("waifuupdates", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Forgiven") + .HasColumnType("tinyint(1)") + .HasColumnName("forgiven"); + + b.Property("ForgivenBy") + .HasColumnType("longtext") + .HasColumnName("forgivenby"); + + b.Property("GuildId") + .HasColumnType("bigint unsigned") + .HasColumnName("guildid"); + + b.Property("Moderator") + .HasColumnType("longtext") + .HasColumnName("moderator"); + + b.Property("Reason") + .HasColumnType("longtext") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("bigint unsigned") + .HasColumnName("userid"); + + b.Property("Weight") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L) + .HasColumnName("weight"); + + b.HasKey("Id") + .HasName("pk_warnings"); + + b.HasIndex("DateAdded") + .HasDatabaseName("ix_warnings_dateadded"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_warnings_guildid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_warnings_userid"); + + b.ToTable("warnings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Count") + .HasColumnType("int") + .HasColumnName("count"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("Punishment") + .HasColumnType("int") + .HasColumnName("punishment"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.Property("Time") + .HasColumnType("int") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("pk_warningpunishment"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_warningpunishment_guildconfigid"); + + b.ToTable("warningpunishment", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("int") + .HasColumnName("amount"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Level") + .HasColumnType("int") + .HasColumnName("level"); + + b.Property("XpSettingsId") + .HasColumnType("int") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_xpcurrencyreward"); + + b.HasIndex("XpSettingsId") + .HasDatabaseName("ix_xpcurrencyreward_xpsettingsid"); + + b.ToTable("xpcurrencyreward", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("Level") + .HasColumnType("int") + .HasColumnName("level"); + + b.Property("Remove") + .HasColumnType("tinyint(1)") + .HasColumnName("remove"); + + b.Property("RoleId") + .HasColumnType("bigint unsigned") + .HasColumnName("roleid"); + + b.Property("XpSettingsId") + .HasColumnType("int") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_xprolereward"); + + b.HasIndex("XpSettingsId", "Level") + .IsUnique() + .HasDatabaseName("ix_xprolereward_xpsettingsid_level"); + + b.ToTable("xprolereward", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("int") + .HasColumnName("guildconfigid"); + + b.Property("ServerExcluded") + .HasColumnType("tinyint(1)") + .HasColumnName("serverexcluded"); + + b.HasKey("Id") + .HasName("pk_xpsettings"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_xpsettings_guildconfigid"); + + b.ToTable("xpsettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubapplicants_clubs_clubid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubapplicants_discorduser_userid"); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubbans_clubs_clubid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubbans_discorduser_userid"); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Db.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_clubs_discorduser_ownerid"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Members") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.NoAction) + .HasConstraintName("fk_discorduser_clubs_clubid"); + + b.Navigation("Club"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_followedstream_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithOne("AntiAltSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiAltSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antialtsetting_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antiraidsetting_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting", null) + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId") + .HasConstraintName("fk_antispamignore_antispamsetting_antispamsettingid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antispamsetting_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel") + .WithMany("Users") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_autotranslateusers_autotranslatechannels_channelid"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_commandalias_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_commandcooldown_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("DelMsgOnCmdChannels") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_delmsgoncmdchannel_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", null) + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId") + .HasConstraintName("fk_excludeditem_xpsettings_xpsettingsid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_feedsub_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterchannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filteredword_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterLinksChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterlinkschannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterwordschannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_gcchannelid_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("SelfAssignableRoleGroupNames") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_groupname_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("LogIgnores") + .HasForeignKey("LogSettingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_ignoredlogchannels_logsettings_logsettingid"); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId") + .HasConstraintName("fk_ignoredvoicepresencechannels_logsettings_logsettingid"); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_muteduserid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("Permissions") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_permissions_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist", null) + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_playlistsong_musicplaylists_musicplaylistid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Answers") + .HasForeignKey("PollId") + .HasConstraintName("fk_pollanswer_poll_pollid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Votes") + .HasForeignKey("PollId") + .HasConstraintName("fk_pollvote_poll_pollid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_shopentry_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry", null) + .WithMany("Items") + .HasForeignKey("ShopEntryId") + .HasConstraintName("fk_shopentryitem_shopentry_shopentryid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_slowmodeignoredrole_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_slowmodeignoreduser_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId") + .HasConstraintName("fk_streamroleblacklisteduser_streamrolesettings_streamrolesetti~"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_streamrolesettings_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId") + .HasConstraintName("fk_streamrolewhitelisteduser_streamrolesettings_streamrolesetti~"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnbanTimer") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unbantimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unmutetimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnroleTimer") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unroletimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_vcroleinfo_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId") + .HasConstraintName("fk_waifuinfo_discorduser_affinityid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId") + .HasConstraintName("fk_waifuinfo_discorduser_claimerid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_waifuinfo_discorduser_waifuid"); + + b.Navigation("Affinity"); + + b.Navigation("Claimer"); + + b.Navigation("Waifu"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId") + .HasConstraintName("fk_waifuitem_waifuinfo_waifuinfoid"); + + b.Navigation("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId") + .HasConstraintName("fk_waifuupdates_discorduser_newid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId") + .HasConstraintName("fk_waifuupdates_discorduser_oldid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_waifuupdates_discorduser_userid"); + + b.Navigation("New"); + + b.Navigation("Old"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_warningpunishment_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("CurrencyRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xpcurrencyreward_xpsettings_xpsettingsid"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xprolereward_xpsettings_xpsettingsid"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xpsettings_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Navigation("Applicants"); + + b.Navigation("Bans"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Navigation("IgnoredChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Navigation("AntiAltSetting"); + + b.Navigation("AntiRaidSetting"); + + b.Navigation("AntiSpamSetting"); + + b.Navigation("CommandAliases"); + + b.Navigation("CommandCooldowns"); + + b.Navigation("DelMsgOnCmdChannels"); + + b.Navigation("FeedSubs"); + + b.Navigation("FilterInvitesChannelIds"); + + b.Navigation("FilterLinksChannelIds"); + + b.Navigation("FilterWordsChannelIds"); + + b.Navigation("FilteredWords"); + + b.Navigation("FollowedStreams"); + + b.Navigation("GenerateCurrencyChannelIds"); + + b.Navigation("MutedUsers"); + + b.Navigation("Permissions"); + + b.Navigation("SelfAssignableRoleGroupNames"); + + b.Navigation("ShopEntries"); + + b.Navigation("SlowmodeIgnoredRoles"); + + b.Navigation("SlowmodeIgnoredUsers"); + + b.Navigation("StreamRole"); + + b.Navigation("UnbanTimer"); + + b.Navigation("UnmuteTimers"); + + b.Navigation("UnroleTimer"); + + b.Navigation("VcRoleInfos"); + + b.Navigation("WarnPunishments"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Navigation("LogIgnores"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Navigation("Songs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Navigation("Answers"); + + b.Navigation("Votes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Navigation("Blacklist"); + + b.Navigation("Whitelist"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Navigation("CurrencyRewards"); + + b.Navigation("ExclusionList"); + + b.Navigation("RoleRewards"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.cs b/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.cs new file mode 100644 index 000000000..239bd2c50 --- /dev/null +++ b/src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NadekoBot.Migrations.Mysql +{ + public partial class stondeldbcache : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "streamonlinemessages", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + channelid = table.Column(type: "bigint unsigned", nullable: false), + messageid = table.Column(type: "bigint unsigned", nullable: false), + type = table.Column(type: "int", nullable: false), + name = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + dateadded = table.Column(type: "datetime(6)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_streamonlinemessages", x => x.id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "streamonlinemessages"); + } + } +} diff --git a/src/NadekoBot/Migrations/MySql/MysqlContextModelSnapshot.cs b/src/NadekoBot/Migrations/MySql/MysqlContextModelSnapshot.cs index 28f3cb7b2..d2def3fdd 100644 --- a/src/NadekoBot/Migrations/MySql/MysqlContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/MySql/MysqlContextModelSnapshot.cs @@ -16,7 +16,7 @@ namespace NadekoBot.Migrations.Mysql { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "6.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => @@ -333,6 +333,39 @@ namespace NadekoBot.Migrations.Mysql b.ToTable("patrons", (string)null); }); + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)") + .HasColumnName("dateadded"); + + b.Property("MessageId") + .HasColumnType("bigint unsigned") + .HasColumnName("messageid"); + + b.Property("Name") + .HasColumnType("longtext") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_streamonlinemessages"); + + b.ToTable("streamonlinemessages", (string)null); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => { b.Property("Id") diff --git a/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.Designer.cs b/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.Designer.cs new file mode 100644 index 000000000..3cbdb90e5 --- /dev/null +++ b/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.Designer.cs @@ -0,0 +1,3656 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NadekoBot.Services.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NadekoBot.Migrations.PostgreSql +{ + [DbContext(typeof(PostgreSqlContext))] + [Migration("20220623090729_stondel-db-cache")] + partial class stondeldbcache + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint") + .HasColumnName("balance"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_bankusers"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("ix_bankusers_userid"); + + b.ToTable("bankusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.Property("ClubId") + .HasColumnType("integer") + .HasColumnName("clubid"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("userid"); + + b.HasKey("ClubId", "UserId") + .HasName("pk_clubapplicants"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_clubapplicants_userid"); + + b.ToTable("clubapplicants", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.Property("ClubId") + .HasColumnType("integer") + .HasColumnName("clubid"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("userid"); + + b.HasKey("ClubId", "UserId") + .HasName("pk_clubbans"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_clubbans_userid"); + + b.ToTable("clubbans", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ImageUrl") + .HasColumnType("text") + .HasColumnName("imageurl"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("name"); + + b.Property("OwnerId") + .HasColumnType("integer") + .HasColumnName("ownerid"); + + b.Property("Xp") + .HasColumnType("integer") + .HasColumnName("xp"); + + b.HasKey("Id") + .HasName("pk_clubs"); + + b.HasAlternateKey("Name") + .HasName("ak_clubs_name"); + + b.HasIndex("OwnerId") + .IsUnique() + .HasDatabaseName("ix_clubs_ownerid"); + + b.ToTable("clubs", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarId") + .HasColumnType("text") + .HasColumnName("avatarid"); + + b.Property("ClubId") + .HasColumnType("integer") + .HasColumnName("clubid"); + + b.Property("CurrencyAmount") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("currencyamount"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Discriminator") + .HasColumnType("text") + .HasColumnName("discriminator"); + + b.Property("IsClubAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isclubadmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("lastlevelup") + .HasDefaultValueSql("timezone('utc', now())"); + + b.Property("LastXpGain") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("lastxpgain") + .HasDefaultValueSql("timezone('utc', now()) - interval '-1 year'"); + + b.Property("NotifyOnLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("notifyonlevelup"); + + b.Property("TotalXp") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("totalxp"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_discorduser"); + + b.HasAlternateKey("UserId") + .HasName("ak_discorduser_userid"); + + b.HasIndex("ClubId") + .HasDatabaseName("ix_discorduser_clubid"); + + b.HasIndex("CurrencyAmount") + .HasDatabaseName("ix_discorduser_currencyamount"); + + b.HasIndex("TotalXp") + .HasDatabaseName("ix_discorduser_totalxp"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_discorduser_userid"); + + b.ToTable("discorduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_followedstream"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_followedstream_guildconfigid"); + + b.ToTable("followedstream", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b => + { + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("FeatureType") + .HasColumnType("integer") + .HasColumnName("featuretype"); + + b.Property("Feature") + .HasColumnType("text") + .HasColumnName("feature"); + + b.Property("DailyCount") + .HasColumnType("bigint") + .HasColumnName("dailycount"); + + b.Property("HourlyCount") + .HasColumnType("bigint") + .HasColumnName("hourlycount"); + + b.Property("MonthlyCount") + .HasColumnType("bigint") + .HasColumnName("monthlycount"); + + b.HasKey("UserId", "FeatureType", "Feature") + .HasName("pk_patronquotas"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_patronquotas_userid"); + + b.ToTable("patronquotas", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amountcents"); + + b.Property("LastCharge") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastcharge"); + + b.Property("UniquePlatformUserId") + .HasColumnType("text") + .HasColumnName("uniqueplatformuserid"); + + b.Property("ValidThru") + .HasColumnType("timestamp with time zone") + .HasColumnName("validthru"); + + b.HasKey("UserId") + .HasName("pk_patrons"); + + b.HasIndex("UniquePlatformUserId") + .IsUnique() + .HasDatabaseName("ix_patrons_uniqueplatformuserid"); + + b.ToTable("patrons", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messageid"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_streamonlinemessages"); + + b.ToTable("streamonlinemessages", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasColumnName("action"); + + b.Property("ActionDurationMinutes") + .HasColumnType("integer") + .HasColumnName("actiondurationminutes"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("MinAge") + .HasColumnType("interval") + .HasColumnName("minage"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_antialtsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antialtsetting_guildconfigid"); + + b.ToTable("antialtsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasColumnName("action"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("PunishDuration") + .HasColumnType("integer") + .HasColumnName("punishduration"); + + b.Property("Seconds") + .HasColumnType("integer") + .HasColumnName("seconds"); + + b.Property("UserThreshold") + .HasColumnType("integer") + .HasColumnName("userthreshold"); + + b.HasKey("Id") + .HasName("pk_antiraidsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antiraidsetting_guildconfigid"); + + b.ToTable("antiraidsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntiSpamSettingId") + .HasColumnType("integer") + .HasColumnName("antispamsettingid"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.HasKey("Id") + .HasName("pk_antispamignore"); + + b.HasIndex("AntiSpamSettingId") + .HasDatabaseName("ix_antispamignore_antispamsettingid"); + + b.ToTable("antispamignore", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasColumnName("action"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("MessageThreshold") + .HasColumnType("integer") + .HasColumnName("messagethreshold"); + + b.Property("MuteTime") + .HasColumnType("integer") + .HasColumnName("mutetime"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_antispamsetting"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_antispamsetting_guildconfigid"); + + b.ToTable("antispamsetting", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("ChannelName") + .HasColumnType("text") + .HasColumnName("channelname"); + + b.Property("CommandText") + .HasColumnType("text") + .HasColumnName("commandtext"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("GuildName") + .HasColumnType("text") + .HasColumnName("guildname"); + + b.Property("Interval") + .HasColumnType("integer") + .HasColumnName("interval"); + + b.Property("VoiceChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("voicechannelid"); + + b.Property("VoiceChannelName") + .HasColumnType("text") + .HasColumnName("voicechannelname"); + + b.HasKey("Id") + .HasName("pk_autocommands"); + + b.ToTable("autocommands", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("autodelete"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.HasKey("Id") + .HasName("pk_autotranslatechannels"); + + b.HasIndex("ChannelId") + .IsUnique() + .HasDatabaseName("ix_autotranslatechannels_channelid"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_autotranslatechannels_guildid"); + + b.ToTable("autotranslatechannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("integer") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b.Property("Target") + .HasColumnType("text") + .HasColumnName("target"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_autotranslateusers"); + + b.HasAlternateKey("ChannelId", "UserId") + .HasName("ak_autotranslateusers_channelid_userid"); + + b.ToTable("autotranslateusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Text") + .HasColumnType("text") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_bantemplates"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_bantemplates_guildid"); + + b.ToTable("bantemplates", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("ItemId") + .HasColumnType("numeric(20,0)") + .HasColumnName("itemid"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Mapping") + .HasColumnType("text") + .HasColumnName("mapping"); + + b.Property("Trigger") + .HasColumnType("text") + .HasColumnName("trigger"); + + b.HasKey("Id") + .HasName("pk_commandalias"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_commandalias_guildconfigid"); + + b.ToTable("commandalias", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommandName") + .HasColumnType("text") + .HasColumnName("commandname"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Seconds") + .HasColumnType("integer") + .HasColumnName("seconds"); + + b.HasKey("Id") + .HasName("pk_commandcooldown"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_commandcooldown_guildconfigid"); + + b.ToTable("commandcooldown", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint") + .HasColumnName("amount"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("text") + .HasColumnName("extra"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("OtherId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("otherid") + .HasDefaultValueSql("NULL"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_currencytransactions"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_currencytransactions_userid"); + + b.ToTable("currencytransactions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("State") + .HasColumnType("boolean") + .HasColumnName("state"); + + b.HasKey("Id") + .HasName("pk_delmsgoncmdchannel"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_delmsgoncmdchannel_guildconfigid"); + + b.ToTable("delmsgoncmdchannel", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Command") + .HasColumnType("text") + .HasColumnName("command"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Perm") + .HasColumnType("numeric(20,0)") + .HasColumnName("perm"); + + b.HasKey("Id") + .HasName("pk_discordpermoverrides"); + + b.HasIndex("GuildId", "Command") + .IsUnique() + .HasDatabaseName("ix_discordpermoverrides_guildid_command"); + + b.ToTable("discordpermoverrides", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("ItemId") + .HasColumnType("numeric(20,0)") + .HasColumnName("itemid"); + + b.Property("ItemType") + .HasColumnType("integer") + .HasColumnName("itemtype"); + + b.Property("XpSettingsId") + .HasColumnType("integer") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_excludeditem"); + + b.HasIndex("XpSettingsId") + .HasDatabaseName("ix_excludeditem_xpsettingsid"); + + b.ToTable("excludeditem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_feedsub"); + + b.HasAlternateKey("GuildConfigId", "Url") + .HasName("ak_feedsub_guildconfigid_url"); + + b.ToTable("feedsub", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterchannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterchannelid_guildconfigid"); + + b.ToTable("filterchannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Word") + .HasColumnType("text") + .HasColumnName("word"); + + b.HasKey("Id") + .HasName("pk_filteredword"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filteredword_guildconfigid"); + + b.ToTable("filteredword", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterlinkschannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterlinkschannelid_guildconfigid"); + + b.ToTable("filterlinkschannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_filterwordschannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_filterwordschannelid_guildconfigid"); + + b.ToTable("filterwordschannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.HasKey("Id") + .HasName("pk_gcchannelid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_gcchannelid_guildconfigid"); + + b.ToTable("gcchannelid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Number") + .HasColumnType("integer") + .HasColumnName("number"); + + b.HasKey("Id") + .HasName("pk_groupname"); + + b.HasIndex("GuildConfigId", "Number") + .IsUnique() + .HasDatabaseName("ix_groupname_guildconfigid_number"); + + b.ToTable("groupname", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoAssignRoleIds") + .HasColumnType("text") + .HasColumnName("autoassignroleids"); + + b.Property("AutoDeleteByeMessagesTimer") + .HasColumnType("integer") + .HasColumnName("autodeletebyemessagestimer"); + + b.Property("AutoDeleteGreetMessagesTimer") + .HasColumnType("integer") + .HasColumnName("autodeletegreetmessagestimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages") + .HasColumnType("boolean") + .HasColumnName("autodeleteselfassignedrolemessages"); + + b.Property("BoostMessage") + .HasColumnType("text") + .HasColumnName("boostmessage"); + + b.Property("BoostMessageChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("boostmessagechannelid"); + + b.Property("BoostMessageDeleteAfter") + .HasColumnType("integer") + .HasColumnName("boostmessagedeleteafter"); + + b.Property("ByeMessageChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("byemessagechannelid"); + + b.Property("ChannelByeMessageText") + .HasColumnType("text") + .HasColumnName("channelbyemessagetext"); + + b.Property("ChannelGreetMessageText") + .HasColumnType("text") + .HasColumnName("channelgreetmessagetext"); + + b.Property("CleverbotEnabled") + .HasColumnType("boolean") + .HasColumnName("cleverbotenabled"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("DeleteMessageOnCommand") + .HasColumnType("boolean") + .HasColumnName("deletemessageoncommand"); + + b.Property("DeleteStreamOnlineMessage") + .HasColumnType("boolean") + .HasColumnName("deletestreamonlinemessage"); + + b.Property("DmGreetMessageText") + .HasColumnType("text") + .HasColumnName("dmgreetmessagetext"); + + b.Property("ExclusiveSelfAssignedRoles") + .HasColumnType("boolean") + .HasColumnName("exclusiveselfassignedroles"); + + b.Property("FilterInvites") + .HasColumnType("boolean") + .HasColumnName("filterinvites"); + + b.Property("FilterLinks") + .HasColumnType("boolean") + .HasColumnName("filterlinks"); + + b.Property("FilterWords") + .HasColumnType("boolean") + .HasColumnName("filterwords"); + + b.Property("GameVoiceChannel") + .HasColumnType("numeric(20,0)") + .HasColumnName("gamevoicechannel"); + + b.Property("GreetMessageChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("greetmessagechannelid"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Locale") + .HasColumnType("text") + .HasColumnName("locale"); + + b.Property("MuteRoleName") + .HasColumnType("text") + .HasColumnName("muterolename"); + + b.Property("NotifyStreamOffline") + .HasColumnType("boolean") + .HasColumnName("notifystreamoffline"); + + b.Property("PermissionRole") + .HasColumnType("text") + .HasColumnName("permissionrole"); + + b.Property("Prefix") + .HasColumnType("text") + .HasColumnName("prefix"); + + b.Property("SendBoostMessage") + .HasColumnType("boolean") + .HasColumnName("sendboostmessage"); + + b.Property("SendChannelByeMessage") + .HasColumnType("boolean") + .HasColumnName("sendchannelbyemessage"); + + b.Property("SendChannelGreetMessage") + .HasColumnType("boolean") + .HasColumnName("sendchannelgreetmessage"); + + b.Property("SendDmGreetMessage") + .HasColumnType("boolean") + .HasColumnName("senddmgreetmessage"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("timezoneid"); + + b.Property("VerboseErrors") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("verboseerrors"); + + b.Property("VerbosePermissions") + .HasColumnType("boolean") + .HasColumnName("verbosepermissions"); + + b.Property("WarnExpireAction") + .HasColumnType("integer") + .HasColumnName("warnexpireaction"); + + b.Property("WarnExpireHours") + .HasColumnType("integer") + .HasColumnName("warnexpirehours"); + + b.Property("WarningsInitialized") + .HasColumnType("boolean") + .HasColumnName("warningsinitialized"); + + b.HasKey("Id") + .HasName("pk_guildconfigs"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_guildconfigs_guildid"); + + b.HasIndex("WarnExpireHours") + .HasDatabaseName("ix_guildconfigs_warnexpirehours"); + + b.ToTable("guildconfigs", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("ItemType") + .HasColumnType("integer") + .HasColumnName("itemtype"); + + b.Property("LogItemId") + .HasColumnType("numeric(20,0)") + .HasColumnName("logitemid"); + + b.Property("LogSettingId") + .HasColumnType("integer") + .HasColumnName("logsettingid"); + + b.HasKey("Id") + .HasName("pk_ignoredlogchannels"); + + b.HasIndex("LogSettingId", "LogItemId", "ItemType") + .IsUnique() + .HasDatabaseName("ix_ignoredlogchannels_logsettingid_logitemid_itemtype"); + + b.ToTable("ignoredlogchannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("LogSettingId") + .HasColumnType("integer") + .HasColumnName("logsettingid"); + + b.HasKey("Id") + .HasName("pk_ignoredvoicepresencechannels"); + + b.HasIndex("LogSettingId") + .HasDatabaseName("ix_ignoredvoicepresencechannels_logsettingid"); + + b.ToTable("ignoredvoicepresencechannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.HasKey("Id") + .HasName("pk_imageonlychannels"); + + b.HasIndex("ChannelId") + .IsUnique() + .HasDatabaseName("ix_imageonlychannels_channelid"); + + b.ToTable("imageonlychannels", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelCreatedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelcreatedid"); + + b.Property("ChannelDestroyedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channeldestroyedid"); + + b.Property("ChannelUpdatedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelupdatedid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("LogOtherId") + .HasColumnType("numeric(20,0)") + .HasColumnName("logotherid"); + + b.Property("LogUserPresenceId") + .HasColumnType("numeric(20,0)") + .HasColumnName("loguserpresenceid"); + + b.Property("LogVoicePresenceId") + .HasColumnType("numeric(20,0)") + .HasColumnName("logvoicepresenceid"); + + b.Property("LogVoicePresenceTTSId") + .HasColumnType("numeric(20,0)") + .HasColumnName("logvoicepresencettsid"); + + b.Property("MessageDeletedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messagedeletedid"); + + b.Property("MessageUpdatedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messageupdatedid"); + + b.Property("UserBannedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userbannedid"); + + b.Property("UserJoinedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userjoinedid"); + + b.Property("UserLeftId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userleftid"); + + b.Property("UserMutedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("usermutedid"); + + b.Property("UserUnbannedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userunbannedid"); + + b.Property("UserUpdatedId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userupdatedid"); + + b.HasKey("Id") + .HasName("pk_logsettings"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_logsettings_guildid"); + + b.ToTable("logsettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDisconnect") + .HasColumnType("boolean") + .HasColumnName("autodisconnect"); + + b.Property("AutoPlay") + .HasColumnType("boolean") + .HasColumnName("autoplay"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("MusicChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("musicchannelid"); + + b.Property("PlayerRepeat") + .HasColumnType("integer") + .HasColumnName("playerrepeat"); + + b.Property("QualityPreset") + .HasColumnType("integer") + .HasColumnName("qualitypreset"); + + b.Property("Volume") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(100) + .HasColumnName("volume"); + + b.HasKey("Id") + .HasName("pk_musicplayersettings"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_musicplayersettings_guildid"); + + b.ToTable("musicplayersettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("AuthorId") + .HasColumnType("numeric(20,0)") + .HasColumnName("authorid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_musicplaylists"); + + b.ToTable("musicplaylists", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_muteduserid"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_muteduserid_guildconfigid"); + + b.ToTable("muteduserid", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowTarget") + .HasColumnType("boolean") + .HasColumnName("allowtarget"); + + b.Property("AutoDeleteTrigger") + .HasColumnType("boolean") + .HasColumnName("autodeletetrigger"); + + b.Property("ContainsAnywhere") + .HasColumnType("boolean") + .HasColumnName("containsanywhere"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("DmResponse") + .HasColumnType("boolean") + .HasColumnName("dmresponse"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Reactions") + .HasColumnType("text") + .HasColumnName("reactions"); + + b.Property("Response") + .HasColumnType("text") + .HasColumnName("response"); + + b.Property("Trigger") + .HasColumnType("text") + .HasColumnName("trigger"); + + b.HasKey("Id") + .HasName("pk_expressions"); + + b.ToTable("expressions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Tag") + .HasColumnType("text") + .HasColumnName("tag"); + + b.HasKey("Id") + .HasName("pk_nsfwblacklistedtags"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_nsfwblacklistedtags_guildid"); + + b.ToTable("nsfwblacklistedtags", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Index") + .HasColumnType("integer") + .HasColumnName("index"); + + b.Property("IsCustomCommand") + .HasColumnType("boolean") + .HasColumnName("iscustomcommand"); + + b.Property("PrimaryTarget") + .HasColumnType("integer") + .HasColumnName("primarytarget"); + + b.Property("PrimaryTargetId") + .HasColumnType("numeric(20,0)") + .HasColumnName("primarytargetid"); + + b.Property("SecondaryTarget") + .HasColumnType("integer") + .HasColumnName("secondarytarget"); + + b.Property("SecondaryTargetName") + .HasColumnType("text") + .HasColumnName("secondarytargetname"); + + b.Property("State") + .HasColumnType("boolean") + .HasColumnName("state"); + + b.HasKey("Id") + .HasName("pk_permissions"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_permissions_guildconfigid"); + + b.ToTable("permissions", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint") + .HasColumnName("amount"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messageid"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_plantedcurrency"); + + b.HasIndex("ChannelId") + .HasDatabaseName("ix_plantedcurrency_channelid"); + + b.HasIndex("MessageId") + .IsUnique() + .HasDatabaseName("ix_plantedcurrency_messageid"); + + b.ToTable("plantedcurrency", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("MusicPlaylistId") + .HasColumnType("integer") + .HasColumnName("musicplaylistid"); + + b.Property("Provider") + .HasColumnType("text") + .HasColumnName("provider"); + + b.Property("ProviderType") + .HasColumnType("integer") + .HasColumnName("providertype"); + + b.Property("Query") + .HasColumnType("text") + .HasColumnName("query"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("Uri") + .HasColumnType("text") + .HasColumnName("uri"); + + b.HasKey("Id") + .HasName("pk_playlistsong"); + + b.HasIndex("MusicPlaylistId") + .HasDatabaseName("ix_playlistsong_musicplaylistid"); + + b.ToTable("playlistsong", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Question") + .HasColumnType("text") + .HasColumnName("question"); + + b.HasKey("Id") + .HasName("pk_poll"); + + b.HasIndex("GuildId") + .IsUnique() + .HasDatabaseName("ix_poll_guildid"); + + b.ToTable("poll", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Index") + .HasColumnType("integer") + .HasColumnName("index"); + + b.Property("PollId") + .HasColumnType("integer") + .HasColumnName("pollid"); + + b.Property("Text") + .HasColumnType("text") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_pollanswer"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_pollanswer_pollid"); + + b.ToTable("pollanswer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("PollId") + .HasColumnType("integer") + .HasColumnName("pollid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("VoteIndex") + .HasColumnType("integer") + .HasColumnName("voteindex"); + + b.HasKey("Id") + .HasName("pk_pollvote"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_pollvote_pollid"); + + b.ToTable("pollvote", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorId") + .HasColumnType("numeric(20,0)") + .HasColumnName("authorid"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("authorname"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Keyword") + .IsRequired() + .HasColumnType("text") + .HasColumnName("keyword"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_quotes"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_quotes_guildid"); + + b.HasIndex("Keyword") + .HasDatabaseName("ix_quotes_keyword"); + + b.ToTable("quotes", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Emote") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("emote"); + + b.Property("Group") + .HasColumnType("integer") + .HasColumnName("group"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("LevelReq") + .HasColumnType("integer") + .HasColumnName("levelreq"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messageid"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_reactionroles"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_reactionroles_guildid"); + + b.HasIndex("MessageId", "Emote") + .IsUnique() + .HasDatabaseName("ix_reactionroles_messageid_emote"); + + b.ToTable("reactionroles", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("IsPrivate") + .HasColumnType("boolean") + .HasColumnName("isprivate"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("ServerId") + .HasColumnType("numeric(20,0)") + .HasColumnName("serverid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("When") + .HasColumnType("timestamp with time zone") + .HasColumnName("when"); + + b.HasKey("Id") + .HasName("pk_reminders"); + + b.HasIndex("When") + .HasDatabaseName("ix_reminders_when"); + + b.ToTable("reminders", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Interval") + .HasColumnType("interval") + .HasColumnName("interval"); + + b.Property("LastMessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("lastmessageid"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("NoRedundant") + .HasColumnType("boolean") + .HasColumnName("noredundant"); + + b.Property("StartTimeOfDay") + .HasColumnType("interval") + .HasColumnName("starttimeofday"); + + b.HasKey("Id") + .HasName("pk_repeaters"); + + b.ToTable("repeaters", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AmountRewardedThisMonth") + .HasColumnType("bigint") + .HasColumnName("amountrewardedthismonth"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("LastReward") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastreward"); + + b.Property("PlatformUserId") + .HasColumnType("text") + .HasColumnName("platformuserid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_rewardedusers"); + + b.HasIndex("PlatformUserId") + .IsUnique() + .HasDatabaseName("ix_rewardedusers_platformuserid"); + + b.ToTable("rewardedusers", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Status") + .HasColumnType("text") + .HasColumnName("status"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_rotatingstatus"); + + b.ToTable("rotatingstatus", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Group") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("group"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("LevelRequirement") + .HasColumnType("integer") + .HasColumnName("levelrequirement"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_selfassignableroles"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique() + .HasDatabaseName("ix_selfassignableroles_guildid_roleid"); + + b.ToTable("selfassignableroles", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorId") + .HasColumnType("numeric(20,0)") + .HasColumnName("authorid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Index") + .HasColumnType("integer") + .HasColumnName("index"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("integer") + .HasColumnName("price"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.Property("RoleName") + .HasColumnType("text") + .HasColumnName("rolename"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_shopentry"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_shopentry_guildconfigid"); + + b.ToTable("shopentry", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("ShopEntryId") + .HasColumnType("integer") + .HasColumnName("shopentryid"); + + b.Property("Text") + .HasColumnType("text") + .HasColumnName("text"); + + b.HasKey("Id") + .HasName("pk_shopentryitem"); + + b.HasIndex("ShopEntryId") + .HasDatabaseName("ix_shopentryitem_shopentryid"); + + b.ToTable("shopentryitem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.HasKey("Id") + .HasName("pk_slowmodeignoredrole"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_slowmodeignoredrole_guildconfigid"); + + b.ToTable("slowmodeignoredrole", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_slowmodeignoreduser"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_slowmodeignoreduser_guildconfigid"); + + b.ToTable("slowmodeignoreduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("integer") + .HasColumnName("streamrolesettingsid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_streamroleblacklisteduser"); + + b.HasIndex("StreamRoleSettingsId") + .HasDatabaseName("ix_streamroleblacklisteduser_streamrolesettingsid"); + + b.ToTable("streamroleblacklisteduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AddRoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("addroleid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasColumnName("enabled"); + + b.Property("FromRoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("fromroleid"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Keyword") + .HasColumnType("text") + .HasColumnName("keyword"); + + b.HasKey("Id") + .HasName("pk_streamrolesettings"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_streamrolesettings_guildconfigid"); + + b.ToTable("streamrolesettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("integer") + .HasColumnName("streamrolesettingsid"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_streamrolewhitelisteduser"); + + b.HasIndex("StreamRoleSettingsId") + .HasDatabaseName("ix_streamrolewhitelisteduser_streamrolesettingsid"); + + b.ToTable("streamrolewhitelisteduser", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("UnbanAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("unbanat"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unbantimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unbantimer_guildconfigid"); + + b.ToTable("unbantimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("UnmuteAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("unmuteat"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unmutetimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unmutetimer_guildconfigid"); + + b.ToTable("unmutetimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.Property("UnbanAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("unbanat"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_unroletimer"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_unroletimer_guildconfigid"); + + b.ToTable("unroletimer", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AwardedXp") + .HasColumnType("bigint") + .HasColumnName("awardedxp"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("lastlevelup") + .HasDefaultValueSql("timezone('utc', now())"); + + b.Property("NotifyOnLevelUp") + .HasColumnType("integer") + .HasColumnName("notifyonlevelup"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("Xp") + .HasColumnType("bigint") + .HasColumnName("xp"); + + b.HasKey("Id") + .HasName("pk_userxpstats"); + + b.HasIndex("AwardedXp") + .HasDatabaseName("ix_userxpstats_awardedxp"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_userxpstats_guildid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_userxpstats_userid"); + + b.HasIndex("Xp") + .HasDatabaseName("ix_userxpstats_xp"); + + b.HasIndex("UserId", "GuildId") + .IsUnique() + .HasDatabaseName("ix_userxpstats_userid_guildid"); + + b.ToTable("userxpstats", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.Property("VoiceChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("voicechannelid"); + + b.HasKey("Id") + .HasName("pk_vcroleinfo"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_vcroleinfo_guildconfigid"); + + b.ToTable("vcroleinfo", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AffinityId") + .HasColumnType("integer") + .HasColumnName("affinityid"); + + b.Property("ClaimerId") + .HasColumnType("integer") + .HasColumnName("claimerid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Price") + .HasColumnType("bigint") + .HasColumnName("price"); + + b.Property("WaifuId") + .HasColumnType("integer") + .HasColumnName("waifuid"); + + b.HasKey("Id") + .HasName("pk_waifuinfo"); + + b.HasIndex("AffinityId") + .HasDatabaseName("ix_waifuinfo_affinityid"); + + b.HasIndex("ClaimerId") + .HasDatabaseName("ix_waifuinfo_claimerid"); + + b.HasIndex("Price") + .HasDatabaseName("ix_waifuinfo_price"); + + b.HasIndex("WaifuId") + .IsUnique() + .HasDatabaseName("ix_waifuinfo_waifuid"); + + b.ToTable("waifuinfo", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("ItemEmoji") + .HasColumnType("text") + .HasColumnName("itememoji"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WaifuInfoId") + .HasColumnType("integer") + .HasColumnName("waifuinfoid"); + + b.HasKey("Id") + .HasName("pk_waifuitem"); + + b.HasIndex("WaifuInfoId") + .HasDatabaseName("ix_waifuitem_waifuinfoid"); + + b.ToTable("waifuitem", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("NewId") + .HasColumnType("integer") + .HasColumnName("newid"); + + b.Property("OldId") + .HasColumnType("integer") + .HasColumnName("oldid"); + + b.Property("UpdateType") + .HasColumnType("integer") + .HasColumnName("updatetype"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("userid"); + + b.HasKey("Id") + .HasName("pk_waifuupdates"); + + b.HasIndex("NewId") + .HasDatabaseName("ix_waifuupdates_newid"); + + b.HasIndex("OldId") + .HasDatabaseName("ix_waifuupdates_oldid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_waifuupdates_userid"); + + b.ToTable("waifuupdates", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Forgiven") + .HasColumnType("boolean") + .HasColumnName("forgiven"); + + b.Property("ForgivenBy") + .HasColumnType("text") + .HasColumnName("forgivenby"); + + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property("Moderator") + .HasColumnType("text") + .HasColumnName("moderator"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("userid"); + + b.Property("Weight") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L) + .HasColumnName("weight"); + + b.HasKey("Id") + .HasName("pk_warnings"); + + b.HasIndex("DateAdded") + .HasDatabaseName("ix_warnings_dateadded"); + + b.HasIndex("GuildId") + .HasDatabaseName("ix_warnings_guildid"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_warnings_userid"); + + b.ToTable("warnings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("integer") + .HasColumnName("count"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("Punishment") + .HasColumnType("integer") + .HasColumnName("punishment"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.Property("Time") + .HasColumnType("integer") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("pk_warningpunishment"); + + b.HasIndex("GuildConfigId") + .HasDatabaseName("ix_warningpunishment_guildconfigid"); + + b.ToTable("warningpunishment", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("integer") + .HasColumnName("amount"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Level") + .HasColumnType("integer") + .HasColumnName("level"); + + b.Property("XpSettingsId") + .HasColumnType("integer") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_xpcurrencyreward"); + + b.HasIndex("XpSettingsId") + .HasDatabaseName("ix_xpcurrencyreward_xpsettingsid"); + + b.ToTable("xpcurrencyreward", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("Level") + .HasColumnType("integer") + .HasColumnName("level"); + + b.Property("Remove") + .HasColumnType("boolean") + .HasColumnName("remove"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)") + .HasColumnName("roleid"); + + b.Property("XpSettingsId") + .HasColumnType("integer") + .HasColumnName("xpsettingsid"); + + b.HasKey("Id") + .HasName("pk_xprolereward"); + + b.HasIndex("XpSettingsId", "Level") + .IsUnique() + .HasDatabaseName("ix_xprolereward_xpsettingsid_level"); + + b.ToTable("xprolereward", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("GuildConfigId") + .HasColumnType("integer") + .HasColumnName("guildconfigid"); + + b.Property("ServerExcluded") + .HasColumnType("boolean") + .HasColumnName("serverexcluded"); + + b.HasKey("Id") + .HasName("pk_xpsettings"); + + b.HasIndex("GuildConfigId") + .IsUnique() + .HasDatabaseName("ix_xpsettings_guildconfigid"); + + b.ToTable("xpsettings", (string)null); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubapplicants_clubs_clubid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubapplicants_discorduser_userid"); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubbans_clubs_clubid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_clubbans_discorduser_userid"); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Db.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_clubs_discorduser_ownerid"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Members") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.NoAction) + .HasConstraintName("fk_discorduser_clubs_clubid"); + + b.Navigation("Club"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_followedstream_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithOne("AntiAltSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiAltSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antialtsetting_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antiraidsetting_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting", null) + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId") + .HasConstraintName("fk_antispamignore_antispamsetting_antispamsettingid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_antispamsetting_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel") + .WithMany("Users") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_autotranslateusers_autotranslatechannels_channelid"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_commandalias_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_commandcooldown_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("DelMsgOnCmdChannels") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_delmsgoncmdchannel_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", null) + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId") + .HasConstraintName("fk_excludeditem_xpsettings_xpsettingsid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_feedsub_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterchannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filteredword_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterLinksChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterlinkschannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_filterwordschannelid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_gcchannelid_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("SelfAssignableRoleGroupNames") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_groupname_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("LogIgnores") + .HasForeignKey("LogSettingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_ignoredlogchannels_logsettings_logsettingid"); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId") + .HasConstraintName("fk_ignoredvoicepresencechannels_logsettings_logsettingid"); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_muteduserid_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("Permissions") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_permissions_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist", null) + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_playlistsong_musicplaylists_musicplaylistid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Answers") + .HasForeignKey("PollId") + .HasConstraintName("fk_pollanswer_poll_pollid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Votes") + .HasForeignKey("PollId") + .HasConstraintName("fk_pollvote_poll_pollid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_shopentry_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry", null) + .WithMany("Items") + .HasForeignKey("ShopEntryId") + .HasConstraintName("fk_shopentryitem_shopentry_shopentryid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_slowmodeignoredrole_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_slowmodeignoreduser_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId") + .HasConstraintName("fk_streamroleblacklisteduser_streamrolesettings_streamrolesett~"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_streamrolesettings_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId") + .HasConstraintName("fk_streamrolewhitelisteduser_streamrolesettings_streamrolesett~"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnbanTimer") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unbantimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unmutetimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnroleTimer") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_unroletimer_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_vcroleinfo_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId") + .HasConstraintName("fk_waifuinfo_discorduser_affinityid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId") + .HasConstraintName("fk_waifuinfo_discorduser_claimerid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_waifuinfo_discorduser_waifuid"); + + b.Navigation("Affinity"); + + b.Navigation("Claimer"); + + b.Navigation("Waifu"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId") + .HasConstraintName("fk_waifuitem_waifuinfo_waifuinfoid"); + + b.Navigation("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId") + .HasConstraintName("fk_waifuupdates_discorduser_newid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId") + .HasConstraintName("fk_waifuupdates_discorduser_oldid"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_waifuupdates_discorduser_userid"); + + b.Navigation("New"); + + b.Navigation("Old"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId") + .HasConstraintName("fk_warningpunishment_guildconfigs_guildconfigid"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("CurrencyRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xpcurrencyreward_xpsettings_xpsettingsid"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xprolereward_xpsettings_xpsettingsid"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xpsettings_guildconfigs_guildconfigid"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Navigation("Applicants"); + + b.Navigation("Bans"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Navigation("IgnoredChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Navigation("AntiAltSetting"); + + b.Navigation("AntiRaidSetting"); + + b.Navigation("AntiSpamSetting"); + + b.Navigation("CommandAliases"); + + b.Navigation("CommandCooldowns"); + + b.Navigation("DelMsgOnCmdChannels"); + + b.Navigation("FeedSubs"); + + b.Navigation("FilterInvitesChannelIds"); + + b.Navigation("FilterLinksChannelIds"); + + b.Navigation("FilterWordsChannelIds"); + + b.Navigation("FilteredWords"); + + b.Navigation("FollowedStreams"); + + b.Navigation("GenerateCurrencyChannelIds"); + + b.Navigation("MutedUsers"); + + b.Navigation("Permissions"); + + b.Navigation("SelfAssignableRoleGroupNames"); + + b.Navigation("ShopEntries"); + + b.Navigation("SlowmodeIgnoredRoles"); + + b.Navigation("SlowmodeIgnoredUsers"); + + b.Navigation("StreamRole"); + + b.Navigation("UnbanTimer"); + + b.Navigation("UnmuteTimers"); + + b.Navigation("UnroleTimer"); + + b.Navigation("VcRoleInfos"); + + b.Navigation("WarnPunishments"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Navigation("LogIgnores"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Navigation("Songs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Navigation("Answers"); + + b.Navigation("Votes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Navigation("Blacklist"); + + b.Navigation("Whitelist"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Navigation("CurrencyRewards"); + + b.Navigation("ExclusionList"); + + b.Navigation("RoleRewards"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.cs b/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.cs new file mode 100644 index 000000000..85a3ea497 --- /dev/null +++ b/src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NadekoBot.Migrations.PostgreSql +{ + public partial class stondeldbcache : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "streamonlinemessages", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + channelid = table.Column(type: "numeric(20,0)", nullable: false), + messageid = table.Column(type: "numeric(20,0)", nullable: false), + type = table.Column(type: "integer", nullable: false), + name = table.Column(type: "text", nullable: true), + dateadded = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_streamonlinemessages", x => x.id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "streamonlinemessages"); + } + } +} diff --git a/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs b/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs index fc3eb8bac..732f9f6e2 100644 --- a/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Migrations.PostgreSql { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "6.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -343,6 +343,41 @@ namespace NadekoBot.Migrations.PostgreSql b.ToTable("patrons", (string)null); }); + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone") + .HasColumnName("dateadded"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("messageid"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_streamonlinemessages"); + + b.ToTable("streamonlinemessages", (string)null); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => { b.Property("Id") diff --git a/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.Designer.cs b/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.Designer.cs new file mode 100644 index 000000000..41161c8f5 --- /dev/null +++ b/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.Designer.cs @@ -0,0 +1,2823 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NadekoBot.Services.Database; + +#nullable disable + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(SqliteContext))] + [Migration("20220623073903_stondel-db-cache")] + partial class stondeldbcache + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); + + modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("BankUsers"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("ImageUrl") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .HasColumnType("INTEGER"); + + b.Property("Xp") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvatarId") + .HasColumnType("TEXT"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CurrencyAmount") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0L); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .HasColumnType("TEXT"); + + b.Property("IsClubAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(false); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("datetime('now')"); + + b.Property("LastXpGain") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("datetime('now', '-1 years')"); + + b.Property("NotifyOnLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("TotalXp") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0L); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.HasIndex("CurrencyAmount"); + + b.HasIndex("TotalXp"); + + b.HasIndex("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("FeatureType") + .HasColumnType("INTEGER"); + + b.Property("Feature") + .HasColumnType("TEXT"); + + b.Property("DailyCount") + .HasColumnType("INTEGER"); + + b.Property("HourlyCount") + .HasColumnType("INTEGER"); + + b.Property("MonthlyCount") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "FeatureType", "Feature"); + + b.HasIndex("UserId"); + + b.ToTable("PatronQuotas"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AmountCents") + .HasColumnType("INTEGER"); + + b.Property("LastCharge") + .HasColumnType("TEXT"); + + b.Property("UniquePlatformUserId") + .HasColumnType("TEXT"); + + b.Property("ValidThru") + .HasColumnType("TEXT"); + + b.HasKey("UserId"); + + b.HasIndex("UniquePlatformUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("StreamOnlineMessages"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("ActionDurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("MinAge") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiAltSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("PunishDuration") + .HasColumnType("INTEGER"); + + b.Property("Seconds") + .HasColumnType("INTEGER"); + + b.Property("UserThreshold") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AntiSpamSettingId") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("MessageThreshold") + .HasColumnType("INTEGER"); + + b.Property("MuteTime") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ChannelName") + .HasColumnType("TEXT"); + + b.Property("CommandText") + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("GuildName") + .HasColumnType("TEXT"); + + b.Property("Interval") + .HasColumnType("INTEGER"); + + b.Property("VoiceChannelId") + .HasColumnType("INTEGER"); + + b.Property("VoiceChannelName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AutoCommands"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.HasIndex("GuildId"); + + b.ToTable("AutoTranslateChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("TEXT"); + + b.Property("Target") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasAlternateKey("ChannelId", "UserId"); + + b.ToTable("AutoTranslateUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("BanTemplates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Blacklist"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Mapping") + .HasColumnType("TEXT"); + + b.Property("Trigger") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CommandName") + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Seconds") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OtherId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("NULL"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("DelMsgOnCmdChannel"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Command") + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Perm") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "Command") + .IsUnique(); + + b.ToTable("DiscordPermOverrides"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("ItemType") + .HasColumnType("INTEGER"); + + b.Property("XpSettingsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildConfigId", "Url"); + + b.ToTable("FeedSub"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Word") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilterLinksChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilterWordsChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId", "Number") + .IsUnique(); + + b.ToTable("GroupName"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoAssignRoleIds") + .HasColumnType("TEXT"); + + b.Property("AutoDeleteByeMessagesTimer") + .HasColumnType("INTEGER"); + + b.Property("AutoDeleteGreetMessagesTimer") + .HasColumnType("INTEGER"); + + b.Property("AutoDeleteSelfAssignedRoleMessages") + .HasColumnType("INTEGER"); + + b.Property("BoostMessage") + .HasColumnType("TEXT"); + + b.Property("BoostMessageChannelId") + .HasColumnType("INTEGER"); + + b.Property("BoostMessageDeleteAfter") + .HasColumnType("INTEGER"); + + b.Property("ByeMessageChannelId") + .HasColumnType("INTEGER"); + + b.Property("ChannelByeMessageText") + .HasColumnType("TEXT"); + + b.Property("ChannelGreetMessageText") + .HasColumnType("TEXT"); + + b.Property("CleverbotEnabled") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DeleteMessageOnCommand") + .HasColumnType("INTEGER"); + + b.Property("DeleteStreamOnlineMessage") + .HasColumnType("INTEGER"); + + b.Property("DmGreetMessageText") + .HasColumnType("TEXT"); + + b.Property("ExclusiveSelfAssignedRoles") + .HasColumnType("INTEGER"); + + b.Property("FilterInvites") + .HasColumnType("INTEGER"); + + b.Property("FilterLinks") + .HasColumnType("INTEGER"); + + b.Property("FilterWords") + .HasColumnType("INTEGER"); + + b.Property("GameVoiceChannel") + .HasColumnType("INTEGER"); + + b.Property("GreetMessageChannelId") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasColumnType("TEXT"); + + b.Property("MuteRoleName") + .HasColumnType("TEXT"); + + b.Property("NotifyStreamOffline") + .HasColumnType("INTEGER"); + + b.Property("PermissionRole") + .HasColumnType("TEXT"); + + b.Property("Prefix") + .HasColumnType("TEXT"); + + b.Property("SendBoostMessage") + .HasColumnType("INTEGER"); + + b.Property("SendChannelByeMessage") + .HasColumnType("INTEGER"); + + b.Property("SendChannelGreetMessage") + .HasColumnType("INTEGER"); + + b.Property("SendDmGreetMessage") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneId") + .HasColumnType("TEXT"); + + b.Property("VerboseErrors") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("VerbosePermissions") + .HasColumnType("INTEGER"); + + b.Property("WarnExpireAction") + .HasColumnType("INTEGER"); + + b.Property("WarnExpireHours") + .HasColumnType("INTEGER"); + + b.Property("WarningsInitialized") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("WarnExpireHours"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("ItemType") + .HasColumnType("INTEGER"); + + b.Property("LogItemId") + .HasColumnType("INTEGER"); + + b.Property("LogSettingId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId", "LogItemId", "ItemType") + .IsUnique(); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("LogSettingId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("ImageOnlyChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelCreatedId") + .HasColumnType("INTEGER"); + + b.Property("ChannelDestroyedId") + .HasColumnType("INTEGER"); + + b.Property("ChannelUpdatedId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("LogOtherId") + .HasColumnType("INTEGER"); + + b.Property("LogUserPresenceId") + .HasColumnType("INTEGER"); + + b.Property("LogVoicePresenceId") + .HasColumnType("INTEGER"); + + b.Property("LogVoicePresenceTTSId") + .HasColumnType("INTEGER"); + + b.Property("MessageDeletedId") + .HasColumnType("INTEGER"); + + b.Property("MessageUpdatedId") + .HasColumnType("INTEGER"); + + b.Property("UserBannedId") + .HasColumnType("INTEGER"); + + b.Property("UserJoinedId") + .HasColumnType("INTEGER"); + + b.Property("UserLeftId") + .HasColumnType("INTEGER"); + + b.Property("UserMutedId") + .HasColumnType("INTEGER"); + + b.Property("UserUnbannedId") + .HasColumnType("INTEGER"); + + b.Property("UserUpdatedId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoDisconnect") + .HasColumnType("INTEGER"); + + b.Property("AutoPlay") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("MusicChannelId") + .HasColumnType("INTEGER"); + + b.Property("PlayerRepeat") + .HasColumnType("INTEGER"); + + b.Property("QualityPreset") + .HasColumnType("INTEGER"); + + b.Property("Volume") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(100); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("MusicPlayerSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .HasColumnType("TEXT"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowTarget") + .HasColumnType("INTEGER"); + + b.Property("AutoDeleteTrigger") + .HasColumnType("INTEGER"); + + b.Property("ContainsAnywhere") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DmResponse") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Reactions") + .HasColumnType("TEXT"); + + b.Property("Response") + .HasColumnType("TEXT"); + + b.Property("Trigger") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Expressions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Tag") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("NsfwBlacklistedTags"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("IsCustomCommand") + .HasColumnType("INTEGER"); + + b.Property("PrimaryTarget") + .HasColumnType("INTEGER"); + + b.Property("PrimaryTargetId") + .HasColumnType("INTEGER"); + + b.Property("SecondaryTarget") + .HasColumnType("INTEGER"); + + b.Property("SecondaryTargetName") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("MessageId") + .IsUnique(); + + b.ToTable("PlantedCurrency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("MusicPlaylistId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("TEXT"); + + b.Property("ProviderType") + .HasColumnType("INTEGER"); + + b.Property("Query") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Question") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("Poll"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("PollId") + .HasColumnType("INTEGER"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PollId"); + + b.ToTable("PollAnswer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("PollId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("VoteIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PollId"); + + b.ToTable("PollVote"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Keyword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.HasIndex("Keyword"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Emote") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Group") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("LevelReq") + .HasColumnType("INTEGER"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.HasIndex("MessageId", "Emote") + .IsUnique(); + + b.ToTable("ReactionRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("When"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Interval") + .HasColumnType("TEXT"); + + b.Property("LastMessageId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NoRedundant") + .HasColumnType("INTEGER"); + + b.Property("StartTimeOfDay") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AmountRewardedThisMonth") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("LastReward") + .HasColumnType("TEXT"); + + b.Property("PlatformUserId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlatformUserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RotatingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Group") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("LevelRequirement") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.Property("RoleName") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("ShopEntryId") + .HasColumnType("INTEGER"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddRoleId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("FromRoleId") + .HasColumnType("INTEGER"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Keyword") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("StreamRoleSettingsId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("UnbanAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnbanTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("UnmuteAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.Property("UnbanAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnroleTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AwardedXp") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("datetime('now')"); + + b.Property("NotifyOnLevelUp") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Xp") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AwardedXp"); + + b.HasIndex("GuildId"); + + b.HasIndex("UserId"); + + b.HasIndex("Xp"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.Property("VoiceChannelId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AffinityId") + .HasColumnType("INTEGER"); + + b.Property("ClaimerId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("WaifuId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("Price"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("ItemEmoji") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("WaifuInfoId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("NewId") + .HasColumnType("INTEGER"); + + b.Property("OldId") + .HasColumnType("INTEGER"); + + b.Property("UpdateType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Forgiven") + .HasColumnType("INTEGER"); + + b.Property("ForgivenBy") + .HasColumnType("TEXT"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("Moderator") + .HasColumnType("TEXT"); + + b.Property("Reason") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DateAdded"); + + b.HasIndex("GuildId"); + + b.HasIndex("UserId"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("Punishment") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("XpSettingsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpCurrencyReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Remove") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.Property("XpSettingsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId", "Level") + .IsUnique(); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("GuildConfigId") + .HasColumnType("INTEGER"); + + b.Property("ServerExcluded") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Club"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Db.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b => + { + b.HasOne("NadekoBot.Db.Models.ClubInfo", "Club") + .WithMany("Members") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Club"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithOne("AntiAltSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiAltSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting", null) + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel") + .WithMany("Users") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("DelMsgOnCmdChannels") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", null) + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("FeedSubs") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterLinksChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithMany("SelfAssignableRoleGroupNames") + .HasForeignKey("GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("LogIgnores") + .HasForeignKey("LogSettingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.Navigation("LogSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist", null) + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Answers") + .HasForeignKey("PollId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Poll", null) + .WithMany("Votes") + .HasForeignKey("PollId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry", null) + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings", null) + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnbanTimer") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("UnroleTimer") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Affinity"); + + b.Navigation("Claimer"); + + b.Navigation("Waifu"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + + b.Navigation("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Db.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Db.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("New"); + + b.Navigation("Old"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null) + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("CurrencyRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings", "XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildConfig"); + }); + + modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b => + { + b.Navigation("Applicants"); + + b.Navigation("Bans"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Navigation("IgnoredChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Navigation("AntiAltSetting"); + + b.Navigation("AntiRaidSetting"); + + b.Navigation("AntiSpamSetting"); + + b.Navigation("CommandAliases"); + + b.Navigation("CommandCooldowns"); + + b.Navigation("DelMsgOnCmdChannels"); + + b.Navigation("FeedSubs"); + + b.Navigation("FilterInvitesChannelIds"); + + b.Navigation("FilterLinksChannelIds"); + + b.Navigation("FilterWordsChannelIds"); + + b.Navigation("FilteredWords"); + + b.Navigation("FollowedStreams"); + + b.Navigation("GenerateCurrencyChannelIds"); + + b.Navigation("MutedUsers"); + + b.Navigation("Permissions"); + + b.Navigation("SelfAssignableRoleGroupNames"); + + b.Navigation("ShopEntries"); + + b.Navigation("SlowmodeIgnoredRoles"); + + b.Navigation("SlowmodeIgnoredUsers"); + + b.Navigation("StreamRole"); + + b.Navigation("UnbanTimer"); + + b.Navigation("UnmuteTimers"); + + b.Navigation("UnroleTimer"); + + b.Navigation("VcRoleInfos"); + + b.Navigation("WarnPunishments"); + + b.Navigation("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Navigation("LogIgnores"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Navigation("Songs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b => + { + b.Navigation("Answers"); + + b.Navigation("Votes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Navigation("Blacklist"); + + b.Navigation("Whitelist"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Navigation("CurrencyRewards"); + + b.Navigation("ExclusionList"); + + b.Navigation("RoleRewards"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.cs b/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.cs new file mode 100644 index 000000000..10f477e02 --- /dev/null +++ b/src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NadekoBot.Migrations +{ + public partial class stondeldbcache : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StreamOnlineMessages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ChannelId = table.Column(type: "INTEGER", nullable: false), + MessageId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + DateAdded = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StreamOnlineMessages", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StreamOnlineMessages"); + } + } +} diff --git a/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs index b0b1ebeed..0dc7e40db 100644 --- a/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => { @@ -262,6 +262,33 @@ namespace NadekoBot.Migrations b.ToTable("Patrons"); }); + modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("StreamOnlineMessages"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b => { b.Property("Id") diff --git a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs index be238ca63..e7ca30dd0 100644 --- a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs @@ -500,14 +500,6 @@ public partial class Administration await ReplyConfirmLocalizedAsync(strs.message_sent); } - [Cmd] - [OwnerOnly] - public async partial Task ImagesReload() - { - await _service.ReloadImagesAsync(); - await ReplyConfirmLocalizedAsync(strs.images_loading); - } - [Cmd] [OwnerOnly] public async partial Task StringsReload() diff --git a/src/NadekoBot/Modules/Administration/Self/SelfService.cs b/src/NadekoBot/Modules/Administration/Self/SelfService.cs index 56ddd88f0..a714ef552 100644 --- a/src/NadekoBot/Modules/Administration/Self/SelfService.cs +++ b/src/NadekoBot/Modules/Administration/Self/SelfService.cs @@ -20,7 +20,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService private ConcurrentDictionary> autoCommands = new(); - private readonly IImageCache _imgs; private readonly IHttpClientFactory _httpFactory; private readonly BotConfigService _bss; private readonly IPubSub _pubSub; @@ -28,7 +27,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService //keys private readonly TypedKey _activitySetKey; - private readonly TypedKey _imagesReloadKey; private readonly TypedKey _guildLeaveKey; public SelfService( @@ -37,7 +35,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService DbService db, IBotStrings strings, IBotCredentials creds, - IDataCache cache, IHttpClientFactory factory, BotConfigService bss, IPubSub pubSub, @@ -48,20 +45,15 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService _strings = strings; _client = client; _creds = creds; - _imgs = cache.LocalImages; _httpFactory = factory; _bss = bss; _pubSub = pubSub; _eb = eb; _activitySetKey = new("activity.set"); - _imagesReloadKey = new("images.reload"); _guildLeaveKey = new("guild.leave"); HandleStatusChanges(); - if (_client.ShardId == 0) - _pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload()); - _pubSub.Sub(_guildLeaveKey, async input => { @@ -325,9 +317,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService uow.SaveChanges(); } - public Task ReloadImagesAsync() - => _pubSub.Pub(_imagesReloadKey, true); - public bool ForwardMessages() { var isForwarding = false; diff --git a/src/NadekoBot/Modules/Gambling/DiceRoll/DiceRollCommands.cs b/src/NadekoBot/Modules/Gambling/DiceRoll/DiceRollCommands.cs index 995326d5b..ab81adda5 100644 --- a/src/NadekoBot/Modules/Gambling/DiceRoll/DiceRollCommands.cs +++ b/src/NadekoBot/Modules/Gambling/DiceRoll/DiceRollCommands.cs @@ -19,8 +19,8 @@ public partial class Gambling private static readonly char[] _fateRolls = { '-', ' ', '+' }; private readonly IImageCache _images; - public DiceRollCommands(IDataCache data) - => _images = data.LocalImages; + public DiceRollCommands(ImageCache images) + => _images = images; [Cmd] public async partial Task Roll() @@ -31,10 +31,10 @@ public partial class Gambling var num1 = gen / 10; var num2 = gen % 10; - using var img1 = GetDice(num1); - using var img2 = GetDice(num2); + using var img1 = await GetDiceAsync(num1); + using var img2 = await GetDiceAsync(num2); using var img = new[] { img1, img2 }.Merge(out var format); - await using var ms = img.ToStream(format); + await using var ms = await img.ToStreamAsync(format); await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}", Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString())))); @@ -96,7 +96,7 @@ public partial class Gambling else toInsert = dice.Count; - dice.Insert(toInsert, GetDice(randomNumber)); + dice.Insert(toInsert, await GetDiceAsync(randomNumber)); values.Insert(toInsert, randomNumber); } @@ -195,20 +195,19 @@ public partial class Gambling await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))); } - private Image GetDice(int num) + private async Task> GetDiceAsync(int num) { if (num is < 0 or > 10) throw new ArgumentOutOfRangeException(nameof(num)); if (num == 10) { - var images = _images.Dice; - using var imgOne = Image.Load(images[1]); - using var imgZero = Image.Load(images[0]); + using var imgOne = Image.Load(await _images.GetDiceAsync(1)); + using var imgZero = Image.Load(await _images.GetDiceAsync(0)); return new[] { imgOne, imgZero }.Merge(); } - return Image.Load(_images.Dice[num]); + return Image.Load(await _images.GetDiceAsync(num)); } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Draw/DrawCommands.cs b/src/NadekoBot/Modules/Gambling/Draw/DrawCommands.cs index d707c96d9..df1ada48d 100644 --- a/src/NadekoBot/Modules/Gambling/Draw/DrawCommands.cs +++ b/src/NadekoBot/Modules/Gambling/Draw/DrawCommands.cs @@ -14,8 +14,8 @@ public partial class Gambling private static readonly ConcurrentDictionary _allDecks = new(); private readonly IImageCache _images; - public DrawCommands(IDataCache data) - => _images = data.LocalImages; + public DrawCommands(IImageCache images) + => _images = images; private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null) { @@ -43,7 +43,8 @@ public partial class Gambling var currentCard = cards.Draw(); cardObjects.Add(currentCard); - images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_')))); + var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_'); + images.Add(Image.Load(await File.ReadAllBytesAsync($"data/images/cards/{cardName}.png"))); } using var img = images.Merge(); diff --git a/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs b/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs index f0ee7a7c7..7059ae46c 100644 --- a/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs +++ b/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs @@ -25,11 +25,17 @@ public partial class Gambling private static readonly NadekoRandom _rng = new(); private readonly IImageCache _images; private readonly ICurrencyService _cs; + private readonly ImagesConfig _ic; - public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) + public FlipCoinCommands( + IImageCache images, + ImagesConfig ic, + ICurrencyService cs, + GamblingConfigService gss) : base(gss) { - _images = data.LocalImages; + _ic = ic; + _images = images; _cs = cs; } @@ -47,8 +53,8 @@ public partial class Gambling var imgs = new Image[count]; for (var i = 0; i < count; i++) { - var headsArr = _images.Heads[_rng.Next(0, _images.Heads.Count)]; - var tailsArr = _images.Tails[_rng.Next(0, _images.Tails.Count)]; + var headsArr = await _images.GetHeadsImageAsync(); + var tailsArr = await _images.GetTailsImageAsync(); if (_rng.Next(0, 10) < 5) { imgs[i] = Image.Load(headsArr); @@ -94,7 +100,7 @@ public partial class Gambling BetFlipGuess result; Uri imageToSend; - var coins = _images.ImageUrls.Coins; + var coins = _ic.Data.Coins; if (_rng.Next(0, 1000) <= 499) { imageToSend = coins.Heads[_rng.Next(0, coins.Heads.Length)]; diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 7e727d8ea..38cb0eca3 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -38,7 +38,6 @@ public partial class Gambling : GamblingModule private readonly DbService _db; private readonly ICurrencyService _cs; - private readonly IDataCache _cache; private readonly DiscordSocketClient _client; private readonly NumberFormatInfo _enUsCulture; private readonly DownloadTracker _tracker; @@ -51,7 +50,6 @@ public partial class Gambling : GamblingModule public Gambling( DbService db, ICurrencyService currency, - IDataCache cache, DiscordSocketClient client, DownloadTracker tracker, GamblingConfigService configService, @@ -61,7 +59,6 @@ public partial class Gambling : GamblingModule { _db = db; _cs = currency; - _cache = cache; _client = client; _bank = bank; _ps = ps; @@ -124,7 +121,7 @@ public partial class Gambling : GamblingModule return; } - if (_cache.AddTimelyClaim(ctx.User.Id, period) is { } rem) + if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } rem) { var now = DateTime.UtcNow; var relativeTag = TimestampTag.FromDateTime(now.Add(rem), TimestampTagStyles.Relative); @@ -145,7 +142,7 @@ public partial class Gambling : GamblingModule [OwnerOnly] public async partial Task TimelyReset() { - _cache.RemoveAllTimelyClaims(); + await _service.RemoveAllTimelyClaimsAsync(); await ReplyConfirmLocalizedAsync(strs.timely_reset); } diff --git a/src/NadekoBot/Modules/Gambling/GamblingService.cs b/src/NadekoBot/Modules/Gambling/GamblingService.cs index f09385bc6..f052a1d85 100644 --- a/src/NadekoBot/Modules/Gambling/GamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/GamblingService.cs @@ -1,16 +1,13 @@ #nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using NadekoBot.Db.Models; -using NadekoBot.Migrations; using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common.Connect4; using NadekoBot.Modules.Gambling.Common.Slot; using NadekoBot.Modules.Gambling.Common.WheelOfFortune; -using Newtonsoft.Json; namespace NadekoBot.Modules.Gambling.Services; @@ -22,7 +19,7 @@ public class GamblingService : INService, IReadyExecutor private readonly ICurrencyService _cs; private readonly Bot _bot; private readonly DiscordSocketClient _client; - private readonly IDataCache _cache; + private readonly IBotCache _cache; private readonly GamblingConfigService _gss; public GamblingService( @@ -30,7 +27,7 @@ public class GamblingService : INService, IReadyExecutor Bot bot, ICurrencyService cs, DiscordSocketClient client, - IDataCache cache, + IBotCache cache, GamblingConfigService gss) { _db = db; @@ -73,6 +70,7 @@ public class GamblingService : INService, IReadyExecutor } } + private static readonly TypedKey _curDecayKey = new("currency:last_decay"); private async Task CurrencyDecayLoopAsync() { if (_bot.Client.ShardId != 0) @@ -88,11 +86,16 @@ public class GamblingService : INService, IReadyExecutor if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) continue; + var now = DateTime.UtcNow; + await using var uow = _db.GetDbContext(); - var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); + var result = await _cache.GetAsync(_curDecayKey); - if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) + if (result.TryPickT0(out var bin, out _) + && (now - DateTime.FromBinary(bin) < TimeSpan.FromHours(config.Decay.HourInterval))) + { continue; + } Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}% | max: {MaxDecay} @@ -115,8 +118,9 @@ public class GamblingService : INService, IReadyExecutor : old.CurrencyAmount - maxDecay }); - _cache.SetLastCurrencyDecay(); await uow.SaveChangesAsync(); + + await _cache.AddAsync(_curDecayKey, now.ToBinary()); } catch (Exception ex) { @@ -161,60 +165,100 @@ public class GamblingService : INService, IReadyExecutor return toReturn; } + private static readonly TypedKey _ecoKey = new("nadeko:economy"); + public async Task GetEconomyAsync() { - if (_cache.TryGetEconomy(out var data)) - { - try + var data = await _cache.GetOrAddAsync(_ecoKey, + async () => { - return JsonConvert.DeserializeObject(data); - } - catch { } - } + await using var uow = _db.GetDbContext(); + var cash = uow.DiscordUser.GetTotalCurrency(); + var onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id); + decimal planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount); + var waifus = uow.WaifuInfo.GetTotalValue(); + var bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id); + decimal bank = await uow.GetTable() + .SumAsyncLinqToDB(x => x.Balance); - decimal cash; - decimal onePercent; - decimal planted; - decimal waifus; - decimal bank; - long bot; + var result = new EconomyResult + { + Cash = cash, + Planted = planted, + Bot = bot, + Waifus = waifus, + OnePercent = onePercent, + Bank = bank + }; - using (var uow = _db.GetDbContext()) - { - cash = uow.DiscordUser.GetTotalCurrency(); - onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id); - planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount); - waifus = uow.WaifuInfo.GetTotalValue(); - bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id); - bank = await uow.GetTable() - .SumAsyncLinqToDB(x => x.Balance); - } + return result; + }, + TimeSpan.FromMinutes(3)); - var result = new EconomyResult - { - Cash = cash, - Planted = planted, - Bot = bot, - Waifus = waifus, - OnePercent = onePercent, - Bank = bank - }; - - _cache.SetEconomy(JsonConvert.SerializeObject(result)); - return result; + return data; } public Task WheelOfFortuneSpinAsync(ulong userId, long bet) => new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync(); - public struct EconomyResult + private static readonly SemaphoreSlim _timelyLock = new (1, 1); + + private static TypedKey> _timelyKey + = new("timely:claims"); + public async Task ClaimTimelyAsync(ulong userId, int period) { - public decimal Cash { get; set; } - public decimal Planted { get; set; } - public decimal Waifus { get; set; } - public decimal OnePercent { get; set; } - public decimal Bank { get; set; } - public long Bot { get; set; } + if (period == 0) + return null; + + await _timelyLock.WaitAsync(); + try + { + // get the dictionary from the cache or get a new one + var dict = (await _cache.GetOrAddAsync(_timelyKey, + () => Task.FromResult(new Dictionary())))!; + + var now = DateTime.UtcNow; + var nowB = now.ToBinary(); + + // try to get users last claim + if (!dict.TryGetValue(userId, out var lastB)) + lastB = dict[userId] = now.ToBinary(); + + var diff = now - DateTime.FromBinary(lastB); + + // if its now, or too long ago => success + if (lastB == nowB || diff > period.Hours()) + { + // update the cache + dict[userId] = nowB; + await _cache.AddAsync(_timelyKey, dict); + + return null; + } + else + { + // otherwise return the remaining time + return period.Hours() - diff; + } + } + finally + { + await _timelyLock.WaitAsync(); + } + } + + public async Task RemoveAllTimelyClaimsAsync() + => await _cache.RemoveAsync(_timelyKey); + + + public readonly struct EconomyResult + { + public decimal Cash { get; init; } + public decimal Planted { get; init; } + public decimal Waifus { get; init; } + public decimal OnePercent { get; init; } + public decimal Bank { get; init; } + public long Bot { get; init; } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/PlantPick/PlantPickService.cs b/src/NadekoBot/Modules/Gambling/PlantPick/PlantPickService.cs index 7036ed7b4..2c4e94fbd 100644 --- a/src/NadekoBot/Modules/Gambling/PlantPick/PlantPickService.cs +++ b/src/NadekoBot/Modules/Gambling/PlantPick/PlantPickService.cs @@ -34,7 +34,7 @@ public class PlantPickService : INService, IExecNoCommand DbService db, CommandHandler cmd, IBotStrings strings, - IDataCache cache, + IImageCache images, FontProvider fonts, ICurrencyService cs, CommandHandler cmdHandler, @@ -43,7 +43,7 @@ public class PlantPickService : INService, IExecNoCommand { _db = db; _strings = strings; - _images = cache.LocalImages; + _images = images; _fonts = fonts; _cs = cs; _cmdHandler = cmdHandler; @@ -110,30 +110,21 @@ public class PlantPickService : INService, IExecNoCommand /// Optional password to add to top left corner. /// Extension of the file, defaults to png /// Stream of the currency image - public Stream GetRandomCurrencyImage(string pass, out string extension) + public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass) { - // get a random currency image bytes - var rng = new NadekoRandom(); - var curImg = _images.Currency[rng.Next(0, _images.Currency.Count)]; + var curImg = await _images.GetCurrencyImageAsync(); if (string.IsNullOrWhiteSpace(pass)) { // determine the extension - using (_ = Image.Load(curImg, out var format)) - { - extension = format.FileExtensions.FirstOrDefault() ?? "png"; - } + using var load = _ = Image.Load(curImg, out var format); // return the image - return curImg.ToStream(); + return (curImg.ToStream(), format.FileExtensions.FirstOrDefault() ?? "png"); } // get the image stream and extension - var (s, ext) = AddPassword(curImg, pass); - // set the out extension parameter to the extension we've got - extension = ext; - // return the image - return s; + return AddPassword(curImg, pass); } /// @@ -214,10 +205,10 @@ public class PlantPickService : INService, IExecNoCommand var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null; IUserMessage sent; - await using (var stream = GetRandomCurrencyImage(pw, out var ext)) - { + var (stream, ext) = await GetRandomCurrencyImageAsync(pw); + + await using (stream) sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend); - } await AddPlantToDatabase(channel.GuildId, channel.Id, @@ -278,7 +269,7 @@ public class PlantPickService : INService, IExecNoCommand if (amount > 0) // give the picked currency to the user await _cs.AddAsync(uid, amount, new("currency", "collect")); - uow.SaveChanges(); + await uow.SaveChangesAsync(); } try @@ -316,11 +307,14 @@ public class PlantPickService : INService, IExecNoCommand msgToSend += " " + GetText(gid, strs.pick_sn(prefix)); //get the image - await using var stream = GetRandomCurrencyImage(pass, out var ext); + var (stream, ext) = await GetRandomCurrencyImageAsync(pass); // send it - var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend); - // return sent message's id (in order to be able to delete it when it's picked) - return msg.Id; + await using (stream) + { + var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend); + // return sent message's id (in order to be able to delete it when it's picked) + return msg.Id; + } } catch { diff --git a/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs b/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs index 54fda72e1..d8898d565 100644 --- a/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs +++ b/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs @@ -32,13 +32,13 @@ public partial class Gambling private readonly DbService _db; public SlotCommands( - IDataCache data, + ImageCache images, FontProvider fonts, DbService db, GamblingConfigService gamb) : base(gamb) { - _images = data.LocalImages; + _images = images; _fonts = fonts; _db = db; } @@ -130,7 +130,8 @@ public partial class Gambling ?? 0; } - using (var bgImage = Image.Load(_images.SlotBackground, out _)) + var slotBg = await _images.GetSlotBgAsync(); + using (var bgImage = Image.Load(slotBg, out _)) { var numbers = new int[3]; result.Rolls.CopyTo(numbers, 0); @@ -184,7 +185,7 @@ public partial class Gambling for (var i = 0; i < 3; i++) { - using var img = Image.Load(_images.SlotEmojis[numbers[i]]); + using var img = Image.Load(await _images.GetSlotEmojiAsync(numbers[i])); bgImage.Mutate(x => x.DrawImage(img, new Point(148 + (105 * i), 217), 1f)); } @@ -201,7 +202,7 @@ public partial class Gambling msg = GetText(strs.slot_jackpot(30)); } - await using (var imgStream = bgImage.ToStream()) + await using (var imgStream = await bgImage.ToStreamAsync()) { await ctx.Channel.SendFileAsync(imgStream, "result.png", diff --git a/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs b/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs index 952633b81..539a01cac 100644 --- a/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs +++ b/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs @@ -1,5 +1,6 @@ #nullable disable using LinqToDB; +using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; @@ -14,7 +15,7 @@ public class WaifuService : INService, IReadyExecutor { private readonly DbService _db; private readonly ICurrencyService _cs; - private readonly IDataCache _cache; + private readonly IBotCache _cache; private readonly GamblingConfigService _gss; private readonly IBotCredentials _creds; private readonly DiscordSocketClient _client; @@ -22,7 +23,7 @@ public class WaifuService : INService, IReadyExecutor public WaifuService( DbService db, ICurrencyService cs, - IDataCache cache, + IBotCache cache, GamblingConfigService gss, IBotCredentials creds, DiscordSocketClient client) @@ -236,8 +237,13 @@ public class WaifuService : INService, IReadyExecutor var newAff = target is null ? null : uow.GetOrCreateUser(target); if (w?.Affinity?.UserId == target?.Id) { + return (null, false, null); } - else if (!_cache.TryAddAffinityCooldown(user.Id, out remaining)) + + remaining = await _cache.GetRatelimitAsync(GetAffinityKey(user.Id), + 30.Minutes()); + + if (remaining is not null) { } else if (w is null) @@ -294,6 +300,12 @@ public class WaifuService : INService, IReadyExecutor return uow.WaifuInfo.GetWaifuUserId(ownerId, name); } + private static TypedKey GetDivorceKey(ulong userId) + => new($"waifu:divorce_cd:{userId}"); + + private static TypedKey GetAffinityKey(ulong userId) + => new($"waifu:affinity:{userId}"); + public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId) { DivorceResult result; @@ -305,10 +317,15 @@ public class WaifuService : INService, IReadyExecutor w = uow.WaifuInfo.ByWaifuUserId(targetId); if (w?.Claimer is null || w.Claimer.UserId != user.Id) result = DivorceResult.NotYourWife; - else if (!_cache.TryAddDivorceCooldown(user.Id, out remaining)) - result = DivorceResult.Cooldown; else { + remaining = await _cache.GetRatelimitAsync(GetDivorceKey(user.Id), 6.Hours()); + if (remaining is TimeSpan rem) + { + result = DivorceResult.Cooldown; + return (w, result, amount, rem); + } + amount = w.Price / 2; if (w.Affinity?.UserId == user.Id) @@ -486,13 +503,13 @@ public class WaifuService : INService, IReadyExecutor .ToList(); } + private static readonly TypedKey _waifuDecayKey = $"waifu:last_decay"; public async Task OnReadyAsync() { // only decay waifu values from shard 0 if (_client.ShardId != 0) return; - var redisKey = $"{_creds.RedisKey()}_last_waifu_decay"; while (true) { try @@ -504,28 +521,31 @@ public class WaifuService : INService, IReadyExecutor if (multi is < 0f or > 1f || decayInterval < 0) continue; - var val = await _cache.Redis.GetDatabase().StringGetAsync(redisKey); - if (val != default) + var now = DateTime.UtcNow; + var nowB = now.ToBinary(); + + var result = await _cache.GetAsync(_waifuDecayKey); + + if (result.TryGetValue(out var val)) { - var lastDecay = DateTime.FromBinary((long)val); + var lastDecay = DateTime.FromBinary(val); var toWait = decayInterval.Hours() - (DateTime.UtcNow - lastDecay); if (toWait > 0.Hours()) continue; } - await _cache.Redis.GetDatabase().StringSetAsync(redisKey, DateTime.UtcNow.ToBinary()); + await _cache.AddAsync(_waifuDecayKey, nowB); await using var uow = _db.GetDbContext(); - await uow.WaifuInfo + await uow.GetTable() .Where(x => x.Price > minPrice && x.ClaimerId == null) .UpdateAsync(old => new() { Price = (long)(old.Price * multi) }); - await uow.SaveChangesAsync(); } catch (Exception ex) { diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 2ff42cba7..4089b0290 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -14,9 +14,9 @@ public partial class Games : NadekoModule private readonly IHttpClientFactory _httpFactory; private readonly Random _rng = new(); - public Games(IDataCache data, IHttpClientFactory factory) + public Games(IImageCache images, IHttpClientFactory factory) { - _images = data.LocalImages; + _images = images; _httpFactory = factory; } diff --git a/src/NadekoBot/Modules/Games/GirlRating.cs b/src/NadekoBot/Modules/Games/GirlRating.cs index 05e38eec7..3c7738b30 100644 --- a/src/NadekoBot/Modules/Games/GirlRating.cs +++ b/src/NadekoBot/Modules/Games/GirlRating.cs @@ -28,11 +28,12 @@ public class GirlRating Roll = roll; Advice = advice; // convenient to have it here, even though atm there are only few different ones. - Stream = new(() => + Stream = new(async () => { try { - using var img = Image.Load(_images.RategirlMatrix); + var bgBytes = await _images.GetRategirlBgAsync(); + using var img = Image.Load(bgBytes); const int minx = 35; const int miny = 385; const int length = 345; @@ -40,7 +41,8 @@ public class GirlRating var pointx = (int)(minx + (length * (Hot / 10))); var pointy = (int)(miny - (length * ((Crazy - 4) / 6))); - using (var pointImg = Image.Load(_images.RategirlDot)) + var dotBytes = await _images.GetRategirlDotAsync(); + using (var pointImg = Image.Load(dotBytes)) { img.Mutate(x => x.DrawImage(pointImg, new(pointx - 10, pointy - 10), new GraphicsOptions())); } diff --git a/src/NadekoBot/Modules/Games/Trivia/TriviaCommands.cs b/src/NadekoBot/Modules/Games/Trivia/TriviaCommands.cs index d7f00e1b1..16c2d1fdf 100644 --- a/src/NadekoBot/Modules/Games/Trivia/TriviaCommands.cs +++ b/src/NadekoBot/Modules/Games/Trivia/TriviaCommands.cs @@ -9,14 +9,14 @@ public partial class Games [Group] public partial class TriviaCommands : NadekoModule { - private readonly IDataCache _cache; + private readonly ILocalDataCache _cache; private readonly ICurrencyService _cs; private readonly GamesConfigService _gamesConfig; private readonly DiscordSocketClient _client; public TriviaCommands( DiscordSocketClient client, - IDataCache cache, + ILocalDataCache cache, ICurrencyService cs, GamesConfigService gamesConfig) { diff --git a/src/NadekoBot/Modules/Games/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Trivia/TriviaGame.cs index 4854721d3..0f07d2d9a 100644 --- a/src/NadekoBot/Modules/Games/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Trivia/TriviaGame.cs @@ -17,7 +17,7 @@ public class TriviaGame public bool GameActive { get; private set; } public bool ShouldStopGame { get; private set; } private readonly SemaphoreSlim _guessLock = new(1, 1); - private readonly IDataCache _cache; + private readonly ILocalDataCache _cache; private readonly IBotStrings _strings; private readonly DiscordSocketClient _client; private readonly GamesConfig _config; @@ -35,7 +35,7 @@ public class TriviaGame IBotStrings strings, DiscordSocketClient client, GamesConfig config, - IDataCache cache, + ILocalDataCache cache, ICurrencyService cs, IGuild guild, ITextChannel channel, @@ -70,7 +70,7 @@ public class TriviaGame showHowToQuit = !showHowToQuit; // load question - CurrentQuestion = _questionPool.GetRandomQuestion(OldQuestions, _options.IsPokemon); + CurrentQuestion = await _questionPool.GetRandomQuestionAsync(OldQuestions, _options.IsPokemon); if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) || string.IsNullOrWhiteSpace(CurrentQuestion.Question)) { diff --git a/src/NadekoBot/Modules/Games/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Trivia/TriviaQuestion.cs index 0d662ef20..159a28113 100644 --- a/src/NadekoBot/Modules/Games/Trivia/TriviaQuestion.cs +++ b/src/NadekoBot/Modules/Games/Trivia/TriviaQuestion.cs @@ -4,6 +4,15 @@ using System.Text.RegularExpressions; // THANKS @ShoMinamimoto for suggestions and coding help namespace NadekoBot.Modules.Games.Common.Trivia; +public sealed class TriviaQuestionModel +{ + public string Category { get; init; } + public string Question { get; init; } + public string ImageUrl { get; init; } + public string AnswerImageUrl { get; init; } + public string Answer { get; init; } +} + public class TriviaQuestion { public const int MAX_STRING_LENGTH = 22; @@ -17,29 +26,30 @@ public class TriviaQuestion new(22, 3) }; - public string Category { get; set; } - public string Question { get; set; } - public string ImageUrl { get; set; } - public string AnswerImageUrl { get; set; } - public string Answer { get; set; } + public string Category + => _qModel.Category; + + public string Question + => _qModel.Question; + + public string ImageUrl + => _qModel.ImageUrl; + + public string AnswerImageUrl + => _qModel.AnswerImageUrl ?? ImageUrl; + + public string Answer + => _qModel.Answer; public string CleanAnswer => cleanAnswer ?? (cleanAnswer = Clean(Answer)); private string cleanAnswer; + private readonly TriviaQuestionModel _qModel; - public TriviaQuestion( - string q, - string a, - string c, - string img = null, - string answerImage = null) + public TriviaQuestion(TriviaQuestionModel qModel) { - Question = q; - Answer = a; - Category = c; - ImageUrl = img; - AnswerImageUrl = answerImage ?? img; + _qModel = qModel; } public string GetHint() diff --git a/src/NadekoBot/Modules/Games/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Trivia/TriviaQuestionPool.cs index f98bce405..7ff0a4fe2 100644 --- a/src/NadekoBot/Modules/Games/Trivia/TriviaQuestionPool.cs +++ b/src/NadekoBot/Modules/Games/Trivia/TriviaQuestionPool.cs @@ -1,45 +1,48 @@ -#nullable disable namespace NadekoBot.Modules.Games.Common.Trivia; public class TriviaQuestionPool { - private TriviaQuestion[] Pool - => _cache.LocalData.TriviaQuestions; - - private IReadOnlyDictionary Map - => _cache.LocalData.PokemonMap; - - private readonly IDataCache _cache; + private readonly ILocalDataCache _cache; private readonly int _maxPokemonId; private readonly NadekoRandom _rng = new(); - public TriviaQuestionPool(IDataCache cache) + public TriviaQuestionPool(ILocalDataCache cache) { _cache = cache; _maxPokemonId = 721; //xd } - public TriviaQuestion GetRandomQuestion(HashSet exclude, bool isPokemon) + public async Task GetRandomQuestionAsync(HashSet exclude, bool isPokemon) { - if (Pool.Length == 0) - return null; - if (isPokemon) { + var pokes = await _cache.GetPokemonMapAsync(); + + if (pokes is null or { Length: 0 }) + return default; + var num = _rng.Next(1, _maxPokemonId + 1); - return new("Who's That Pokémon?", - Map[num].ToTitleCase(), - "Pokemon", - $@"https://nadeko.bot/images/pokemon/shadows/{num}.png", - $@"https://nadeko.bot/images/pokemon/real/{num}.png"); + return new(new() + { + Question = "Who's That Pokémon?", + Answer = pokes[num].Name.ToTitleCase(), + Category = "Pokemon", + ImageUrl = $@"https://nadeko.bot/images/pokemon/shadows/{num}.png", + AnswerImageUrl = $@"https://nadeko.bot/images/pokemon/real/{num}.png" + }); } TriviaQuestion randomQuestion; - while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)])) + var pool = await _cache.GetTriviaQuestionsAsync(); + + if(pool is null) + return default; + + while (exclude.Contains(randomQuestion = new(pool[_rng.Next(0, pool.Length)]))) { // if too many questions are excluded, clear the exclusion list and start over - if (exclude.Count > Pool.Length / 10 * 9) + if (exclude.Count > pool.Length / 10 * 9) { exclude.Clear(); break; diff --git a/src/NadekoBot/Modules/Music/_Common/Impl/RedisTrackCacher.cs b/src/NadekoBot/Modules/Music/_Common/Impl/RedisTrackCacher.cs deleted file mode 100644 index 8960616d5..000000000 --- a/src/NadekoBot/Modules/Music/_Common/Impl/RedisTrackCacher.cs +++ /dev/null @@ -1,209 +0,0 @@ -using StackExchange.Redis; -using System.Runtime.CompilerServices; -using System.Text.Json; - -namespace NadekoBot.Modules.Music; - -public sealed class RedisTrackCacher : ITrackCacher -{ - private readonly ConnectionMultiplexer _multiplexer; - - public RedisTrackCacher(ConnectionMultiplexer multiplexer) - => _multiplexer = multiplexer; - - public async Task GetOrCreateStreamLink( - string id, - MusicPlatform platform, - Func> streamUrlFactory) - { - var trackStreamKey = CreateStreamKey(id, platform); - - var value = await GetStreamFromCacheInternalAsync(trackStreamKey); - - // if there is no cached value - if (value == default) - { - // otherwise retrieve and cache a new value, and run this method again - var success = await CreateAndCacheStreamUrlAsync(trackStreamKey, streamUrlFactory); - if (!success) - return null; - - return await GetOrCreateStreamLink(id, platform, streamUrlFactory); - } - - // cache new one for future use - _ = Task.Run(() => CreateAndCacheStreamUrlAsync(trackStreamKey, streamUrlFactory)); - - return value; - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string CreateStreamKey(string id, MusicPlatform platform) - => $"track:stream:{platform}:{id}"; - - private async Task CreateAndCacheStreamUrlAsync( - string trackStreamKey, - Func> factory) - { - try - { - var data = await factory(); - if (data == default) - return false; - - await CacheStreamUrlInternalAsync(trackStreamKey, data.StreamUrl, data.Expiry); - return true; - } - catch (Exception ex) - { - Log.Error(ex, "Error resolving stream link for {TrackCacheKey}", trackStreamKey); - return false; - } - } - - public Task CacheStreamUrlAsync( - string id, - MusicPlatform platform, - string url, - TimeSpan expiry) - => CacheStreamUrlInternalAsync(CreateStreamKey(id, platform), url, expiry); - - private async Task CacheStreamUrlInternalAsync(string trackStreamKey, string url, TimeSpan expiry) - { - // keys need to be expired after an hour - // to make sure client doesn't get an expired stream url - // to achieve this, track keys will be just pointers to real data - // but that data will expire - - var db = _multiplexer.GetDatabase(); - var dataKey = $"entry:{Guid.NewGuid()}:{trackStreamKey}"; - await db.StringSetAsync(dataKey, url, expiry); - await db.ListRightPushAsync(trackStreamKey, dataKey); - } - - private async Task GetStreamFromCacheInternalAsync(string trackStreamKey) - { - // Job of the method which retrieves keys is to pop the elements - // from the list of cached trackurls until it finds a non-expired key - - var db = _multiplexer.GetDatabase(); - while (true) - { - string? dataKey = await db.ListLeftPopAsync(trackStreamKey); - if (dataKey == default) - return null; - - var streamUrl = await db.StringGetAsync(dataKey); - if (streamUrl == default) - continue; - - return streamUrl; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string CreateCachedDataKey(string id, MusicPlatform platform) - => $"track:data:{platform}:{id}"; - - public Task CacheTrackDataAsync(ICachableTrackData data) - { - var db = _multiplexer.GetDatabase(); - - var trackDataKey = CreateCachedDataKey(data.Id, data.Platform); - var dataString = JsonSerializer.Serialize((object)data); - // cache for 1 day - return db.StringSetAsync(trackDataKey, dataString, TimeSpan.FromDays(1)); - } - - public async Task GetCachedDataByIdAsync(string id, MusicPlatform platform) - { - var db = _multiplexer.GetDatabase(); - - var trackDataKey = CreateCachedDataKey(id, platform); - var data = await db.StringGetAsync(trackDataKey); - if (data == default) - return null; - - return JsonSerializer.Deserialize(data); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string CreateCachedQueryDataKey(string query, MusicPlatform platform) - => $"track:query_to_id:{platform}:{query}"; - - public async Task GetCachedDataByQueryAsync(string query, MusicPlatform platform) - { - query = Uri.EscapeDataString(query.Trim()); - - var db = _multiplexer.GetDatabase(); - var queryDataKey = CreateCachedQueryDataKey(query, platform); - - var trackId = await db.StringGetAsync(queryDataKey); - if (trackId == default) - return null; - - return await GetCachedDataByIdAsync(trackId, platform); - } - - public async Task CacheTrackDataByQueryAsync(string query, ICachableTrackData data) - { - query = Uri.EscapeDataString(query.Trim()); - - // first cache the data - await CacheTrackDataAsync(data); - - // then map the query to cached data's id - var db = _multiplexer.GetDatabase(); - - var queryDataKey = CreateCachedQueryDataKey(query, data.Platform); - await db.StringSetAsync(queryDataKey, data.Id, TimeSpan.FromDays(7)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string CreateCachedPlaylistKey(string playlistId, MusicPlatform platform) - => $"playlist:{platform}:{playlistId}"; - - public async Task> GetPlaylistTrackIdsAsync(string playlistId, MusicPlatform platform) - { - var db = _multiplexer.GetDatabase(); - var key = CreateCachedPlaylistKey(playlistId, platform); - var vals = await db.ListRangeAsync(key); - if (vals == default || vals.Length == 0) - return Array.Empty(); - - return vals.Select(x => x.ToString()).ToList(); - } - - public async Task CachePlaylistTrackIdsAsync(string playlistId, MusicPlatform platform, IEnumerable ids) - { - var db = _multiplexer.GetDatabase(); - var key = CreateCachedPlaylistKey(playlistId, platform); - await db.ListRightPushAsync(key, ids.Select(x => (RedisValue)x).ToArray()); - await db.KeyExpireAsync(key, TimeSpan.FromDays(7)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string CreateCachedPlaylistQueryKey(string query, MusicPlatform platform) - => $"playlist:query:{platform}:{query}"; - - public Task CachePlaylistIdByQueryAsync(string query, MusicPlatform platform, string playlistId) - { - query = Uri.EscapeDataString(query.Trim()); - var key = CreateCachedPlaylistQueryKey(query, platform); - var db = _multiplexer.GetDatabase(); - return db.StringSetAsync(key, playlistId, TimeSpan.FromDays(7)); - } - - public async Task GetPlaylistIdByQueryAsync(string query, MusicPlatform platform) - { - query = Uri.EscapeDataString(query.Trim()); - var key = CreateCachedPlaylistQueryKey(query, platform); - - var val = await _multiplexer.GetDatabase().StringGetAsync(key); - if (val == default) - return null; - - return val; - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/_Common/Impl/TrackCacher.cs b/src/NadekoBot/Modules/Music/_Common/Impl/TrackCacher.cs new file mode 100644 index 000000000..0ae4f7744 --- /dev/null +++ b/src/NadekoBot/Modules/Music/_Common/Impl/TrackCacher.cs @@ -0,0 +1,95 @@ +namespace NadekoBot.Modules.Music; + +public sealed class TrackCacher : ITrackCacher +{ + private readonly IBotCache _cache; + + public TrackCacher(IBotCache cache) + => _cache = cache; + + + private TypedKey GetStreamLinkKey(MusicPlatform platform, string id) + => new($"music:stream:{platform}:{id}"); + + public async Task GetOrCreateStreamLink( + string id, + MusicPlatform platform, + Func> streamUrlFactory) + { + var key = GetStreamLinkKey(platform, id); + + var streamUrl = await _cache.GetOrDefaultAsync(key); + await _cache.RemoveAsync(key); + + if (streamUrl == default) + { + (streamUrl, _) = await streamUrlFactory(); + } + + // make a new one for later use + _ = Task.Run(async () => + { + (streamUrl, var expiry) = await streamUrlFactory(); + await CacheStreamUrlAsync(id, platform, streamUrl, expiry); + }); + + return streamUrl; + } + + public async Task CacheStreamUrlAsync( + string id, + MusicPlatform platform, + string url, + TimeSpan expiry) + => await _cache.AddAsync(GetStreamLinkKey(platform, id), url, expiry); + + // track data by id + private TypedKey GetTrackDataKey(MusicPlatform platform, string id) + => new($"music:track:{platform}:{id}"); + public async Task CacheTrackDataAsync(ICachableTrackData data) + => await _cache.AddAsync(GetTrackDataKey(data.Platform, data.Id), data); + + public async Task GetCachedDataByIdAsync(string id, MusicPlatform platform) + => await _cache.GetOrDefaultAsync(GetTrackDataKey(platform, id)); + + + // track data by query + private TypedKey GetTrackDataQueryKey(MusicPlatform platform, string query) + => new($"music:track:{platform}:q:{query}"); + + public async Task CacheTrackDataByQueryAsync(string query, ICachableTrackData data) + => await Task.WhenAll( + _cache.AddAsync(GetTrackDataQueryKey(data.Platform, query), data).AsTask(), + _cache.AddAsync(GetTrackDataKey(data.Platform, data.Id), data).AsTask()); + + public async Task GetCachedDataByQueryAsync(string query, MusicPlatform platform) + => await _cache.GetOrDefaultAsync(GetTrackDataQueryKey(platform, query)); + + + // playlist track ids by playlist id + private TypedKey> GetPlaylistTracksCacheKey(string playlist, MusicPlatform platform) + => new($"music:playlist_tracks:{platform}:{playlist}"); + + public async Task CachePlaylistTrackIdsAsync(string playlistId, MusicPlatform platform, IEnumerable ids) + => await _cache.AddAsync(GetPlaylistTracksCacheKey(playlistId, platform), ids.ToList()); + + public async Task> GetPlaylistTrackIdsAsync(string playlistId, MusicPlatform platform) + { + var result = await _cache.GetAsync(GetPlaylistTracksCacheKey(playlistId, platform)); + if (result.TryGetValue(out var val)) + return val; + + return Array.Empty(); + } + + + // playlist id by query + private TypedKey GetPlaylistCacheKey(string query, MusicPlatform platform) + => new($"music:playlist_id:{platform}:{query}"); + + public async Task CachePlaylistIdByQueryAsync(string query, MusicPlatform platform, string playlistId) + => await _cache.AddAsync(GetPlaylistCacheKey(query, platform), playlistId); + + public async Task GetPlaylistIdByQueryAsync(string query, MusicPlatform platform) + => await _cache.GetOrDefaultAsync(GetPlaylistCacheKey(query, platform)); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs b/src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs index bd7216965..15dcabdd4 100644 --- a/src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs +++ b/src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs @@ -1,34 +1,38 @@ #nullable disable -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Searches.Common; public class AnimeResult { + [JsonPropertyName("id")] public int Id { get; set; } - public string AiringStatus - => AiringStatusParsed.ToTitleCase(); - - [JsonProperty("airing_status")] + [JsonPropertyName("airing_status")] public string AiringStatusParsed { get; set; } - [JsonProperty("title_english")] + [JsonPropertyName("title_english")] public string TitleEnglish { get; set; } - [JsonProperty("total_episodes")] + [JsonPropertyName("total_episodes")] public int TotalEpisodes { get; set; } - + + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty("image_url_lge")] + [JsonPropertyName("image_url_lge")] public string ImageUrlLarge { get; set; } + [JsonPropertyName("genres")] public string[] Genres { get; set; } - [JsonProperty("average_score")] - public string AverageScore { get; set; } + [JsonPropertyName("average_score")] + public float AverageScore { get; set; } + + public string AiringStatus + => AiringStatusParsed.ToTitleCase(); + public string Link => "http://anilist.co/anime/" + Id; diff --git a/src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs b/src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs index a3a870694..8a871ea9d 100644 --- a/src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs @@ -1,17 +1,15 @@ #nullable disable -using AngleSharp; -using AngleSharp.Html.Dom; using NadekoBot.Modules.Searches.Common; -using Newtonsoft.Json; +using System.Net.Http.Json; namespace NadekoBot.Modules.Searches.Services; public class AnimeSearchService : INService { - private readonly IDataCache _cache; + private readonly IBotCache _cache; private readonly IHttpClientFactory _httpFactory; - public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory) + public AnimeSearchService(IBotCache cache, IHttpClientFactory httpFactory) { _cache = cache; _httpFactory = httpFactory; @@ -21,24 +19,25 @@ public class AnimeSearchService : INService { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); + + TypedKey GetKey(string link) + => new TypedKey($"anime2:{link}"); + try { - var link = "https://aniapi.nadeko.bot/anime/" - + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); + var suffix = Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); + var link = $"https://aniapi.nadeko.bot/anime/{suffix}"; link = link.ToLowerInvariant(); - var (ok, data) = await _cache.TryGetAnimeDataAsync(link); - if (!ok) + var result = await _cache.GetAsync(GetKey(link)); + if (!result.TryPickT0(out var data, out _)) { - using (var http = _httpFactory.CreateClient()) - { - data = await http.GetStringAsync(link); - } + using var http = _httpFactory.CreateClient(); + data = await http.GetFromJsonAsync(link); - await _cache.SetAnimeDataAsync(link, data); + await _cache.AddAsync(GetKey(link), data, expiry: TimeSpan.FromHours(12)); } - - return JsonConvert.DeserializeObject(data); + return data; } catch { @@ -46,95 +45,31 @@ public class AnimeSearchService : INService } } - public async Task GetNovelData(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - query = query.Replace(" ", "-", StringComparison.InvariantCulture); - try - { - var link = "https://www.novelupdates.com/series/" - + Uri.EscapeDataString(query.Replace(" ", "-").Replace("/", " ")); - link = link.ToLowerInvariant(); - var (ok, data) = await _cache.TryGetNovelDataAsync(link); - if (!ok) - { - var config = Configuration.Default.WithDefaultLoader(); - using var document = await BrowsingContext.New(config).OpenAsync(link); - var imageElem = document.QuerySelector("div.seriesimg > img"); - if (imageElem is null) - return null; - var imageUrl = ((IHtmlImageElement)imageElem).Source; - - var descElem = document.QuerySelector("div#editdescription > p"); - var desc = descElem.InnerHtml; - - var genres = document.QuerySelector("div#seriesgenre") - .Children.Select(x => x as IHtmlAnchorElement) - .Where(x => x is not null) - .Select(x => $"[{x.InnerHtml}]({x.Href})") - .ToArray(); - - var authors = document.QuerySelector("div#showauthors") - .Children.Select(x => x as IHtmlAnchorElement) - .Where(x => x is not null) - .Select(x => $"[{x.InnerHtml}]({x.Href})") - .ToArray(); - - var score = ((IHtmlSpanElement)document.QuerySelector("h5.seriesother > span.uvotes")).InnerHtml; - - var status = document.QuerySelector("div#editstatus").InnerHtml; - var title = document.QuerySelector("div.w-blog-content > div.seriestitlenu").InnerHtml; - - var obj = new NovelResult - { - Description = desc, - Authors = authors, - Genres = genres, - ImageUrl = imageUrl, - Link = link, - Score = score, - Status = status, - Title = title - }; - - await _cache.SetNovelDataAsync(link, JsonConvert.SerializeObject(obj)); - - return obj; - } - - return JsonConvert.DeserializeObject(data); - } - catch (Exception ex) - { - Log.Error(ex, "Error getting novel data"); - return null; - } - } - public async Task GetMangaData(string query) { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); + + TypedKey GetKey(string link) + => new TypedKey($"manga2:{link}"); + try { var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture)); link = link.ToLowerInvariant(); - var (ok, data) = await _cache.TryGetAnimeDataAsync(link); - if (!ok) + + var result = await _cache.GetAsync(GetKey(link)); + if (!result.TryPickT0(out var data, out _)) { - using (var http = _httpFactory.CreateClient()) - { - data = await http.GetStringAsync(link); - } + using var http = _httpFactory.CreateClient(); + data = await http.GetFromJsonAsync(link); - await _cache.SetAnimeDataAsync(link, data); + await _cache.AddAsync(GetKey(link), data, expiry: TimeSpan.FromHours(3)); } - return JsonConvert.DeserializeObject(data); + return data; } catch { diff --git a/src/NadekoBot/Modules/Searches/Anime/MangaResult.cs b/src/NadekoBot/Modules/Searches/Anime/MangaResult.cs index 02530a951..0e9e34eaa 100644 --- a/src/NadekoBot/Modules/Searches/Anime/MangaResult.cs +++ b/src/NadekoBot/Modules/Searches/Anime/MangaResult.cs @@ -1,32 +1,36 @@ #nullable disable -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Searches.Common; public class MangaResult { + [JsonPropertyName("id")] public int Id { get; set; } - [JsonProperty("publishing_status")] + [JsonPropertyName("publishing_status")] public string PublishingStatus { get; set; } - [JsonProperty("image_url_lge")] + [JsonPropertyName("image_url_lge")] public string ImageUrlLge { get; set; } - [JsonProperty("title_english")] + [JsonPropertyName("title_english")] public string TitleEnglish { get; set; } - [JsonProperty("total_chapters")] + [JsonPropertyName("total_chapters")] public int TotalChapters { get; set; } - [JsonProperty("total_volumes")] + [JsonPropertyName("total_volumes")] public int TotalVolumes { get; set; } + [JsonPropertyName("description")] public string Description { get; set; } + + [JsonPropertyName("genres")] public string[] Genres { get; set; } - [JsonProperty("average_score")] - public string AverageScore { get; set; } + [JsonPropertyName("average_score")] + public float AverageScore { get; set; } public string Link => "http://anilist.co/manga/" + Id; diff --git a/src/NadekoBot/Modules/Searches/Crypto/CryptoService.cs b/src/NadekoBot/Modules/Searches/Crypto/CryptoService.cs index 8fdd67ad4..c5f9816e8 100644 --- a/src/NadekoBot/Modules/Searches/Crypto/CryptoService.cs +++ b/src/NadekoBot/Modules/Searches/Crypto/CryptoService.cs @@ -15,13 +15,13 @@ namespace NadekoBot.Modules.Searches.Services; public class CryptoService : INService { - private readonly IDataCache _cache; + private readonly IBotCache _cache; private readonly IHttpClientFactory _httpFactory; private readonly IBotCredentials _creds; private readonly SemaphoreSlim _getCryptoLock = new(1, 1); - public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds) + public CryptoService(IBotCache cache, IHttpClientFactory httpFactory, IBotCredentials creds) { _cache = cache; _httpFactory = httpFactory; @@ -40,7 +40,8 @@ public class CryptoService : INService Span points = new PointF[gElement.ChildNodes.Count]; var cnt = 0; - bool GetValuesFromAttributes(XmlAttributeCollection attrs, + bool GetValuesFromAttributes( + XmlAttributeCollection attrs, out float x1, out float y1, out float x2, @@ -56,7 +57,7 @@ public class CryptoService : INService && attrs["y2"]?.Value is string y2Str && float.TryParse(y2Str, NumberStyles.Any, CultureInfo.InvariantCulture, out y2); } - + foreach (XmlElement x in gElement.ChildNodes) { if (x.Name != "line") @@ -67,22 +68,22 @@ public class CryptoService : INService points[cnt++] = new(x1, y1); // this point will be set twice to the same value // on all points except the last one - if(cnt + 1 < points.Length) + if (cnt + 1 < points.Length) points[cnt + 1] = new(x2, y2); } } if (cnt == 0) return Array.Empty(); - + return points.Slice(0, cnt).ToArray(); } - + private SixLabors.ImageSharp.Image GenerateSparklineChart(PointF[] points, bool up) { const int width = 164; const int height = 48; - + var img = new Image(width, height, Color.Transparent); var color = up ? Color.Green @@ -92,10 +93,10 @@ public class CryptoService : INService { x.DrawLines(color, 2, points); }); - + return img; } - + public async Task<(CmcResponseData? Data, CmcResponseData? Nearest)> GetCryptoData(string name) { if (string.IsNullOrWhiteSpace(name)) @@ -130,20 +131,20 @@ public class CryptoService : INService await _getCryptoLock.WaitAsync(); try { - var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", - async _ => + var data = await _cache.GetOrAddAsync(new("nadeko:crypto_data"), + async () => { try { using var http = _httpFactory.CreateClient(); - var strData = await http.GetFromJsonAsync( + var data = await http.GetFromJsonAsync( "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" + $"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" + "&start=1" + "&limit=5000" + "&convert=USD"); - return JsonSerializer.Serialize(strData); + return data; } catch (Exception ex) { @@ -151,13 +152,12 @@ public class CryptoService : INService return default; } }, - "", TimeSpan.FromHours(2)); - if (fullStrData is null) + if (data is null) return default; - - return JsonSerializer.Deserialize(fullStrData)?.Data ?? new(); + + return data.Data; } catch (Exception ex) { @@ -170,44 +170,33 @@ public class CryptoService : INService } } + private TypedKey GetSparklineKey(int id) + => new($"crypto:sparkline:{id}"); + public async Task GetSparklineAsync(int id, bool up) - { - var key = $"crypto:sparkline:{id}"; - - // attempt to get from cache - var db = _cache.Redis.GetDatabase(); - byte[] bytes = await db.StringGetAsync(key); - // if it succeeds, return it - if (bytes is { Length: > 0 }) - { - return bytes.ToStream(); - } - - // if it fails, generate a new one - var points = await DownloadSparklinePointsAsync(id); - if (points is null) - return default; - - var sparkline = GenerateSparklineChart(points, up); - - // add to cache for 1h and return it - - var stream = sparkline.ToStream(); - await db.StringSetAsync(key, stream.ToArray(), expiry: TimeSpan.FromHours(1)); - return stream; - } - - private async Task DownloadSparklinePointsAsync(int id) { try { - using var http = _httpFactory.CreateClient(); - var str = await http.GetStringAsync( - $"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{id}.svg"); - var points = GetSparklinePointsFromSvgText(str); - return points; + var bytes = await _cache.GetOrAddAsync(GetSparklineKey(id), + async () => + { + // if it fails, generate a new one + var points = await DownloadSparklinePointsAsync(id); + var sparkline = GenerateSparklineChart(points, up); + + using var stream = await sparkline.ToStreamAsync(); + return stream.ToArray(); + }, + TimeSpan.FromHours(1)); + + if (bytes is { Length: > 0 }) + { + return bytes.ToStream(); + } + + return default; } - catch(Exception ex) + catch (Exception ex) { Log.Warning(ex, "Exception occurred while downloading sparkline points: {ErrorMessage}", @@ -215,4 +204,13 @@ public class CryptoService : INService return default; } } + + private async Task DownloadSparklinePointsAsync(int id) + { + using var http = _httpFactory.CreateClient(); + var str = await http.GetStringAsync( + $"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{id}.svg"); + var points = GetSparklinePointsFromSvgText(str); + return points; + } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs index 2049e72a7..8d2acd71f 100644 --- a/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/PokemonSearchCommands.cs @@ -9,15 +9,9 @@ public partial class Searches [Group] public partial class PokemonSearchCommands : NadekoModule { - public IReadOnlyDictionary Pokemons - => _cache.LocalData.Pokemons; + private readonly ILocalDataCache _cache; - public IReadOnlyDictionary PokemonAbilities - => _cache.LocalData.PokemonAbilities; - - private readonly IDataCache _cache; - - public PokemonSearchCommands(IDataCache cache) + public PokemonSearchCommands(ILocalDataCache cache) => _cache = cache; [Cmd] @@ -27,7 +21,7 @@ public partial class Searches if (string.IsNullOrWhiteSpace(pokemon)) return; - foreach (var kvp in Pokemons) + foreach (var kvp in await _cache.GetPokemonsAsync()) { if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant()) { @@ -58,7 +52,7 @@ public partial class Searches ability = ability?.Trim().ToUpperInvariant().Replace(" ", "", StringComparison.InvariantCulture); if (string.IsNullOrWhiteSpace(ability)) return; - foreach (var kvp in PokemonAbilities) + foreach (var kvp in await _cache.GetPokemonAbilitiesAsync()) { if (kvp.Key.ToUpperInvariant() == ability) { diff --git a/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs b/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs index c7c46a369..68fd9231d 100644 --- a/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs @@ -10,14 +10,14 @@ public partial class Searches public partial class SearchCommands : NadekoModule { private readonly ISearchServiceFactory _searchFactory; - private readonly ConnectionMultiplexer _redis; + private readonly IBotCache _cache; public SearchCommands( ISearchServiceFactory searchFactory, - ConnectionMultiplexer redis) + IBotCache cache) { _searchFactory = searchFactory; - _redis = redis; + _cache = cache; } [Cmd] @@ -127,18 +127,17 @@ public partial class Searches await ctx.Channel.EmbedAsync(null, embeds: embeds); } + private TypedKey GetYtCacheKey(string query) + => new($"search:youtube:{query}"); + private async Task AddYoutubeUrlToCacheAsync(string query, string url) - { - var db = _redis.GetDatabase(); - await db.StringSetAsync($"search:youtube:{query}", url, expiry: 1.Hours()); - } + => await _cache.AddAsync(GetYtCacheKey(query), url, expiry: 1.Hours()); private async Task GetYoutubeUrlFromCacheAsync(string query) { - var db = _redis.GetDatabase(); - var url = await db.StringGetAsync($"search:youtube:{query}"); + var result = await _cache.GetAsync(GetYtCacheKey(query)); - if (string.IsNullOrWhiteSpace(url)) + if (!result.TryGetValue(out var url) || string.IsNullOrWhiteSpace(url)) return null; return new VideoInfo() diff --git a/src/NadekoBot/Modules/Searches/SearchesService.cs b/src/NadekoBot/Modules/Searches/SearchesService.cs index d1cebc3fd..b1604b32b 100644 --- a/src/NadekoBot/Modules/Searches/SearchesService.cs +++ b/src/NadekoBot/Modules/Searches/SearchesService.cs @@ -1,6 +1,4 @@ #nullable disable -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; using Html2Markdown; using NadekoBot.Modules.Searches.Common; using Newtonsoft.Json; @@ -10,7 +8,6 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using System.Net; using Color = SixLabors.ImageSharp.Color; using Image = SixLabors.ImageSharp.Image; @@ -31,9 +28,9 @@ public class SearchesService : INService private readonly IHttpClientFactory _httpFactory; private readonly IGoogleApiService _google; private readonly IImageCache _imgs; - private readonly IDataCache _cache; + private readonly IBotCache _c; private readonly FontProvider _fonts; - private readonly IBotCredentials _creds; + private readonly IBotCredsProvider _creds; private readonly NadekoRandom _rng; private readonly List _yomamaJokes; @@ -42,15 +39,16 @@ public class SearchesService : INService public SearchesService( IGoogleApiService google, - IDataCache cache, + IImageCache images, + IBotCache c, IHttpClientFactory factory, FontProvider fonts, - IBotCredentials creds) + IBotCredsProvider creds) { _httpFactory = factory; _google = google; - _imgs = cache.LocalImages; - _cache = cache; + _imgs = images; + _c = c; _fonts = fonts; _creds = creds; _rng = new(); @@ -76,36 +74,28 @@ public class SearchesService : INService } public async Task GetRipPictureAsync(string text, Uri imgUrl) - { - var data = await _cache.GetOrAddCachedDataAsync($"nadeko_rip_{text}_{imgUrl}", - GetRipPictureFactory, - (text, imgUrl), - TimeSpan.FromDays(1)); - - return data.ToStream(); - } + => (await GetRipPictureFactory(text, imgUrl)).ToStream(); private void DrawAvatar(Image bg, Image avatarImage) => bg.Mutate(x => x.Grayscale().DrawImage(avatarImage, new(83, 139), new GraphicsOptions())); - public async Task GetRipPictureFactory((string text, Uri avatarUrl) arg) + public async Task GetRipPictureFactory(string text, Uri avatarUrl) { - var (text, avatarUrl) = arg; - using var bg = Image.Load(_imgs.Rip.ToArray()); - var (succ, data) = (false, (byte[])null); //await _cache.TryGetImageDataAsync(avatarUrl); - if (!succ) + using var bg = Image.Load(await _imgs.GetRipBgAsync()); + var result = await _c.GetImageDataAsync(avatarUrl); + if (!result.TryPickT0(out var data, out _)) { using var http = _httpFactory.CreateClient(); data = await http.GetByteArrayAsync(avatarUrl); using (var avatarImg = Image.Load(data)) { avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42)); - await using var avStream = avatarImg.ToStream(); + await using var avStream = await avatarImg.ToStreamAsync(); data = avStream.ToArray(); DrawAvatar(bg, avatarImg); } - await _cache.SetImageDataAsync(avatarUrl, data); + await _c.SetImageDataAsync(avatarUrl, data); } else { @@ -128,7 +118,7 @@ public class SearchesService : INService new(25, 225))); //flowa - using (var flowers = Image.Load(_imgs.RipOverlay.ToArray())) + using (var flowers = Image.Load(await _imgs.GetRipOverlayAsync())) { bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions())); } @@ -137,13 +127,12 @@ public class SearchesService : INService return stream.ToArray(); } - public Task GetWeatherDataAsync(string query) + public async Task GetWeatherDataAsync(string query) { query = query.Trim().ToLowerInvariant(); - return _cache.GetOrAddCachedDataAsync($"nadeko_weather_{query}", - GetWeatherDataFactory, - query, + return await _c.GetOrAddAsync(new($"nadeko_weather_{query}"), + async () => await GetWeatherDataFactory(query), TimeSpan.FromHours(3)); } @@ -184,26 +173,28 @@ public class SearchesService : INService if (string.IsNullOrEmpty(query)) return (default, TimeErrors.InvalidInput); - if (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) || string.IsNullOrWhiteSpace(_creds.TimezoneDbApiKey)) + + var locIqKey = _creds.GetCreds().LocationIqApiKey; + var tzDbKey = _creds.GetCreds().TimezoneDbApiKey; + if (string.IsNullOrWhiteSpace(locIqKey) || string.IsNullOrWhiteSpace(tzDbKey)) return (default, TimeErrors.ApiKeyMissing); try { using var http = _httpFactory.CreateClient(); - var res = await _cache.GetOrAddCachedDataAsync($"geo_{query}", - _ => + var res = await _c.GetOrAddAsync(new($"searches:geo:{query}"), + async () => { var url = "https://eu1.locationiq.com/v1/search.php?" - + (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) + + (string.IsNullOrWhiteSpace(locIqKey) ? "key=" - : $"key={_creds.LocationIqApiKey}&") + : $"key={locIqKey}&") + $"q={Uri.EscapeDataString(query)}&" + "format=json"; - var res = http.GetStringAsync(url); + var res = await http.GetStringAsync(url); return res; }, - "", TimeSpan.FromHours(1)); var responses = JsonConvert.DeserializeObject(res); @@ -217,7 +208,7 @@ public class SearchesService : INService using var req = new HttpRequestMessage(HttpMethod.Get, "http://api.timezonedb.com/v2.1/get-time-zone?" - + $"key={_creds.TimezoneDbApiKey}" + + $"key={tzDbKey}" + $"&format=json" + $"&by=position" + $"&lat={geoData.Lat}" @@ -315,9 +306,8 @@ public class SearchesService : INService public async Task GetMtgCardAsync(string search) { search = search.Trim().ToLowerInvariant(); - var data = await _cache.GetOrAddCachedDataAsync($"nadeko_mtg_{search}", - GetMtgCardFactory, - search, + var data = await _c.GetOrAddAsync(new($"mtg:{search}"), + async () => await GetMtgCardFactory(search), TimeSpan.FromDays(1)); if (data is null || data.Length == 0) @@ -368,12 +358,11 @@ public class SearchesService : INService return await cards.Select(GetMtgDataAsync).WhenAll(); } - public Task GetHearthstoneCardDataAsync(string name) + public async Task GetHearthstoneCardDataAsync(string name) { name = name.ToLowerInvariant(); - return _cache.GetOrAddCachedDataAsync($"nadeko_hearthstone_{name}", - HearthstoneCardDataFactory, - name, + return await _c.GetOrAddAsync($"hearthstone:{name}", + () => HearthstoneCardDataFactory(name), TimeSpan.FromDays(1)); } @@ -381,7 +370,7 @@ public class SearchesService : INService { using var http = _httpFactory.CreateClient(); http.DefaultRequestHeaders.Clear(); - http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.RapidApiKey); + http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.GetCreds().RapidApiKey); try { var response = await http.GetStringAsync("https://omgvamp-hearthstone-v1.p.rapidapi.com/" @@ -410,16 +399,22 @@ public class SearchesService : INService } } - public Task GetMovieDataAsync(string name) + public async Task GetMovieDataAsync(string name) { name = name.Trim().ToLowerInvariant(); - return _cache.GetOrAddCachedDataAsync($"nadeko_movie_{name}", GetMovieDataFactory, name, TimeSpan.FromDays(1)); + return await _c.GetOrAddAsync(new($"movie:{name}"), + () => GetMovieDataFactory(name), + TimeSpan.FromDays(1)); } private async Task GetMovieDataFactory(string name) { using var http = _httpFactory.CreateClient(); - var res = await http.GetStringAsync(string.Format("https://omdbapi.nadeko.bot/?t={0}&y=&plot=full&r=json", + var res = await http.GetStringAsync(string.Format("https://omdbapi.nadeko.bot/" + + "?t={0}" + + "&y=" + + "&plot=full" + + "&r=json", name.Trim().Replace(' ', '+'))); var movie = JsonConvert.DeserializeObject(res); if (movie?.Title is null) @@ -432,10 +427,11 @@ public class SearchesService : INService { const string steamGameIdsKey = "steam_names_to_appid"; - var gamesMap = await _cache.GetOrAddCachedDataAsync(steamGameIdsKey, - async _ => + var gamesMap = await _c.GetOrAddAsync(new(steamGameIdsKey), + async () => { using var http = _httpFactory.CreateClient(); + // https://api.steampowered.com/ISteamApps/GetAppList/v2/ var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/"); var apps = JsonConvert @@ -446,23 +442,18 @@ public class SearchesService : INService { apps = new List() } - }) + })! .applist.apps; return apps.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase) .GroupBy(x => x.Name) .ToDictionary(x => x.Key, x => x.First().AppId); - //await db.HashSetAsync("steam_game_ids", apps.Select(app => new HashEntry(app.Name.Trim().ToLowerInvariant(), app.AppId)).ToArray()); - //await db.StringSetAsync("steam_game_ids", gamesStr, TimeSpan.FromHours(24)); - //await db.KeyExpireAsync("steam_game_ids", TimeSpan.FromHours(24), CommandFlags.FireAndForget); }, - default(string), TimeSpan.FromHours(24)); if (gamesMap is null) return -1; - - + query = query.Trim(); var keyList = gamesMap.Keys.ToList(); diff --git a/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs b/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs index 1992b8223..a4f15e1ba 100644 --- a/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs +++ b/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs @@ -1,4 +1,6 @@ #nullable disable +using LinqToDB; +using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; @@ -6,10 +8,98 @@ using NadekoBot.Db.Models; using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Common.StreamNotifications; using NadekoBot.Services.Database.Models; -using StackExchange.Redis; namespace NadekoBot.Modules.Searches.Services; +public sealed class StreamOnlineMessageDeleterService : INService, IReadyExecutor +{ + private readonly StreamNotificationService _notifService; + private readonly DbService _db; + private readonly DiscordSocketClient _client; + private readonly IPubSub _pubSub; + + public StreamOnlineMessageDeleterService( + StreamNotificationService notifService, + DbService db, + IPubSub pubSub, + DiscordSocketClient client) + { + _notifService = notifService; + _db = db; + _client = client; + _pubSub = pubSub; + } + + public async Task OnReadyAsync() + { + _notifService.OnlineMessagesSent += OnOnlineMessagesSent; + + if(_client.ShardId == 0) + await _pubSub.Sub(_notifService.StreamsOfflineKey, OnStreamsOffline); + } + + private async Task OnOnlineMessagesSent(FollowedStream.FType type, string name, IReadOnlyCollection<(ulong, ulong)> pairs) + { + await using var ctx = _db.GetDbContext(); + foreach (var (channelId, messageId) in pairs) + { + await ctx.GetTable() + .InsertAsync(() => new() + { + Name = name, + Type = type, + MessageId = messageId, + ChannelId = channelId, + DateAdded = DateTime.UtcNow, + }); + } + } + + private async ValueTask OnStreamsOffline(List streamDatas) + { + if (_client.ShardId != 0) + return; + + var pairs = await GetMessagesToDelete(streamDatas); + + foreach (var (channelId, messageId) in pairs) + { + try + { + var textChannel = await _client.GetChannelAsync(channelId) as ITextChannel; + if (textChannel is null) + continue; + + await textChannel.DeleteMessageAsync(messageId); + } + catch + { + continue; + } + } + } + + private async Task> GetMessagesToDelete(List streamDatas) + { + await using var ctx = _db.GetDbContext(); + + var toReturn = new List<(ulong, ulong)>(); + foreach (var sd in streamDatas) + { + var key = sd.CreateKey(); + var toDelete = await ctx.GetTable() + .Where(x => (x.Type == key.Type && x.Name == key.Name) + || Sql.DateDiff(Sql.DateParts.Day, x.DateAdded, DateTime.UtcNow) > 1) + .DeleteWithOutputAsync(); + + toReturn.AddRange(toDelete.Select(x => (x.ChannelId, x.MessageId))); + } + + return toReturn; + } +} + + public sealed class StreamNotificationService : INService, IReadyExecutor { private readonly DbService _db; @@ -29,18 +119,22 @@ public sealed class StreamNotificationService : INService, IReadyExecutor private readonly IPubSub _pubSub; private readonly IEmbedBuilderService _eb; - private readonly TypedKey> _streamsOnlineKey; - private readonly TypedKey> _streamsOfflineKey; + public TypedKey> StreamsOnlineKey { get; } + public TypedKey> StreamsOfflineKey { get; } private readonly TypedKey _streamFollowKey; private readonly TypedKey _streamUnfollowKey; - private readonly ConnectionMultiplexer _redis; + + public event Func< + FollowedStream.FType, + string, + IReadOnlyCollection<(ulong, ulong)>, + Task> OnlineMessagesSent = static delegate { return Task.CompletedTask; }; public StreamNotificationService( DbService db, DiscordSocketClient client, IBotStrings strings, - ConnectionMultiplexer redis, IBotCredsProvider creds, IHttpClientFactory httpFactory, Bot bot, @@ -52,11 +146,11 @@ public sealed class StreamNotificationService : INService, IReadyExecutor _strings = strings; _pubSub = pubSub; _eb = eb; - _redis = redis; - _streamTracker = new(httpFactory, creds, redis, creds.GetCreds().RedisKey(), client.ShardId == 0); + + _streamTracker = new(httpFactory, creds); - _streamsOnlineKey = new("streams.online"); - _streamsOfflineKey = new("streams.offline"); + StreamsOnlineKey = new("streams.online"); + StreamsOfflineKey = new("streams.offline"); _streamFollowKey = new("stream.follow"); _streamUnfollowKey = new("stream.unfollow"); @@ -100,7 +194,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor var allFollowedStreams = uow.Set().AsQueryable().ToList(); foreach (var fs in allFollowedStreams) - _streamTracker.CacheAddData(fs.CreateKey(), null, false); + _streamTracker.AddLastData(fs.CreateKey(), null, false); _trackCounter = allFollowedStreams.GroupBy(x => new { @@ -112,8 +206,8 @@ public sealed class StreamNotificationService : INService, IReadyExecutor } } - _pubSub.Sub(_streamsOfflineKey, HandleStreamsOffline); - _pubSub.Sub(_streamsOnlineKey, HandleStreamsOnline); + _pubSub.Sub(StreamsOfflineKey, HandleStreamsOffline); + _pubSub.Sub(StreamsOnlineKey, HandleStreamsOnline); if (client.ShardId == 0) { @@ -186,7 +280,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor /// private ValueTask HandleFollowStream(FollowStreamPubData info) { - _streamTracker.CacheAddData(info.Key, null, false); + _streamTracker.AddLastData(info.Key, null, false); lock (_shardLock) { var key = info.Key; @@ -251,45 +345,8 @@ public sealed class StreamNotificationService : INService, IReadyExecutor .WhenAll(); } } - - if (_client.ShardId == 0) - { - foreach (var stream in offlineStreams) - { - await DeleteOnlineMessages(stream); - } - } - } - - private async Task DeleteOnlineMessages(StreamData stream) - { - var db = _redis.GetDatabase(); - var data = await db.ListRangeAsync($"streams_online_del:{stream.CreateKey()}"); - await db.KeyDeleteAsync($"streams_online_del:{stream.CreateKey()}"); - - foreach (string pair in data) - { - var pairArr = pair.Split(','); - if (pairArr.Length != 2) - continue; - - if (!ulong.TryParse(pairArr[0], out var chId) || !ulong.TryParse(pairArr[1], out var msgId)) - continue; - - try - { - var textChannel = await _client.GetChannelAsync(chId) as ITextChannel; - if (textChannel is null) - continue; - - await textChannel.DeleteMessageAsync(msgId); - } - catch - { - continue; - } - } } + private async ValueTask HandleStreamsOnline(List onlineStreams) { @@ -331,14 +388,11 @@ public sealed class StreamNotificationService : INService, IReadyExecutor { var pairs = messages .Where(x => x != default) - .Select(x => (RedisValue)$"{x.Item1},{x.Item2}") - .ToArray(); + .Select(x => (x.Item1, x.Item2)) + .ToList(); - if (pairs.Length > 0) - { - var db = _redis.GetDatabase(); - await db.ListRightPushAsync($"streams_online_del:{key}", pairs); - } + if (pairs.Count > 0) + await OnlineMessagesSent(key.Type, key.Name, pairs); } catch { @@ -349,10 +403,10 @@ public sealed class StreamNotificationService : INService, IReadyExecutor } private Task OnStreamsOnline(List data) - => _pubSub.Pub(_streamsOnlineKey, data); + => _pubSub.Pub(StreamsOnlineKey, data); private Task OnStreamsOffline(List data) - => _pubSub.Pub(_streamsOfflineKey, data); + => _pubSub.Pub(StreamsOfflineKey, data); private Task ClientOnJoinedGuild(GuildConfig guildConfig) { diff --git a/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/Models/StreamDataKey.cs b/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/Models/StreamDataKey.cs index 69845e68e..bc01d37c6 100644 --- a/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/Models/StreamDataKey.cs +++ b/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/Models/StreamDataKey.cs @@ -6,10 +6,9 @@ namespace NadekoBot.Modules.Searches.Common; public readonly struct StreamDataKey { - public FollowedStream.FType Type { get; } - public string Name { get; } + public FollowedStream.FType Type { get; init; } + public string Name { get; init; } - [JsonConstructor] public StreamDataKey(FollowedStream.FType type, string name) { Type = type; diff --git a/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/NotifChecker.cs b/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/NotifChecker.cs index 03de2151a..be18267b4 100644 --- a/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/NotifChecker.cs +++ b/src/NadekoBot/Modules/Searches/_Common/StreamNotifications/NotifChecker.cs @@ -1,7 +1,5 @@ using NadekoBot.Db.Models; using NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; -using Newtonsoft.Json; -using StackExchange.Redis; namespace NadekoBot.Modules.Searches.Common.StreamNotifications; @@ -9,30 +7,22 @@ public class NotifChecker { public event Func, Task> OnStreamsOffline = _ => Task.CompletedTask; public event Func, Task> OnStreamsOnline = _ => Task.CompletedTask; - private readonly ConnectionMultiplexer _multi; - private readonly string _key; - private readonly Dictionary _streamProviders; + private readonly IReadOnlyDictionary _streamProviders; private readonly HashSet<(FollowedStream.FType, string)> _offlineBuffer; + private readonly ConcurrentDictionary _cache = new(); public NotifChecker( IHttpClientFactory httpClientFactory, - IBotCredsProvider credsProvider, - ConnectionMultiplexer multi, - string uniqueCacheKey, - bool isMaster) + IBotCredsProvider credsProvider) { - _multi = multi; - _key = $"{uniqueCacheKey}_followed_streams_data"; - _streamProviders = new() + _streamProviders = new Dictionary() { { FollowedStream.FType.Twitch, new TwitchHelixProvider(httpClientFactory, credsProvider) }, { FollowedStream.FType.Picarto, new PicartoProvider(httpClientFactory) }, { FollowedStream.FType.Trovo, new TrovoProvider(httpClientFactory, credsProvider) } }; _offlineBuffer = new(); - if (isMaster) - CacheClearAllData(); } // gets all streams which have been failing for more than the provided timespan @@ -61,7 +51,7 @@ public class NotifChecker { try { - var allStreamData = CacheGetAllData(); + var allStreamData = GetAllData(); var oldStreamDataDict = allStreamData // group by type @@ -101,7 +91,7 @@ public class NotifChecker || !typeDict.TryGetValue(key.Name, out var oldData) || oldData is null) { - CacheAddData(key, newData, true); + AddLastData(key, newData, true); continue; } @@ -109,7 +99,7 @@ public class NotifChecker if (string.IsNullOrWhiteSpace(newData.Game)) newData.Game = oldData.Game; - CacheAddData(key, newData, true); + AddLastData(key, newData, true); // if the stream is offline, we need to check if it was // marked as offline once previously @@ -158,39 +148,22 @@ public class NotifChecker } }); - public bool CacheAddData(StreamDataKey key, StreamData? data, bool replace) + public bool AddLastData(StreamDataKey key, StreamData? data, bool replace) { - var db = _multi.GetDatabase(); - return db.HashSet(_key, - JsonConvert.SerializeObject(key), - JsonConvert.SerializeObject(data), - replace ? When.Always : When.NotExists); + if (replace) + { + _cache[key] = data; + return true; + } + + return _cache.TryAdd(key, data); } - public void CacheDeleteData(StreamDataKey key) - { - var db = _multi.GetDatabase(); - db.HashDelete(_key, JsonConvert.SerializeObject(key)); - } + public void DeleteLastData(StreamDataKey key) + => _cache.TryRemove(key, out _); - public void CacheClearAllData() - { - var db = _multi.GetDatabase(); - db.KeyDelete(_key); - } - - public Dictionary CacheGetAllData() - { - var db = _multi.GetDatabase(); - if (!db.KeyExists(_key)) - return new(); - - return db.HashGetAll(_key) - .ToDictionary(entry => JsonConvert.DeserializeObject(entry.Name), - entry => entry.Value.IsNullOrEmpty - ? default - : JsonConvert.DeserializeObject(entry.Value)); - } + public Dictionary GetAllData() + => _cache.ToDictionary(x => x.Key, x => x.Value); public async Task GetStreamDataByUrlAsync(string url) { @@ -234,9 +207,9 @@ public class NotifChecker // if stream is found, add it to the cache for tracking only if it doesn't already exist // because stream will be checked and events will fire in a loop. We don't want to override old state - return CacheAddData(data.CreateKey(), data, false); + return AddLastData(data.CreateKey(), data, false); } public void UntrackStreamByKey(in StreamDataKey key) - => CacheDeleteData(key); + => DeleteLastData(key); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Patronage/PatronageService.cs b/src/NadekoBot/Modules/Utility/Patronage/PatronageService.cs index 32035ebe1..f2a47a620 100644 --- a/src/NadekoBot/Modules/Utility/Patronage/PatronageService.cs +++ b/src/NadekoBot/Modules/Utility/Patronage/PatronageService.cs @@ -31,9 +31,10 @@ public sealed class PatronageService private readonly DiscordSocketClient _client; private readonly ISubscriptionHandler _subsHandler; private readonly IEmbedBuilderService _eb; - private readonly ConnectionMultiplexer _redis; - private readonly IBotCredentials _creds; - private readonly TypedKey _quotaKey; + private static readonly TypedKey _quotaKey + = new($"quota:last_hourly_reset"); + + private readonly IBotCache _cache; public PatronageService( PatronageConfig pConf, @@ -41,18 +42,14 @@ public sealed class PatronageService DiscordSocketClient client, ISubscriptionHandler subsHandler, IEmbedBuilderService eb, - ConnectionMultiplexer redis, - IBotCredentials creds) + IBotCache cache) { _pConf = pConf; _db = db; _client = client; _subsHandler = subsHandler; _eb = eb; - _redis = redis; - _creds = creds; - - _quotaKey = new TypedKey($"{_creds.RedisKey()}:quota:last_hourly_reset"); + _cache = cache; } public Task OnReadyAsync() @@ -101,11 +98,10 @@ public sealed class PatronageService var now = DateTime.UtcNow; var lastRun = DateTime.MinValue; - var rdb = _redis.GetDatabase(); - var lastVal = await rdb.StringGetAsync(_quotaKey.Key); - if (lastVal != default) + var result = await _cache.GetAsync(_quotaKey); + if (result.TryGetValue(out var lastVal) && lastVal != default) { - lastRun = DateTime.FromBinary((long)lastVal); + lastRun = DateTime.FromBinary(lastVal); } var nowDate = now.ToDateOnly(); @@ -130,8 +126,6 @@ public sealed class PatronageService HourlyCount = 0, DailyCount = 0, }); - - await rdb.StringSetAsync(_quotaKey.Key, true); } else if (now.Hour != lastRun.Hour) // if it's not, just reset hourly quotas { @@ -143,7 +137,7 @@ public sealed class PatronageService } // assumes that the code above runs in less than an hour - await rdb.StringSetAsync(_quotaKey.Key, now.ToBinary()); + await _cache.AddAsync(_quotaKey, now.ToBinary()); await tran.CommitAsync(); } catch (Exception ex) diff --git a/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs b/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs index e1acdb97e..69b4345bf 100644 --- a/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs +++ b/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs @@ -1,23 +1,24 @@ #nullable disable using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Modules.Utility.Common; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Utility.Services; public class ConverterService : INService, IReadyExecutor { - public ConvertUnit[] Units - => _cache.Redis.GetDatabase().StringGet("converter_units").ToString().MapJson(); + private static readonly TypedKey> _convertKey = + new("convert:units"); private readonly TimeSpan _updateInterval = new(12, 0, 0); private readonly DiscordSocketClient _client; - private readonly IDataCache _cache; + private readonly IBotCache _cache; private readonly IHttpClientFactory _httpFactory; public ConverterService( DiscordSocketClient client, - IDataCache cache, + IBotCache cache, IHttpClientFactory factory) { _client = client; @@ -48,7 +49,7 @@ public class ConverterService : INService, IReadyExecutor { using var http = _httpFactory.CreateClient(); var res = await http.GetStringAsync("https://convertapi.nadeko.bot/latest"); - return JsonConvert.DeserializeObject(res); + return JsonSerializer.Deserialize(res); } private async Task UpdateCurrency() @@ -61,29 +62,38 @@ public class ConverterService : INService, IReadyExecutor Modifier = decimal.One, UnitType = unitTypeString }; - var range = currencyRates.ConversionRates.Select(u => new ConvertUnit + var units = currencyRates.ConversionRates.Select(u => new ConvertUnit { Triggers = new[] { u.Key }, Modifier = u.Value, UnitType = unitTypeString }) - .ToArray(); + .ToList(); - var fileData = JsonConvert.DeserializeObject(File.ReadAllText("data/units.json")) - ?.Where(x => x.UnitType != "currency"); - if (fileData is null) - return; - - var data = JsonConvert.SerializeObject(range.Append(baseType).Concat(fileData).ToList()); - _cache.Redis.GetDatabase().StringSet("converter_units", data); + var stream = File.OpenRead("data/units.json"); + var defaultUnits = await JsonSerializer.DeserializeAsync(stream); + if(defaultUnits is not null) + units.AddRange(defaultUnits); + + units.Add(baseType); + + await _cache.AddAsync(_convertKey, units); } + + public async Task> GetUnitsAsync() + => (await _cache.GetAsync(_convertKey)).TryGetValue(out var list) + ? list + : Array.Empty(); } public class Rates { + [JsonPropertyName("base")] public string Base { get; set; } + + [JsonPropertyName("date")] public DateTime Date { get; set; } - [JsonProperty("rates")] + [JsonPropertyName("rates")] public Dictionary ConversionRates { get; set; } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/UnitConversion/UnitConversionCommands.cs b/src/NadekoBot/Modules/Utility/UnitConversion/UnitConversionCommands.cs index 1d2d8ff6c..54875203e 100644 --- a/src/NadekoBot/Modules/Utility/UnitConversion/UnitConversionCommands.cs +++ b/src/NadekoBot/Modules/Utility/UnitConversion/UnitConversionCommands.cs @@ -11,7 +11,7 @@ public partial class Utility [Cmd] public async partial Task ConvertList() { - var units = _service.Units; + var units = await _service.GetUnitsAsync(); var embed = _eb.Create().WithTitle(GetText(strs.convertlist)).WithOkColor(); @@ -29,9 +29,10 @@ public partial class Utility [Priority(0)] public async partial Task Convert(string origin, string target, decimal value) { - var originUnit = _service.Units.FirstOrDefault(x + var units = await _service.GetUnitsAsync(); + var originUnit = units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(origin.ToUpperInvariant())); - var targetUnit = _service.Units.FirstOrDefault(x + var targetUnit = units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(target.ToUpperInvariant())); if (originUnit is null || targetUnit is null) { diff --git a/src/NadekoBot/Modules/Xp/XpService.cs b/src/NadekoBot/Modules/Xp/XpService.cs index ca3b611df..58b38125e 100644 --- a/src/NadekoBot/Modules/Xp/XpService.cs +++ b/src/NadekoBot/Modules/Xp/XpService.cs @@ -23,10 +23,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand public const int XP_REQUIRED_LVL_1 = 36; private readonly DbService _db; - private readonly CommandHandler _cmd; private readonly IImageCache _images; private readonly IBotStrings _strings; - private readonly IDataCache _cache; private readonly FontProvider _fonts; private readonly IBotCredentials _creds; private readonly ICurrencyService _cs; @@ -45,14 +43,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand private readonly TypedKey _xpTemplateReloadKey; private readonly IPatronageService _ps; + private readonly IBotCache _c; public XpService( DiscordSocketClient client, - CommandHandler cmd, Bot bot, DbService db, IBotStrings strings, - IDataCache cache, + IImageCache images, + IBotCache c, FontProvider fonts, IBotCredentials creds, ICurrencyService cs, @@ -63,10 +62,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand IPatronageService ps) { _db = db; - _cmd = cmd; - _images = cache.LocalImages; + _images = images; _strings = strings; - _cache = cache; _fonts = fonts; _creds = creds; _cs = cs; @@ -79,6 +76,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand _client = client; _xpTemplateReloadKey = new("xp.template.reload"); _ps = ps; + _c = c; InternalReloadXpTemplate(); @@ -453,10 +451,10 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand private Task Client_OnGuildAvailable(SocketGuild guild) { - Task.Run(() => + Task.Run(async () => { foreach (var channel in guild.VoiceChannels) - ScanChannelForVoiceXp(channel); + await ScanChannelForVoiceXp(channel); }); return Task.CompletedTask; @@ -467,33 +465,33 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand if (socketUser is not SocketGuildUser user || user.IsBot) return Task.CompletedTask; - _ = Task.Run(() => + _ = Task.Run(async () => { if (before.VoiceChannel is not null) - ScanChannelForVoiceXp(before.VoiceChannel); + await ScanChannelForVoiceXp(before.VoiceChannel); if (after.VoiceChannel is not null && after.VoiceChannel != before.VoiceChannel) - ScanChannelForVoiceXp(after.VoiceChannel); + await ScanChannelForVoiceXp(after.VoiceChannel); else if (after.VoiceChannel is null) // In this case, the user left the channel and the previous for loops didn't catch // it because it wasn't in any new channel. So we need to get rid of it. - UserLeftVoiceChannel(user, before.VoiceChannel); + await UserLeftVoiceChannel(user, before.VoiceChannel); }); return Task.CompletedTask; } - private void ScanChannelForVoiceXp(SocketVoiceChannel channel) + private async Task ScanChannelForVoiceXp(SocketVoiceChannel channel) { if (ShouldTrackVoiceChannel(channel)) { foreach (var user in channel.Users) - ScanUserForVoiceXp(user, channel); + await ScanUserForVoiceXp(user, channel); } else { foreach (var user in channel.Users) - UserLeftVoiceChannel(user, channel); + await UserLeftVoiceChannel(user, channel); } } @@ -502,12 +500,12 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand /// /// /// - private void ScanUserForVoiceXp(SocketGuildUser user, SocketVoiceChannel channel) + private async Task ScanUserForVoiceXp(SocketGuildUser user, SocketVoiceChannel channel) { if (UserParticipatingInVoiceChannel(user) && ShouldTrackXp(user, channel.Id)) - UserJoinedVoiceChannel(user); + await UserJoinedVoiceChannel(user); else - UserLeftVoiceChannel(user, channel); + await UserLeftVoiceChannel(user, channel); } private bool ShouldTrackVoiceChannel(SocketVoiceChannel channel) @@ -516,32 +514,31 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand private bool UserParticipatingInVoiceChannel(SocketGuildUser user) => !user.IsDeafened && !user.IsMuted && !user.IsSelfDeafened && !user.IsSelfMuted; - private void UserJoinedVoiceChannel(SocketGuildUser user) + private TypedKey GetVoiceXpKey(ulong userId) + => new($"xp:vc_join:{userId}"); + + private async Task UserJoinedVoiceChannel(SocketGuildUser user) { - var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; var value = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - _cache.Redis.GetDatabase() - .StringSet(key, - value, - TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), - when: When.NotExists); + await _c.AddAsync(GetVoiceXpKey(user.Id), + value, + TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), + overwrite: false); } - private void UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel) + private async Task UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel) { - var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; - var value = _cache.Redis.GetDatabase().StringGet(key); - _cache.Redis.GetDatabase().KeyDelete(key); + var key = GetVoiceXpKey(user.Id); + var result = await _c.GetAsync(key); + if (!await _c.RemoveAsync(key)) + return; // Allow for if this function gets called multiple times when a user leaves a channel. - if (value.IsNull) + if (!result.TryGetValue(out var unixTime)) return; - if (!value.TryParse(out long startUnixTime)) - return; - - var dateStart = DateTimeOffset.FromUnixTimeSeconds(startUnixTime); + var dateStart = DateTimeOffset.FromUnixTimeSeconds(unixTime); var dateEnd = DateTimeOffset.UtcNow; var minutes = (dateEnd - dateStart).TotalMinutes; var xp = _xpConfig.Data.VoiceXpPerMinute * minutes; @@ -577,7 +574,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand if (arg.Author is not SocketGuildUser user || user.IsBot) return Task.CompletedTask; - _ = Task.Run(() => + _ = Task.Run(async () => { if (!ShouldTrackXp(user, arg.Channel.Id)) return; @@ -593,7 +590,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand if (xp <= 0) return; - if (!SetUserRewarded(user.Id)) + if (!await SetUserRewardedAsync(user.Id)) return; _addMessageXp.Enqueue(new() @@ -650,16 +647,14 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand return Enumerable.Empty(); } - private bool SetUserRewarded(ulong userId) - { - var r = _cache.Redis.GetDatabase(); - var key = $"{_creds.RedisKey()}_user_xp_gain_{userId}"; + private static TypedKey GetUserRewKey(ulong userId) + => new($"xp:user_gain:{userId}"); - return r.StringSet(key, + private async Task SetUserRewardedAsync(ulong userId) + => await _c.AddAsync(GetUserRewKey(userId), true, - TimeSpan.FromMinutes(_xpConfig.Data.MessageXpCooldown), - when: When.NotExists); - } + expiry: TimeSpan.FromMinutes(_xpConfig.Data.MessageXpCooldown), + overwrite: false); public async Task GetUserStatsAsync(IGuildUser user) { @@ -782,7 +777,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand } }.WithFallbackFonts(_fonts.FallBackFonts); - using var img = Image.Load(_images.XpBackground, out var imageFormat); + using var img = Image.Load(await GetXpBackgroundAsync(stats.User.UserId), out var imageFormat); if (template.User.Name.Show) { var fontSize = (int)(template.User.Name.FontSize * 0.9); @@ -979,8 +974,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand { var avatarUrl = stats.User.RealAvatarUrl(); - var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); - if (!succ) + var result = await _c.GetImageDataAsync(avatarUrl); + if (!result.TryPickT0(out var data, out _)) { using (var http = _httpFactory.CreateClient()) { @@ -999,7 +994,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand } } - await _cache.SetImageDataAsync(avatarUrl, data); + await _c.SetImageDataAsync(avatarUrl, data); } using var toDraw = Image.Load(data); @@ -1033,7 +1028,13 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand return output; }); -// #if GLOBAL_NADEKO + private async Task GetXpBackgroundAsync(ulong userId) + { + var img = await _images.GetXpBackgroundImageAsync(); + return img; + } + + // #if GLOBAL_NADEKO private async Task DrawFrame(Image img, ulong userId) { var patron = await _ps.GetPatronAsync(userId); @@ -1103,8 +1104,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand try { var imgUrl = new Uri(stats.User.Club.ImageUrl); - var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); - if (!succ) + var result = await _c.GetImageDataAsync(imgUrl); + if (!result.TryPickT0(out var data, out _)) { using (var http = _httpFactory.CreateClient()) using (var temp = await http.GetAsync(imgUrl, HttpCompletionOption.ResponseHeadersRead)) @@ -1127,7 +1128,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand } } - await _cache.SetImageDataAsync(imgUrl, data); + await _c.SetImageDataAsync(imgUrl, data); } using var toDraw = Image.Load(data); diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index e8bc4b226..0966d4396 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -52,6 +52,7 @@ + diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs deleted file mode 100644 index 48829696e..000000000 --- a/src/NadekoBot/Services/IDataCache.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable disable -using StackExchange.Redis; - -namespace NadekoBot.Services; - -public interface IDataCache -{ - ConnectionMultiplexer Redis { get; } - IImageCache LocalImages { get; } - ILocalDataCache LocalData { get; } - - Task<(bool Success, byte[] Data)> TryGetImageDataAsync(Uri key); - Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key); - Task<(bool Success, string Data)> TryGetNovelDataAsync(string key); - Task SetImageDataAsync(Uri key, byte[] data); - Task SetAnimeDataAsync(string link, string data); - Task SetNovelDataAsync(string link, string data); - TimeSpan? AddTimelyClaim(ulong id, int period); - TimeSpan? TryAddRatelimit(ulong id, string name, int expireIn); - void RemoveAllTimelyClaims(); - bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time); - bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time); - bool TryGetEconomy(out string data); - void SetEconomy(string data); - - Task GetOrAddCachedDataAsync( - string key, - Func> factory, - TParam param, - TimeSpan expiry) - where TOut : class; - - DateTime GetLastCurrencyDecay(); - void SetLastCurrencyDecay(); -} \ No newline at end of file diff --git a/src/NadekoBot/Services/IImageCache.cs b/src/NadekoBot/Services/IImageCache.cs deleted file mode 100644 index 05666eef2..000000000 --- a/src/NadekoBot/Services/IImageCache.cs +++ /dev/null @@ -1,29 +0,0 @@ -#nullable disable -namespace NadekoBot.Services; - -public interface IImageCache -{ - ImageUrls ImageUrls { get; } - - IReadOnlyList Heads { get; } - IReadOnlyList Tails { get; } - - IReadOnlyList Dice { get; } - - IReadOnlyList SlotEmojis { get; } - IReadOnlyList Currency { get; } - - byte[] SlotBackground { get; } - - byte[] RategirlMatrix { get; } - byte[] RategirlDot { get; } - - byte[] XpBackground { get; } - - byte[] Rip { get; } - byte[] RipOverlay { get; } - - byte[] GetCard(string key); - - Task Reload(); -} \ No newline at end of file diff --git a/src/NadekoBot/Services/ILocalDataCache.cs b/src/NadekoBot/Services/ILocalDataCache.cs index 2bcbbf3b6..2e3d3edba 100644 --- a/src/NadekoBot/Services/ILocalDataCache.cs +++ b/src/NadekoBot/Services/ILocalDataCache.cs @@ -6,8 +6,8 @@ namespace NadekoBot.Services; public interface ILocalDataCache { - IReadOnlyDictionary Pokemons { get; } - IReadOnlyDictionary PokemonAbilities { get; } - IReadOnlyDictionary PokemonMap { get; } - TriviaQuestion[] TriviaQuestions { get; } + Task> GetPokemonsAsync(); + Task> GetPokemonAbilitiesAsync(); + Task GetTriviaQuestionsAsync(); + Task GetPokemonMapAsync(); } \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/BotCredsProvider.cs b/src/NadekoBot/Services/Impl/BotCredsProvider.cs index 84d212dd7..73fb19051 100644 --- a/src/NadekoBot/Services/Impl/BotCredsProvider.cs +++ b/src/NadekoBot/Services/Impl/BotCredsProvider.cs @@ -172,9 +172,10 @@ public sealed class BotCredsProvider : IBotCredsProvider if (File.Exists(CREDS_FILE_NAME)) { var creds = Yaml.Deserializer.Deserialize(File.ReadAllText(CREDS_FILE_NAME)); - if (creds.Version <= 4) + if (creds.Version <= 5) { - creds.Version = 5; + creds.Version = 6; + creds.BotCache = BotCacheImplemenation.Redis; File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds)); } } diff --git a/src/NadekoBot/Services/Impl/LocalDataCache.cs b/src/NadekoBot/Services/Impl/LocalDataCache.cs new file mode 100644 index 000000000..4714c7703 --- /dev/null +++ b/src/NadekoBot/Services/Impl/LocalDataCache.cs @@ -0,0 +1,82 @@ +using NadekoBot.Common.Pokemon; +using NadekoBot.Modules.Games.Common.Trivia; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NadekoBot.Services; + +public sealed class LocalDataCache : ILocalDataCache, INService +{ + private const string POKEMON_ABILITIES_FILE = "data/pokemon/pokemon_abilities.json"; + private const string POKEMON_LIST_FILE = "data/pokemon/pokemon_list.json"; + private const string POKEMON_MAP_PATH = "data/pokemon/name-id_map.json"; + private const string QUESTIONS_FILE = "data/trivia_questions.json"; + + private readonly IBotCache _cache; + + private readonly JsonSerializerOptions _opts = new JsonSerializerOptions() + { + AllowTrailingCommas = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true + }; + + public LocalDataCache(IBotCache cache) + => _cache = cache; + + private async Task GetOrCreateCachedDataAsync( + TypedKey key, + string fileName) + => await _cache.GetOrAddAsync(key, + async () => + { + if (!File.Exists(fileName)) + { + Log.Warning($"{fileName} is missing. Relevant data can't be loaded"); + return default; + } + + try + { + await using var stream = File.OpenRead(fileName); + return await JsonSerializer.DeserializeAsync(stream, _opts); + } + catch (Exception ex) + { + Log.Error(ex, + "Error reading {FileName} file: {ErrorMessage}", + fileName, + ex.Message); + + return default; + } + }); + + + private static TypedKey> _pokemonListKey + = new("pokemon:list"); + + public async Task?> GetPokemonsAsync() + => await GetOrCreateCachedDataAsync(_pokemonListKey, POKEMON_LIST_FILE); + + + private static TypedKey> _pokemonAbilitiesKey + = new("pokemon:abilities"); + + public async Task?> GetPokemonAbilitiesAsync() + => await GetOrCreateCachedDataAsync(_pokemonAbilitiesKey, POKEMON_ABILITIES_FILE); + + + private static TypedKey _pokeMapKey + = new("pokemon:ab_map"); + + public async Task GetPokemonMapAsync() + => await GetOrCreateCachedDataAsync(_pokeMapKey, POKEMON_MAP_PATH); + + + private static TypedKey _triviaKey + = new("trivia:questions"); + + public async Task GetTriviaQuestionsAsync() + => await GetOrCreateCachedDataAsync(_triviaKey, QUESTIONS_FILE); +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs deleted file mode 100644 index b857c668f..000000000 --- a/src/NadekoBot/Services/Impl/RedisCache.cs +++ /dev/null @@ -1,216 +0,0 @@ -#nullable disable -using Newtonsoft.Json; -using StackExchange.Redis; -using System.Net; - -namespace NadekoBot.Services; - -public class RedisCache : IDataCache -{ - public ConnectionMultiplexer Redis { get; } - - public IImageCache LocalImages { get; } - public ILocalDataCache LocalData { get; } - - private readonly string _redisKey; - private readonly EndPoint _redisEndpoint; - - private readonly object _timelyLock = new(); - - public RedisCache( - ConnectionMultiplexer redis, - IBotCredentials creds, - IImageCache imageCache, - ILocalDataCache dataCache) - { - Redis = redis; - _redisEndpoint = Redis.GetEndPoints().First(); - LocalImages = imageCache; - LocalData = dataCache; - _redisKey = creds.RedisKey(); - } - - // things here so far don't need the bot id - // because it's a good thing if different bots - // which are hosted on the same PC - // can re-use the same image/anime data - public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(Uri key) - { - var db = Redis.GetDatabase(); - byte[] x = await db.StringGetAsync("image_" + key); - return (x is not null, x); - } - - public Task SetImageDataAsync(Uri key, byte[] data) - { - var db = Redis.GetDatabase(); - return db.StringSetAsync("image_" + key, data); - } - - public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key) - { - var db = Redis.GetDatabase(); - string x = await db.StringGetAsync("anime_" + key); - return (x is not null, x); - } - - public Task SetAnimeDataAsync(string key, string data) - { - var db = Redis.GetDatabase(); - return db.StringSetAsync("anime_" + key, data, TimeSpan.FromHours(3)); - } - - public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key) - { - var db = Redis.GetDatabase(); - string x = await db.StringGetAsync("novel_" + key); - return (x is not null, x); - } - - public Task SetNovelDataAsync(string key, string data) - { - var db = Redis.GetDatabase(); - return db.StringSetAsync("novel_" + key, data, TimeSpan.FromHours(3)); - } - - public TimeSpan? AddTimelyClaim(ulong id, int period) - { - if (period == 0) - return null; - lock (_timelyLock) - { - var time = TimeSpan.FromHours(period); - var db = Redis.GetDatabase(); - if ((bool?)db.StringGet($"{_redisKey}_timelyclaim_{id}") is null) - { - db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time); - return null; - } - - return db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}"); - } - } - - public void RemoveAllTimelyClaims() - { - var server = Redis.GetServer(_redisEndpoint); - var db = Redis.GetDatabase(); - foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*")) - db.KeyDelete(k, CommandFlags.FireAndForget); - } - - public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time) - { - var db = Redis.GetDatabase(); - time = db.KeyTimeToLive($"{_redisKey}_affinity_{userId}"); - if (time is null) - { - time = TimeSpan.FromMinutes(30); - db.StringSet($"{_redisKey}_affinity_{userId}", true, time); - return true; - } - - return false; - } - - public bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time) - { - var db = Redis.GetDatabase(); - time = db.KeyTimeToLive($"{_redisKey}_divorce_{userId}"); - if (time is null) - { - time = TimeSpan.FromHours(6); - db.StringSet($"{_redisKey}_divorce_{userId}", true, time); - return true; - } - - return false; - } - - public Task SetStreamDataAsync(string url, string data) - { - var db = Redis.GetDatabase(); - return db.StringSetAsync($"{_redisKey}_stream_{url}", data, TimeSpan.FromHours(6)); - } - - public bool TryGetStreamData(string url, out string dataStr) - { - var db = Redis.GetDatabase(); - dataStr = db.StringGet($"{_redisKey}_stream_{url}"); - - return !string.IsNullOrWhiteSpace(dataStr); - } - - public TimeSpan? TryAddRatelimit(ulong id, string name, int expireIn) - { - var db = Redis.GetDatabase(); - if (db.StringSet($"{_redisKey}_ratelimit_{id}_{name}", - 0, // i don't use the value - TimeSpan.FromSeconds(expireIn), - when: When.NotExists)) - { - return null; - } - - return db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}"); - } - - public bool TryGetEconomy(out string data) - { - var db = Redis.GetDatabase(); - data = db.StringGet($"{_redisKey}_economy"); - if (data is not null) - return true; - - return false; - } - - public void SetEconomy(string data) - { - var db = Redis.GetDatabase(); - db.StringSet($"{_redisKey}_economy", data, TimeSpan.FromMinutes(3)); - } - - public async Task GetOrAddCachedDataAsync( - string key, - Func> factory, - TParam param, - TimeSpan expiry) - where TOut : class - { - var db = Redis.GetDatabase(); - - var data = await db.StringGetAsync(key); - if (!data.HasValue) - { - var obj = await factory(param); - - if (obj is null) - return default; - - await db.StringSetAsync(key, JsonConvert.SerializeObject(obj), expiry); - - return obj; - } - - return (TOut)JsonConvert.DeserializeObject(data, typeof(TOut)); - } - - public DateTime GetLastCurrencyDecay() - { - var db = Redis.GetDatabase(); - - var str = (string)db.StringGet($"{_redisKey}_last_currency_decay"); - if (string.IsNullOrEmpty(str)) - return DateTime.MinValue; - - return JsonConvert.DeserializeObject(str); - } - - public void SetLastCurrencyDecay() - { - var db = Redis.GetDatabase(); - - db.StringSet($"{_redisKey}_last_currency_decay", JsonConvert.SerializeObject(DateTime.UtcNow)); - } -} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/RedisImagesCache.cs b/src/NadekoBot/Services/Impl/RedisImagesCache.cs index 86bc8253e..7e6ce7586 100644 --- a/src/NadekoBot/Services/Impl/RedisImagesCache.cs +++ b/src/NadekoBot/Services/Impl/RedisImagesCache.cs @@ -1,297 +1,111 @@ -#nullable disable -using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Common.Yml; -using Newtonsoft.Json; -using StackExchange.Redis; +using NadekoBot.Common.Configs; namespace NadekoBot.Services; -public sealed class RedisImagesCache : IImageCache, IReadyExecutor +public sealed class ImagesConfig : ConfigServiceBase { - public enum ImageKeys + private const string PATH = "data/images.yml"; + + private static readonly TypedKey _changeKey = + new("config.images.updated"); + + public override string Name + => "images"; + + public ImagesConfig(IConfigSeria serializer, IPubSub pubSub) + : base(PATH, serializer, pubSub, _changeKey) { - CoinHeads, - CoinTails, - Dice, - SlotBg, - SlotEmojis, - Currency, - RategirlMatrix, - RategirlDot, - RipOverlay, - RipBg, - XpBg + } +} + +public interface IImageCache +{ + Task GetHeadsImageAsync(); + Task GetTailsImageAsync(); + Task GetCurrencyImageAsync(); + Task GetXpBackgroundImageAsync(); + Task GetRategirlBgAsync(); + Task GetRategirlDotAsync(); + Task GetDiceAsync(int num); + Task GetSlotEmojiAsync(int number); + Task GetSlotBgAsync(); + Task GetRipBgAsync(); + Task GetRipOverlayAsync(); +} + +public sealed class ImageCache : IImageCache, INService +{ + private readonly IBotCache _cache; + private readonly ImagesConfig _ic; + private readonly Random _rng; + private readonly IHttpClientFactory _httpFactory; + + public ImageCache( + IBotCache cache, + ImagesConfig ic, + IHttpClientFactory httpFactory) + { + _cache = cache; + _ic = ic; + _httpFactory = httpFactory; + _rng = new NadekoRandom(); } - private const string BASE_PATH = "data/"; - private const string CARDS_PATH = $"{BASE_PATH}images/cards"; + private static TypedKey GetImageKey(Uri url) + => new($"image:{url}"); - private IDatabase Db - => _con.GetDatabase(); - - public ImageUrls ImageUrls { get; private set; } - - public IReadOnlyList Heads - => GetByteArrayData(ImageKeys.CoinHeads); - - public IReadOnlyList Tails - => GetByteArrayData(ImageKeys.CoinTails); - - public IReadOnlyList Dice - => GetByteArrayData(ImageKeys.Dice); - - public IReadOnlyList SlotEmojis - => GetByteArrayData(ImageKeys.SlotEmojis); - - public IReadOnlyList Currency - => GetByteArrayData(ImageKeys.Currency); - - public byte[] SlotBackground - => GetByteData(ImageKeys.SlotBg); - - public byte[] RategirlMatrix - => GetByteData(ImageKeys.RategirlMatrix); - - public byte[] RategirlDot - => GetByteData(ImageKeys.RategirlDot); - - public byte[] XpBackground - => GetByteData(ImageKeys.XpBg); - - public byte[] Rip - => GetByteData(ImageKeys.RipBg); - - public byte[] RipOverlay - => GetByteData(ImageKeys.RipOverlay); - - private readonly ConnectionMultiplexer _con; - private readonly IBotCredentials _creds; - private readonly HttpClient _http; - private readonly string _imagesPath; - - public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds) - { - _con = con; - _creds = creds; - _http = new(); - _imagesPath = Path.Combine(BASE_PATH, "images.yml"); - - Migrate(); - - ImageUrls = Yaml.Deserializer.Deserialize(File.ReadAllText(_imagesPath)); - } - - public byte[] GetCard(string key) - // since cards are always local for now, don't cache them - => File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg")); - - public async Task OnReadyAsync() - { - if (await AllKeysExist()) - return; - - await Reload(); - } - - private void Migrate() - { - // migrate to yml - if (File.Exists(Path.Combine(BASE_PATH, "images.json"))) - { - var oldFilePath = Path.Combine(BASE_PATH, "images.json"); - var backupFilePath = Path.Combine(BASE_PATH, "images.json.backup"); - - var oldData = JsonConvert.DeserializeObject(File.ReadAllText(oldFilePath)); - - if (oldData is not null) + private async Task GetImageDataAsync(Uri url) + => await _cache.GetOrAddAsync( + GetImageKey(url), + async () => { - var newData = new ImageUrls - { - Coins = - new() - { - Heads = - oldData.Coins.Heads.Length == 1 - && oldData.Coins.Heads[0].ToString() - == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png" - ? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") } - : oldData.Coins.Heads, - Tails = oldData.Coins.Tails.Length == 1 - && oldData.Coins.Tails[0].ToString() - == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png" - ? new[] { new Uri("https://cdn.nadeko.bot/coins/tails3.png") } - : oldData.Coins.Tails - }, - Dice = oldData.Dice.Map(x => x.ToNewCdn()), - Currency = oldData.Currency.Map(x => x.ToNewCdn()), - Rategirl = - new() - { - Dot = oldData.Rategirl.Dot.ToNewCdn(), - Matrix = oldData.Rategirl.Matrix.ToNewCdn() - }, - Rip = new() - { - Bg = oldData.Rip.Bg.ToNewCdn(), - Overlay = oldData.Rip.Overlay.ToNewCdn() - }, - Slots = new() - { - Bg = new("https://cdn.nadeko.bot/slots/slots_bg.png"), - Emojis = new[] - { - "https://cdn.nadeko.bot/slots/0.png", "https://cdn.nadeko.bot/slots/1.png", - "https://cdn.nadeko.bot/slots/2.png", "https://cdn.nadeko.bot/slots/3.png", - "https://cdn.nadeko.bot/slots/4.png", "https://cdn.nadeko.bot/slots/5.png" - }.Map(x => new Uri(x)) - }, - Xp = new() - { - Bg = oldData.Xp.Bg.ToNewCdn() - }, - Version = 2 - }; - - File.Move(oldFilePath, backupFilePath, true); - File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(newData)); - } - } - - // removed numbers from slots - var localImageUrls = Yaml.Deserializer.Deserialize(File.ReadAllText(_imagesPath)); - if (localImageUrls.Version == 2) - { - localImageUrls.Version = 3; - File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(localImageUrls)); - } - - if (localImageUrls.Version == 3) - { - localImageUrls.Version = 4; - if (localImageUrls.Xp?.Bg.ToString() == "https://cdn.nadeko.bot/other/xp/bg.png") - localImageUrls.Xp.Bg = new("https://cdn.nadeko.bot/other/xp/bg_k.png"); - - File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(localImageUrls)); - } - } - - public async Task Reload() - { - ImageUrls = Yaml.Deserializer.Deserialize(await File.ReadAllTextAsync(_imagesPath)); - foreach (var key in GetAllKeys()) - { - switch (key) - { - case ImageKeys.CoinHeads: - await Load(key, ImageUrls.Coins.Heads); - break; - case ImageKeys.CoinTails: - await Load(key, ImageUrls.Coins.Tails); - break; - case ImageKeys.Dice: - await Load(key, ImageUrls.Dice); - break; - case ImageKeys.SlotBg: - await Load(key, ImageUrls.Slots.Bg); - break; - case ImageKeys.SlotEmojis: - await Load(key, ImageUrls.Slots.Emojis); - break; - case ImageKeys.Currency: - await Load(key, ImageUrls.Currency); - break; - case ImageKeys.RategirlMatrix: - await Load(key, ImageUrls.Rategirl.Matrix); - break; - case ImageKeys.RategirlDot: - await Load(key, ImageUrls.Rategirl.Dot); - break; - case ImageKeys.RipOverlay: - await Load(key, ImageUrls.Rip.Overlay); - break; - case ImageKeys.RipBg: - await Load(key, ImageUrls.Rip.Bg); - break; - case ImageKeys.XpBg: - await Load(key, ImageUrls.Xp.Bg); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - private async Task Load(ImageKeys key, Uri uri) - { - var data = await GetImageData(uri); - if (data is null) - return; - - await Db.StringSetAsync(GetRedisKey(key), data); - } - - private async Task Load(ImageKeys key, Uri[] uris) - { - await Db.KeyDeleteAsync(GetRedisKey(key)); - var imageData = await uris.Select(GetImageData).WhenAll(); - var vals = imageData.Where(x => x is not null).Select(x => (RedisValue)x).ToArray(); - - await Db.ListRightPushAsync(GetRedisKey(key), vals); - - if (uris.Length != vals.Length) - { - Log.Information( - "{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" - + "Some of the supplied URIs are either unavailable or invalid", - vals.Length, - uris.Length, - key); - } - } - - private async Task GetImageData(Uri uri) - { - if (uri.IsFile) - { - try - { - var bytes = await File.ReadAllBytesAsync(uri.LocalPath); + using var http = _httpFactory.CreateClient(); + var bytes = await http.GetByteArrayAsync(url); return bytes; - } - catch (Exception ex) - { - Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString()); - return null; - } - } + }, + expiry: 48.Hours()); - try - { - return await _http.GetByteArrayAsync(uri); - } - catch (Exception ex) - { - Log.Warning(ex, "Image url you provided is not a valid image: {Uri}", uri.ToString()); - return null; - } - } - - private async Task AllKeysExist() + private async Task GetRandomImageDataAsync(Uri[] urls) { - var tasks = await GetAllKeys().Select(x => Db.KeyExistsAsync(GetRedisKey(x))).WhenAll(); + if (urls.Length == 0) + return null; - return tasks.All(exist => exist); + var url = urls[_rng.Next(0, urls.Length)]; + + var data = await GetImageDataAsync(url); + return data; } - private IEnumerable GetAllKeys() - => Enum.GetValues(); + public Task GetHeadsImageAsync() + => GetRandomImageDataAsync(_ic.Data.Coins.Heads); - private byte[][] GetByteArrayData(ImageKeys key) - => Db.ListRange(GetRedisKey(key)).Map(x => (byte[])x); + public Task GetTailsImageAsync() + => GetRandomImageDataAsync(_ic.Data.Coins.Tails); - private byte[] GetByteData(ImageKeys key) - => Db.StringGet(GetRedisKey(key)); + public Task GetCurrencyImageAsync() + => GetRandomImageDataAsync(_ic.Data.Currency); - private RedisKey GetRedisKey(ImageKeys key) - => _creds.RedisKey() + "_image_" + key; -} \ No newline at end of file + public Task GetXpBackgroundImageAsync() + => GetImageDataAsync(_ic.Data.Xp.Bg); + + public Task GetRategirlBgAsync() + => GetImageDataAsync(_ic.Data.Rategirl.Matrix); + + public Task GetRategirlDotAsync() + => GetImageDataAsync(_ic.Data.Rategirl.Dot); + + public Task GetDiceAsync(int num) + => GetImageDataAsync(_ic.Data.Dice[num]); + + public Task GetSlotEmojiAsync(int number) + => GetImageDataAsync(_ic.Data.Slots.Emojis[number]); + + public Task GetSlotBgAsync() + => GetImageDataAsync(_ic.Data.Slots.Bg); + + public Task GetRipBgAsync() + => GetImageDataAsync(_ic.Data.Rip.Bg); + + public Task GetRipOverlayAsync() + => GetImageDataAsync(_ic.Data.Rip.Overlay); +} diff --git a/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs b/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs deleted file mode 100644 index d5ad8de3c..000000000 --- a/src/NadekoBot/Services/Impl/RedisLocalDataCache.cs +++ /dev/null @@ -1,90 +0,0 @@ -#nullable disable -using NadekoBot.Common.Pokemon; -using NadekoBot.Modules.Games.Common.Trivia; -using Newtonsoft.Json; -using StackExchange.Redis; - -namespace NadekoBot.Services; - -public class RedisLocalDataCache : ILocalDataCache -{ - private const string POKEMON_ABILITIES_FILE = "data/pokemon/pokemon_abilities.json"; - private const string POKEMON_LIST_FILE = "data/pokemon/pokemon_list.json"; - private const string POKEMON_MAP_PATH = "data/pokemon/name-id_map.json"; - private const string QUESTIONS_FILE = "data/trivia_questions.json"; - - public IReadOnlyDictionary Pokemons - { - get => Get>("pokemon_list"); - private init => Set("pokemon_list", value); - } - - public IReadOnlyDictionary PokemonAbilities - { - get => Get>("pokemon_abilities"); - private init => Set("pokemon_abilities", value); - } - - public TriviaQuestion[] TriviaQuestions - { - get => Get("trivia_questions"); - private init => Set("trivia_questions", value); - } - - public IReadOnlyDictionary PokemonMap - { - get => Get>("pokemon_map"); - private init => Set("pokemon_map", value); - } - - private readonly ConnectionMultiplexer _con; - private readonly IBotCredentials _creds; - - public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client) - { - _con = con; - _creds = creds; - var shardId = client.ShardId; - - if (shardId == 0) - { - if (!File.Exists(POKEMON_LIST_FILE)) - Log.Warning($"{POKEMON_LIST_FILE} is missing. Pokemon abilities not loaded"); - else - { - Pokemons = - JsonConvert.DeserializeObject>( - File.ReadAllText(POKEMON_LIST_FILE)); - } - - if (!File.Exists(POKEMON_ABILITIES_FILE)) - Log.Warning($"{POKEMON_ABILITIES_FILE} is missing. Pokemon abilities not loaded."); - else - { - PokemonAbilities = - JsonConvert.DeserializeObject>( - File.ReadAllText(POKEMON_ABILITIES_FILE)); - } - - try - { - TriviaQuestions = JsonConvert.DeserializeObject(File.ReadAllText(QUESTIONS_FILE)); - PokemonMap = JsonConvert.DeserializeObject(File.ReadAllText(POKEMON_MAP_PATH)) - ?.ToDictionary(x => x.Id, x => x.Name) - ?? new(); - } - catch (Exception ex) - { - Log.Error(ex, "Error loading local data"); - throw; - } - } - } - - private T Get(string key) - where T : class - => JsonConvert.DeserializeObject(_con.GetDatabase().StringGet($"{_creds.RedisKey()}_localdata_{key}")); - - private void Set(string key, object obj) - => _con.GetDatabase().StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj)); -} \ No newline at end of file diff --git a/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs b/src/NadekoBot/Services/strings/impl/MemoryBotStringsProvider.cs similarity index 89% rename from src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs rename to src/NadekoBot/Services/strings/impl/MemoryBotStringsProvider.cs index b8d570400..b75b94cf0 100644 --- a/src/NadekoBot/Services/strings/impl/LocalBotStringsProvider.cs +++ b/src/NadekoBot/Services/strings/impl/MemoryBotStringsProvider.cs @@ -1,13 +1,13 @@ #nullable disable namespace NadekoBot.Services; -public class LocalBotStringsProvider : IBotStringsProvider +public class MemoryBotStringsProvider : IBotStringsProvider { private readonly IStringsSource _source; private IReadOnlyDictionary> responseStrings; private IReadOnlyDictionary> commandStrings; - public LocalBotStringsProvider(IStringsSource source) + public MemoryBotStringsProvider(IStringsSource source) { _source = source; Reload(); diff --git a/src/NadekoBot/_Extensions/ArrayExtensions.cs b/src/NadekoBot/_Extensions/ArrayExtensions.cs index 27f628bbb..7300c45d3 100644 --- a/src/NadekoBot/_Extensions/ArrayExtensions.cs +++ b/src/NadekoBot/_Extensions/ArrayExtensions.cs @@ -32,7 +32,7 @@ public static class ArrayExtensions public static TOut[] Map(this TIn[] arr, Func f) => Array.ConvertAll(arr, x => f(x)); - public static IReadOnlyCollection Map(this IReadOnlyCollection col, Func f) + public static TOut[] Map(this IReadOnlyCollection col, Func f) { var toReturn = new TOut[col.Count]; diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 3ee2299ba..607bcdbba 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -37,8 +37,9 @@ public static class Extensions _ => throw new ArgumentOutOfRangeException(nameof(text)) }; - public static List GetGuildIds(this DiscordSocketClient client) - => client.Guilds.Select(x => x.Id).ToList(); + public static ulong[] GetGuildIds(this DiscordSocketClient client) + => client.Guilds + .Map(x => x.Id); /// /// Generates a string in the format HHH:mm if timespan is >= 2m. diff --git a/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs b/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs index 14c1966e4..a54af943f 100644 --- a/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs +++ b/src/NadekoBot/_Extensions/ServiceCollectionExtensions.cs @@ -9,10 +9,10 @@ namespace NadekoBot.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards) - => totalShards <= 1 + public static IServiceCollection AddBotStringsServices(this IServiceCollection services, BotCacheImplemenation botCache) + => botCache == BotCacheImplemenation.Memory ? services.AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() : services.AddSingleton() .AddSingleton() @@ -23,17 +23,6 @@ public static class ServiceCollectionExtensions services.Scan(x => x.FromCallingAssembly() .AddClasses(f => f.AssignableTo(typeof(ConfigServiceBase<>))) .AsSelfWithInterfaces()); - - // var baseType = typeof(ConfigServiceBase<>); - // - // foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed)) - // { - // if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType) - // { - // services.AddSingleton(type); - // services.AddSingleton(x => (IConfigService)x.GetRequiredService(type)); - // } - // } return services; } @@ -48,7 +37,7 @@ public static class ServiceCollectionExtensions .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(svc => svc.GetRequiredService()); @@ -65,10 +54,23 @@ public static class ServiceCollectionExtensions return services; } - public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions) + public static IServiceCollection AddCache(this IServiceCollection services, IBotCredentials creds) { - var conf = ConfigurationOptions.Parse(redisOptions); - services.AddSingleton(ConnectionMultiplexer.Connect(conf)); - return services; + if (creds.BotCache == BotCacheImplemenation.Redis) + { + var conf = ConfigurationOptions.Parse(creds.RedisOptions); + services.AddSingleton(ConnectionMultiplexer.Connect(conf)) + .AddSingleton() + .AddSingleton(); + } + else + { + services.AddSingleton() + .AddSingleton(); + + } + + return services + .AddBotStringsServices(creds.BotCache); } } \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/UserExtensions.cs b/src/NadekoBot/_Extensions/UserExtensions.cs index 8fc82c7a4..79da0c824 100644 --- a/src/NadekoBot/_Extensions/UserExtensions.cs +++ b/src/NadekoBot/_Extensions/UserExtensions.cs @@ -30,9 +30,9 @@ public static class UserExtensions => usr.AvatarId is null ? new(usr.GetDefaultAvatarUrl()) : new Uri(usr.GetAvatarUrl(ImageFormat.Auto, size)); // This method is only used for the xp card - public static Uri? RealAvatarUrl(this DiscordUser usr) + public static Uri RealAvatarUrl(this DiscordUser usr) => usr.AvatarId is null - ? null + ? new(CDN.GetDefaultUserAvatarUrl(ushort.Parse(usr.Discriminator))) : new Uri(usr.AvatarId.StartsWith("a_", StringComparison.InvariantCulture) ? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif" : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png"); diff --git a/src/NadekoBot/creds_example.yml b/src/NadekoBot/creds_example.yml index 4b8b8ac01..8b2b8d803 100644 --- a/src/NadekoBot/creds_example.yml +++ b/src/NadekoBot/creds_example.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 5 +version: 6 # Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/ token: '' # List of Ids of the users who have bot owner permissions @@ -9,6 +9,9 @@ ownerIds: [] usePrivilegedIntents: true # The number of shards that the bot will be running on. # Leave at 1 if you don't know what you're doing. +# +# note: If you are planning to have more than one shard, then you must change botCache to 'redis'. +# Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value. totalShards: 1 # Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it. # Then, go to APIs and Services -> Credentials and click Create credentials -> API key. @@ -53,7 +56,12 @@ patreon: botListToken: '' # Official cleverbot api key. cleverbotApiKey: '' +# Which cache implementation should bot use. +# 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. +# 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml +botCache: Memory # Redis connection string. Don't change if you don't know what you're doing. +# Only used if botCache is set to 'redis' redisOptions: localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password= # Database options. Don't change if you don't know what you're doing. Leave null for default values db: @@ -99,7 +107,7 @@ twitchClientSecret: # args: "NadekoBot.dll -- {0}" # Windows default # cmd: NadekoBot.exe -# args: {0} +# args: "{0}" restartCommand: cmd: args: diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index 221ffbedd..f8b929355 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -862,8 +862,6 @@ unsetmusicchannel: musicquality: - musicquality - mquality -imagesreload: -- imagesreload stringsreload: - stringsreload shardstats: diff --git a/src/NadekoBot/data/patron.yml b/src/NadekoBot/data/patron.yml index a331d6760..6485e1cdb 100644 --- a/src/NadekoBot/data/patron.yml +++ b/src/NadekoBot/data/patron.yml @@ -24,7 +24,7 @@ quotas: # Dictionary of commands with their respective quota data commands: cleverbot: - V: null + V: prune: X: PerHour: 1 diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index b74fe840b..3dd021fa1 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -1496,10 +1496,6 @@ musicquality: - "" - "High" - "Low" -imagesreload: - desc: "Reloads images bot is using. Safe to use even when bot is being used heavily." - args: - - "" stringsreload: desc: "Reloads localized bot strings." args: