From 06802f42a3e62fb6dbd657df847ba54f9e1d2705 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 8 Jul 2024 14:37:13 +1200 Subject: [PATCH 01/14] Added privacy-policy.md --- privacy-policy.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 privacy-policy.md diff --git a/privacy-policy.md b/privacy-policy.md new file mode 100644 index 0000000..25c43ce --- /dev/null +++ b/privacy-policy.md @@ -0,0 +1,11 @@ +# Privacy Policy + +## Profile Information +Ellie stores userids, avatars, usernames, discriminators and nicknames of users who were targeted by or have used commands which require Xp, Clubs or Waifu features (not limited to these, as other features may be added over time). + +## Other +Ellie doesn't do analytics, doesn't store messages, doesn't track users, doesn't store their emails etc. +Ellie only stores user settings and states as the result of executed commands or as the effect of administration tools (for example warnings or protection commands). + +## Sensitive Information +Ellie doesn't store sensitive information, and users are strongly discouraged from adding their passwords, keys, or other important information as quotes or expressions. \ No newline at end of file From c3340a16ba55876e58d6c6dd8a9267e336036f9b Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:40:34 +1200 Subject: [PATCH 02/14] Updated data files --- src/EllieBot/data/aliases.yml | 8 ++++- .../data/strings/commands/commands.en-US.yml | 35 +++++++++++++++---- .../strings/responses/responses.en-US.json | 2 ++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index 064dfb2..9140342 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -30,6 +30,8 @@ greettest: - greettest greetdmtest: - greetdmtest +boosttest: + - boosttest byetest: - byetest boost: @@ -1404,4 +1406,8 @@ cleanupguilddata: prompt: - prompt honeypot: - - honeypot \ No newline at end of file + - honeypot +coins: + - coins + - crypto + - cryptos \ No newline at end of file diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index a444726..d137d44 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -112,6 +112,14 @@ greettest: params: - user: desc: "The user to impersonate when sending the greeting, defaulting to yourself if not specified." +boosttest: + desc: Sends the boost message in the current channel as if you just boosted the server. You can optionally specify a different user. + ex: + - '' + - '@SomeoneElse' + params: + - user: + desc: "The user to impersonate when sending the boost message, defaulting to yourself if not specified." greetdmtest: desc: Sends the greet direct message to you as if you just joined the server. You can optionally specify a different user. ex: @@ -2748,8 +2756,10 @@ waifutransfer: desc: "The user to whom ownership of the waifu is being transferred." waifugift: desc: -| - Gift an item to someone. - This will increase their waifu value by a percentage of the gift's value. + Gift an item to a waifu user. + The waifu's value will be increased by the percentage of the gift's value. + You can optionally prefix the gift with a multiplier to gift the item that many times. + For example, 3xRose will give the waifu 3 roses, 10xBread will give the waifu 10 breads. Do not use plural forms. Negative gifts will not show up in waifuinfo. Provide no parameters to see a list of items that you can gift. ex: @@ -2757,9 +2767,9 @@ waifugift: - Rose @Himesama params: - page: - desc: "The number of pages to display when listing available gifting options." - - itemName: - desc: "The name of an item to be gifted, which is used to determine the percentage increase in waifu value." + desc: "The number of the page to display." + - items: + desc: "The name of an item to be gifted. With an optional multiplier prefix." waifu: desc: "The user who is receiving the gift." waifulb: @@ -4256,8 +4266,10 @@ bankbalance: Shows how much currency is in your bank account. This differs from your cash amount, as the cash amount is publicly available, but only you have access to your bank balance. However, you have to withdraw it first in order to use it. + Bot Owner can also check another user's bank balance. ex: - '' + - '@User' params: - {} banktake: @@ -4545,4 +4557,15 @@ honeypot: ex: - '' params: - - {} \ No newline at end of file + - {} +coins: + desc: |- + Shows a list of 10 crypto currencies ordered by market cap. + Shows their price, change in the last24h, market cap and circulating and total supply. + Paginated with 10 per page. + ex: + - '' + - '2' + params: + - page: + desc: "Page number to show. Starts at 1." diff --git a/src/EllieBot/data/strings/responses/responses.en-US.json b/src/EllieBot/data/strings/responses/responses.en-US.json index 7e45dd8..4c0893e 100644 --- a/src/EllieBot/data/strings/responses/responses.en-US.json +++ b/src/EllieBot/data/strings/responses/responses.en-US.json @@ -889,6 +889,7 @@ "club_kick_hierarchy": "Only club owner can kick club admins. Owner can't be kicked.", "club_renamed": "Club has been renamed to {0}", "club_name_taken": "A club with that name already exists.", + "rank": "Rank", "template_reloaded": "Xp template has been reloaded.", "expr_edited": "Expression Edited", "self_assign_are_exclusive": "You can only choose 1 role from each group.", @@ -1039,6 +1040,7 @@ "marmalade_already_loaded": "Marmalade {0} is already loaded", "marmalade_invalid_not_found": "Marmalade with that name wasn't found or the file was invalid", "bank_balance": "You have {0} in your bank account.", + "bank_balance_other": "User {0} has {1} in the bank.", "bank_deposited": "You deposited {0} to your bank account.", "bank_withdrew": "You withdrew {0} from your bank account.", "bank_withdraw_insuff": "You don't have sufficient {0} in your bank account.", From 32db627aef3476570d51177cff16d7aacf6ac33f Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:43:33 +1200 Subject: [PATCH 03/14] Updated Administration module --- .../Administration/GreetBye/GreetCommands.cs | 14 ++++++++++++++ .../Administration/GreetBye/GreetService.cs | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/EllieBot/Modules/Administration/GreetBye/GreetCommands.cs b/src/EllieBot/Modules/Administration/GreetBye/GreetCommands.cs index 53dd058..104e4e7 100644 --- a/src/EllieBot/Modules/Administration/GreetBye/GreetCommands.cs +++ b/src/EllieBot/Modules/Administration/GreetBye/GreetCommands.cs @@ -225,5 +225,19 @@ public partial class Administration if (!enabled) await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync(); } + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.ManageGuild)] + [Ratelimit(5)] + public async Task BoostTest([Leftover] IGuildUser? user = null) + { + user ??= (IGuildUser)ctx.User; + + await _service.BoostTest((ITextChannel)ctx.Channel, user); + var enabled = _service.GetBoostEnabled(ctx.Guild.Id); + if (!enabled) + await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync(); + } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs b/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs index aec7e9e..1511913 100644 --- a/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs +++ b/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs @@ -242,7 +242,7 @@ public class GreetService : IEService, IReadyExecutor guild: channel.Guild, channel: channel, users: users.ToArray()); - + var text = SmartText.CreateFrom(conf.ChannelGreetMessageText); text = await _repSvc.ReplaceAsync(text, repCtx); try @@ -630,6 +630,13 @@ public class GreetService : IEService, IReadyExecutor return conf.SendChannelByeMessage; } + public bool GetBoostEnabled(ulong guildId) + { + using var uow = _db.GetDbContext(); + var conf = uow.GuildConfigsForId(guildId, set => set); + return conf.SendBoostMessage; + } + #endregion #region Test Messages From 90dd47e0132a20b2e653d0c2e581d123f76fb49c Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:44:07 +1200 Subject: [PATCH 04/14] Updated Gambling module I really need to rename this module. --- .../Modules/Gambling/Bank/BankCommands.cs | 21 ++ .../Gambling/Waifus/WaifuClaimCommands.cs | 64 ++--- .../Modules/Gambling/Waifus/WaifuService.cs | 234 ++++++++++-------- .../Waifus/_common/MultipleWaifuItems.cs | 6 + .../_common/MultipleWaifuItemsTypeReader.cs | 47 ++++ 5 files changed, 239 insertions(+), 133 deletions(-) create mode 100644 src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItems.cs create mode 100644 src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItemsTypeReader.cs diff --git a/src/EllieBot/Modules/Gambling/Bank/BankCommands.cs b/src/EllieBot/Modules/Gambling/Bank/BankCommands.cs index 91388ef..ee66b6e 100644 --- a/src/EllieBot/Modules/Gambling/Bank/BankCommands.cs +++ b/src/EllieBot/Modules/Gambling/Bank/BankCommands.cs @@ -74,6 +74,27 @@ public partial class Gambling } } + [Cmd] + [OwnerOnly] + public async Task BankBalance([Leftover] IUser user) + { + var bal = await _bank.GetBalanceAsync(user.Id); + + var eb = _sender.CreateEmbed() + .WithOkColor() + .WithDescription(GetText(strs.bank_balance_other(user.ToString(), N(bal)))); + + try + { + await Response().User(ctx.User).Embed(eb).SendAsync(); + await ctx.OkAsync(); + } + catch + { + await Response().Error(strs.cant_dm).SendAsync(); + } + } + private async Task BankTakeInternalAsync(long amount, ulong userId) { if (await _bank.TakeAsync(userId, amount)) diff --git a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs index 5f7db11..19b1dc5 100644 --- a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs +++ b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs @@ -3,6 +3,7 @@ using EllieBot.Modules.Gambling.Common; using EllieBot.Modules.Gambling.Common.Waifu; using EllieBot.Modules.Gambling.Services; using EllieBot.Db.Models; +using TwitchLib.Api.Helix.Models.Teams; namespace EllieBot.Modules.Gambling; @@ -21,8 +22,8 @@ public partial class Gambling { var price = _service.GetResetPrice(ctx.User); var embed = _sender.CreateEmbed() - .WithTitle(GetText(strs.waifu_reset_confirm)) - .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price))))); + .WithTitle(GetText(strs.waifu_reset_confirm)) + .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price))))); if (!await PromptUserConfirmAsync(embed)) return; @@ -307,24 +308,26 @@ public partial class Gambling fansStr = "-"; var embed = _sender.CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.waifu) - + " " - + (wi.FullName ?? name ?? targetId.ToString()) - + " - \"the " - + _service.GetClaimTitle(wi.ClaimCount) - + "\"") - .AddField(GetText(strs.price), N(wi.Price), true) - .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true) - .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true) - .AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true) - .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true) - .AddField("\u200B", "\u200B", true) - .AddField(GetText(strs.fans(fansList.Count)), fansStr, true) - .AddField($"Waifus ({wi.ClaimCount})", - wi.ClaimCount == 0 ? nobody : claimsStr, - true) - .AddField(GetText(strs.gifts), itemsStr, true); + .WithOkColor() + .WithTitle(GetText(strs.waifu) + + " " + + (wi.FullName ?? name ?? targetId.ToString()) + + " - \"the " + + _service.GetClaimTitle(wi.ClaimCount) + + "\"") + .AddField(GetText(strs.price), N(wi.Price), true) + .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true) + .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true) + .AddField(GetText(strs.changes_of_heart), + $"{wi.AffinityCount} - \"the {affInfo}\"", + true) + .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true) + .AddField("\u200B", "\u200B", true) + .AddField(GetText(strs.fans(fansList.Count)), fansStr, true) + .AddField($"Waifus ({wi.ClaimCount})", + wi.ClaimCount == 0 ? nobody : claimsStr, + true) + .AddField(GetText(strs.gifts), itemsStr, true); await Response().Embed(embed).SendAsync(); } @@ -348,7 +351,7 @@ public partial class Gambling .Page((items, _) => { var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor(); - + items .ToList() .ForEach(x => embed.AddField( @@ -364,30 +367,27 @@ public partial class Gambling [Cmd] [RequireContext(ContextType.Guild)] [Priority(0)] - public async Task WaifuGift(string itemName, [Leftover] IUser waifu) + public async Task WaifuGift(MultipleWaifuItems items, [Leftover] IUser waifu) { if (waifu.Id == ctx.User.Id) return; - var allItems = _service.GetWaifuItems(); - var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant()); - if (item is null) - { - await Response().Error(strs.waifu_gift_not_exist).SendAsync(); - return; - } - - var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item); + var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, items.Item, items.Count); if (sucess) { await Response() - .Confirm(strs.waifu_gift(Format.Bold(item + " " + item.ItemEmoji), + .Confirm(strs.waifu_gift(Format.Bold($"{GetCountString(items)}{items.Item} {items.Item.ItemEmoji}"), Format.Bold(waifu.ToString()))) .SendAsync(); } else await Response().Error(strs.not_enough(CurrencySign)).SendAsync(); } + + private static string GetCountString(MultipleWaifuItems items) + => items.Count > 1 + ? $"{items.Count}x " + : string.Empty; } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs b/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs index dded8a9..ab5e021 100644 --- a/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs +++ b/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs @@ -7,6 +7,7 @@ using EllieBot.Db; using EllieBot.Db.Models; using EllieBot.Modules.Gambling.Common; using EllieBot.Modules.Gambling.Common.Waifu; +using SixLabors.ImageSharp; namespace EllieBot.Modules.Gambling.Services; @@ -89,9 +90,14 @@ public class WaifuService : IEService, IReadyExecutor if (waifu is null) return settings.Waifu.MinPrice; - var divorces = uow.Set().Count(x - => x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null); - var affs = uow.Set().AsQueryable() + var divorces = uow.Set() + .Count(x + => x.Old != null + && x.Old.UserId == user.Id + && x.UpdateType == WaifuUpdateType.Claimed + && x.New == null); + var affs = uow.Set() + .AsQueryable() .Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged && w.New != null) @@ -110,12 +116,14 @@ public class WaifuService : IEService, IReadyExecutor if (!await _cs.RemoveAsync(user.Id, price, new("waifu", "reset"))) return false; - var affs = uow.Set().AsQueryable() + var affs = uow.Set() + .AsQueryable() .Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged && w.New != null); - var divorces = uow.Set().AsQueryable() + var divorces = uow.Set() + .AsQueryable() .Where(x => x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed @@ -158,20 +166,22 @@ public class WaifuService : IEService, IReadyExecutor result = WaifuClaimResult.NotEnoughFunds; else { - uow.Set().Add(w = new() - { - Waifu = waifu, - Claimer = claimer, - Affinity = null, - Price = amount - }); - uow.Set().Add(new() - { - User = waifu, - Old = null, - New = claimer, - UpdateType = WaifuUpdateType.Claimed - }); + uow.Set() + .Add(w = new() + { + Waifu = waifu, + Claimer = claimer, + Affinity = null, + Price = amount + }); + uow.Set() + .Add(new() + { + User = waifu, + Old = null, + New = claimer, + UpdateType = WaifuUpdateType.Claimed + }); result = WaifuClaimResult.Success; } } @@ -186,13 +196,14 @@ public class WaifuService : IEService, IReadyExecutor w.Price = amount + (amount / 4); result = WaifuClaimResult.Success; - uow.Set().Add(new() - { - User = w.Waifu, - Old = oldClaimer, - New = w.Claimer, - UpdateType = WaifuUpdateType.Claimed - }); + uow.Set() + .Add(new() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); } } else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity @@ -206,13 +217,14 @@ public class WaifuService : IEService, IReadyExecutor w.Price = amount; result = WaifuClaimResult.Success; - uow.Set().Add(new() - { - User = w.Waifu, - Old = oldClaimer, - New = w.Claimer, - UpdateType = WaifuUpdateType.Claimed - }); + uow.Set() + .Add(new() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); } } else @@ -241,29 +253,31 @@ public class WaifuService : IEService, IReadyExecutor remaining = await _cache.GetRatelimitAsync(GetAffinityKey(user.Id), 30.Minutes()); - + if (remaining is not null) { } else if (w is null) { var thisUser = uow.GetOrCreateUser(user); - uow.Set().Add(new() - { - Affinity = newAff, - Waifu = thisUser, - Price = 1, - Claimer = null - }); + uow.Set() + .Add(new() + { + Affinity = newAff, + Waifu = thisUser, + Price = 1, + Claimer = null + }); success = true; - uow.Set().Add(new() - { - User = thisUser, - Old = null, - New = newAff, - UpdateType = WaifuUpdateType.AffinityChanged - }); + uow.Set() + .Add(new() + { + User = thisUser, + Old = null, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); } else { @@ -272,13 +286,14 @@ public class WaifuService : IEService, IReadyExecutor w.Affinity = newAff; success = true; - uow.Set().Add(new() - { - User = w.Waifu, - Old = oldAff, - New = newAff, - UpdateType = WaifuUpdateType.AffinityChanged - }); + uow.Set() + .Add(new() + { + User = w.Waifu, + Old = oldAff, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); } await uow.SaveChangesAsync(); @@ -301,10 +316,10 @@ public class WaifuService : IEService, IReadyExecutor private static TypedKey GetDivorceKey(ulong userId) => new($"waifu:divorce_cd:{userId}"); - + private static TypedKey GetAffinityKey(ulong userId) => new($"waifu:affinity:{userId}"); - + public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId) { DivorceResult result; @@ -343,13 +358,14 @@ public class WaifuService : IEService, IReadyExecutor var oldClaimer = w.Claimer; w.Claimer = null; - uow.Set().Add(new() - { - User = w.Waifu, - Old = oldClaimer, - New = null, - UpdateType = WaifuUpdateType.Claimed - }); + uow.Set() + .Add(new() + { + User = w.Waifu, + Old = oldClaimer, + New = null, + UpdateType = WaifuUpdateType.Claimed + }); } await uow.SaveChangesAsync(); @@ -358,40 +374,54 @@ public class WaifuService : IEService, IReadyExecutor return (w, result, amount, remaining); } - public async Task GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj) + public async Task GiftWaifuAsync( + IUser from, + IUser giftedWaifu, + WaifuItemModel itemObj, + int count) { - if (!await _cs.RemoveAsync(from, itemObj.Price, new("waifu", "item"))) + ArgumentOutOfRangeException.ThrowIfLessThan(count, 1, nameof(count)); + + if (!await _cs.RemoveAsync(from, itemObj.Price * count, new("waifu", "item"))) return false; + var totalValue = itemObj.Price * count; + await using var uow = _db.GetDbContext(); - var w = uow.Set().ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer)); + var w = uow.Set() + .ByWaifuUserId(giftedWaifu.Id, + set => set + .Include(x => x.Items) + .Include(x => x.Claimer)); if (w is null) { - uow.Set().Add(w = new() - { - Affinity = null, - Claimer = null, - Price = 1, - Waifu = uow.GetOrCreateUser(giftedWaifu) - }); + uow.Set() + .Add(w = new() + { + Affinity = null, + Claimer = null, + Price = 1, + Waifu = uow.GetOrCreateUser(giftedWaifu) + }); } if (!itemObj.Negative) { - w.Items.Add(new() - { - Name = itemObj.Name.ToLowerInvariant(), - ItemEmoji = itemObj.ItemEmoji - }); + w.Items.AddRange(Enumerable.Range(0, count) + .Select((_) => new WaifuItem() + { + Name = itemObj.Name.ToLowerInvariant(), + ItemEmoji = itemObj.ItemEmoji + })); if (w.Claimer?.UserId == from.Id) - w.Price += (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect); + w.Price += (long)(totalValue * _gss.Data.Waifu.Multipliers.GiftEffect); else - w.Price += itemObj.Price / 2; + w.Price += totalValue / 2; } else { - w.Price -= (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect); + w.Price -= (long)(totalValue * _gss.Data.Waifu.Multipliers.NegativeGiftEffect); if (w.Price < 1) w.Price = 1; } @@ -492,6 +522,7 @@ public class WaifuService : IEService, IReadyExecutor } private static readonly TypedKey _waifuDecayKey = $"waifu:last_decay"; + public async Task OnReadyAsync() { // only decay waifu values from shard 0 @@ -513,7 +544,7 @@ public class WaifuService : IEService, IReadyExecutor var nowB = now.ToBinary(); var result = await _cache.GetAsync(_waifuDecayKey); - + if (result.TryGetValue(out var val)) { var lastDecay = DateTime.FromBinary(val); @@ -533,7 +564,6 @@ public class WaifuService : IEService, IReadyExecutor { Price = (long)(old.Price * multi) }); - } catch (Exception ex) { @@ -550,33 +580,35 @@ public class WaifuService : IEService, IReadyExecutor { await using var ctx = _db.GetDbContext(); return await ctx.GetTable() - .Where(x => ctx.GetTable() - .Where(wi => wi.ClaimerId == waifuId) - .Select(wi => wi.WaifuId) - .Contains(x.Id)) - .Select(x => $"{x.Username}#{x.Discriminator}") - .ToListAsyncEF(); + .Where(x => ctx.GetTable() + .Where(wi => wi.ClaimerId == waifuId) + .Select(wi => wi.WaifuId) + .Contains(x.Id)) + .Select(x => $"{x.Username}#{x.Discriminator}") + .ToListAsyncEF(); } + public async Task> GetFansNames(int waifuId) { await using var ctx = _db.GetDbContext(); return await ctx.GetTable() - .Where(x => ctx.GetTable() - .Where(wi => wi.AffinityId == waifuId) - .Select(wi => wi.WaifuId) - .Contains(x.Id)) - .Select(x => $"{x.Username}#{x.Discriminator}") - .ToListAsyncEF(); + .Where(x => ctx.GetTable() + .Where(wi => wi.AffinityId == waifuId) + .Select(wi => wi.WaifuId) + .Contains(x.Id)) + .Select(x => $"{x.Username}#{x.Discriminator}") + .ToListAsyncEF(); } public async Task> GetItems(int waifuId) { await using var ctx = _db.GetDbContext(); return await ctx.GetTable() - .Where(x => x.WaifuInfoId == ctx.GetTable() - .Where(x => x.WaifuId == waifuId) - .Select(x => x.Id) - .FirstOrDefault()) - .ToListAsyncEF(); + .Where(x => x.WaifuInfoId + == ctx.GetTable() + .Where(x => x.WaifuId == waifuId) + .Select(x => x.Id) + .FirstOrDefault()) + .ToListAsyncEF(); } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItems.cs b/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItems.cs new file mode 100644 index 0000000..63b5742 --- /dev/null +++ b/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItems.cs @@ -0,0 +1,6 @@ +#nullable disable +using EllieBot.Modules.Gambling.Common; + +namespace EllieBot.Modules.Gambling; + +public record class MultipleWaifuItems(int Count, WaifuItemModel Item); \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItemsTypeReader.cs b/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItemsTypeReader.cs new file mode 100644 index 0000000..bac72c1 --- /dev/null +++ b/src/EllieBot/Modules/Gambling/Waifus/_common/MultipleWaifuItemsTypeReader.cs @@ -0,0 +1,47 @@ +#nullable disable +using EllieBot.Common.TypeReaders; +using EllieBot.Modules.Gambling.Services; +using System.Text.RegularExpressions; + +namespace EllieBot.Modules.Gambling; + +public partial class MultipleWaifuItemsTypeReader : EllieTypeReader +{ + private readonly WaifuService _service; + + [GeneratedRegex(@"(?:(?\d+)[x*])?(?.+)")] + private static partial Regex ItemRegex(); + + public MultipleWaifuItemsTypeReader(WaifuService service) + { + _service = service; + } + public override ValueTask> ReadAsync(ICommandContext ctx, string input) + { + input = input.ToLowerInvariant(); + var match = ItemRegex().Match(input); + if (!match.Success) + { + return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid input.")); + } + + var count = 1; + if (match.Groups["count"].Success) + { + if (!int.TryParse(match.Groups["count"].Value, out count) || count < 1) + { + return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid count.")); + } + } + + var itemName = match.Groups["item"].Value?.ToLowerInvariant(); + var allItems = _service.GetWaifuItems(); + var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName); + if (item is null) + { + return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Waifu gift does not exist.")); + } + + return new(Discord.Commands.TypeReaderResult.FromSuccess(new MultipleWaifuItems(count, item))); + } +} From ca64765c34c367785de8ef0b45b6b7460516660e Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:44:30 +1200 Subject: [PATCH 05/14] Updated Games module --- .../Games/ChatterBot/ChatterbotService.cs | 28 +++--- .../Games/ChatterBot/_common/Choice.cs | 9 ++ .../Games/ChatterBot/_common/Gpt3Response.cs | 61 ------------- .../Games/ChatterBot/_common/Message.cs | 9 ++ .../_common/OpenAiApi/OpenAiApiMessage.cs | 15 ++++ .../_common/OpenAiApi/OpenAiApiRequest.cs | 18 ++++ .../_common/OpenAiApi/OpenAiApiUsageData.cs | 15 ++++ .../OpenAiApi/OpenAiCompletionResponse.cs | 13 +++ ...icialGptSession.cs => OpenAiApiSession.cs} | 87 +++++++++---------- .../Games/ChatterBot/_common/ThinkResponse.cs | 11 +++ src/EllieBot/Modules/Games/GamesConfig.cs | 71 +++++++++------ .../Modules/Games/GamesConfigService.cs | 61 ++++++++----- 12 files changed, 230 insertions(+), 168 deletions(-) create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs delete mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/Gpt3Response.cs create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiMessage.cs create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiRequest.cs create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiUsageData.cs create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiCompletionResponse.cs rename src/EllieBot/Modules/Games/ChatterBot/_common/{OfficialGptSession.cs => OpenAiApiSession.cs} (69%) create mode 100644 src/EllieBot/Modules/Games/ChatterBot/_common/ThinkResponse.cs diff --git a/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs b/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs index a8c06d8..2129d3b 100644 --- a/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs +++ b/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs @@ -1,6 +1,5 @@ #nullable disable using EllieBot.Common.ModuleBehaviors; -using EllieBot.Db.Models; using EllieBot.Modules.Games.Common; using EllieBot.Modules.Games.Common.ChatterBot; using EllieBot.Modules.Patronage; @@ -58,18 +57,21 @@ public class ChatterBotService : IExecOnMessage Log.Information("Cleverbot will not work as the api key is missing"); return null; - case ChatBotImplementation.Gpt: + case ChatBotImplementation.OpenAi: + var data = _gcs.Data; if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey)) - return new OfficialGptSession(_creds.Gpt3ApiKey, - _gcs.Data.ChatGpt.ModelName, - _gcs.Data.ChatGpt.ChatHistory, - _gcs.Data.ChatGpt.MaxTokens, - _gcs.Data.ChatGpt.MinTokens, - _gcs.Data.ChatGpt.PersonalityPrompt, + return new OpenAiApiSession( + data.ChatGpt.ApiUrl, + _creds.Gpt3ApiKey, + data.ChatGpt.ModelName, + data.ChatGpt.ChatHistory, + data.ChatGpt.MaxTokens, + data.ChatGpt.MinTokens, + data.ChatGpt.PersonalityPrompt, _client.CurrentUser.Username, _httpFactory); - Log.Information("Gpt3 will not work as the api key is missing"); + Log.Information("Openai Api will likely not work as the api key is missing"); return null; default: return null; @@ -88,15 +90,15 @@ public class ChatterBotService : IExecOnMessage public string PrepareMessage(IUserMessage msg) { - var ellieId = _client.CurrentUser.Id; - var normalMention = $"<@{ellieId}> "; - var nickMention = $"<@!{ellieId}> "; + var nadekoId = _client.CurrentUser.Id; + var normalMention = $"<@{nadekoId}> "; + var nickMention = $"<@!{nadekoId}> "; string message; if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture)) message = msg.Content[normalMention.Length..].Trim(); else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture)) message = msg.Content[nickMention.Length..].Trim(); - else if (msg.ReferencedMessage?.Author.Id == ellieId) + else if (msg.ReferencedMessage?.Author.Id == nadekoId) message = msg.Content; else return null; diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs new file mode 100644 index 0000000..db71eee --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class Choice +{ + [JsonPropertyName("message")] + public Message Message { get; init; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/Gpt3Response.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/Gpt3Response.cs deleted file mode 100644 index e983338..0000000 --- a/src/EllieBot/Modules/Games/ChatterBot/_common/Gpt3Response.cs +++ /dev/null @@ -1,61 +0,0 @@ -#nullable disable -using System.Text.Json.Serialization; - -namespace EllieBot.Modules.Games.Common.ChatterBot; - -public class OpenAiCompletionResponse -{ - [JsonPropertyName("choices")] - public Choice[] Choices { get; set; } - - [JsonPropertyName("usage")] - public OpenAiUsageData Usage { get; set; } -} - -public class OpenAiUsageData -{ - [JsonPropertyName("prompt_tokens")] - public int PromptTokens { get; set; } - - [JsonPropertyName("completion_tokens")] - public int CompletionTokens { get; set; } - - [JsonPropertyName("total_tokens")] - public int TotalTokens { get; set; } -} - -public class Choice -{ - [JsonPropertyName("message")] - public Message Message { get; init; } -} - -public class Message { - [JsonPropertyName("content")] - public string Content { get; init; } -} - -public class Gpt3ApiRequest -{ - [JsonPropertyName("model")] - public string Model { get; init; } - - [JsonPropertyName("messages")] - public List Messages { get; init; } - - [JsonPropertyName("temperature")] - public int Temperature { get; init; } - - [JsonPropertyName("max_tokens")] - public int MaxTokens { get; init; } -} - -public class GPTMessage -{ - [JsonPropertyName("role")] - public string Role {get; init;} - [JsonPropertyName("content")] - public string Content {get; init;} - [JsonPropertyName("name")] - public string Name {get; init;} -} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs new file mode 100644 index 0000000..04532f5 --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class Message +{ + [JsonPropertyName("content")] + public string Content { get; init; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiMessage.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiMessage.cs new file mode 100644 index 0000000..0fdaf71 --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiMessage.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class OpenAiApiMessage +{ + [JsonPropertyName("role")] + public string Role { get; init; } + + [JsonPropertyName("content")] + public string Content { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiRequest.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiRequest.cs new file mode 100644 index 0000000..1ea5d69 --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiRequest.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class OpenAiApiRequest +{ + [JsonPropertyName("model")] + public string Model { get; init; } + + [JsonPropertyName("messages")] + public List Messages { get; init; } + + [JsonPropertyName("temperature")] + public int Temperature { get; init; } + + [JsonPropertyName("max_tokens")] + public int MaxTokens { get; init; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiUsageData.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiUsageData.cs new file mode 100644 index 0000000..1525dac --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiApiUsageData.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class OpenAiApiUsageData +{ + [JsonPropertyName("prompt_tokens")] + public int PromptTokens { get; set; } + + [JsonPropertyName("completion_tokens")] + public int CompletionTokens { get; set; } + + [JsonPropertyName("total_tokens")] + public int TotalTokens { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiCompletionResponse.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiCompletionResponse.cs new file mode 100644 index 0000000..1b7bdcf --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApi/OpenAiCompletionResponse.cs @@ -0,0 +1,13 @@ +#nullable disable +using System.Text.Json.Serialization; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public class OpenAiCompletionResponse +{ + [JsonPropertyName("choices")] + public Choice[] Choices { get; set; } + + [JsonPropertyName("usage")] + public OpenAiApiUsageData Usage { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OfficialGptSession.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs similarity index 69% rename from src/EllieBot/Modules/Games/ChatterBot/_common/OfficialGptSession.cs rename to src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs index e822b53..4511988 100644 --- a/src/EllieBot/Modules/Games/ChatterBot/_common/OfficialGptSession.cs +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs @@ -1,77 +1,78 @@ #nullable disable using Newtonsoft.Json; using OneOf.Types; -using System.Net.Http.Json; using SharpToken; -using System.CodeDom; +using System.Net.Http.Json; using System.Text.RegularExpressions; namespace EllieBot.Modules.Games.Common.ChatterBot; -public partial class OfficialGptSession : IChatterBotSession +public partial class OpenAiApiSession : IChatterBotSession { - private string Uri - => $"https://api.openai.com/v1/chat/completions"; - + private readonly string _baseUrl; private readonly string _apiKey; private readonly string _model; private readonly int _maxHistory; private readonly int _maxTokens; private readonly int _minTokens; - private readonly string _ellieUsername; + private readonly string _nadekoUsername; private readonly GptEncoding _encoding; - private List messages = new(); + private List messages = new(); private readonly IHttpClientFactory _httpFactory; - public OfficialGptSession( + public OpenAiApiSession( + string url, string apiKey, - ChatGptModel model, + string model, int chatHistory, int maxTokens, int minTokens, string personality, - string ellieUsername, + string nadekoUsername, IHttpClientFactory factory) { - _apiKey = apiKey; - _httpFactory = factory; - - _model = model switch + if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out _)) { - ChatGptModel.Gpt35Turbo => "gpt-3.5-turbo", - ChatGptModel.Gpt4o => "gpt-4o", - _ => throw new ArgumentException("Unknown, unsupported or obsolete model", nameof(model)) - }; + throw new ArgumentException("Invalid OpenAi api url provided", nameof(url)); + } + _baseUrl = url.TrimEnd('/'); + + _apiKey = apiKey; + _model = model; + _httpFactory = factory; _maxHistory = chatHistory; _maxTokens = maxTokens; _minTokens = minTokens; - _ellieUsername = UsernameCleaner().Replace(ellieUsername, ""); - _encoding = GptEncoding.GetEncodingForModel(_model); - messages.Add(new() + _nadekoUsername = UsernameCleaner().Replace(nadekoUsername, ""); + _encoding = GptEncoding.GetEncodingForModel("gpt-4o"); + if (!string.IsNullOrWhiteSpace(personality)) { - Role = "system", - Content = personality, - Name = _ellieUsername - }); + messages.Add(new() + { + Role = "system", + Content = personality, + Name = _nadekoUsername + }); + } } [GeneratedRegex("[^a-zA-Z0-9_-]")] private static partial Regex UsernameCleaner(); - + public async Task>> Think(string input, string username) { username = UsernameCleaner().Replace(username, ""); - + messages.Add(new() { Role = "user", Content = input, Name = username }); - + while (messages.Count > _maxHistory + 2) { messages.RemoveAt(1); @@ -92,28 +93,29 @@ public partial class OfficialGptSession : IChatterBotSession } else { - return new Error("Token count exceeded, please increase the number of tokens in the bot config and restart."); + return new Error( + "Token count exceeded, please increase the number of tokens in the bot config and restart."); } } using var http = _httpFactory.CreateClient(); http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey); - - var data = await http.PostAsJsonAsync(Uri, - new Gpt3ApiRequest() + + var data = await http.PostAsJsonAsync($"{_baseUrl}/v1/chat/completions", + new OpenAiApiRequest() { Model = _model, Messages = messages, MaxTokens = _maxTokens - tokensUsed, Temperature = 1, }); - + var dataString = await data.Content.ReadAsStringAsync(); try { var response = JsonConvert.DeserializeObject(dataString); - - Log.Information("Received response: {response} ", dataString); + + // Log.Information("Received response: {Response} ", dataString); var res = response?.Choices?[0]; var message = res?.Message?.Content; @@ -121,14 +123,14 @@ public partial class OfficialGptSession : IChatterBotSession { return new Error("ChatGpt: Received no response."); } - + messages.Add(new() { Role = "assistant", Content = message, - Name = _ellieUsername + Name = _nadekoUsername }); - + return new ThinkResult() { Text = message, @@ -142,11 +144,4 @@ public partial class OfficialGptSession : IChatterBotSession return new Error("Unexpected response received"); } } -} - -public sealed class ThinkResult -{ - public string Text { get; set; } - public int TokensIn { get; set; } - public int TokensOut { get; set; } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/ThinkResponse.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/ThinkResponse.cs new file mode 100644 index 0000000..a5b0b5f --- /dev/null +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/ThinkResponse.cs @@ -0,0 +1,11 @@ +#nullable disable +using System.CodeDom; + +namespace EllieBot.Modules.Games.Common.ChatterBot; + +public sealed class ThinkResult +{ + public string Text { get; set; } + public int TokensIn { get; set; } + public int TokensOut { get; set; } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/GamesConfig.cs b/src/EllieBot/Modules/Games/GamesConfig.cs index e56cc70..9308329 100644 --- a/src/EllieBot/Modules/Games/GamesConfig.cs +++ b/src/EllieBot/Modules/Games/GamesConfig.cs @@ -8,7 +8,7 @@ namespace EllieBot.Modules.Games.Common; public sealed partial class GamesConfig : ICloneable { [Comment("DO NOT CHANGE")] - public int Version { get; set; } = 4; + public int Version { get; set; } = 5; [Comment("Hangman related settings (.hangman command)")] public HangmanConfig Hangman { get; set; } = new() @@ -103,10 +103,13 @@ public sealed partial class GamesConfig : ICloneable } ]; - [Comment(@"Which chatbot API should bot use. -'cleverbot' - bot will use Cleverbot API. -'gpt' - bot will use GPT API")] - public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.Gpt; + [Comment( + """ + Which chatbot API should bot use. + 'cleverbot' - bot will use Cleverbot API. + 'openai' - bot will use OpenAi API + """)] + public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.OpenAi; public ChatGptConfig ChatGpt { get; set; } = new(); } @@ -114,19 +117,38 @@ public sealed partial class GamesConfig : ICloneable [Cloneable] public sealed partial class ChatGptConfig { - [Comment(@"Which GPT Model should bot use. - gpt35turbo - cheapest - gpt4o - more expensive, higher quality -")] - public ChatGptModel ModelName { get; set; } = ChatGptModel.Gpt35Turbo; + [Comment(""" + Url to any openai api compatible url. + Make sure to modify the modelName appropriately + DO NOT add /v1/chat/completions suffix to the url + """)] + public string ApiUrl { get; set; } = "https://api.openai.com"; - [Comment(@"How should the chat bot behave, what's its personality? (Usage of this counts towards the max tokens)")] - public string PersonalityPrompt { get; set; } = "You are a chat bot willing to have a conversation with anyone about anything."; + [Comment(""" + Which GPT Model should bot use. + gpt-3.5-turbo - cheapest + gpt-4o - more expensive, higher quality - [Comment(@"The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)")] + If you are using another openai compatible api, you may use any of the models supported by that api + """)] + public string ModelName { get; set; } = "gpt-3.5-turbo"; + + [Comment(""" + How should the chatbot behave, what's its personality? + This will be sent as a system message. + Usage of this counts towards the max tokens. + """)] + public string PersonalityPrompt { get; set; } = + "You are a chat bot willing to have a conversation with anyone about anything."; + + [Comment( + """ + The maximum number of messages in a conversation that can be remembered. + This will increase the number of tokens used. + """)] public int ChatHistory { get; set; } = 5; - [Comment(@"The maximum number of tokens to use per GPT API call")] + [Comment(@"The maximum number of tokens to use per OpenAi API call")] public int MaxTokens { get; set; } = 100; [Comment(@"The minimum number of tokens to use per GPT API call, such that chat history is removed to make room.")] @@ -147,9 +169,9 @@ public sealed partial class TriviaConfig public long CurrencyReward { get; set; } [Comment(""" - Users won't be able to start trivia games which have - a smaller win requirement than the one specified by this setting. - """)] + Users won't be able to start trivia games which have + a smaller win requirement than the one specified by this setting. + """)] public int MinimumWinReq { get; set; } = 1; } @@ -163,18 +185,11 @@ public sealed partial class RaceAnimal public enum ChatBotImplementation { Cleverbot, + OpenAi = 1, + + [Obsolete] Gpt = 1, + [Obsolete] Gpt3 = 1, -} - -public enum ChatGptModel -{ - [Obsolete] - Gpt4, - [Obsolete] - Gpt432k, - - Gpt35Turbo, - Gpt4o, } \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/GamesConfigService.cs b/src/EllieBot/Modules/Games/GamesConfigService.cs index 06b75c3..f6ff635 100644 --- a/src/EllieBot/Modules/Games/GamesConfigService.cs +++ b/src/EllieBot/Modules/Games/GamesConfigService.cs @@ -32,29 +32,21 @@ public sealed class GamesConfigService : ConfigServiceBase gs => gs.ChatBot, ConfigParsers.InsensitiveEnum, ConfigPrinters.ToString); + + AddParsedProp("gpt.apiUrl", + gs => gs.ChatGpt.ApiUrl, + ConfigParsers.String, + ConfigPrinters.ToString); + AddParsedProp("gpt.modelName", gs => gs.ChatGpt.ModelName, - ConfigParsers.InsensitiveEnum, + ConfigParsers.String, ConfigPrinters.ToString); + AddParsedProp("gpt.personality", gs => gs.ChatGpt.PersonalityPrompt, ConfigParsers.String, ConfigPrinters.ToString); - AddParsedProp("gpt.chathistory", - gs => gs.ChatGpt.ChatHistory, - int.TryParse, - ConfigPrinters.ToString, - val => val > 0); - AddParsedProp("gpt.max_tokens", - gs => gs.ChatGpt.MaxTokens, - int.TryParse, - ConfigPrinters.ToString, - val => val > 0); - AddParsedProp("gpt.min_tokens", - gs => gs.ChatGpt.MinTokens, - int.TryParse, - ConfigPrinters.ToString, - val => val > 0); Migrate(); } @@ -78,7 +70,7 @@ public sealed class GamesConfigService : ConfigServiceBase ModifyConfig(c => { c.Version = 3; - c.ChatGpt.ModelName = ChatGptModel.Gpt35Turbo; + c.ChatGpt.ModelName = "gpt35turbo"; }); } @@ -89,11 +81,40 @@ public sealed class GamesConfigService : ConfigServiceBase c.Version = 4; #pragma warning disable CS0612 // Type or member is obsolete c.ChatGpt.ModelName = - c.ChatGpt.ModelName == ChatGptModel.Gpt4 || c.ChatGpt.ModelName == ChatGptModel.Gpt432k - ? ChatGptModel.Gpt4o - : c.ChatGpt.ModelName; + c.ChatGpt.ModelName.Equals("gpt4", StringComparison.OrdinalIgnoreCase) + || c.ChatGpt.ModelName.Equals("gpt432k", StringComparison.OrdinalIgnoreCase) + ? "gpt-4o" + : "gpt-3.5-turbo"; #pragma warning restore CS0612 // Type or member is obsolete }); } + + if (data.Version < 5) + { + ModifyConfig(c => + { + c.Version = 5; + c.ChatBot = c.ChatBot == ChatBotImplementation.OpenAi + ? ChatBotImplementation.OpenAi + : c.ChatBot; + + if (c.ChatGpt.ModelName.Equals("gpt4o", StringComparison.OrdinalIgnoreCase)) + { + c.ChatGpt.ModelName = "gpt-4o"; + } + else if (c.ChatGpt.ModelName.Equals("gpt35turbo", StringComparison.OrdinalIgnoreCase)) + { + c.ChatGpt.ModelName = "gpt-3.5-turbo"; + } + else + { + Log.Warning( + "Unknown OpenAI api model name: {ModelName}. " + + "It will be reset to 'gpt-3.5-turbo' only this time", + c.ChatGpt.ModelName); + c.ChatGpt.ModelName = "gpt-3.5-turbo"; + } + }); + } } } \ No newline at end of file From 6b6f822ec8db131b52d72ae2ffbc2d1682cd9a9e Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:44:58 +1200 Subject: [PATCH 06/14] Updated Searches module --- .../Modules/Searches/Crypto/CryptoCommands.cs | 139 ++++++++++++------ .../Modules/Searches/Crypto/CryptoService.cs | 53 +++++++ 2 files changed, 146 insertions(+), 46 deletions(-) diff --git a/src/EllieBot/Modules/Searches/Crypto/CryptoCommands.cs b/src/EllieBot/Modules/Searches/Crypto/CryptoCommands.cs index 37353b1..13112e1 100644 --- a/src/EllieBot/Modules/Searches/Crypto/CryptoCommands.cs +++ b/src/EllieBot/Modules/Searches/Crypto/CryptoCommands.cs @@ -16,12 +16,12 @@ public partial class Searches _stocksService = stocksService; _stockDrawingService = stockDrawingService; } - + [Cmd] - public async Task Stock([Leftover]string query) + public async Task Stock([Leftover] string query) { using var typing = ctx.Channel.EnterTypingState(); - + var stock = await _stocksService.GetStockDataAsync(query); if (stock is null) @@ -36,9 +36,9 @@ public partial class Searches var symbol = symbols.First(); var promptEmbed = _sender.CreateEmbed() - .WithDescription(symbol.Description) - .WithTitle(GetText(strs.did_you_mean(symbol.Symbol))); - + .WithDescription(symbol.Description) + .WithTitle(GetText(strs.did_you_mean(symbol.Symbol))); + if (!await PromptUserConfirmAsync(promptEmbed)) return; @@ -54,7 +54,7 @@ public partial class Searches var candles = await _stocksService.GetCandleDataAsync(query); var stockImageTask = _stockDrawingService.GenerateCombinedChartAsync(candles); - + var localCulture = (CultureInfo)Culture.Clone(); localCulture.NumberFormat.CurrencySymbol = "$"; @@ -64,34 +64,34 @@ public partial class Searches var change = (stock.Price - stock.Close).ToString("N2", Culture); var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture); - + var sign50 = stock.Change50d >= 0 ? "\\🔼" : "\\🔻"; var change50 = (stock.Change50d).ToString("P1", Culture); - + var sign200 = stock.Change200d >= 0 ? "\\🔼" : "\\🔻"; - + var change200 = (stock.Change200d).ToString("P1", Culture); - + var price = stock.Price.ToString("C2", localCulture); var eb = _sender.CreateEmbed() - .WithOkColor() - .WithAuthor(stock.Symbol) - .WithUrl($"https://www.tradingview.com/chart/?symbol={stock.Symbol}") - .WithTitle(stock.Name) - .AddField(GetText(strs.price), $"{sign} **{price}**", true) - .AddField(GetText(strs.market_cap), stock.MarketCap, true) - .AddField(GetText(strs.volume_24h), stock.DailyVolume.ToString("C0", localCulture), true) - .AddField("Change", $"{change} ({changePercent})", true) - // .AddField("Change 50d", $"{sign50}{change50}", true) - // .AddField("Change 200d", $"{sign200}{change200}", true) - .WithFooter(stock.Exchange); - + .WithOkColor() + .WithAuthor(stock.Symbol) + .WithUrl($"https://www.tradingview.com/chart/?symbol={stock.Symbol}") + .WithTitle(stock.Name) + .AddField(GetText(strs.price), $"{sign} **{price}**", true) + .AddField(GetText(strs.market_cap), stock.MarketCap, true) + .AddField(GetText(strs.volume_24h), stock.DailyVolume.ToString("C0", localCulture), true) + .AddField("Change", $"{change} ({changePercent})", true) + // .AddField("Change 50d", $"{sign50}{change50}", true) + // .AddField("Change 200d", $"{sign200}{change200}", true) + .WithFooter(stock.Exchange); + var message = await Response().Embed(eb).SendAsync(); await using var imageData = await stockImageTask; if (imageData is null) @@ -105,15 +105,12 @@ public partial class Searches await message.ModifyAsync(mp => { mp.Attachments = - new(new[] - { - attachment - }); + new(new[] { attachment }); mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build(); }); } - + [Cmd] public async Task Crypto(string name) @@ -128,9 +125,9 @@ public partial class Searches if (nearest is not null) { var embed = _sender.CreateEmbed() - .WithTitle(GetText(strs.crypto_not_found)) - .WithDescription( - GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})")))); + .WithTitle(GetText(strs.crypto_not_found)) + .WithDescription( + GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})")))); if (await PromptUserConfirmAsync(embed)) crypto = nearest; @@ -146,7 +143,7 @@ public partial class Searches var localCulture = (CultureInfo)Culture.Clone(); localCulture.NumberFormat.CurrencySymbol = "$"; - + var sevenDay = (usd.PercentChange7d / 100).ToString("P2", localCulture); var lastDay = (usd.PercentChange24h / 100).ToString("P2", localCulture); var price = usd.Price < 0.01 @@ -159,28 +156,29 @@ public partial class Searches await using var sparkline = await _service.GetSparklineAsync(crypto.Id, usd.PercentChange7d >= 0); var fileName = $"{crypto.Slug}_7d.png"; - + var toSend = _sender.CreateEmbed() - .WithOkColor() - .WithAuthor($"#{crypto.CmcRank}") - .WithTitle($"{crypto.Name} ({crypto.Symbol})") - .WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/") - .WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png") - .AddField(GetText(strs.market_cap), marketCap, true) - .AddField(GetText(strs.price), price, true) - .AddField(GetText(strs.volume_24h), volume, true) - .AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true) - .AddField(GetText(strs.market_cap_dominance), dominance, true) - .WithImageUrl($"attachment://{fileName}"); + .WithOkColor() + .WithAuthor($"#{crypto.CmcRank}") + .WithTitle($"{crypto.Name} ({crypto.Symbol})") + .WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/") + .WithThumbnailUrl( + $"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png") + .AddField(GetText(strs.market_cap), marketCap, true) + .AddField(GetText(strs.price), price, true) + .AddField(GetText(strs.volume_24h), volume, true) + .AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true) + .AddField(GetText(strs.market_cap_dominance), dominance, true) + .WithImageUrl($"attachment://{fileName}"); if (crypto.CirculatingSupply is double cs) { var csStr = cs.ToString("N0", localCulture); - + if (crypto.MaxSupply is double ms) { var perc = (cs / ms).ToString("P1", localCulture); - + toSend.AddField(GetText(strs.circulating_supply), $"{csStr} ({perc})", true); } else @@ -192,5 +190,54 @@ public partial class Searches await ctx.Channel.SendFileAsync(sparkline, fileName, embed: toSend.Build()); } + + [Cmd] + public async Task Coins(int page = 1) + { + if (--page < 0) + return; + + if (page > 25) + page = 25; + + await Response() + .Paginated() + .PageItems(async (page) => + { + var coins = await _service.GetTopCoins(page); + return coins; + }) + .PageSize(10) + .Page((items, _) => + { + var embed = _sender.CreateEmbed() + .WithOkColor(); + + if (items.Count > 0) + { + foreach (var coin in items) + { + embed.AddField($"#{coin.MarketCapRank} {coin.Symbol} - {coin.Name}", + $""" + `Price:` {GetArrowEmoji(coin.PercentChange24h)} {coin.CurrentPrice.ToShortString()}$ ({GetSign(coin.PercentChange24h)}{Math.Round(coin.PercentChange24h, 2)}%) + `MarketCap:` {coin.MarketCap.ToShortString()}$ + `Supply:` {(coin.CirculatingSupply?.ToShortString() ?? "?")} / {(coin.TotalSupply?.ToShortString() ?? "?")} + """, + inline: false); + } + } + + return embed; + }) + .CurrentPage(page) + .AddFooter(false) + .SendAsync(); + } + + private static string GetArrowEmoji(decimal value) + => value > 0 ? "▲" : "▼"; + + private static string GetSign(decimal value) + => value >= 0 ? "+" : "-"; } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Searches/Crypto/CryptoService.cs b/src/EllieBot/Modules/Searches/Crypto/CryptoService.cs index 146dac3..59463fa 100644 --- a/src/EllieBot/Modules/Searches/Crypto/CryptoService.cs +++ b/src/EllieBot/Modules/Searches/Crypto/CryptoService.cs @@ -4,8 +4,10 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using System.Collections.ObjectModel; using System.Globalization; using System.Net.Http.Json; +using System.Text.Json.Serialization; using System.Xml; using Color = SixLabors.ImageSharp.Color; using StringExtensions = EllieBot.Extensions.StringExtensions; @@ -212,4 +214,55 @@ public class CryptoService : IEService var points = GetSparklinePointsFromSvgText(str); return points; } + + private static TypedKey> GetTopCoinsKey() + => new($"crypto:top_coins"); + + public async Task?> GetTopCoins(int page) + { + if (page >= 25) + page = 24; + + using var http = _httpFactory.CreateClient(); + + http.AddFakeHeaders(); + + var result = await _cache.GetOrAddAsync>(GetTopCoinsKey(), + async () => await http.GetFromJsonAsync>( + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250") + ?? [], + expiry: TimeSpan.FromHours(1)); + + return result!.Skip(page * 10).Take(10).ToList(); + } +} + +public sealed class GeckoCoinsResult +{ + [JsonPropertyName("id")] + public required string Id { get; init; } + + [JsonPropertyName("name")] + public required string Name { get; init; } + + [JsonPropertyName("symbol")] + public required string Symbol { get; init; } + + [JsonPropertyName("current_price")] + public required decimal CurrentPrice { get; init; } + + [JsonPropertyName("price_change_percentage_24h")] + public required decimal PercentChange24h { get; init; } + + [JsonPropertyName("market_cap")] + public required decimal MarketCap { get; init; } + + [JsonPropertyName("circulating_supply")] + public required decimal? CirculatingSupply { get; init; } + + [JsonPropertyName("total_supply")] + public required decimal? TotalSupply { get; init; } + + [JsonPropertyName("market_cap_rank")] + public required int MarketCapRank { get; init; } } \ No newline at end of file From b2b8e4c3d3ec6f8215532cb341fca47f1042a3ea Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:45:13 +1200 Subject: [PATCH 07/14] Updated Utility module --- src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs b/src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs index 612247b..6f6c03c 100644 --- a/src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs +++ b/src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs @@ -245,7 +245,7 @@ public sealed class AiAssistantService { if (guild is not SocketGuild sg) return false; - + var sess = _cbs.GetOrCreateSession(guild.Id); if (sess is null) return false; @@ -302,7 +302,7 @@ public sealed class AiAssistantService await _sender.Response(channel) .Error(errorMsg) .SendAsync(); - + return true; } From 624171c35f62f0bc9bb31ed3c402f75c8e09e541 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:45:33 +1200 Subject: [PATCH 08/14] Updated Xp module --- src/EllieBot/Modules/Xp/Club/Club.cs | 19 +++--- src/EllieBot/Modules/Xp/Club/ClubService.cs | 68 ++++++++++++-------- src/EllieBot/Modules/Xp/Club/IClubService.cs | 3 +- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/EllieBot/Modules/Xp/Club/Club.cs b/src/EllieBot/Modules/Xp/Club/Club.cs index d584eba..9a84dc9 100644 --- a/src/EllieBot/Modules/Xp/Club/Club.cs +++ b/src/EllieBot/Modules/Xp/Club/Club.cs @@ -113,16 +113,18 @@ public partial class Xp { var lvl = new LevelStats(club.Xp); var allUsers = club.Members.OrderByDescending(x => - { - var l = new LevelStats(x.TotalXp).Level; - if (club.OwnerId == x.Id) - return int.MaxValue; - if (x.IsClubAdmin) - return (int.MaxValue / 2) + l; - return l; - }) + { + var l = new LevelStats(x.TotalXp).Level; + if (club.OwnerId == x.Id) + return int.MaxValue; + if (x.IsClubAdmin) + return (int.MaxValue / 2) + l; + return l; + }) .ToList(); + var rank = await _service.GetClubRankAsync(club.Id); + await Response() .Paginated() .Items(allUsers) @@ -135,6 +137,7 @@ public partial class Xp .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)"))) .AddField(GetText(strs.desc), string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description) + .AddField(GetText(strs.rank), $"#{rank}", true) .AddField(GetText(strs.owner), club.Owner.ToString(), true) // .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true) .AddField(GetText(strs.members), diff --git a/src/EllieBot/Modules/Xp/Club/ClubService.cs b/src/EllieBot/Modules/Xp/Club/ClubService.cs index be89659..d35a05d 100644 --- a/src/EllieBot/Modules/Xp/Club/ClubService.cs +++ b/src/EllieBot/Modules/Xp/Club/ClubService.cs @@ -23,22 +23,22 @@ public class ClubService : IEService, IClubService { if (!CheckClubName(clubName)) return ClubCreateResult.NameTooLong; - + //must be lvl 5 and must not be in a club already await using var uow = _db.GetDbContext(); var du = uow.GetOrCreateUser(user); var xp = new LevelStats(du.TotalXp); - - if (xp.Level < 5) + + if (xp.Level < 5) return ClubCreateResult.InsufficientLevel; - + if (du.ClubId is not null) return ClubCreateResult.AlreadyInAClub; if (await uow.Set().AnyAsyncEF(x => x.Name == clubName)) return ClubCreateResult.NameTaken; - + du.IsClubAdmin = true; du.Club = new() { @@ -53,7 +53,7 @@ public class ClubService : IEService, IClubService return ClubCreateResult.Success; } - + public OneOf TransferClub(IUser from, IUser newOwner) { using var uow = _db.GetDbContext(); @@ -62,7 +62,7 @@ public class ClubService : IEService, IClubService if (club is null || club.Owner.UserId != from.Id) return ClubTransferError.NotOwner; - + if (!club.Members.Contains(newOwnerUser)) return ClubTransferError.TargetNotMember; @@ -72,22 +72,22 @@ public class ClubService : IEService, IClubService uow.SaveChanges(); return club; } - + public async Task ToggleAdminAsync(IUser owner, IUser toAdmin) { if (owner.Id == toAdmin.Id) return ToggleAdminResult.CantTargetThyself; - + await using var uow = _db.GetDbContext(); var club = uow.Set().GetByOwner(owner.Id); var adminUser = uow.GetOrCreateUser(toAdmin); if (club is null) return ToggleAdminResult.NotOwner; - - if(!club.Members.Contains(adminUser)) + + if (!club.Members.Contains(adminUser)) return ToggleAdminResult.TargetNotMember; - + var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin; await uow.SaveChangesAsync(); return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin; @@ -99,17 +99,17 @@ public class ClubService : IEService, IClubService var member = uow.Set().GetByMember(user.Id); return member; } - + public async Task SetClubIconAsync(ulong ownerUserId, string url) { if (url is not null) { using var http = _httpFactory.CreateClient(); using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); - - if (!temp.IsImage()) + + if (!temp.IsImage()) return SetClubIconResult.InvalidFileType; - + if (temp.GetContentLength() > 5.Megabytes()) return SetClubIconResult.TooLarge; } @@ -134,6 +134,18 @@ public class ClubService : IEService, IClubService return club is not null; } + public async Task GetClubRankAsync(int clubId) + { + await using var uow = _db.GetDbContext(); + + var rank = await uow.Clubs + .ToLinqToDBTable() + .Where(x => x.Xp > (uow.Clubs.First(c => c.Id == clubId).Xp)) + .CountAsyncLinqToDB(); + + return rank + 1; + } + public ClubApplyResult ApplyToClub(IUser user, ClubInfo club) { using var uow = _db.GetDbContext(); @@ -144,10 +156,10 @@ public class ClubService : IEService, IClubService // or doesn't min minumum level requirement, can't apply if (du.ClubId is not null) return ClubApplyResult.AlreadyInAClub; - + if (club.Bans.Any(x => x.UserId == du.Id)) return ClubApplyResult.Banned; - + if (club.Applicants.Any(x => x.UserId == du.Id)) return ClubApplyResult.AlreadyApplied; @@ -162,7 +174,7 @@ public class ClubService : IEService, IClubService return ClubApplyResult.Success; } - + public ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser) { discordUser = null; @@ -188,7 +200,7 @@ public class ClubService : IEService, IClubService uow.SaveChanges(); return ClubAcceptResult.Accepted; } - + public ClubDenyResult RejectApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser) { discordUser = null; @@ -201,9 +213,9 @@ public class ClubService : IEService, IClubService club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); if (applicant is null) return ClubDenyResult.NoSuchApplicant; - + club.Applicants.Remove(applicant); - + discordUser = applicant.User; uow.SaveChanges(); return ClubDenyResult.Rejected; @@ -220,7 +232,7 @@ public class ClubService : IEService, IClubService using var uow = _db.GetDbContext(); var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club)); if (du.Club is null) - return ClubLeaveResult.NotInAClub; + return ClubLeaveResult.NotInAClub; if (du.Club.OwnerId == du.Id) return ClubLeaveResult.OwnerCantLeave; @@ -306,7 +318,7 @@ public class ClubService : IEService, IClubService return ClubUnbanResult.Success; } - + public ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club) { using var uow = _db.GetDbContext(); @@ -342,14 +354,14 @@ public class ClubService : IEService, IClubService { if (!CheckClubName(clubName)) return ClubRenameResult.NameTooLong; - + await using var uow = _db.GetDbContext(); - + var club = uow.Set().GetByOwnerOrAdmin(userId); - + if (club is null) return ClubRenameResult.NotOwnerOrAdmin; - + if (await uow.Set().AnyAsyncEF(x => x.Name == clubName)) return ClubRenameResult.NameTaken; diff --git a/src/EllieBot/Modules/Xp/Club/IClubService.cs b/src/EllieBot/Modules/Xp/Club/IClubService.cs index 7e9bbf0..8a45e8d 100644 --- a/src/EllieBot/Modules/Xp/Club/IClubService.cs +++ b/src/EllieBot/Modules/Xp/Club/IClubService.cs @@ -6,7 +6,7 @@ namespace EllieBot.Modules.Xp.Services; public interface IClubService { Task CreateClubAsync(IUser user, string clubName); - OneOf TransferClub(IUser from, IUser newOwner); + OneOf TransferClub(IUser from, IUser newOwner); Task ToggleAdminAsync(IUser owner, IUser toAdmin); ClubInfo? GetClubByMember(IUser user); Task SetClubIconAsync(ulong ownerUserId, string? url); @@ -23,6 +23,7 @@ public interface IClubService ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club); List GetClubLeaderboardPage(int page); Task RenameClubAsync(ulong userId, string clubName); + Task GetClubRankAsync(int clubId); } public enum ClubApplyResult From 81ebad702b8d4c19e01dbc708750cb581334c735 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:45:55 +1200 Subject: [PATCH 09/14] Updated Common files --- .../Extensions/StringExtensions.cs | 15 ++++++------ .../_common/_Extensions/NumberExtensions.cs | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/EllieBot/_common/Abstractions/Extensions/StringExtensions.cs b/src/EllieBot/_common/Abstractions/Extensions/StringExtensions.cs index f2fd050..c96f74a 100644 --- a/src/EllieBot/_common/Abstractions/Extensions/StringExtensions.cs +++ b/src/EllieBot/_common/Abstractions/Extensions/StringExtensions.cs @@ -82,14 +82,14 @@ public static class StringExtensions // Step 3 for (var i = 1; i <= n; i++) //Step 4 - for (var j = 1; j <= m; j++) - { - // Step 5 - var cost = t[j - 1] == s[i - 1] ? 0 : 1; + for (var j = 1; j <= m; j++) + { + // Step 5 + var cost = t[j - 1] == s[i - 1] ? 0 : 1; - // Step 6 - d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); - } + // Step 6 + d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); + } // Step 7 return d[n, m]; @@ -147,4 +147,5 @@ public static class StringExtensions var newString = str.UnescapeUnicodeCodePoint(); return newString; }); + } \ No newline at end of file diff --git a/src/EllieBot/_common/_Extensions/NumberExtensions.cs b/src/EllieBot/_common/_Extensions/NumberExtensions.cs index 3e0f703..3e28588 100644 --- a/src/EllieBot/_common/_Extensions/NumberExtensions.cs +++ b/src/EllieBot/_common/_Extensions/NumberExtensions.cs @@ -1,7 +1,30 @@ +using System.Globalization; + namespace EllieBot.Extensions; public static class NumberExtensions { public static DateTimeOffset ToUnixTimestamp(this double number) => new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number); + + public static string ToShortString(this decimal value) + { + if (value <= 1_000) + return Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + if (value <= 1_000_000) + return Math.Round(value, 1).ToString(CultureInfo.InvariantCulture); + var tokens = " MBtq"; + var i = 2; + while (true) + { + var num = (decimal)Math.Pow(1000, i); + if (num > value) + { + var num2 = (decimal)Math.Pow(1000, i - 1); + return $"{Math.Round((value / num2), 1)}{tokens[i - 1]}".Trim(); + } + + i++; + } + } } \ No newline at end of file From f318ec274181b43fecda120c7825914ccf623ef9 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:46:29 +1200 Subject: [PATCH 10/14] Updated editorconfig file and added build files --- build.ps1 | 46 ++++++++++++++++++++++++++++++++++++++ build.sh | 18 +++++++++++++++ src/EllieBot/.editorconfig | 6 ++--- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 build.ps1 create mode 100644 build.sh diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..3b99cec --- /dev/null +++ b/build.ps1 @@ -0,0 +1,46 @@ +echo "" +echo "███████╗██╗ ██╗ ██╗███████╗██████╗ ██████╗ ████████╗" +echo "██╔════╝██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝" +echo "█████╗ ██║ ██║ ██║█████╗ ██████╔╝██║ ██║ ██║ " +echo "██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══██╗██║ ██║ ██║ " +echo "███████╗███████╗███████╗██║███████╗██████╔╝╚██████╔╝ ██║ " +echo "╚══════╝╚══════╝╚══════╝╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ " +echo "" +echo "Copyright © 2024 Toastie_t0ast & EllieBotDevs" +echo "" + +echo "" +echo "Publishing EllieBot" +echo "" + +echo "" +dotnet publish -c Release -r linux-x64 --self-contained -o elliebot-linux-x64 src/EllieBot/EllieBot.csproj +echo "" +dotnet publish -c Release -r linux-arm64 --self-contained -o elliebot-linux-arm64 src/EllieBot/EllieBot.csproj +echo "" +dotnet publish -c Release -r win-x64 --self-contained -o elliebot-windows-x64 src/EllieBot/EllieBot.csproj +echo "" +dotnet publish -c Release -r win-arm64 --self-contained -o elliebot-windows-arm64 src/EllieBot/EllieBot.csproj +echo "" +dotnet publish -c Release -r osx-x64 --self-contained -o elliebot-osx-x64 src/EllieBot/EllieBot.csproj +echo "" +dotnet publish -c Release -r osx-arm64 --self-contained -o elliebot-osx-arm64 src/EllieBot/EllieBot.csproj + +echo "" +echo "Preparing the Windows installer build." +echo "" + +dotnet clean +dotnet restore -f --no-cache -v n +dotnet publish -c Release --self-contained --runtime win-x64 /p:Version=5.1.3 src/EllieBot + +echo "" +echo "" +echo "Finished the initial build script" +echo "" +echo "To build the Windows installer please install Inno Setup from" +echo "https://jrsoftware.org/isdl.php" +echo "And compile the exe_builder.iss" +echo "" +echo "If you are running on Windows please run the wsl command" +echo "then run build.sh" \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..5944016 --- /dev/null +++ b/build.sh @@ -0,0 +1,18 @@ +echo "" +echo "Compressing build files" +echo "" + +tar cvf 5.1.3-linux-x64-build.tar elliebot-linux-x64/* +tar cvf 5.1.3-linux-arm64-build.tar elliebot-linux-arm64/* +tar cvf 5.1.3-osx-x64-build.tar elliebot-osx-x64/* +tar cvf 5.1.3-osx-arm64-build.tar elliebot-osx-arm64/* +zip -r 5.1.3-windows-x64-build.zip elliebot-windows-x64/* +zip -r 5.1.3-windows-arm64-build.zip elliebot-windows-arm64/* + +echo "" +echo "Moving the installer file you would have generated" +echo "if you followed the instructions in the bottom of the build.ps1 file" +echo "to the directory this script in run in." +echo "" + +#mv ellie-installers/5.1.3/ellie-setup-5.1.3.exe ellie-setup-5.1.3.exe \ No newline at end of file diff --git a/src/EllieBot/.editorconfig b/src/EllieBot/.editorconfig index 041e023..bc6d4d4 100644 --- a/src/EllieBot/.editorconfig +++ b/src/EllieBot/.editorconfig @@ -181,9 +181,9 @@ dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field dotnet_naming_rule.private_readonly_field.style = begins_with_underscore dotnet_naming_rule.private_readonly_field.severity = warning -dotnet_naming_rule.private_field.symbols = private_field -dotnet_naming_rule.private_field.style = camel_case -dotnet_naming_rule.private_field.severity = warning +# dotnet_naming_rule.private_field.symbols = private_field +# dotnet_naming_rule.private_field.style = camel_case +# dotnet_naming_rule.private_field.severity = warning dotnet_naming_rule.const_fields.symbols = const_fields dotnet_naming_rule.const_fields.style = all_upper From e49dfda0db6603074935dd13f335d1d10d7fb870 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 15:54:57 +1200 Subject: [PATCH 11/14] Updated changelog, upped version to 5.1.4 --- CHANGELOG.md | 23 +++++++++++++++++++++++ src/EllieBot/EllieBot.csproj | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47bf5fc..72eda49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +## [5.1.4] - Unreleased + +### Added + +- Added `'coins` command which lists top 10 cryptos ordered by marketcap +- Added Clubs rank in the leaderboard to `'clubinfo` +- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot) +- You can now send multiple waifu gifts at once to waifus. For example `'waifugift 3xRose @user` will give that user 3 roses + - The format is `x`, no spaces +- Added `'boosttest` command + +### Changed + +- Updated command strings to clarify `'say` and `'send` usages + +### Fixed + +- Fixed `'waifugift` help string + +### Removed + +- Removed selfhost button from `'donate` command, no idea why it was there in the first place + ## [5.1.3] - 08.07.2024 ### Added diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj index 86a9972..de5f076 100644 --- a/src/EllieBot/EllieBot.csproj +++ b/src/EllieBot/EllieBot.csproj @@ -4,7 +4,7 @@ enable true en - 5.1.3 + 5.1.4 $(MSBuildProjectDirectory) From 7e660639906e5e00e7b5e72f2cb83749472b69a7 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 16:08:18 +1200 Subject: [PATCH 12/14] Updated build scripts for this release. --- build.ps1 | 66 +++++++++++++++++++++++++++---------------------------- build.sh | 14 ++++++------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.ps1 b/build.ps1 index 3b99cec..45e4d6b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,46 +1,46 @@ -echo "" -echo "███████╗██╗ ██╗ ██╗███████╗██████╗ ██████╗ ████████╗" -echo "██╔════╝██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝" -echo "█████╗ ██║ ██║ ██║█████╗ ██████╔╝██║ ██║ ██║ " -echo "██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══██╗██║ ██║ ██║ " -echo "███████╗███████╗███████╗██║███████╗██████╔╝╚██████╔╝ ██║ " -echo "╚══════╝╚══════╝╚══════╝╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ " -echo "" -echo "Copyright © 2024 Toastie_t0ast & EllieBotDevs" -echo "" +Write-Output "" +Write-Output "███████╗██╗ ██╗ ██╗███████╗██████╗ ██████╗ ████████╗" +Write-Output "██╔════╝██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝" +Write-Output "█████╗ ██║ ██║ ██║█████╗ ██████╔╝██║ ██║ ██║ " +Write-Output "██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══██╗██║ ██║ ██║ " +Write-Output "███████╗███████╗███████╗██║███████╗██████╔╝╚██████╔╝ ██║ " +Write-Output "╚══════╝╚══════╝╚══════╝╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ " +Write-Output "" +Write-Output "Copyright © 2024 Toastie_t0ast & EllieBotDevs" +Write-Output "" -echo "" -echo "Publishing EllieBot" -echo "" +Write-Output "" +Write-Output "Publishing EllieBot" +Write-Output "" -echo "" +Write-Output "" dotnet publish -c Release -r linux-x64 --self-contained -o elliebot-linux-x64 src/EllieBot/EllieBot.csproj -echo "" +Write-Output "" dotnet publish -c Release -r linux-arm64 --self-contained -o elliebot-linux-arm64 src/EllieBot/EllieBot.csproj -echo "" +Write-Output "" dotnet publish -c Release -r win-x64 --self-contained -o elliebot-windows-x64 src/EllieBot/EllieBot.csproj -echo "" +Write-Output "" dotnet publish -c Release -r win-arm64 --self-contained -o elliebot-windows-arm64 src/EllieBot/EllieBot.csproj -echo "" +Write-Output "" dotnet publish -c Release -r osx-x64 --self-contained -o elliebot-osx-x64 src/EllieBot/EllieBot.csproj -echo "" +Write-Output "" dotnet publish -c Release -r osx-arm64 --self-contained -o elliebot-osx-arm64 src/EllieBot/EllieBot.csproj -echo "" -echo "Preparing the Windows installer build." -echo "" +Write-Output "" +Write-Output "Preparing the Windows installer build." +Write-Output "" dotnet clean dotnet restore -f --no-cache -v n -dotnet publish -c Release --self-contained --runtime win-x64 /p:Version=5.1.3 src/EllieBot +dotnet publish -c Release --self-contained --runtime win-x64 /p:Version=5.1.4 src/EllieBot -echo "" -echo "" -echo "Finished the initial build script" -echo "" -echo "To build the Windows installer please install Inno Setup from" -echo "https://jrsoftware.org/isdl.php" -echo "And compile the exe_builder.iss" -echo "" -echo "If you are running on Windows please run the wsl command" -echo "then run build.sh" \ No newline at end of file +Write-Output "" +Write-Output "" +Write-Output "Finished the initial build script" +Write-Output "" +Write-Output "To build the Windows installer please install Inno Setup from" +Write-Output "https://jrsoftware.org/isdl.php" +Write-Output "And compile the exe_builder.iss" +Write-Output "" +Write-Output "If you are running on Windows please run the wsl command" +Write-Output "then run build.sh" \ No newline at end of file diff --git a/build.sh b/build.sh index 5944016..e58b709 100644 --- a/build.sh +++ b/build.sh @@ -2,12 +2,12 @@ echo "" echo "Compressing build files" echo "" -tar cvf 5.1.3-linux-x64-build.tar elliebot-linux-x64/* -tar cvf 5.1.3-linux-arm64-build.tar elliebot-linux-arm64/* -tar cvf 5.1.3-osx-x64-build.tar elliebot-osx-x64/* -tar cvf 5.1.3-osx-arm64-build.tar elliebot-osx-arm64/* -zip -r 5.1.3-windows-x64-build.zip elliebot-windows-x64/* -zip -r 5.1.3-windows-arm64-build.zip elliebot-windows-arm64/* +tar cvf 5.1.4-linux-x64-build.tar elliebot-linux-x64/* +tar cvf 5.1.4-linux-arm64-build.tar elliebot-linux-arm64/* +tar cvf 5.1.4-osx-x64-build.tar elliebot-osx-x64/* +tar cvf 5.1.4-osx-arm64-build.tar elliebot-osx-arm64/* +zip -r 5.1.4-windows-x64-build.zip elliebot-windows-x64/* +zip -r 5.1.4-windows-arm64-build.zip elliebot-windows-arm64/* echo "" echo "Moving the installer file you would have generated" @@ -15,4 +15,4 @@ echo "if you followed the instructions in the bottom of the build.ps1 file" echo "to the directory this script in run in." echo "" -#mv ellie-installers/5.1.3/ellie-setup-5.1.3.exe ellie-setup-5.1.3.exe \ No newline at end of file +mv ellie-installers/5.1.4/ellie-setup-5.1.4.exe ellie-setup-5.1.4.exe \ No newline at end of file From 151146378b7f8d10dbbd8cfb597cc0f649db8fc2 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 16:26:21 +1200 Subject: [PATCH 13/14] Updated CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72eda49..8679a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o -## [5.1.4] - Unreleased +## [5.1.4] - 15.07.2024 ### Added From 668d9e4734cc3b1b3b6fa5ba1760903bdabb85e7 Mon Sep 17 00:00:00 2001 From: Toastie Date: Mon, 15 Jul 2024 16:37:57 +1200 Subject: [PATCH 14/14] Found stuff that is wrong. --- .../Modules/Games/ChatterBot/ChatterbotService.cs | 8 ++++---- .../Games/ChatterBot/_common/OpenAiApiSession.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs b/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs index 2129d3b..66decdc 100644 --- a/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs +++ b/src/EllieBot/Modules/Games/ChatterBot/ChatterbotService.cs @@ -90,15 +90,15 @@ public class ChatterBotService : IExecOnMessage public string PrepareMessage(IUserMessage msg) { - var nadekoId = _client.CurrentUser.Id; - var normalMention = $"<@{nadekoId}> "; - var nickMention = $"<@!{nadekoId}> "; + var ellieId = _client.CurrentUser.Id; + var normalMention = $"<@{ellieId}> "; + var nickMention = $"<@!{ellieId}> "; string message; if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture)) message = msg.Content[normalMention.Length..].Trim(); else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture)) message = msg.Content[nickMention.Length..].Trim(); - else if (msg.ReferencedMessage?.Author.Id == nadekoId) + else if (msg.ReferencedMessage?.Author.Id == ellieId) message = msg.Content; else return null; diff --git a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs index 4511988..42afd22 100644 --- a/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs +++ b/src/EllieBot/Modules/Games/ChatterBot/_common/OpenAiApiSession.cs @@ -15,7 +15,7 @@ public partial class OpenAiApiSession : IChatterBotSession private readonly int _maxHistory; private readonly int _maxTokens; private readonly int _minTokens; - private readonly string _nadekoUsername; + private readonly string _ellieUsername; private readonly GptEncoding _encoding; private List messages = new(); private readonly IHttpClientFactory _httpFactory; @@ -29,7 +29,7 @@ public partial class OpenAiApiSession : IChatterBotSession int maxTokens, int minTokens, string personality, - string nadekoUsername, + string ellieUsername, IHttpClientFactory factory) { if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out _)) @@ -45,7 +45,7 @@ public partial class OpenAiApiSession : IChatterBotSession _maxHistory = chatHistory; _maxTokens = maxTokens; _minTokens = minTokens; - _nadekoUsername = UsernameCleaner().Replace(nadekoUsername, ""); + _ellieUsername = UsernameCleaner().Replace(ellieUsername, ""); _encoding = GptEncoding.GetEncodingForModel("gpt-4o"); if (!string.IsNullOrWhiteSpace(personality)) { @@ -53,7 +53,7 @@ public partial class OpenAiApiSession : IChatterBotSession { Role = "system", Content = personality, - Name = _nadekoUsername + Name = _ellieUsername }); } } @@ -128,7 +128,7 @@ public partial class OpenAiApiSession : IChatterBotSession { Role = "assistant", Content = message, - Name = _nadekoUsername + Name = _ellieUsername }); return new ThinkResult()