diff --git a/src/EllieBot/Db/EllieContext.cs b/src/EllieBot/Db/EllieContext.cs new file mode 100644 index 0000000..d61d2f7 --- /dev/null +++ b/src/EllieBot/Db/EllieContext.cs @@ -0,0 +1,535 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using EllieBot.Db.Models; + +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace EllieBot.Db; + +public abstract class EllieContext : DbContext +{ + public DbSet GuildConfigs { get; set; } + + public DbSet Quotes { get; set; } + public DbSet Reminders { get; set; } + public DbSet SelfAssignableRoles { get; set; } + public DbSet MusicPlaylists { get; set; } + public DbSet Expressions { get; set; } + public DbSet CurrencyTransactions { get; set; } + public DbSet WaifuUpdates { get; set; } + public DbSet WaifuItem { get; set; } + public DbSet Warnings { get; set; } + public DbSet UserXpStats { get; set; } + public DbSet Clubs { get; set; } + public DbSet ClubBans { get; set; } + public DbSet ClubApplicants { get; set; } + + + //logging + public DbSet LogSettings { get; set; } + public DbSet IgnoredVoicePresenceCHannels { get; set; } + public DbSet IgnoredLogChannels { get; set; } + + public DbSet RotatingStatus { get; set; } + public DbSet Blacklist { get; set; } + public DbSet AutoCommands { get; set; } + public DbSet RewardedUsers { get; set; } + public DbSet PlantedCurrency { get; set; } + public DbSet BanTemplates { get; set; } + public DbSet DiscordPermOverrides { get; set; } + public DbSet DiscordUser { get; set; } + public DbSet MusicPlayerSettings { get; set; } + public DbSet Repeaters { get; set; } + public DbSet WaifuInfo { get; set; } + public DbSet ImageOnlyChannels { get; set; } + public DbSet AutoTranslateChannels { get; set; } + public DbSet AutoTranslateUsers { get; set; } + + public DbSet Permissions { get; set; } + + public DbSet BankUsers { get; set; } + + public DbSet ReactionRoles { get; set; } + + public DbSet Patrons { get; set; } + + public DbSet PatronQuotas { get; set; } + + public DbSet StreamOnlineMessages { get; set; } + + public DbSet StickyRoles { get; set; } + + public DbSet Todos { get; set; } + public DbSet TodosArchive { get; set; } + + // todo add guild colors + // public DbSet GuildColors { get; set; } + + + #region Mandatory Provider-Specific Values + + protected abstract string CurrencyTransactionOtherIdDefaultValue { get; } + + #endregion + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + #region QUOTES + + var quoteEntity = modelBuilder.Entity(); + quoteEntity.HasIndex(x => x.GuildId); + quoteEntity.HasIndex(x => x.Keyword); + + #endregion + + #region GuildConfig + + var configEntity = modelBuilder.Entity(); + configEntity.HasIndex(c => c.GuildId) + .IsUnique(); + + configEntity.Property(x => x.VerboseErrors) + .HasDefaultValue(true); + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.AntiSpamSetting); + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.AntiRaidSetting); + + modelBuilder.Entity() + .HasOne(x => x.AntiAltSetting) + .WithOne() + .HasForeignKey(x => x.GuildConfigId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasAlternateKey(x => new + { + x.GuildConfigId, + x.Url + }); + + modelBuilder.Entity().HasIndex(x => x.MessageId).IsUnique(); + + modelBuilder.Entity().HasIndex(x => x.ChannelId); + + configEntity.HasIndex(x => x.WarnExpireHours).IsUnique(false); + + #endregion + + #region streamrole + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.StreamRole); + + #endregion + + #region Self Assignable Roles + + var selfassignableRolesEntity = modelBuilder.Entity(); + + selfassignableRolesEntity.HasIndex(s => new + { + s.GuildId, + s.RoleId + }) + .IsUnique(); + + selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0); + + #endregion + + #region MusicPlaylists + + var musicPlaylistEntity = modelBuilder.Entity(); + + musicPlaylistEntity.HasMany(p => p.Songs).WithOne().OnDelete(DeleteBehavior.Cascade); + + #endregion + + #region Waifus + + var wi = modelBuilder.Entity(); + wi.HasOne(x => x.Waifu).WithOne(); + + wi.HasIndex(x => x.Price); + wi.HasIndex(x => x.ClaimerId); + // wi.HasMany(x => x.Items) + // .WithOne() + // .OnDelete(DeleteBehavior.Cascade); + + #endregion + + #region DiscordUser + + modelBuilder.Entity(du => + { + du.Property(x => x.IsClubAdmin) + .HasDefaultValue(false); + + du.Property(x => x.NotifyOnLevelUp) + .HasDefaultValue(XpNotificationLocation.None); + + du.Property(x => x.TotalXp) + .HasDefaultValue(0); + + du.Property(x => x.CurrencyAmount) + .HasDefaultValue(0); + + du.HasAlternateKey(w => w.UserId); + du.HasOne(x => x.Club) + .WithMany(x => x.Members) + .IsRequired(false) + .OnDelete(DeleteBehavior.NoAction); + + du.HasIndex(x => x.TotalXp); + du.HasIndex(x => x.CurrencyAmount); + du.HasIndex(x => x.UserId); + }); + + #endregion + + #region Warnings + + modelBuilder.Entity(warn => + { + warn.HasIndex(x => x.GuildId); + warn.HasIndex(x => x.UserId); + warn.HasIndex(x => x.DateAdded); + warn.Property(x => x.Weight).HasDefaultValue(1); + }); + + #endregion + + #region XpStats + + var xps = modelBuilder.Entity(); + xps.HasIndex(x => new + { + x.UserId, + x.GuildId + }) + .IsUnique(); + + xps.HasIndex(x => x.UserId); + xps.HasIndex(x => x.GuildId); + xps.HasIndex(x => x.Xp); + xps.HasIndex(x => x.AwardedXp); + + #endregion + + #region XpSettings + + modelBuilder.Entity().HasOne(x => x.GuildConfig).WithOne(x => x.XpSettings); + + #endregion + + #region XpRoleReward + + modelBuilder.Entity() + .HasIndex(x => new + { + x.XpSettingsId, + x.Level + }) + .IsUnique(); + + #endregion + + #region Club + + var ci = modelBuilder.Entity(); + ci.HasOne(x => x.Owner) + .WithOne() + .HasForeignKey(x => x.OwnerId) + .OnDelete(DeleteBehavior.SetNull); + + ci.HasIndex(x => new + { + x.Name + }) + .IsUnique(); + + #endregion + + #region ClubManytoMany + + modelBuilder.Entity() + .HasKey(t => new + { + t.ClubId, + t.UserId + }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Applicants); + + modelBuilder.Entity() + .HasKey(t => new + { + t.ClubId, + t.UserId + }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Bans); + + #endregion + + #region CurrencyTransactions + + modelBuilder.Entity(e => + { + e.HasIndex(x => x.UserId) + .IsUnique(false); + + e.Property(x => x.OtherId) + .HasDefaultValueSql(CurrencyTransactionOtherIdDefaultValue); + + e.Property(x => x.Type) + .IsRequired(); + + e.Property(x => x.Extra) + .IsRequired(); + }); + + #endregion + + #region Reminders + + modelBuilder.Entity().HasIndex(x => x.When); + + #endregion + + #region GroupName + + modelBuilder.Entity() + .HasIndex(x => new + { + x.GuildConfigId, + x.Number + }) + .IsUnique(); + + modelBuilder.Entity() + .HasOne(x => x.GuildConfig) + .WithMany(x => x.SelfAssignableRoleGroupNames) + .IsRequired(); + + #endregion + + #region BanTemplate + + modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); + modelBuilder.Entity() + .Property(x => x.PruneDays) + .HasDefaultValue(null) + .IsRequired(false); + + #endregion + + #region Perm Override + + modelBuilder.Entity() + .HasIndex(x => new + { + x.GuildId, + x.Command + }) + .IsUnique(); + + #endregion + + #region Music + + modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); + + modelBuilder.Entity().Property(x => x.Volume).HasDefaultValue(100); + + #endregion + + #region Reaction roles + + modelBuilder.Entity(rr2 => + { + rr2.HasIndex(x => x.GuildId) + .IsUnique(false); + + rr2.HasIndex(x => new + { + x.MessageId, + x.Emote + }) + .IsUnique(); + }); + + #endregion + + #region LogSettings + + modelBuilder.Entity(ls => ls.HasIndex(x => x.GuildId).IsUnique()); + + modelBuilder.Entity(ls => ls + .HasMany(x => x.LogIgnores) + .WithOne(x => x.LogSetting) + .OnDelete(DeleteBehavior.Cascade)); + + modelBuilder.Entity(ili => ili + .HasIndex(x => new + { + x.LogSettingId, + x.LogItemId, + x.ItemType + }) + .IsUnique()); + + #endregion + + modelBuilder.Entity(ioc => ioc.HasIndex(x => x.ChannelId).IsUnique()); + + var atch = modelBuilder.Entity(); + atch.HasIndex(x => x.GuildId).IsUnique(false); + + atch.HasIndex(x => x.ChannelId).IsUnique(); + + atch.HasMany(x => x.Users).WithOne(x => x.Channel).OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity(atu => atu.HasAlternateKey(x => new + { + x.ChannelId, + x.UserId + })); + + #region BANK + + modelBuilder.Entity(bu => bu.HasIndex(x => x.UserId).IsUnique()); + + #endregion + + + #region Patron + + // currency rewards + var pr = modelBuilder.Entity(); + pr.HasIndex(x => x.PlatformUserId).IsUnique(); + + // patrons + // patrons are not identified by their user id, but by their platform user id + // as multiple accounts (even maybe on different platforms) could have + // the same account connected to them + modelBuilder.Entity(pu => + { + pu.HasIndex(x => x.UniquePlatformUserId).IsUnique(); + pu.HasKey(x => x.UserId); + }); + + // quotes are per user id + modelBuilder.Entity(pq => + { + pq.HasIndex(x => x.UserId).IsUnique(false); + pq.HasKey(x => new + { + x.UserId, + x.FeatureType, + x.Feature + }); + }); + + #endregion + + #region Xp Item Shop + + modelBuilder.Entity( + x => + { + // user can own only one of each item + x.HasIndex(model => new + { + model.UserId, + model.ItemType, + model.ItemKey + }) + .IsUnique(); + }); + + #endregion + + #region AutoPublish + + modelBuilder.Entity(apc => apc + .HasIndex(x => x.GuildId) + .IsUnique()); + + #endregion + + #region GamblingStats + + modelBuilder.Entity(gs => gs + .HasIndex(x => x.Feature) + .IsUnique()); + + #endregion + + #region Sticky Roles + + modelBuilder.Entity(sr => sr.HasIndex(x => new + { + x.GuildId, + x.UserId + }).IsUnique()); + + #endregion + + + #region Giveaway + + modelBuilder.Entity() + .HasMany(x => x.Participants) + .WithOne() + .HasForeignKey(x => x.GiveawayId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity(gu => gu + .HasIndex(x => new + { + x.GiveawayId, + x.UserId + }) + .IsUnique()); + + #endregion + + #region Todo + + modelBuilder.Entity() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .HasIndex(x => x.UserId) + .IsUnique(false); + + modelBuilder.Entity() + .HasMany(x => x.Items) + .WithOne() + .HasForeignKey(x => x.ArchiveId) + .OnDelete(DeleteBehavior.Cascade); + + #endregion + } + +#if DEBUG + private static readonly ILoggerFactory _debugLoggerFactory = LoggerFactory.Create(x => x.AddConsole()); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseLoggerFactory(_debugLoggerFactory); +#endif +} \ No newline at end of file diff --git a/src/EllieBot/Db/EllieDbService.cs b/src/EllieBot/Db/EllieDbService.cs new file mode 100644 index 0000000..2a3382e --- /dev/null +++ b/src/EllieBot/Db/EllieDbService.cs @@ -0,0 +1,76 @@ +using LinqToDB.Common; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace EllieBot.Db; + +public sealed class EllieDbService : DbService +{ + private readonly IBotCredsProvider _creds; + + // these are props because creds can change at runtime + private string DbType => _creds.GetCreds().Db.Type.ToLowerInvariant().Trim(); + private string ConnString => _creds.GetCreds().Db.ConnectionString; + + public EllieDbService(IBotCredsProvider creds) + { + LinqToDBForEFTools.Initialize(); + Configuration.Linq.DisableQueryCache = true; + + _creds = creds; + } + + public override async Task SetupAsync() + { + var dbType = DbType; + var connString = ConnString; + + await using var context = CreateRawDbContext(dbType, connString); + + // make sure sqlite db is in wal journal mode + if (context is SqliteContext) + { + await context.Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL"); + } + + await context.Database.MigrateAsync(); + } + + public override EllieContext CreateRawDbContext(string dbType, string connString) + { + switch (dbType) + { + case "postgresql": + case "postgres": + case "pgsql": + return new PostgreSqlContext(connString); + case "mysql": + return new MysqlContext(connString); + case "sqlite": + return new SqliteContext(connString); + default: + throw new NotSupportedException($"The database provide type of '{dbType}' is not supported."); + } + } + + private EllieContext GetDbContextInternal() + { + var dbType = DbType; + var connString = ConnString; + + var context = CreateRawDbContext(dbType, connString); + if (context is SqliteContext) + { + var conn = context.Database.GetDbConnection(); + conn.Open(); + using var com = conn.CreateCommand(); + com.CommandText = "PRAGMA synchronous=OFF"; + com.ExecuteNonQuery(); + } + + return context; + } + + public override EllieContext GetDbContext() + => GetDbContextInternal(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/ClubExtensions.cs b/src/EllieBot/Db/Extensions/ClubExtensions.cs new file mode 100644 index 0000000..d8183fc --- /dev/null +++ b/src/EllieBot/Db/Extensions/ClubExtensions.cs @@ -0,0 +1,34 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class ClubExtensions +{ + private static IQueryable Include(this DbSet clubs) + => clubs.Include(x => x.Owner) + .Include(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Members) + .AsQueryable(); + + public static ClubInfo GetByOwner(this DbSet clubs, ulong userId) + => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId); + + public static ClubInfo GetByOwnerOrAdmin(this DbSet clubs, ulong userId) + => Include(clubs) + .FirstOrDefault(c => c.Owner.UserId == userId || c.Members.Any(u => u.UserId == userId && u.IsClubAdmin)); + + public static ClubInfo GetByMember(this DbSet clubs, ulong userId) + => Include(clubs).FirstOrDefault(c => c.Members.Any(u => u.UserId == userId)); + + public static ClubInfo GetByName(this DbSet clubs, string name) + => Include(clubs) + .FirstOrDefault(c => c.Name == name); + + public static List GetClubLeaderboardPage(this DbSet clubs, int page) + => clubs.AsNoTracking().OrderByDescending(x => x.Xp).Skip(page * 9).Take(9).ToList(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/CurrencyTransactionExtensions.cs b/src/EllieBot/Db/Extensions/CurrencyTransactionExtensions.cs new file mode 100644 index 0000000..69401b0 --- /dev/null +++ b/src/EllieBot/Db/Extensions/CurrencyTransactionExtensions.cs @@ -0,0 +1,20 @@ +#nullable disable +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class CurrencyTransactionExtensions +{ + public static Task> GetPageFor( + this DbSet set, + ulong userId, + int page) + => set.ToLinqToDBTable() + .Where(x => x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(15 * page) + .Take(15) + .ToListAsyncLinqToDB(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/DbExtensions.cs b/src/EllieBot/Db/Extensions/DbExtensions.cs new file mode 100644 index 0000000..fafade9 --- /dev/null +++ b/src/EllieBot/Db/Extensions/DbExtensions.cs @@ -0,0 +1,12 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class DbExtensions +{ + public static T GetById(this DbSet set, int id) + where T : DbEntity + => set.FirstOrDefault(x => x.Id == id); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/DiscordUserExtensions.cs b/src/EllieBot/Db/Extensions/DiscordUserExtensions.cs new file mode 100644 index 0000000..6d74316 --- /dev/null +++ b/src/EllieBot/Db/Extensions/DiscordUserExtensions.cs @@ -0,0 +1,125 @@ +#nullable disable +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class DiscordUserExtensions +{ + public static Task GetByUserIdAsync( + this IQueryable set, + ulong userId) + => set.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId); + + public static void EnsureUserCreated( + this DbContext ctx, + ulong userId, + string username, + string discrim, + string avatarId) + => ctx.GetTable() + .InsertOrUpdate( + () => new() + { + UserId = userId, + Username = username, + Discriminator = discrim, + AvatarId = avatarId, + TotalXp = 0, + CurrencyAmount = 0 + }, + old => new() + { + Username = username, + Discriminator = discrim, + AvatarId = avatarId + }, + () => new() + { + UserId = userId + }); + + public static Task EnsureUserCreatedAsync( + this DbContext ctx, + ulong userId) + => ctx.GetTable() + .InsertOrUpdateAsync( + () => new() + { + UserId = userId, + Username = "Unknown", + Discriminator = "????", + AvatarId = string.Empty, + TotalXp = 0, + CurrencyAmount = 0 + }, + old => new() + { + }, + () => new() + { + UserId = userId + }); + + //temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown + public static DiscordUser GetOrCreateUser( + this DbContext ctx, + ulong userId, + string username, + string discrim, + string avatarId, + Func, IQueryable> includes = null) + { + ctx.EnsureUserCreated(userId, username, discrim, avatarId); + + IQueryable queryable = ctx.Set(); + if (includes is not null) + queryable = includes(queryable); + return queryable.First(u => u.UserId == userId); + } + + + public static int GetUserGlobalRank(this DbSet users, ulong id) + => users.AsQueryable() + .Where(x => x.TotalXp + > users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault()) + .Count() + + 1; + + public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet users, int page, int perPage) + => users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * perPage).Take(perPage).AsEnumerable() + .ToArray(); + + public static Task> GetTopRichest( + this DbSet users, + ulong botId, + int page = 0, int perPage = 9) + => users.AsQueryable() + .Where(c => c.CurrencyAmount > 0 && botId != c.UserId) + .OrderByDescending(c => c.CurrencyAmount) + .Skip(page * perPage) + .Take(perPage) + .ToListAsyncLinqToDB(); + + public static async Task GetUserCurrencyAsync(this DbSet users, ulong userId) + => (await users.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId))?.CurrencyAmount ?? 0; + + public static void RemoveFromMany(this DbSet users, IEnumerable ids) + { + var items = users.AsQueryable().Where(x => ids.Contains(x.UserId)); + foreach (var item in items) + item.CurrencyAmount = 0; + } + + public static decimal GetTotalCurrency(this DbSet users) + => users.Sum((Func)(x => x.CurrencyAmount)); + + public static decimal GetTopOnePercentCurrency(this DbSet users, ulong botId) + => users.AsQueryable() + .Where(x => x.UserId != botId) + .OrderByDescending(x => x.CurrencyAmount) + .Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100) + .Sum(x => x.CurrencyAmount); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/EllieExpressionExtensions.cs b/src/EllieBot/Db/Extensions/EllieExpressionExtensions.cs new file mode 100644 index 0000000..02a3453 --- /dev/null +++ b/src/EllieBot/Db/Extensions/EllieExpressionExtensions.cs @@ -0,0 +1,15 @@ +#nullable disable +using LinqToDB; +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class EllieExpressionExtensions +{ + public static int ClearFromGuild(this DbSet exprs, ulong guildId) + => exprs.Delete(x => x.GuildId == guildId); + + public static IEnumerable ForId(this DbSet exprs, ulong id) + => exprs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/GuildConfigExtensions.cs b/src/EllieBot/Db/Extensions/GuildConfigExtensions.cs new file mode 100644 index 0000000..bffa943 --- /dev/null +++ b/src/EllieBot/Db/Extensions/GuildConfigExtensions.cs @@ -0,0 +1,227 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class GuildConfigExtensions +{ + private static List DefaultWarnPunishments + => new() + { + new() + { + Count = 3, + Punishment = PunishmentAction.Kick + }, + new() + { + Count = 5, + Punishment = PunishmentAction.Ban + } + }; + + /// + /// Gets full stream role settings for the guild with the specified id. + /// + /// Db Context + /// Id of the guild to get stream role settings for. + /// Guild'p stream role settings + public static StreamRoleSettings GetStreamRoleSettings(this DbContext ctx, ulong guildId) + { + var conf = ctx.GuildConfigsForId(guildId, + set => set.Include(y => y.StreamRole) + .Include(y => y.StreamRole.Whitelist) + .Include(y => y.StreamRole.Blacklist)); + + if (conf.StreamRole is null) + conf.StreamRole = new(); + + return conf.StreamRole; + } + + private static IQueryable IncludeEverything(this DbSet configs) + => configs.AsQueryable() + .AsSplitQuery() + .Include(gc => gc.CommandCooldowns) + .Include(gc => gc.FollowedStreams) + .Include(gc => gc.StreamRole) + .Include(gc => gc.XpSettings) + .ThenInclude(x => x.ExclusionList) + .Include(gc => gc.DelMsgOnCmdChannels); + + public static IEnumerable GetAllGuildConfigs( + this DbSet configs, + IReadOnlyList availableGuilds) + => configs.IncludeEverything().AsNoTracking().Where(x => availableGuilds.Contains(x.GuildId)).ToList(); + + /// + /// Gets and creates if it doesn't exist a config for a guild. + /// + /// Context + /// Id of the guide + /// Use to manipulate the set however you want. Pass null to include everything + /// Config for the guild + public static GuildConfig GuildConfigsForId( + this DbContext ctx, + ulong guildId, + Func, IQueryable> includes) + { + GuildConfig config; + + if (includes is null) + config = ctx.Set().IncludeEverything().FirstOrDefault(c => c.GuildId == guildId); + else + { + var set = includes(ctx.Set()); + config = set.FirstOrDefault(c => c.GuildId == guildId); + } + + if (config is null) + { + ctx.Set().Add(config = new() + { + GuildId = guildId, + Permissions = Permissionv2.GetDefaultPermlist, + WarningsInitialized = true, + WarnPunishments = DefaultWarnPunishments + }); + ctx.SaveChanges(); + } + + if (!config.WarningsInitialized) + { + config.WarningsInitialized = true; + config.WarnPunishments = DefaultWarnPunishments; + } + + return config; + + // ctx.GuildConfigs + // .ToLinqToDBTable() + // .InsertOrUpdate(() => new() + // { + // GuildId = guildId, + // Permissions = Permissionv2.GetDefaultPermlist, + // WarningsInitialized = true, + // WarnPunishments = DefaultWarnPunishments + // }, + // _ => new(), + // () => new() + // { + // GuildId = guildId + // }); + // + // if(includes is null) + // return ctx.GuildConfigs + // .ToLinqToDBTable() + // .First(x => x.GuildId == guildId); + } + + public static LogSetting LogSettingsFor(this DbContext ctx, ulong guildId) + { + var logSetting = ctx.Set() + .AsQueryable() + .Include(x => x.LogIgnores) + .Where(x => x.GuildId == guildId) + .FirstOrDefault(); + + if (logSetting is null) + { + ctx.Set() + .Add(logSetting = new() + { + GuildId = guildId + }); + ctx.SaveChanges(); + } + + return logSetting; + } + + public static IEnumerable PermissionsForAll(this DbSet configs, List include) + { + var query = configs.AsQueryable().Where(x => include.Contains(x.GuildId)).Include(gc => gc.Permissions); + + return query.ToList(); + } + + public static GuildConfig GcWithPermissionsFor(this DbContext ctx, ulong guildId) + { + var config = ctx.Set().AsQueryable() + .Where(gc => gc.GuildId == guildId) + .Include(gc => gc.Permissions) + .FirstOrDefault(); + + if (config is null) // if there is no guildconfig, create new one + { + ctx.Set().Add(config = new() + { + GuildId = guildId, + Permissions = Permissionv2.GetDefaultPermlist + }); + ctx.SaveChanges(); + } + else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones + { + config.Permissions = Permissionv2.GetDefaultPermlist; + ctx.SaveChanges(); + } + + return config; + } + + public static IEnumerable GetFollowedStreams(this DbSet configs) + => configs.AsQueryable().Include(x => x.FollowedStreams).SelectMany(gc => gc.FollowedStreams).ToArray(); + + public static IEnumerable GetFollowedStreams(this DbSet configs, List included) + => configs.AsQueryable() + .Where(gc => included.Contains(gc.GuildId)) + .Include(gc => gc.FollowedStreams) + .SelectMany(gc => gc.FollowedStreams) + .ToList(); + + public static void SetCleverbotEnabled(this DbSet configs, ulong id, bool cleverbotEnabled) + { + var conf = configs.FirstOrDefault(gc => gc.GuildId == id); + + if (conf is null) + return; + + conf.CleverbotEnabled = cleverbotEnabled; + } + + public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId) + { + var gc = ctx.GuildConfigsForId(guildId, + set => set.Include(x => x.XpSettings) + .ThenInclude(x => x.RoleRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.CurrencyRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.ExclusionList)); + + if (gc.XpSettings is null) + gc.XpSettings = new(); + + return gc.XpSettings; + } + + public static IEnumerable GetGeneratingChannels(this DbSet configs) + => configs.AsQueryable() + .Include(x => x.GenerateCurrencyChannelIds) + .Where(x => x.GenerateCurrencyChannelIds.Any()) + .SelectMany(x => x.GenerateCurrencyChannelIds) + .Select(x => new GeneratingChannel + { + ChannelId = x.ChannelId, + GuildId = x.GuildConfig.GuildId + }) + .ToArray(); + + public class GeneratingChannel + { + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/QuoteExtensions.cs b/src/EllieBot/Db/Extensions/QuoteExtensions.cs new file mode 100644 index 0000000..67698e9 --- /dev/null +++ b/src/EllieBot/Db/Extensions/QuoteExtensions.cs @@ -0,0 +1,53 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class QuoteExtensions +{ + public static IEnumerable GetForGuild(this DbSet quotes, ulong guildId) + => quotes.AsQueryable().Where(x => x.GuildId == guildId); + + public static IReadOnlyCollection GetGroup( + this DbSet quotes, + ulong guildId, + int page, + OrderType order) + { + var q = quotes.AsQueryable().Where(x => x.GuildId == guildId); + if (order == OrderType.Keyword) + q = q.OrderBy(x => x.Keyword); + else + q = q.OrderBy(x => x.Id); + + return q.Skip(15 * page).Take(15).ToArray(); + } + + public static async Task GetRandomQuoteByKeywordAsync( + this DbSet quotes, + ulong guildId, + string keyword) + { + return (await quotes.AsQueryable().Where(q => q.GuildId == guildId && q.Keyword == keyword).ToArrayAsync()) + .RandomOrDefault(); + } + + public static async Task SearchQuoteKeywordTextAsync( + this DbSet quotes, + ulong guildId, + string keyword, + string text) + { + return (await quotes.AsQueryable() + .Where(q => q.GuildId == guildId + && (keyword == null || q.Keyword == keyword) + && (EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%") + || EF.Functions.Like(q.AuthorName, text))) + .ToArrayAsync()) + .RandomOrDefault(); + } + + public static void RemoveAllByKeyword(this DbSet quotes, ulong guildId, string keyword) + => quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword)); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/ReminderExtensions.cs b/src/EllieBot/Db/Extensions/ReminderExtensions.cs new file mode 100644 index 0000000..8a7d992 --- /dev/null +++ b/src/EllieBot/Db/Extensions/ReminderExtensions.cs @@ -0,0 +1,23 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class ReminderExtensions +{ + public static IEnumerable GetIncludedReminders( + this DbSet reminders, + IEnumerable guildIds) + => reminders.AsQueryable().Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0).ToList(); + + public static IEnumerable RemindersFor(this DbSet reminders, ulong userId, int page) + => reminders.AsQueryable().Where(x => x.UserId == userId).OrderBy(x => x.DateAdded).Skip(page * 10).Take(10); + + public static IEnumerable RemindersForServer(this DbSet reminders, ulong serverId, int page) + => reminders.AsQueryable() + .Where(x => x.ServerId == serverId) + .OrderBy(x => x.DateAdded) + .Skip(page * 10) + .Take(10); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/SelfAssignableRolesExtensions.cs b/src/EllieBot/Db/Extensions/SelfAssignableRolesExtensions.cs new file mode 100644 index 0000000..740a155 --- /dev/null +++ b/src/EllieBot/Db/Extensions/SelfAssignableRolesExtensions.cs @@ -0,0 +1,22 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class SelfAssignableRolesExtensions +{ + public static bool DeleteByGuildAndRoleId(this DbSet roles, ulong guildId, ulong roleId) + { + var role = roles.FirstOrDefault(s => s.GuildId == guildId && s.RoleId == roleId); + + if (role is null) + return false; + + roles.Remove(role); + return true; + } + + public static IReadOnlyCollection GetFromGuild(this DbSet roles, ulong guildId) + => roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/UserXpExtensions.cs b/src/EllieBot/Db/Extensions/UserXpExtensions.cs new file mode 100644 index 0000000..c7e0f8c --- /dev/null +++ b/src/EllieBot/Db/Extensions/UserXpExtensions.cs @@ -0,0 +1,71 @@ +#nullable disable +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class UserXpExtensions +{ + public static UserXpStats GetOrCreateUserXpStats(this DbContext ctx, ulong guildId, ulong userId) + { + var usr = ctx.Set().FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId); + + if (usr is null) + { + ctx.Add(usr = new() + { + Xp = 0, + UserId = userId, + NotifyOnLevelUp = XpNotificationLocation.None, + GuildId = guildId + }); + } + + return usr; + } + + public static List GetUsersFor(this DbSet xps, ulong guildId, int page) + => xps.AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .OrderByDescending(x => x.Xp + x.AwardedXp) + .Skip(page * 9) + .Take(9) + .ToList(); + + public static List GetTopUserXps(this DbSet xps, ulong guildId, int count) + => xps.AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId) + .OrderByDescending(x => x.Xp + x.AwardedXp) + .Take(count) + .ToList(); + + public static int GetUserGuildRanking(this DbSet xps, ulong userId, ulong guildId) + => xps.AsQueryable() + .AsNoTracking() + .Where(x => x.GuildId == guildId + && x.Xp + x.AwardedXp + > xps.AsQueryable() + .Where(y => y.UserId == userId && y.GuildId == guildId) + .Select(y => y.Xp + y.AwardedXp) + .FirstOrDefault()) + .Count() + + 1; + + public static void ResetGuildUserXp(this DbSet xps, ulong userId, ulong guildId) + => xps.Delete(x => x.UserId == userId && x.GuildId == guildId); + + public static void ResetGuildXp(this DbSet xps, ulong guildId) + => xps.Delete(x => x.GuildId == guildId); + + public static async Task GetLevelDataFor(this ITable userXp, ulong guildId, ulong userId) + => await userXp + .Where(x => x.GuildId == guildId && x.UserId == userId) + .FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs + ? new(uxs.Xp + uxs.AwardedXp) + : new(0); + +} \ No newline at end of file diff --git a/src/EllieBot/Db/Extensions/WarningExtensions.cs b/src/EllieBot/Db/Extensions/WarningExtensions.cs new file mode 100644 index 0000000..c223f7d --- /dev/null +++ b/src/EllieBot/Db/Extensions/WarningExtensions.cs @@ -0,0 +1,60 @@ +#nullable disable +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public static class WarningExtensions +{ + public static Warning[] ForId(this DbSet warnings, ulong guildId, ulong userId) + { + var query = warnings.AsQueryable() + .Where(x => x.GuildId == guildId && x.UserId == userId) + .OrderByDescending(x => x.DateAdded); + + return query.ToArray(); + } + + public static bool Forgive( + this DbSet warnings, + ulong guildId, + ulong userId, + string mod, + int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + + var warn = warnings.AsQueryable() + .Where(x => x.GuildId == guildId && x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(index) + .FirstOrDefault(); + + if (warn is null || warn.Forgiven) + return false; + + warn.Forgiven = true; + warn.ForgivenBy = mod; + return true; + } + + public static async Task ForgiveAll( + this DbSet warnings, + ulong guildId, + ulong userId, + string mod) + => await warnings.AsQueryable() + .Where(x => x.GuildId == guildId && x.UserId == userId) + .ForEachAsync(x => + { + if (x.Forgiven != true) + { + x.Forgiven = true; + x.ForgivenBy = mod; + } + }); + + public static Warning[] GetForGuild(this DbSet warnings, ulong id) + => warnings.AsQueryable().Where(x => x.GuildId == id).ToArray(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Helpers/ActivityType.cs b/src/EllieBot/Db/Helpers/ActivityType.cs new file mode 100644 index 0000000..64bdd73 --- /dev/null +++ b/src/EllieBot/Db/Helpers/ActivityType.cs @@ -0,0 +1,22 @@ +namespace EllieBot.Db; + +public enum DbActivityType +{ + /// The user is playing a game. + Playing, + + /// The user is streaming online. + Streaming, + + /// The user is listening to a song. + Listening, + + /// The user is watching some form of media. + Watching, + + /// The user has set a custom status. + CustomStatus, + + /// The user is competing in a game. + Competing, +} \ No newline at end of file diff --git a/src/EllieBot/Db/Helpers/GuildPerm.cs b/src/EllieBot/Db/Helpers/GuildPerm.cs new file mode 100644 index 0000000..4713afa --- /dev/null +++ b/src/EllieBot/Db/Helpers/GuildPerm.cs @@ -0,0 +1,47 @@ +namespace EllieBot.Db; + +[Flags] +public enum GuildPerm : ulong +{ + CreateInstantInvite = 1, + KickMembers = 2, + BanMembers = 4, + Administrator = 8, + ManageChannels = 16, // 0x0000000000000010 + ManageGuild = 32, // 0x0000000000000020 + ViewGuildInsights = 524288, // 0x0000000000080000 + AddReactions = 64, // 0x0000000000000040 + ViewAuditLog = 128, // 0x0000000000000080 + ViewChannel = 1024, // 0x0000000000000400 + SendMessages = 2048, // 0x0000000000000800 + SendTTSMessages = 4096, // 0x0000000000001000 + ManageMessages = 8192, // 0x0000000000002000 + EmbedLinks = 16384, // 0x0000000000004000 + AttachFiles = 32768, // 0x0000000000008000 + ReadMessageHistory = 65536, // 0x0000000000010000 + MentionEveryone = 131072, // 0x0000000000020000 + UseExternalEmojis = 262144, // 0x0000000000040000 + Connect = 1048576, // 0x0000000000100000 + Speak = 2097152, // 0x0000000000200000 + MuteMembers = 4194304, // 0x0000000000400000 + DeafenMembers = 8388608, // 0x0000000000800000 + MoveMembers = 16777216, // 0x0000000001000000 + UseVAD = 33554432, // 0x0000000002000000 + PrioritySpeaker = 256, // 0x0000000000000100 + Stream = 512, // 0x0000000000000200 + ChangeNickname = 67108864, // 0x0000000004000000 + ManageNicknames = 134217728, // 0x0000000008000000 + ManageRoles = 268435456, // 0x0000000010000000 + ManageWebhooks = 536870912, // 0x0000000020000000 + ManageEmojisAndStickers = 1073741824, // 0x0000000040000000 + UseApplicationCommands = 2147483648, // 0x0000000080000000 + RequestToSpeak = 4294967296, // 0x0000000100000000 + ManageEvents = 8589934592, // 0x0000000200000000 + ManageThreads = 17179869184, // 0x0000000400000000 + CreatePublicThreads = 34359738368, // 0x0000000800000000 + CreatePrivateThreads = 68719476736, // 0x0000001000000000 + UseExternalStickers = 137438953472, // 0x0000002000000000 + SendMessagesInThreads = 274877906944, // 0x0000004000000000 + StartEmbeddedActivities = 549755813888, // 0x0000008000000000 + ModerateMembers = 1099511627776, // 0x0000010000000000 +} diff --git a/src/EllieBot/Db/LevelStats.cs b/src/EllieBot/Db/LevelStats.cs new file mode 100644 index 0000000..fbb2e47 --- /dev/null +++ b/src/EllieBot/Db/LevelStats.cs @@ -0,0 +1,40 @@ +#nullable disable +namespace EllieBot.Db; + +public readonly struct LevelStats +{ + public const int XP_REQUIRED_LVL_1 = 36; + + public long Level { get; } + public long LevelXp { get; } + public long RequiredXp { get; } + public long TotalXp { get; } + + public LevelStats(long xp) + { + if (xp < 0) + xp = 0; + + TotalXp = xp; + + const int baseXp = XP_REQUIRED_LVL_1; + + var required = baseXp; + var totalXp = 0; + var lvl = 1; + while (true) + { + required = (int)(baseXp + (baseXp / 4.0 * (lvl - 1))); + + if (required + totalXp > xp) + break; + + totalXp += required; + lvl++; + } + + Level = lvl - 1; + LevelXp = xp - totalXp; + RequiredXp = required; + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/AutoCommand.cs b/src/EllieBot/Db/Models/AutoCommand.cs new file mode 100644 index 0000000..f412448 --- /dev/null +++ b/src/EllieBot/Db/Models/AutoCommand.cs @@ -0,0 +1,14 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class AutoCommand : DbEntity +{ + public string CommandText { get; set; } + public ulong ChannelId { get; set; } + public string ChannelName { get; set; } + public ulong? GuildId { get; set; } + public string GuildName { get; set; } + public ulong? VoiceChannelId { get; set; } + public string VoiceChannelName { get; set; } + public int Interval { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/AutoPublishChannel.cs b/src/EllieBot/Db/Models/AutoPublishChannel.cs new file mode 100644 index 0000000..c632776 --- /dev/null +++ b/src/EllieBot/Db/Models/AutoPublishChannel.cs @@ -0,0 +1,7 @@ +namespace EllieBot.Db.Models; + +public class AutoPublishChannel : DbEntity +{ + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/AutoTranslateChannel.cs b/src/EllieBot/Db/Models/AutoTranslateChannel.cs new file mode 100644 index 0000000..33423c1 --- /dev/null +++ b/src/EllieBot/Db/Models/AutoTranslateChannel.cs @@ -0,0 +1,10 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class AutoTranslateChannel : DbEntity +{ + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public bool AutoDelete { get; set; } + public IList Users { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/AutoTranslateUser.cs b/src/EllieBot/Db/Models/AutoTranslateUser.cs new file mode 100644 index 0000000..459d58b --- /dev/null +++ b/src/EllieBot/Db/Models/AutoTranslateUser.cs @@ -0,0 +1,11 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class AutoTranslateUser : DbEntity +{ + public int ChannelId { get; set; } + public AutoTranslateChannel Channel { get; set; } + public ulong UserId { get; set; } + public string Source { get; set; } + public string Target { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/BlacklistEntry.cs b/src/EllieBot/Db/Models/BlacklistEntry.cs new file mode 100644 index 0000000..d457e8a --- /dev/null +++ b/src/EllieBot/Db/Models/BlacklistEntry.cs @@ -0,0 +1,15 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class BlacklistEntry : DbEntity +{ + public ulong ItemId { get; set; } + public BlacklistType Type { get; set; } +} + +public enum BlacklistType +{ + Server, + Channel, + User +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/CommandAlias.cs b/src/EllieBot/Db/Models/CommandAlias.cs new file mode 100644 index 0000000..28f614a --- /dev/null +++ b/src/EllieBot/Db/Models/CommandAlias.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class CommandAlias : DbEntity +{ + public string Trigger { get; set; } + public string Mapping { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/CommandCooldown.cs b/src/EllieBot/Db/Models/CommandCooldown.cs new file mode 100644 index 0000000..e12ef9c --- /dev/null +++ b/src/EllieBot/Db/Models/CommandCooldown.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class CommandCooldown : DbEntity +{ + public int Seconds { get; set; } + public string CommandName { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/CurrencyTransaction.cs b/src/EllieBot/Db/Models/CurrencyTransaction.cs new file mode 100644 index 0000000..8b9f6a6 --- /dev/null +++ b/src/EllieBot/Db/Models/CurrencyTransaction.cs @@ -0,0 +1,12 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class CurrencyTransaction : DbEntity +{ + public long Amount { get; set; } + public string Note { get; set; } + public ulong UserId { get; set; } + public string Type { get; set; } + public string Extra { get; set; } + public ulong? OtherId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/DbEntity.cs b/src/EllieBot/Db/Models/DbEntity.cs new file mode 100644 index 0000000..0ed8388 --- /dev/null +++ b/src/EllieBot/Db/Models/DbEntity.cs @@ -0,0 +1,12 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Db.Models; + +public class DbEntity +{ + [Key] + public int Id { get; set; } + + public DateTime? DateAdded { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/DelMsgOnCmdChannel.cs b/src/EllieBot/Db/Models/DelMsgOnCmdChannel.cs new file mode 100644 index 0000000..6cbe756 --- /dev/null +++ b/src/EllieBot/Db/Models/DelMsgOnCmdChannel.cs @@ -0,0 +1,14 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class DelMsgOnCmdChannel : DbEntity +{ + public ulong ChannelId { get; set; } + public bool State { get; set; } + + public override int GetHashCode() + => ChannelId.GetHashCode(); + + public override bool Equals(object obj) + => obj is DelMsgOnCmdChannel x && x.ChannelId == ChannelId; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/DiscordPemOverride.cs b/src/EllieBot/Db/Models/DiscordPemOverride.cs new file mode 100644 index 0000000..b9ecd24 --- /dev/null +++ b/src/EllieBot/Db/Models/DiscordPemOverride.cs @@ -0,0 +1,10 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class DiscordPermOverride : DbEntity +{ + public GuildPerm Perm { get; set; } + + public ulong? GuildId { get; set; } + public string Command { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/DiscordUser.cs b/src/EllieBot/Db/Models/DiscordUser.cs new file mode 100644 index 0000000..83bda60 --- /dev/null +++ b/src/EllieBot/Db/Models/DiscordUser.cs @@ -0,0 +1,35 @@ +#nullable disable +namespace EllieBot.Db.Models; + + +// FUTURE remove LastLevelUp from here and UserXpStats +public class DiscordUser : DbEntity +{ + public ulong UserId { get; set; } + public string Username { get; set; } + public string Discriminator { get; set; } + public string AvatarId { get; set; } + + public int? ClubId { get; set; } + public ClubInfo Club { get; set; } + public bool IsClubAdmin { get; set; } + + public long TotalXp { get; set; } + public XpNotificationLocation NotifyOnLevelUp { get; set; } + + public long CurrencyAmount { get; set; } + + public override bool Equals(object obj) + => obj is DiscordUser du ? du.UserId == UserId : false; + + public override int GetHashCode() + => UserId.GetHashCode(); + + public override string ToString() + { + if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000") + return Username; + + return Username + "#" + Discriminator; + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/Event.cs b/src/EllieBot/Db/Models/Event.cs new file mode 100644 index 0000000..63202f6 --- /dev/null +++ b/src/EllieBot/Db/Models/Event.cs @@ -0,0 +1,49 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class CurrencyEvent +{ + public enum Type + { + Reaction, + + GameStatus + //NotRaid, + } + + public ulong ServerId { get; set; } + public ulong ChannelId { get; set; } + public ulong MessageId { get; set; } + public Type EventType { get; set; } + + /// + /// Amount of currency that the user will be rewarded. + /// + public long Amount { get; set; } + + /// + /// Maximum amount of currency that can be handed out. + /// + public long PotSize { get; set; } + + public List AwardedUsers { get; set; } + + /// + /// Used as extra data storage for events which need it. + /// + public ulong ExtraId { get; set; } + + /// + /// May be used for some future event. + /// + public ulong ExtraId2 { get; set; } + + /// + /// May be used for some future event. + /// + public string ExtraString { get; set; } +} + +public class AwardedUser +{ +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/FeedSub.cs b/src/EllieBot/Db/Models/FeedSub.cs new file mode 100644 index 0000000..66fc6f1 --- /dev/null +++ b/src/EllieBot/Db/Models/FeedSub.cs @@ -0,0 +1,19 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class FeedSub : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public ulong ChannelId { get; set; } + public string Url { get; set; } + + public string Message { get; set; } + + public override int GetHashCode() + => Url.GetHashCode(StringComparison.InvariantCulture) ^ GuildConfigId.GetHashCode(); + + public override bool Equals(object obj) + => obj is FeedSub s && s.Url.ToLower() == Url.ToLower() && s.GuildConfigId == GuildConfigId; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/FollowedStream.cs b/src/EllieBot/Db/Models/FollowedStream.cs new file mode 100644 index 0000000..c880a8d --- /dev/null +++ b/src/EllieBot/Db/Models/FollowedStream.cs @@ -0,0 +1,33 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class FollowedStream : DbEntity +{ + public enum FType + { + Twitch = 0, + Picarto = 3, + Youtube = 4, + Facebook = 5, + Trovo = 6 + } + + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public string Username { get; set; } + public FType Type { get; set; } + public string Message { get; set; } + + protected bool Equals(FollowedStream other) + => ChannelId == other.ChannelId + && Username.Trim().ToUpperInvariant() == other.Username.Trim().ToUpperInvariant() + && Type == other.Type; + + public override int GetHashCode() + => HashCode.Combine(ChannelId, Username, (int)Type); + + public override bool Equals(object obj) + => obj is FollowedStream fs && Equals(fs); + + +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/GCChannelId.cs b/src/EllieBot/Db/Models/GCChannelId.cs new file mode 100644 index 0000000..c6f922b --- /dev/null +++ b/src/EllieBot/Db/Models/GCChannelId.cs @@ -0,0 +1,14 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class GCChannelId : DbEntity +{ + public GuildConfig GuildConfig { get; set; } + public ulong ChannelId { get; set; } + + public override bool Equals(object obj) + => obj is GCChannelId gc && gc.ChannelId == ChannelId; + + public override int GetHashCode() + => ChannelId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/GamblingStats.cs b/src/EllieBot/Db/Models/GamblingStats.cs new file mode 100644 index 0000000..3c91a3b --- /dev/null +++ b/src/EllieBot/Db/Models/GamblingStats.cs @@ -0,0 +1,9 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class GamblingStats : DbEntity +{ + public string Feature { get; set; } + public decimal Bet { get; set; } + public decimal PaidOut { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/GroupName.cs b/src/EllieBot/Db/Models/GroupName.cs new file mode 100644 index 0000000..3e29b31 --- /dev/null +++ b/src/EllieBot/Db/Models/GroupName.cs @@ -0,0 +1,11 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class GroupName : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public int Number { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/GuildColors.cs b/src/EllieBot/Db/Models/GuildColors.cs new file mode 100644 index 0000000..efd5fc1 --- /dev/null +++ b/src/EllieBot/Db/Models/GuildColors.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Db.Models; + +public class GuildColors +{ + [Key] + public ulong GuildId { get; set; } + + [Length(0, 9)] + public string? OkColor { get; set; } + + [Length(0, 9)] + public string? ErrorColor { get; set; } + + [Length(0, 9)] + public string? PendingColor { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/GuildConfig.cs b/src/EllieBot/Db/Models/GuildConfig.cs new file mode 100644 index 0000000..a7b5ac5 --- /dev/null +++ b/src/EllieBot/Db/Models/GuildConfig.cs @@ -0,0 +1,107 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class GuildConfig : DbEntity +{ + public ulong GuildId { get; set; } + + public string Prefix { get; set; } + + public bool DeleteMessageOnCommand { get; set; } + public HashSet DelMsgOnCmdChannels { get; set; } = new(); + + public string AutoAssignRoleIds { get; set; } + + //greet stuff + public int AutoDeleteGreetMessagesTimer { get; set; } = 30; + public int AutoDeleteByeMessagesTimer { get; set; } = 30; + + public ulong GreetMessageChannelId { get; set; } + public ulong ByeMessageChannelId { get; set; } + + public bool SendDmGreetMessage { get; set; } + public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + + public bool SendChannelGreetMessage { get; set; } + public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + + public bool SendChannelByeMessage { get; set; } + public string ChannelByeMessageText { get; set; } = "%user% has left!"; + + //self assignable roles + public bool ExclusiveSelfAssignedRoles { get; set; } + public bool AutoDeleteSelfAssignedRoleMessages { get; set; } + + //stream notifications + public HashSet FollowedStreams { get; set; } = new(); + + //currencyGeneration + public HashSet GenerateCurrencyChannelIds { get; set; } = new(); + + public List Permissions { get; set; } + public bool VerbosePermissions { get; set; } = true; + public string PermissionRole { get; set; } + + public HashSet CommandCooldowns { get; set; } = new(); + + //filtering + public bool FilterInvites { get; set; } + public bool FilterLinks { get; set; } + public HashSet FilterInvitesChannelIds { get; set; } = new(); + public HashSet FilterLinksChannelIds { get; set; } = new(); + + //public bool FilterLinks { get; set; } + //public HashSet FilterLinksChannels { get; set; } = new HashSet(); + + public bool FilterWords { get; set; } + public HashSet FilteredWords { get; set; } = new(); + public HashSet FilterWordsChannelIds { get; set; } = new(); + + public HashSet MutedUsers { get; set; } = new(); + + public string MuteRoleName { get; set; } + public bool CleverbotEnabled { get; set; } + + public AntiRaidSetting AntiRaidSetting { get; set; } + public AntiSpamSetting AntiSpamSetting { get; set; } + public AntiAltSetting AntiAltSetting { get; set; } + + public string Locale { get; set; } + public string TimeZoneId { get; set; } + + public HashSet UnmuteTimers { get; set; } = new(); + public HashSet UnbanTimer { get; set; } = new(); + public HashSet UnroleTimer { get; set; } = new(); + public HashSet VcRoleInfos { get; set; } + public HashSet CommandAliases { get; set; } = new(); + public List WarnPunishments { get; set; } = new(); + public bool WarningsInitialized { get; set; } + public HashSet SlowmodeIgnoredUsers { get; set; } + public HashSet SlowmodeIgnoredRoles { get; set; } + + public List ShopEntries { get; set; } + public ulong? GameVoiceChannel { get; set; } + public bool VerboseErrors { get; set; } = true; + + public StreamRoleSettings StreamRole { get; set; } + + public XpSettings XpSettings { get; set; } + public List FeedSubs { get; set; } = new(); + public bool NotifyStreamOffline { get; set; } + public bool DeleteStreamOnlineMessage { get; set; } + public List SelfAssignableRoleGroupNames { get; set; } + public int WarnExpireHours { get; set; } + public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear; + + public bool DisableGlobalExpressions { get; set; } = false; + + #region Boost Message + + public bool SendBoostMessage { get; set; } + public string BoostMessage { get; set; } = "%user% just boosted this server!"; + public ulong BoostMessageChannelId { get; set; } + public int BoostMessageDeleteAfter { get; set; } + public bool StickyRoles { get; set; } + + #endregion +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/IgnoredLogItem.cs b/src/EllieBot/Db/Models/IgnoredLogItem.cs new file mode 100644 index 0000000..7424d84 --- /dev/null +++ b/src/EllieBot/Db/Models/IgnoredLogItem.cs @@ -0,0 +1,16 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class IgnoredLogItem : DbEntity +{ + public int LogSettingId { get; set; } + public LogSetting LogSetting { get; set; } + public ulong LogItemId { get; set; } + public IgnoredItemType ItemType { get; set; } +} + +public enum IgnoredItemType +{ + Channel, + User +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/IgnoredVoicePresenceChannel.cs b/src/EllieBot/Db/Models/IgnoredVoicePresenceChannel.cs new file mode 100644 index 0000000..cbbda9e --- /dev/null +++ b/src/EllieBot/Db/Models/IgnoredVoicePresenceChannel.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class IgnoredVoicePresenceChannel : DbEntity +{ + public LogSetting LogSetting { get; set; } + public ulong ChannelId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/ImageOnlyChannel.cs b/src/EllieBot/Db/Models/ImageOnlyChannel.cs new file mode 100644 index 0000000..01d01fa --- /dev/null +++ b/src/EllieBot/Db/Models/ImageOnlyChannel.cs @@ -0,0 +1,15 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class ImageOnlyChannel : DbEntity +{ + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public OnlyChannelType Type { get; set; } +} + +public enum OnlyChannelType +{ + Image, + Link +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/LogSetting.cs b/src/EllieBot/Db/Models/LogSetting.cs new file mode 100644 index 0000000..677a128 --- /dev/null +++ b/src/EllieBot/Db/Models/LogSetting.cs @@ -0,0 +1,38 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class LogSetting : DbEntity +{ + public List LogIgnores { get; set; } = new(); + + public ulong GuildId { get; set; } + public ulong? LogOtherId { get; set; } + public ulong? MessageUpdatedId { get; set; } + public ulong? MessageDeletedId { get; set; } + + public ulong? UserJoinedId { get; set; } + public ulong? UserLeftId { get; set; } + public ulong? UserBannedId { get; set; } + public ulong? UserUnbannedId { get; set; } + public ulong? UserUpdatedId { get; set; } + + public ulong? ChannelCreatedId { get; set; } + public ulong? ChannelDestroyedId { get; set; } + public ulong? ChannelUpdatedId { get; set; } + + + public ulong? ThreadDeletedId { get; set; } + public ulong? ThreadCreatedId { get; set; } + + public ulong? UserMutedId { get; set; } + + //userpresence + public ulong? LogUserPresenceId { get; set; } + + //voicepresence + + public ulong? LogVoicePresenceId { get; set; } + + public ulong? LogVoicePresenceTTSId { get; set; } + public ulong? LogWarnsId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/Permission.cs b/src/EllieBot/Db/Models/Permission.cs new file mode 100644 index 0000000..5670dd8 --- /dev/null +++ b/src/EllieBot/Db/Models/Permission.cs @@ -0,0 +1,55 @@ +#nullable disable +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics; + +namespace EllieBot.Db.Models; + +[DebuggerDisplay("{PrimaryTarget}{SecondaryTarget} {SecondaryTargetName} {State} {PrimaryTargetId}")] +public class Permissionv2 : DbEntity, IIndexed +{ + public int? GuildConfigId { get; set; } + public int Index { get; set; } + + public PrimaryPermissionType PrimaryTarget { get; set; } + public ulong PrimaryTargetId { get; set; } + + public SecondaryPermissionType SecondaryTarget { get; set; } + public string SecondaryTargetName { get; set; } + + public bool IsCustomCommand { get; set; } + + public bool State { get; set; } + + [NotMapped] + public static Permissionv2 AllowAllPerm + => new() + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = true, + Index = 0 + }; + + public static List GetDefaultPermlist + => new() + { + AllowAllPerm + }; +} + +public enum PrimaryPermissionType +{ + User, + Channel, + Role, + Server +} + +public enum SecondaryPermissionType +{ + Module, + Command, + AllModules +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/PlantedCurrency.cs b/src/EllieBot/Db/Models/PlantedCurrency.cs new file mode 100644 index 0000000..7e640a5 --- /dev/null +++ b/src/EllieBot/Db/Models/PlantedCurrency.cs @@ -0,0 +1,12 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class PlantedCurrency : DbEntity +{ + public long Amount { get; set; } + public string Password { get; set; } + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public ulong UserId { get; set; } + public ulong MessageId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/PlaylistSong.cs b/src/EllieBot/Db/Models/PlaylistSong.cs new file mode 100644 index 0000000..ccd9312 --- /dev/null +++ b/src/EllieBot/Db/Models/PlaylistSong.cs @@ -0,0 +1,18 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class PlaylistSong : DbEntity +{ + public string Provider { get; set; } + public MusicType ProviderType { get; set; } + public string Title { get; set; } + public string Uri { get; set; } + public string Query { get; set; } +} + +public enum MusicType +{ + Radio, + YouTube, + Local, +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/Reminder.cs b/src/EllieBot/Db/Models/Reminder.cs new file mode 100644 index 0000000..046615a --- /dev/null +++ b/src/EllieBot/Db/Models/Reminder.cs @@ -0,0 +1,19 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class Reminder : DbEntity +{ + public DateTime When { get; set; } + public ulong ChannelId { get; set; } + public ulong ServerId { get; set; } + public ulong UserId { get; set; } + public string Message { get; set; } + public bool IsPrivate { get; set; } + public ReminderType Type { get; set; } +} + +public enum ReminderType +{ + User, + Timely +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/Repeater.cs b/src/EllieBot/Db/Models/Repeater.cs new file mode 100644 index 0000000..d0ef69e --- /dev/null +++ b/src/EllieBot/Db/Models/Repeater.cs @@ -0,0 +1,15 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class Repeater +{ + public int Id { get; set; } + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public ulong? LastMessageId { get; set; } + public string Message { get; set; } + public TimeSpan Interval { get; set; } + public TimeSpan? StartTimeOfDay { get; set; } + public bool NoRedundant { get; set; } + public DateTime DateAdded { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/RotatingPlayingStatus.cs b/src/EllieBot/Db/Models/RotatingPlayingStatus.cs new file mode 100644 index 0000000..6cf5cc4 --- /dev/null +++ b/src/EllieBot/Db/Models/RotatingPlayingStatus.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class RotatingPlayingStatus : DbEntity +{ + public string Status { get; set; } + public DbActivityType Type { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/ShopEntry.cs b/src/EllieBot/Db/Models/ShopEntry.cs new file mode 100644 index 0000000..34cc8fe --- /dev/null +++ b/src/EllieBot/Db/Models/ShopEntry.cs @@ -0,0 +1,46 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public enum ShopEntryType +{ + Role, + + List, + Command +} + +public class ShopEntry : DbEntity, IIndexed +{ + public int Index { get; set; } + public int Price { get; set; } + public string Name { get; set; } + public ulong AuthorId { get; set; } + + public ShopEntryType Type { get; set; } + + //role + public string RoleName { get; set; } + public ulong RoleId { get; set; } + + //list + public HashSet Items { get; set; } = new(); + public ulong? RoleRequirement { get; set; } + + // command + public string Command { get; set; } +} + +public class ShopEntryItem : DbEntity +{ + public string Text { get; set; } + + public override bool Equals(object obj) + { + if (obj is null || GetType() != obj.GetType()) + return false; + return ((ShopEntryItem)obj).Text == Text; + } + + public override int GetHashCode() + => Text.GetHashCode(StringComparison.InvariantCulture); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/StreamOnlineMessage.cs b/src/EllieBot/Db/Models/StreamOnlineMessage.cs new file mode 100644 index 0000000..c6443a6 --- /dev/null +++ b/src/EllieBot/Db/Models/StreamOnlineMessage.cs @@ -0,0 +1,11 @@ +#nullable disable +namespace EllieBot.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/EllieBot/Db/Models/StreamRoleSettings.cs b/src/EllieBot/Db/Models/StreamRoleSettings.cs new file mode 100644 index 0000000..02674d5 --- /dev/null +++ b/src/EllieBot/Db/Models/StreamRoleSettings.cs @@ -0,0 +1,68 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class StreamRoleSettings : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + /// + /// Whether the feature is enabled in the guild. + /// + public bool Enabled { get; set; } + + /// + /// Id of the role to give to the users in the role 'FromRole' when they start streaming + /// + public ulong AddRoleId { get; set; } + + /// + /// Id of the role whose users are eligible to get the 'AddRole' + /// + public ulong FromRoleId { get; set; } + + /// + /// If set, feature will only apply to users who have this keyword in their streaming status. + /// + public string Keyword { get; set; } + + /// + /// A collection of whitelisted users' IDs. Whitelisted users don't require 'keyword' in + /// order to get the stream role. + /// + public HashSet Whitelist { get; set; } = new(); + + /// + /// A collection of blacklisted users' IDs. Blacklisted useres will never get the stream role. + /// + public HashSet Blacklist { get; set; } = new(); +} + +public class StreamRoleBlacklistedUser : DbEntity +{ + public ulong UserId { get; set; } + public string Username { get; set; } + + public override bool Equals(object obj) + { + if (obj is not StreamRoleBlacklistedUser x) + return false; + + return x.UserId == UserId; + } + + public override int GetHashCode() + => UserId.GetHashCode(); +} + +public class StreamRoleWhitelistedUser : DbEntity +{ + public ulong UserId { get; set; } + public string Username { get; set; } + + public override bool Equals(object obj) + => obj is StreamRoleWhitelistedUser x ? x.UserId == UserId : false; + + public override int GetHashCode() + => UserId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/VcRoleInfo.cs b/src/EllieBot/Db/Models/VcRoleInfo.cs new file mode 100644 index 0000000..bb28450 --- /dev/null +++ b/src/EllieBot/Db/Models/VcRoleInfo.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class VcRoleInfo : DbEntity +{ + public ulong VoiceChannelId { get; set; } + public ulong RoleId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/Waifu.cs b/src/EllieBot/Db/Models/Waifu.cs new file mode 100644 index 0000000..78ca0b3 --- /dev/null +++ b/src/EllieBot/Db/Models/Waifu.cs @@ -0,0 +1,75 @@ +#nullable disable +using EllieBot.Db.Models; + +namespace EllieBot.Services.Database.Models; + +public class WaifuInfo : DbEntity +{ + public int WaifuId { get; set; } + public DiscordUser Waifu { get; set; } + + public int? ClaimerId { get; set; } + public DiscordUser Claimer { get; set; } + + public int? AffinityId { get; set; } + public DiscordUser Affinity { get; set; } + + public long Price { get; set; } + public List Items { get; set; } = new(); + + public override string ToString() + { + var status = string.Empty; + + var waifuUsername = Waifu.ToString().TrimTo(20); + var claimer = Claimer?.ToString().TrimTo(20) + ?? "no one"; + + var affinity = Affinity?.ToString().TrimTo(20); + + if (AffinityId is null) + status = $"... but {waifuUsername}'s heart is empty"; + else if (AffinityId == ClaimerId) + status = $"... and {waifuUsername} likes {claimer} too <3"; + else + { + status = + $"... but {waifuUsername}'s heart belongs to {affinity}"; + } + + return $"**{waifuUsername}** - claimed by **{claimer}**\n\t{status}"; + } +} + +public class WaifuLbResult +{ + public string Username { get; set; } + public string Discrim { get; set; } + + public string Claimer { get; set; } + public string ClaimerDiscrim { get; set; } + + public string Affinity { get; set; } + public string AffinityDiscrim { get; set; } + + public long Price { get; set; } + + public override string ToString() + { + var claimer = "no one"; + var status = string.Empty; + + var waifuUsername = Username.TrimTo(20); + var claimerUsername = Claimer?.TrimTo(20); + + if (Claimer is not null) + claimer = $"{claimerUsername}#{ClaimerDiscrim}"; + if (Affinity is null) + status = $"... but {waifuUsername}'s heart is empty"; + else if (Affinity + AffinityDiscrim == Claimer + ClaimerDiscrim) + status = $"... and {waifuUsername} likes {claimerUsername} too <3"; + else + status = $"... but {waifuUsername}'s heart belongs to {Affinity.TrimTo(20)}#{AffinityDiscrim}"; + return $"**{waifuUsername}#{Discrim}** - claimed by **{claimer}**\n\t{status}"; + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/anti/AntiAltSetting.cs b/src/EllieBot/Db/Models/anti/AntiAltSetting.cs new file mode 100644 index 0000000..b9f9e58 --- /dev/null +++ b/src/EllieBot/Db/Models/anti/AntiAltSetting.cs @@ -0,0 +1,11 @@ +namespace EllieBot.Db.Models; + +public class AntiAltSetting +{ + public int Id { get; set; } + public int GuildConfigId { get; set; } + public TimeSpan MinAge { get; set; } + public PunishmentAction Action { get; set; } + public int ActionDurationMinutes { get; set; } + public ulong? RoleId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/anti/AntiRaidSetting.cs b/src/EllieBot/Db/Models/anti/AntiRaidSetting.cs new file mode 100644 index 0000000..aef2658 --- /dev/null +++ b/src/EllieBot/Db/Models/anti/AntiRaidSetting.cs @@ -0,0 +1,19 @@ +#nullable disable +namespace EllieBot.Db.Models; + + +public class AntiRaidSetting : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public int UserThreshold { get; set; } + public int Seconds { get; set; } + public PunishmentAction Action { get; set; } + + /// + /// Duration of the punishment, in minutes. This works only for supported Actions, like: + /// Mute, Chatmute, Voicemute, etc... + /// + public int PunishDuration { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/anti/AntiSpamIgnore.cs b/src/EllieBot/Db/Models/anti/AntiSpamIgnore.cs new file mode 100644 index 0000000..a3cd623 --- /dev/null +++ b/src/EllieBot/Db/Models/anti/AntiSpamIgnore.cs @@ -0,0 +1,12 @@ +namespace EllieBot.Db.Models; + +public class AntiSpamIgnore : DbEntity +{ + public ulong ChannelId { get; set; } + + public override int GetHashCode() + => ChannelId.GetHashCode(); + + public override bool Equals(object? obj) + => obj is AntiSpamIgnore inst && inst.ChannelId == ChannelId; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/anti/AntiSpamSetting.cs b/src/EllieBot/Db/Models/anti/AntiSpamSetting.cs new file mode 100644 index 0000000..42c2183 --- /dev/null +++ b/src/EllieBot/Db/Models/anti/AntiSpamSetting.cs @@ -0,0 +1,14 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public class AntiSpamSetting : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public PunishmentAction Action { get; set; } + public int MessageThreshold { get; set; } = 3; + public int MuteTime { get; set; } + public ulong? RoleId { get; set; } + public HashSet IgnoredChannels { get; set; } = new(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/club/ClubInfo.cs b/src/EllieBot/Db/Models/club/ClubInfo.cs new file mode 100644 index 0000000..e5b7407 --- /dev/null +++ b/src/EllieBot/Db/Models/club/ClubInfo.cs @@ -0,0 +1,41 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Db.Models; + +public class ClubInfo : DbEntity +{ + [MaxLength(20)] + public string Name { get; set; } + public string Description { get; set; } + public string ImageUrl { get; set; } = string.Empty; + + public int Xp { get; set; } = 0; + public int? OwnerId { get; set; } + public DiscordUser Owner { get; set; } + + public List Members { get; set; } = new(); + public List Applicants { get; set; } = new(); + public List Bans { get; set; } = new(); + + public override string ToString() + => Name; +} + +public class ClubApplicants +{ + public int ClubId { get; set; } + public ClubInfo Club { get; set; } + + public int UserId { get; set; } + public DiscordUser User { get; set; } +} + +public class ClubBans +{ + public int ClubId { get; set; } + public ClubInfo Club { get; set; } + + public int UserId { get; set; } + public DiscordUser User { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/currency/BankUser.cs b/src/EllieBot/Db/Models/currency/BankUser.cs new file mode 100644 index 0000000..b62b49d --- /dev/null +++ b/src/EllieBot/Db/Models/currency/BankUser.cs @@ -0,0 +1,7 @@ +namespace EllieBot.Db.Models; + +public class BankUser : DbEntity +{ + public ulong UserId { get; set; } + public long Balance { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/expr/EllieExpression.cs b/src/EllieBot/Db/Models/expr/EllieExpression.cs new file mode 100644 index 0000000..53eef8b --- /dev/null +++ b/src/EllieBot/Db/Models/expr/EllieExpression.cs @@ -0,0 +1,27 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class EllieExpression : DbEntity +{ + public ulong? GuildId { get; set; } + public string Response { get; set; } + public string Trigger { get; set; } + + public bool AutoDeleteTrigger { get; set; } + public bool DmResponse { get; set; } + public bool ContainsAnywhere { get; set; } + public bool AllowTarget { get; set; } + public string Reactions { get; set; } + + public string[] GetReactions() + => string.IsNullOrWhiteSpace(Reactions) ? Array.Empty() : Reactions.Split("@@@"); + + public bool IsGlobal() + => GuildId is null or 0; +} + +public class ReactionResponse : DbEntity +{ + public bool OwnerOnly { get; set; } + public string Text { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/expr/Quote.cs b/src/EllieBot/Db/Models/expr/Quote.cs new file mode 100644 index 0000000..62f57d7 --- /dev/null +++ b/src/EllieBot/Db/Models/expr/Quote.cs @@ -0,0 +1,26 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Db.Models; + +public class Quote : DbEntity +{ + public ulong GuildId { get; set; } + + [Required] + public string Keyword { get; set; } + + [Required] + public string AuthorName { get; set; } + + public ulong AuthorId { get; set; } + + [Required] + public string Text { get; set; } +} + +public enum OrderType +{ + Id = -1, + Keyword = -2 +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/filter/FilterChannelId.cs b/src/EllieBot/Db/Models/filter/FilterChannelId.cs new file mode 100644 index 0000000..fe3b97b --- /dev/null +++ b/src/EllieBot/Db/Models/filter/FilterChannelId.cs @@ -0,0 +1,30 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class FilterChannelId : DbEntity +{ + public ulong ChannelId { get; set; } + + public bool Equals(FilterChannelId other) + => ChannelId == other.ChannelId; + + public override bool Equals(object obj) + => obj is FilterChannelId fci && Equals(fci); + + public override int GetHashCode() + => ChannelId.GetHashCode(); +} + +public class FilterWordsChannelId : DbEntity +{ + public ulong ChannelId { get; set; } + + public bool Equals(FilterWordsChannelId other) + => ChannelId == other.ChannelId; + + public override bool Equals(object obj) + => obj is FilterWordsChannelId fci && Equals(fci); + + public override int GetHashCode() + => ChannelId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/filter/FilterLinksChannelId.cs b/src/EllieBot/Db/Models/filter/FilterLinksChannelId.cs new file mode 100644 index 0000000..50aca96 --- /dev/null +++ b/src/EllieBot/Db/Models/filter/FilterLinksChannelId.cs @@ -0,0 +1,13 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class FilterLinksChannelId : DbEntity +{ + public ulong ChannelId { get; set; } + + public override bool Equals(object obj) + => obj is FilterLinksChannelId f && f.ChannelId == ChannelId; + + public override int GetHashCode() + => ChannelId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/filter/FilteredWord.cs b/src/EllieBot/Db/Models/filter/FilteredWord.cs new file mode 100644 index 0000000..de66d7a --- /dev/null +++ b/src/EllieBot/Db/Models/filter/FilteredWord.cs @@ -0,0 +1,7 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class FilteredWord : DbEntity +{ + public string Word { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/giveaway/GiveawayModel.cs b/src/EllieBot/Db/Models/giveaway/GiveawayModel.cs new file mode 100644 index 0000000..ca077b2 --- /dev/null +++ b/src/EllieBot/Db/Models/giveaway/GiveawayModel.cs @@ -0,0 +1,14 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public sealed class GiveawayModel +{ + public int Id { get; set; } + public ulong GuildId { get; set; } + public ulong MessageId { get; set; } + public ulong ChannelId { get; set; } + public string Message { get; set; } + + public IList Participants { get; set; } = new List(); + public DateTime EndsAt { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/giveaway/GiveawayUser.cs b/src/EllieBot/Db/Models/giveaway/GiveawayUser.cs new file mode 100644 index 0000000..a8b964e --- /dev/null +++ b/src/EllieBot/Db/Models/giveaway/GiveawayUser.cs @@ -0,0 +1,10 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public sealed class GiveawayUser +{ + public int Id { get; set; } + public int GiveawayId { get; set; } + public ulong UserId { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/BanTemplate.cs b/src/EllieBot/Db/Models/punish/BanTemplate.cs new file mode 100644 index 0000000..0c8519f --- /dev/null +++ b/src/EllieBot/Db/Models/punish/BanTemplate.cs @@ -0,0 +1,9 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class BanTemplate : DbEntity +{ + public ulong GuildId { get; set; } + public string Text { get; set; } + public int? PruneDays { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/MutedUserId.cs b/src/EllieBot/Db/Models/punish/MutedUserId.cs new file mode 100644 index 0000000..f067e77 --- /dev/null +++ b/src/EllieBot/Db/Models/punish/MutedUserId.cs @@ -0,0 +1,13 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class MutedUserId : DbEntity +{ + public ulong UserId { get; set; } + + public override int GetHashCode() + => UserId.GetHashCode(); + + public override bool Equals(object obj) + => obj is MutedUserId mui ? mui.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/PunishmentAction.cs b/src/EllieBot/Db/Models/punish/PunishmentAction.cs new file mode 100644 index 0000000..5788e65 --- /dev/null +++ b/src/EllieBot/Db/Models/punish/PunishmentAction.cs @@ -0,0 +1,15 @@ +namespace EllieBot.Db.Models; + +public enum PunishmentAction +{ + Mute, + Kick, + Ban, + Softban, + RemoveRoles, + ChatMute, + VoiceMute, + AddRole, + Warn, + TimeOut +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/WarnExpireAction.cs b/src/EllieBot/Db/Models/punish/WarnExpireAction.cs new file mode 100644 index 0000000..0de916e --- /dev/null +++ b/src/EllieBot/Db/Models/punish/WarnExpireAction.cs @@ -0,0 +1,8 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public enum WarnExpireAction +{ + Clear, + Delete +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/Warning.cs b/src/EllieBot/Db/Models/punish/Warning.cs new file mode 100644 index 0000000..454a4cb --- /dev/null +++ b/src/EllieBot/Db/Models/punish/Warning.cs @@ -0,0 +1,13 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class Warning : DbEntity +{ + public ulong GuildId { get; set; } + public ulong UserId { get; set; } + public string Reason { get; set; } + public bool Forgiven { get; set; } + public string ForgivenBy { get; set; } + public string Moderator { get; set; } + public long Weight { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/punish/WarningPunishment.cs b/src/EllieBot/Db/Models/punish/WarningPunishment.cs new file mode 100644 index 0000000..5368938 --- /dev/null +++ b/src/EllieBot/Db/Models/punish/WarningPunishment.cs @@ -0,0 +1,10 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class WarningPunishment : DbEntity +{ + public int Count { get; set; } + public PunishmentAction Punishment { get; set; } + public int Time { get; set; } + public ulong? RoleId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/roles/ReactionRole.cs b/src/EllieBot/Db/Models/roles/ReactionRole.cs new file mode 100644 index 0000000..2dedbfe --- /dev/null +++ b/src/EllieBot/Db/Models/roles/ReactionRole.cs @@ -0,0 +1,18 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Db.Models; + +public class ReactionRoleV2 : DbEntity +{ + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + + public ulong MessageId { get; set; } + + [MaxLength(100)] + public string Emote { get; set; } + public ulong RoleId { get; set; } + public int Group { get; set; } + public int LevelReq { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/roles/SelfAssignableRole.cs b/src/EllieBot/Db/Models/roles/SelfAssignableRole.cs new file mode 100644 index 0000000..ac147b6 --- /dev/null +++ b/src/EllieBot/Db/Models/roles/SelfAssignableRole.cs @@ -0,0 +1,11 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class SelfAssignedRole : DbEntity +{ + public ulong GuildId { get; set; } + public ulong RoleId { get; set; } + + public int Group { get; set; } + public int LevelRequirement { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/roles/StickyRoles.cs b/src/EllieBot/Db/Models/roles/StickyRoles.cs new file mode 100644 index 0000000..3e01ae9 --- /dev/null +++ b/src/EllieBot/Db/Models/roles/StickyRoles.cs @@ -0,0 +1,14 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public class StickyRole : DbEntity +{ + public ulong GuildId { get; set; } + public string RoleIds { get; set; } + public ulong UserId { get; set; } + + public ulong[] GetRoleIds() + => string.IsNullOrWhiteSpace(RoleIds) + ? [] + : RoleIds.Split(',').Select(ulong.Parse).ToArray(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredRole.cs b/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredRole.cs new file mode 100644 index 0000000..e41c2e7 --- /dev/null +++ b/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredRole.cs @@ -0,0 +1,20 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class SlowmodeIgnoredRole : DbEntity +{ + public ulong RoleId { get; set; } + + // override object.Equals + public override bool Equals(object obj) + { + if (obj is null || GetType() != obj.GetType()) + return false; + + return ((SlowmodeIgnoredRole)obj).RoleId == RoleId; + } + + // override object.GetHashCode + public override int GetHashCode() + => RoleId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredUser.cs b/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredUser.cs new file mode 100644 index 0000000..7ae0a05 --- /dev/null +++ b/src/EllieBot/Db/Models/slowmode/SlowmodeIgnoredUser.cs @@ -0,0 +1,20 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class SlowmodeIgnoredUser : DbEntity +{ + public ulong UserId { get; set; } + + // override object.Equals + public override bool Equals(object obj) + { + if (obj is null || GetType() != obj.GetType()) + return false; + + return ((SlowmodeIgnoredUser)obj).UserId == UserId; + } + + // override object.GetHashCode + public override int GetHashCode() + => UserId.GetHashCode(); +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/support/PatronQuota.cs b/src/EllieBot/Db/Models/support/PatronQuota.cs new file mode 100644 index 0000000..b87dcbc --- /dev/null +++ b/src/EllieBot/Db/Models/support/PatronQuota.cs @@ -0,0 +1,48 @@ +#nullable disable +namespace EllieBot.Db.Models; + +/// +/// Contains data about usage of Patron-Only commands per user +/// in order to provide support for quota limitations +/// (allow user x who is pledging amount y to use the specified command only +/// x amount of times in the specified time period) +/// +public class PatronQuota +{ + public ulong UserId { get; set; } + public FeatureType FeatureType { get; set; } + public string Feature { get; set; } + public uint HourlyCount { get; set; } + public uint DailyCount { get; set; } + public uint MonthlyCount { get; set; } +} + +public enum FeatureType +{ + Command, + Group, + Module, + Limit +} + +public class PatronUser +{ + public string UniquePlatformUserId { get; set; } + public ulong UserId { get; set; } + public int AmountCents { get; set; } + + public DateTime LastCharge { get; set; } + + // Date Only component + public DateTime ValidThru { get; set; } + + public PatronUser Clone() + => new PatronUser() + { + UniquePlatformUserId = this.UniquePlatformUserId, + UserId = this.UserId, + AmountCents = this.AmountCents, + LastCharge = this.LastCharge, + ValidThru = this.ValidThru + }; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/support/RewardedUser.cs b/src/EllieBot/Db/Models/support/RewardedUser.cs new file mode 100644 index 0000000..bc12bdd --- /dev/null +++ b/src/EllieBot/Db/Models/support/RewardedUser.cs @@ -0,0 +1,10 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class RewardedUser : DbEntity +{ + public ulong UserId { get; set; } + public string PlatformUserId { get; set; } + public long AmountRewardedThisMonth { get; set; } + public DateTime LastReward { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/todo/ArchivedTodoListModel.cs b/src/EllieBot/Db/Models/todo/ArchivedTodoListModel.cs new file mode 100644 index 0000000..b213788 --- /dev/null +++ b/src/EllieBot/Db/Models/todo/ArchivedTodoListModel.cs @@ -0,0 +1,10 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public sealed class ArchivedTodoListModel +{ + public int Id { get; set; } + public ulong UserId { get; set; } + public string Name { get; set; } + public List Items { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/todo/TodoModel.cs b/src/EllieBot/Db/Models/todo/TodoModel.cs new file mode 100644 index 0000000..ba3c8c1 --- /dev/null +++ b/src/EllieBot/Db/Models/todo/TodoModel.cs @@ -0,0 +1,13 @@ +namespace EllieBot.Db.Models; + +#nullable disable +public sealed class TodoModel +{ + public int Id { get; set; } + public ulong UserId { get; set; } + public string Todo { get; set; } + + public DateTime DateAdded { get; set; } + public bool IsDone { get; set; } + public int? ArchiveId { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/untimer/UnbanTimer.cs b/src/EllieBot/Db/Models/untimer/UnbanTimer.cs new file mode 100644 index 0000000..2f61402 --- /dev/null +++ b/src/EllieBot/Db/Models/untimer/UnbanTimer.cs @@ -0,0 +1,14 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class UnbanTimer : DbEntity +{ + public ulong UserId { get; set; } + public DateTime UnbanAt { get; set; } + + public override int GetHashCode() + => UserId.GetHashCode(); + + public override bool Equals(object obj) + => obj is UnbanTimer ut ? ut.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/untimer/UnmuteTimer.cs b/src/EllieBot/Db/Models/untimer/UnmuteTimer.cs new file mode 100644 index 0000000..18b2903 --- /dev/null +++ b/src/EllieBot/Db/Models/untimer/UnmuteTimer.cs @@ -0,0 +1,14 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class UnmuteTimer : DbEntity +{ + public ulong UserId { get; set; } + public DateTime UnmuteAt { get; set; } + + public override int GetHashCode() + => UserId.GetHashCode(); + + public override bool Equals(object obj) + => obj is UnmuteTimer ut ? ut.UserId == UserId : false; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/untimer/UnroleTimer.cs b/src/EllieBot/Db/Models/untimer/UnroleTimer.cs new file mode 100644 index 0000000..27193c2 --- /dev/null +++ b/src/EllieBot/Db/Models/untimer/UnroleTimer.cs @@ -0,0 +1,15 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class UnroleTimer : DbEntity +{ + public ulong UserId { get; set; } + public ulong RoleId { get; set; } + public DateTime UnbanAt { get; set; } + + public override int GetHashCode() + => UserId.GetHashCode() ^ RoleId.GetHashCode(); + + public override bool Equals(object obj) + => obj is UnroleTimer ut ? ut.UserId == UserId && ut.RoleId == RoleId : false; +} \ No newline at end of file diff --git a/src/EllieBot/Db/Models/xp/UserXpStats.cs b/src/EllieBot/Db/Models/xp/UserXpStats.cs new file mode 100644 index 0000000..d603360 --- /dev/null +++ b/src/EllieBot/Db/Models/xp/UserXpStats.cs @@ -0,0 +1,13 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class UserXpStats : DbEntity +{ + public ulong UserId { get; set; } + public ulong GuildId { get; set; } + public long Xp { get; set; } + public long AwardedXp { get; set; } + public XpNotificationLocation NotifyOnLevelUp { get; set; } +} + +public enum XpNotificationLocation { None, Dm, Channel } \ No newline at end of file diff --git a/src/EllieBot/Db/Models/xp/XpSettings.cs b/src/EllieBot/Db/Models/xp/XpSettings.cs new file mode 100644 index 0000000..694b289 --- /dev/null +++ b/src/EllieBot/Db/Models/xp/XpSettings.cs @@ -0,0 +1,62 @@ +#nullable disable +namespace EllieBot.Db.Models; + +public class XpSettings : DbEntity +{ + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public HashSet RoleRewards { get; set; } = new(); + public HashSet CurrencyRewards { get; set; } = new(); + public HashSet ExclusionList { get; set; } = new(); + public bool ServerExcluded { get; set; } +} + +public enum ExcludedItemType { Channel, Role } + +public class XpRoleReward : DbEntity +{ + public int XpSettingsId { get; set; } + public XpSettings XpSettings { get; set; } + + public int Level { get; set; } + public ulong RoleId { get; set; } + + /// + /// Whether the role should be removed (true) or added (false) + /// + public bool Remove { get; set; } + + public override int GetHashCode() + => Level.GetHashCode() ^ XpSettingsId.GetHashCode(); + + public override bool Equals(object obj) + => obj is XpRoleReward xrr && xrr.Level == Level && xrr.XpSettingsId == XpSettingsId; +} + +public class XpCurrencyReward : DbEntity +{ + public int XpSettingsId { get; set; } + public XpSettings XpSettings { get; set; } + + public int Level { get; set; } + public int Amount { get; set; } + + public override int GetHashCode() + => Level.GetHashCode() ^ XpSettingsId.GetHashCode(); + + public override bool Equals(object obj) + => obj is XpCurrencyReward xrr && xrr.Level == Level && xrr.XpSettingsId == XpSettingsId; +} + +public class ExcludedItem : DbEntity +{ + public ulong ItemId { get; set; } + public ExcludedItemType ItemType { get; set; } + + public override int GetHashCode() + => ItemId.GetHashCode() ^ ItemType.GetHashCode(); + + public override bool Equals(object obj) + => obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType; +} \ No newline at end of file diff --git a/src/EllieBot/Db/MysqlContext.cs b/src/EllieBot/Db/MysqlContext.cs new file mode 100644 index 0000000..e8f4eba --- /dev/null +++ b/src/EllieBot/Db/MysqlContext.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Db; + +public sealed class MysqlContext : EllieContext +{ + private readonly string _connStr; + private readonly string _version; + + protected override string CurrencyTransactionOtherIdDefaultValue + => "NULL"; + + public MysqlContext(string connStr = "Server=localhost", string version = "8.0") + { + _connStr = connStr; + _version = version; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder + .UseLowerCaseNamingConvention() + .UseMySql(_connStr, ServerVersion.Parse(_version)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // mysql is case insensitive by default + // we can set binary collation to change that + modelBuilder.Entity() + .Property(x => x.Name) + .UseCollation("utf8mb4_bin"); + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/PostgreSqlContext.cs b/src/EllieBot/Db/PostgreSqlContext.cs new file mode 100644 index 0000000..aea3e7c --- /dev/null +++ b/src/EllieBot/Db/PostgreSqlContext.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; + +namespace EllieBot.Db; + +public sealed class PostgreSqlContext : EllieContext +{ + private readonly string _connStr; + + protected override string CurrencyTransactionOtherIdDefaultValue + => "NULL"; + + public PostgreSqlContext(string connStr = "Host=localhost") + { + _connStr = connStr; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + + base.OnConfiguring(optionsBuilder); + optionsBuilder + .UseLowerCaseNamingConvention() + .UseNpgsql(_connStr); + } +} \ No newline at end of file diff --git a/src/EllieBot/Db/SqliteContext.cs b/src/EllieBot/Db/SqliteContext.cs new file mode 100644 index 0000000..e284968 --- /dev/null +++ b/src/EllieBot/Db/SqliteContext.cs @@ -0,0 +1,26 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace EllieBot.Db; + +public sealed class SqliteContext : EllieContext +{ + private readonly string _connectionString; + + protected override string CurrencyTransactionOtherIdDefaultValue + => "NULL"; + + public SqliteContext(string connectionString = "Data Source=data/EllieBot.db", int commandTimeout = 60) + { + _connectionString = connectionString; + Database.SetCommandTimeout(commandTimeout); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + var builder = new SqliteConnectionStringBuilder(_connectionString); + builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); + optionsBuilder.UseSqlite(builder.ToString()); + } +} \ No newline at end of file