#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NadekoBot.Db.Models; using NadekoBot.Services.Database; namespace NadekoBot.Db; public static class DiscordUserExtensions { public static void EnsureUserCreated( this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId) => ctx.DiscordUser.ToLinqToDBTable() .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 }); //temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown public static DiscordUser GetOrCreateUser( this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId) { ctx.EnsureUserCreated(userId, username, discrim, avatarId); return ctx.DiscordUser.Include(x => x.Club).First(u => u.UserId == userId); } public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original) => ctx.GetOrCreateUser(original.Id, original.Username, original.Discriminator, original.AvatarId); 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) => users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * 9).Take(9).AsEnumerable().ToArray(); public static List GetTopRichest( this DbSet users, ulong botId, int count, int page = 0) => users.AsQueryable() .Where(c => c.CurrencyAmount > 0 && botId != c.UserId) .OrderByDescending(c => c.CurrencyAmount) .Skip(page * 9) .Take(count) .ToList(); public static long GetUserCurrency(this DbSet users, ulong userId) => users.AsNoTracking().FirstOrDefault(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 bool TryUpdateCurrencyState( this NadekoContext ctx, ulong userId, string name, string discrim, string avatarId, long amount, bool allowNegative = false) { if (amount == 0) return true; // if remove - try to remove if he has more or equal than the amount // and return number of rows > 0 (was there a change) if (amount < 0 && !allowNegative) { var rows = ctx.Database.ExecuteSqlInterpolated($@" UPDATE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} WHERE UserId={userId} AND CurrencyAmount>={-amount};"); return rows > 0; } // if remove and negative is allowed, just remove without any condition if (amount < 0 && allowNegative) { var rows = ctx.Database.ExecuteSqlInterpolated($@" UPDATE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} WHERE UserId={userId};"); return rows > 0; } // if add - create a new user with default values if it doesn't exist // if it exists, sum current amount with the new one, if it doesn't // he just has the new amount var updatedUserData = !string.IsNullOrWhiteSpace(name); name ??= "Unknown"; discrim ??= "????"; avatarId ??= ""; // just update the amount, there is no new user data if (!updatedUserData) ctx.Database.ExecuteSqlInterpolated($@" UPDATE OR IGNORE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount} WHERE UserId={userId}; INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp) VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0); "); else ctx.Database.ExecuteSqlInterpolated($@" UPDATE OR IGNORE DiscordUser SET CurrencyAmount=CurrencyAmount+{amount}, Username={name}, Discriminator={discrim}, AvatarId={avatarId} WHERE UserId={userId}; INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp) VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0); "); return true; } 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); }