From 0b3582e476ed61104059972680b691b5c61c581d Mon Sep 17 00:00:00 2001 From: Toastie Date: Sat, 9 Nov 2024 18:41:00 +1300 Subject: [PATCH] added .snipe command added .gsreset and .bsreset commands improved .timely rewards for patrons Improved how blacklist works under the hood --- .../AnimalRacing/AnimalRacingCommands.cs | 2 +- .../Modules/Gambling/BetStatsCommands.cs | 175 ++++++++++++++++++ .../Gambling/BlackJack/BlackJackCommands.cs | 2 +- .../Gambling/Connect4/Connect4Commands.cs | 2 +- .../Modules/Gambling/Draw/DrawCommands.cs | 2 +- .../Gambling/Events/CurrencyEventsCommands.cs | 2 +- .../Gambling/FlipCoin/FlipCoinCommands.cs | 2 +- src/EllieBot/Modules/Gambling/Gambling.cs | 94 ---------- .../Gambling/GamblingTopLevelModule.cs | 8 - .../PlantPick/PlantAndPickCommands.cs | 2 +- .../Modules/Gambling/Shop/ShopCommands.cs | 2 +- .../Modules/Gambling/Slot/SlotCommands.cs | 2 +- .../Modules/Gambling/UserBetStatsService.cs | 55 ++++++ .../Gambling/Waifus/WaifuClaimCommands.cs | 2 +- .../Modules/Patronage/PatronageService.cs | 6 +- .../Blacklist/BlacklistCommands.cs | 76 ++++---- src/EllieBot/Modules/Utility/Utility.cs | 24 +++ .../Services/Currency/GamblingTxTracker.cs | 3 + .../_common/Services/Impl/BlacklistService.cs | 163 ++++++++++------ src/EllieBot/data/aliases.yml | 13 +- .../data/strings/commands/commands.en-US.yml | 27 ++- .../strings/responses/responses.en-US.json | 3 +- 22 files changed, 453 insertions(+), 214 deletions(-) create mode 100644 src/EllieBot/Modules/Gambling/BetStatsCommands.cs create mode 100644 src/EllieBot/Modules/Gambling/UserBetStatsService.cs diff --git a/src/EllieBot/Modules/Gambling/AnimalRacing/AnimalRacingCommands.cs b/src/EllieBot/Modules/Gambling/AnimalRacing/AnimalRacingCommands.cs index 0ec0e7b..04356df 100644 --- a/src/EllieBot/Modules/Gambling/AnimalRacing/AnimalRacingCommands.cs +++ b/src/EllieBot/Modules/Gambling/AnimalRacing/AnimalRacingCommands.cs @@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class AnimalRacingCommands : GamblingSubmodule + public partial class AnimalRacingCommands : GamblingModule { private readonly ICurrencyService _cs; private readonly DiscordSocketClient _client; diff --git a/src/EllieBot/Modules/Gambling/BetStatsCommands.cs b/src/EllieBot/Modules/Gambling/BetStatsCommands.cs new file mode 100644 index 0000000..4fe5c11 --- /dev/null +++ b/src/EllieBot/Modules/Gambling/BetStatsCommands.cs @@ -0,0 +1,175 @@ +#nullable disable +using EllieBot.Modules.Gambling.Common; +using EllieBot.Modules.Gambling.Services; + +namespace EllieBot.Modules.Gambling; + +public partial class Gambling +{ + [Group] + public sealed class BetStatsCommands : GamblingModule + { + private readonly GamblingTxTracker _gamblingTxTracker; + + public BetStatsCommands( + GamblingTxTracker gamblingTxTracker, + GamblingConfigService gcs) + : base(gcs) + { + _gamblingTxTracker = gamblingTxTracker; + } + + [Cmd] + public async Task BetStatsReset(GamblingGame? game = null) + { + var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game); + + var result = await PromptUserConfirmAsync(_sender.CreateEmbed() + .WithDescription( + $""" + Are you sure you want to reset your bet stats for **{GetGameName(game)}**? + + It will cost you {N(price)} + """)); + + if (!result) + return; + + var success = await _service.ResetStatsAsync(ctx.User.Id, game); + + if (success) + { + await ctx.OkAsync(); + } + else + { + await Response() + .Error(strs.not_enough(CurrencySign)) + .SendAsync(); + } + } + + private string GetGameName(GamblingGame? game) + { + if (game is null) + return "all games"; + + return game.ToString(); + } + + [Cmd] + [Priority(3)] + public async Task BetStats() + => await BetStats(ctx.User, null); + + [Cmd] + [Priority(2)] + public async Task BetStats(GamblingGame game) + => await BetStats(ctx.User, game); + + [Cmd] + [Priority(1)] + public async Task BetStats([Leftover] IUser user) + => await BetStats(user, null); + + [Cmd] + [Priority(0)] + public async Task BetStats(IUser user, GamblingGame? game) + { + var stats = await _gamblingTxTracker.GetUserStatsAsync(user.Id, game); + + if (stats.Count == 0) + stats = new() + { + new() + { + TotalBet = 1 + } + }; + + var eb = _sender.CreateEmbed() + .WithOkColor() + .WithAuthor(user) + .AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true) + .AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true) + .AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true) + .AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true) + .AddField("Payout", + (stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture), + true); + if (game == null) + { + var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount); + eb.AddField("Favorite Game", + favGame.Game + "\n" + Format.Italics((favGame.WinCount + favGame.LoseCount) + " plays"), + true); + } + else + { + eb.WithDescription(game.ToString()) + .AddField("# Wins", stats.Sum(x => x.WinCount), true); + } + + await Response() + .Embed(eb) + .SendAsync(); + } + + [Cmd] + public async Task GambleStats() + { + var stats = await _gamblingTxTracker.GetAllAsync(); + + var eb = _sender.CreateEmbed() + .WithOkColor(); + + var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n"; + str += "――――――――――――――――――――\n"; + foreach (var stat in stats) + { + var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture); + str += $"`{stat.Feature.PadBoth(9)}`" + + $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`" + + $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`" + + $"|`{perc.PadLeft(6, ' ')}`\n"; + } + + var bet = stats.Sum(x => x.Bet); + var paidOut = stats.Sum(x => x.PaidOut); + + if (bet == 0) + bet = 1; + + var tPerc = (paidOut / bet).ToString("P2", Culture); + str += "――――――――――――――――――――\n"; + str += $"` {("TOTAL").PadBoth(7)}` " + + $"|**{N(bet).PadLeft(8, ' ')}**" + + $"|**{N(paidOut).PadLeft(8, ' ')}**" + + $"|`{tPerc.PadLeft(6, ' ')}`"; + + eb.WithDescription(str); + + await Response().Embed(eb).SendAsync(); + } + + [Cmd] + [OwnerOnly] + public async Task GambleStatsReset() + { + if (!await PromptUserConfirmAsync(_sender.CreateEmbed() + .WithDescription( + """ + Are you sure? + This will completely reset Gambling Stats. + + This action is irreversible. + """))) + return; + + await GambleStats(); + await _service.ResetGamblingStatsAsync(); + + await ctx.OkAsync(); + } + } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/BlackJack/BlackJackCommands.cs b/src/EllieBot/Modules/Gambling/BlackJack/BlackJackCommands.cs index 772cb4f..14798e9 100644 --- a/src/EllieBot/Modules/Gambling/BlackJack/BlackJackCommands.cs +++ b/src/EllieBot/Modules/Gambling/BlackJack/BlackJackCommands.cs @@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { - public partial class BlackJackCommands : GamblingSubmodule + public partial class BlackJackCommands : GamblingModule { public enum BjAction { diff --git a/src/EllieBot/Modules/Gambling/Connect4/Connect4Commands.cs b/src/EllieBot/Modules/Gambling/Connect4/Connect4Commands.cs index d9a245d..ca2900c 100644 --- a/src/EllieBot/Modules/Gambling/Connect4/Connect4Commands.cs +++ b/src/EllieBot/Modules/Gambling/Connect4/Connect4Commands.cs @@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class Connect4Commands : GamblingSubmodule + public partial class Connect4Commands : GamblingModule { private static readonly string[] _numbers = [ diff --git a/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs b/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs index e39003d..1bf1986 100644 --- a/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs +++ b/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs @@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class DrawCommands : GamblingSubmodule + public partial class DrawCommands : GamblingModule { private static readonly ConcurrentDictionary _allDecks = new(); private readonly IImageCache _images; diff --git a/src/EllieBot/Modules/Gambling/Events/CurrencyEventsCommands.cs b/src/EllieBot/Modules/Gambling/Events/CurrencyEventsCommands.cs index c5e836c..2f959fc 100644 --- a/src/EllieBot/Modules/Gambling/Events/CurrencyEventsCommands.cs +++ b/src/EllieBot/Modules/Gambling/Events/CurrencyEventsCommands.cs @@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class CurrencyEventsCommands : GamblingSubmodule + public partial class CurrencyEventsCommands : GamblingModule { public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf) diff --git a/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs b/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs index 2704495..8f3f589 100644 --- a/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs +++ b/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs @@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class FlipCoinCommands : GamblingSubmodule + public partial class FlipCoinCommands : GamblingModule { public enum BetFlipGuess : byte { diff --git a/src/EllieBot/Modules/Gambling/Gambling.cs b/src/EllieBot/Modules/Gambling/Gambling.cs index 358eef2..6a0433f 100644 --- a/src/EllieBot/Modules/Gambling/Gambling.cs +++ b/src/EllieBot/Modules/Gambling/Gambling.cs @@ -80,100 +80,6 @@ public partial class Gambling : GamblingModule return N(bal); } - [Cmd] - [Priority(3)] - public async Task BetStats() - => await BetStats(ctx.User, null); - - [Cmd] - [Priority(2)] - public async Task BetStats(GamblingGame game) - => await BetStats(ctx.User, game); - - [Cmd] - [Priority(1)] - public async Task BetStats([Leftover] IUser user) - => await BetStats(user, null); - - [Cmd] - [Priority(0)] - public async Task BetStats(IUser user, GamblingGame? game) - { - var stats = await _gamblingTxTracker.GetUserStatsAsync(user.Id, game); - - if (stats.Count == 0) - stats = new() - { - new() - { - TotalBet = 1 - } - }; - - var eb = _sender.CreateEmbed() - .WithOkColor() - .WithAuthor(user) - .AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true) - .AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true) - .AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true) - .AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true) - .AddField("Payout", - (stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture), - true); - if (game == null) - { - var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount); - eb.AddField("Favorite Game", - favGame.Game + "\n" + Format.Italics((favGame.WinCount + favGame.LoseCount) + " plays"), - true); - } - else - { - eb.WithDescription(game.ToString()) - .AddField("# Wins", stats.Sum(x => x.WinCount), true); - } - - await Response() - .Embed(eb) - .SendAsync(); - } - - [Cmd] - public async Task GambleStats() - { - var stats = await _gamblingTxTracker.GetAllAsync(); - - var eb = _sender.CreateEmbed() - .WithOkColor(); - - var str = "` Feature `|`   Bet  `|`Paid Out`|`  RoI  `\n"; - str += "――――――――――――――――――――\n"; - foreach (var stat in stats) - { - var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture); - str += $"`{stat.Feature.PadBoth(9)}`" - + $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`" - + $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`" - + $"|`{perc.PadLeft(6, ' ')}`\n"; - } - - var bet = stats.Sum(x => x.Bet); - var paidOut = stats.Sum(x => x.PaidOut); - - if (bet == 0) - bet = 1; - - var tPerc = (paidOut / bet).ToString("P2", Culture); - str += "――――――――――――――――――――\n"; - str += $"` {("TOTAL").PadBoth(7)}` " - + $"|**{N(bet).PadLeft(8, ' ')}**" - + $"|**{N(paidOut).PadLeft(8, ' ')}**" - + $"|`{tPerc.PadLeft(6, ' ')}`"; - - eb.WithDescription(str); - - await Response().Embed(eb).SendAsync(); - } private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when) { diff --git a/src/EllieBot/Modules/Gambling/GamblingTopLevelModule.cs b/src/EllieBot/Modules/Gambling/GamblingTopLevelModule.cs index 25cbb73..ae73908 100644 --- a/src/EllieBot/Modules/Gambling/GamblingTopLevelModule.cs +++ b/src/EllieBot/Modules/Gambling/GamblingTopLevelModule.cs @@ -57,12 +57,4 @@ public abstract class GamblingModule : EllieModule return Task.FromResult(true); return InternalCheckBet(amount); } -} - -public abstract class GamblingSubmodule : GamblingModule -{ - protected GamblingSubmodule(GamblingConfigService gamblingConfService) - : base(gamblingConfService) - { - } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/PlantPick/PlantAndPickCommands.cs b/src/EllieBot/Modules/Gambling/PlantPick/PlantAndPickCommands.cs index 67994c8..7723a6a 100644 --- a/src/EllieBot/Modules/Gambling/PlantPick/PlantAndPickCommands.cs +++ b/src/EllieBot/Modules/Gambling/PlantPick/PlantAndPickCommands.cs @@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class PlantPickCommands : GamblingSubmodule + public partial class PlantPickCommands : GamblingModule { private readonly ILogCommandService _logService; diff --git a/src/EllieBot/Modules/Gambling/Shop/ShopCommands.cs b/src/EllieBot/Modules/Gambling/Shop/ShopCommands.cs index 2d0749e..2c2dca6 100644 --- a/src/EllieBot/Modules/Gambling/Shop/ShopCommands.cs +++ b/src/EllieBot/Modules/Gambling/Shop/ShopCommands.cs @@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class ShopCommands : GamblingSubmodule + public partial class ShopCommands : GamblingModule { public enum List { diff --git a/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs b/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs index 4e8d045..239b7bc 100644 --- a/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs +++ b/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs @@ -21,7 +21,7 @@ public enum GamblingError public partial class Gambling { [Group] - public partial class SlotCommands : GamblingSubmodule + public partial class SlotCommands : GamblingModule { private readonly IImageCache _images; private readonly FontProvider _fonts; diff --git a/src/EllieBot/Modules/Gambling/UserBetStatsService.cs b/src/EllieBot/Modules/Gambling/UserBetStatsService.cs new file mode 100644 index 0000000..8363ffd --- /dev/null +++ b/src/EllieBot/Modules/Gambling/UserBetStatsService.cs @@ -0,0 +1,55 @@ +#nullable disable +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using EllieBot.Db.Models; + +namespace EllieBot.Modules.Gambling.Services; + +public sealed class UserBetStatsService : IEService +{ + private const long RESET_MIN_PRICE = 1000; + private const decimal RESET_TOTAL_MULTIPLIER = 0.02m; + + private readonly DbService _db; + private readonly ICurrencyService _cs; + + public UserBetStatsService(DbService db, ICurrencyService cs) + { + _db = db; + _cs = cs; + } + + public async Task GetResetStatsPriceAsync(ulong userId, GamblingGame? game) + { + await using var ctx = _db.GetDbContext(); + + var totalBet = await ctx.GetTable() + .Where(x => x.UserId == userId && (game == null || x.Game == game)) + .SumAsyncLinqToDB(x => x.TotalBet); + + return Math.Max(RESET_MIN_PRICE, (long)Math.Ceiling(totalBet * RESET_TOTAL_MULTIPLIER)); + } + + public async Task ResetStatsAsync(ulong userId, GamblingGame? game) + { + var price = await GetResetStatsPriceAsync(userId, game); + + if (!await _cs.RemoveAsync(userId, price, new("betstats", "reset"))) + { + return false; + } + + await using var ctx = _db.GetDbContext(); + await ctx.GetTable() + .DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game)); + + return true; + } + + public async Task ResetGamblingStatsAsync() + { + await using var ctx = _db.GetDbContext(); + await ctx.GetTable() + .DeleteAsync(); + } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs index 12d93e1..d52a22b 100644 --- a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs +++ b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs @@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class WaifuClaimCommands : GamblingSubmodule + public partial class WaifuClaimCommands : GamblingModule { public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService) diff --git a/src/EllieBot/Modules/Patronage/PatronageService.cs b/src/EllieBot/Modules/Patronage/PatronageService.cs index b33fc1e..17e418c 100644 --- a/src/EllieBot/Modules/Patronage/PatronageService.cs +++ b/src/EllieBot/Modules/Patronage/PatronageService.cs @@ -404,9 +404,9 @@ public sealed class PatronageService { >= 10_000 => 100, >= 5000 => 50, - >= 2000 => 20, - >= 1000 => 10, - >= 500 => 5, + >= 2000 => 30, + >= 1000 => 20, + >= 500 => 10, _ => 0 }; diff --git a/src/EllieBot/Modules/Permissions/Blacklist/BlacklistCommands.cs b/src/EllieBot/Modules/Permissions/Blacklist/BlacklistCommands.cs index d53aab5..5ce851b 100644 --- a/src/EllieBot/Modules/Permissions/Blacklist/BlacklistCommands.cs +++ b/src/EllieBot/Modules/Permissions/Blacklist/BlacklistCommands.cs @@ -18,39 +18,39 @@ public partial class Permissions { ArgumentOutOfRangeException.ThrowIfNegative(page); - var list = _service.GetBlacklist(); - var allItems = await list.Where(x => x.Type == type) - .Select(i => - { - try - { - return Task.FromResult(i.Type switch - { - BlacklistType.Channel => Format.Code(i.ItemId.ToString()) - + " " - + (_client.GetChannel(i.ItemId)?.ToString() - ?? ""), - BlacklistType.User => Format.Code(i.ItemId.ToString()) - + " " - + ((_client.GetUser(i.ItemId)) - ?.ToString() - ?? ""), - BlacklistType.Server => Format.Code(i.ItemId.ToString()) + var list = await _service.GetBlacklist(type); + var allItems = await list + .Select(i => + { + try + { + return Task.FromResult(type switch + { + BlacklistType.Channel => Format.Code(i.ItemId.ToString()) + " " - + (_client.GetGuild(i.ItemId)?.ToString() ?? ""), - _ => Format.Code(i.ItemId.ToString()) - }); - } - catch - { - Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]", - i.Type, - i.ItemId); - - return Task.FromResult(Format.Code(i.ItemId.ToString())); - } - }) - .WhenAll(); + + (_client.GetChannel(i.ItemId)?.ToString() + ?? ""), + BlacklistType.User => Format.Code(i.ItemId.ToString()) + + " " + + ((_client.GetUser(i.ItemId)) + ?.ToString() + ?? ""), + BlacklistType.Server => Format.Code(i.ItemId.ToString()) + + " " + + (_client.GetGuild(i.ItemId)?.ToString() ?? ""), + _ => Format.Code(i.ItemId.ToString()) + }); + } + catch + { + Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]", + i.Type, + i.ItemId); + + return Task.FromResult(Format.Code(i.ItemId.ToString())); + } + }) + .WhenAll(); await Response() .Paginated() @@ -61,14 +61,14 @@ public partial class Permissions { if (pageItems.Count == 0) return _sender.CreateEmbed() - .WithOkColor() - .WithTitle(title) - .WithDescription(GetText(strs.empty_page)); + .WithOkColor() + .WithTitle(title) + .WithDescription(GetText(strs.empty_page)); return _sender.CreateEmbed() - .WithTitle(title) - .WithDescription(pageItems.Join('\n')) - .WithOkColor(); + .WithTitle(title) + .WithDescription(pageItems.Join('\n')) + .WithOkColor(); }) .SendAsync(); } diff --git a/src/EllieBot/Modules/Utility/Utility.cs b/src/EllieBot/Modules/Utility/Utility.cs index 8463d36..146a97c 100644 --- a/src/EllieBot/Modules/Utility/Utility.cs +++ b/src/EllieBot/Modules/Utility/Utility.cs @@ -783,4 +783,28 @@ public partial class Utility : EllieModule await Response().Error(ex.Message).SendAsync(); } } + + [Cmd] + public async Task Snipe() + { + if (ctx.Message.ReferencedMessage is not { } msg) + { + var msgs = await ctx.Channel.GetMessagesAsync(ctx.Message, Direction.Before, 3).FlattenAsync(); + msg = msgs.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Content) || (x.Attachments.FirstOrDefault()?.Width is not null)) as IUserMessage; + + if (msg is null) + return; + } + + var eb = _sender.CreateEmbed() + .WithOkColor() + .WithDescription(msg.Content) + .WithAuthor(msg.Author) + .WithTimestamp(msg.Timestamp) + .WithImageUrl(msg.Attachments.FirstOrDefault()?.Url) + .WithFooter(GetText(strs.sniped_by(ctx.User.ToString())), ctx.User.GetDisplayAvatarUrl()); + + ctx.Message.DeleteAfter(1); + await Response().Embed(eb).SendAsync(); + } } \ No newline at end of file diff --git a/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs b/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs index 16976a0..5a40474 100644 --- a/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs +++ b/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs @@ -4,6 +4,7 @@ using LinqToDB.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; using EllieBot.Services.Currency; using EllieBot.Db.Models; +using EllieBot.Modules.Gambling; using System.Collections.Concurrent; namespace EllieBot.Services; @@ -317,6 +318,8 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor GamblingGame.Race => 0.06m, _ => 0 }; + + } public sealed class UserBetStats diff --git a/src/EllieBot/_common/Services/Impl/BlacklistService.cs b/src/EllieBot/_common/Services/Impl/BlacklistService.cs index 01ad347..6655149 100644 --- a/src/EllieBot/_common/Services/Impl/BlacklistService.cs +++ b/src/EllieBot/_common/Services/Impl/BlacklistService.cs @@ -4,10 +4,11 @@ using LinqToDB.Data; using LinqToDB.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; using EllieBot.Db.Models; +using System.Collections.Frozen; namespace EllieBot.Modules.Permissions.Services; -public sealed class BlacklistService : IExecOnMessage +public sealed class BlacklistService : IExecOnMessage, IReadyExecutor { public int Priority => int.MaxValue; @@ -15,69 +16,115 @@ public sealed class BlacklistService : IExecOnMessage private readonly DbService _db; private readonly IPubSub _pubSub; private readonly IBotCreds _creds; - private IReadOnlyList blacklist; + private readonly DiscordSocketClient _client; - private readonly TypedKey _blPubKey = new("blacklist.reload"); + private FrozenSet blacklistedGuilds = new HashSet().ToFrozenSet(); + private FrozenSet blacklistedUsers = new HashSet().ToFrozenSet(); + private FrozenSet blacklistedChannels = new HashSet().ToFrozenSet(); - public BlacklistService(DbService db, IPubSub pubSub, IBotCreds creds) + private readonly TypedKey _blPubKey = new("blacklist.reload"); + + public BlacklistService( + DbService db, + IPubSub pubSub, + IBotCreds creds, + DiscordSocketClient client) { _db = db; _pubSub = pubSub; _creds = creds; + _client = client; - Reload(false); - _pubSub.Sub(_blPubKey, OnReload); + _pubSub.Sub(_blPubKey, async _ => await Reload(false)); + } + + public async Task OnReadyAsync() + { + _client.JoinedGuild += async (g) => + { + if (blacklistedGuilds.Contains(g.Id)) + { + await g.LeaveAsync(); + } + }; + + await Reload(false); } private ValueTask OnReload(BlacklistEntry[] newBlacklist) { - blacklist = newBlacklist; + if (newBlacklist is null) + return default; + + blacklistedGuilds = + new HashSet(newBlacklist.Where(x => x.Type == BlacklistType.Server).Select(x => x.ItemId)) + .ToFrozenSet(); + blacklistedChannels = + new HashSet(newBlacklist.Where(x => x.Type == BlacklistType.Channel).Select(x => x.ItemId)) + .ToFrozenSet(); + blacklistedUsers = + new HashSet(newBlacklist.Where(x => x.Type == BlacklistType.User).Select(x => x.ItemId)) + .ToFrozenSet(); + return default; } public Task ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg) { - foreach (var bl in blacklist) + if (blacklistedGuilds.Contains(guild.Id)) { - if (guild is not null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id) - { - Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id); + Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", + guild.Name, + guild.Id.ToString()); + return Task.FromResult(true); + } - return Task.FromResult(true); - } + if (blacklistedChannels.Contains(usrMsg.Channel.Id)) + { + Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]", + usrMsg.Channel.Name, + usrMsg.Channel.Id.ToString()); + } - if (bl.Type == BlacklistType.Channel && bl.ItemId == usrMsg.Channel.Id) - { - Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]", - usrMsg.Channel.Name, - usrMsg.Channel.Id); - return Task.FromResult(true); - } - - if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id) - { - Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]", - usrMsg.Author.ToString(), - usrMsg.Author.Id); - - return Task.FromResult(true); - } + if (blacklistedUsers.Contains(usrMsg.Author.Id)) + { + Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]", + usrMsg.Author.ToString(), + usrMsg.Author.Id.ToString()); + return Task.FromResult(true); } return Task.FromResult(false); } - public IReadOnlyList GetBlacklist() - => blacklist; - - public void Reload(bool publish = true) + public async Task> GetBlacklist(BlacklistType type) { - using var uow = _db.GetDbContext(); - var toPublish = uow.GetTable().ToArray(); - blacklist = toPublish; + await using var uow = _db.GetDbContext(); + + return await uow + .GetTable() + .Where(x => x.Type == type) + .ToListAsync(); + } + + public async Task Reload(bool publish = true) + { + var totalShards = _creds.TotalShards; + await using var uow = _db.GetDbContext(); + var items = uow.GetTable() + .Where(x => x.Type != BlacklistType.Server + || (x.Type == BlacklistType.Server + && Linq2DbExpressions.GuildOnShard(x.ItemId, totalShards, _client.ShardId))) + .ToArray(); + + if (publish) - _pubSub.Pub(_blPubKey, toPublish); + { + await _pubSub.Pub(_blPubKey, true); + } + + await OnReload(items); } public async Task Blacklist(BlacklistType type, ulong id) @@ -88,34 +135,34 @@ public sealed class BlacklistService : IExecOnMessage await using var uow = _db.GetDbContext(); await uow - .GetTable() - .InsertAsync(() => new() - { - ItemId = id, - Type = type, - }); + .GetTable() + .InsertAsync(() => new() + { + ItemId = id, + Type = type, + }); if (type == BlacklistType.User) { await uow.GetTable() - .Where(x => x.UserId == id) - .UpdateAsync(_ => new() - { - CurrencyAmount = 0 - }); + .Where(x => x.UserId == id) + .UpdateAsync(_ => new() + { + CurrencyAmount = 0 + }); } - Reload(); + await Reload(); } public async Task UnBlacklist(BlacklistType type, ulong id) { await using var uow = _db.GetDbContext(); await uow.GetTable() - .Where(bi => bi.ItemId == id && bi.Type == type) - .DeleteAsync(); + .Where(bi => bi.ItemId == id && bi.Type == type) + .DeleteAsync(); - Reload(); + await Reload(); } public async Task BlacklistUsers(IReadOnlyCollection toBlacklist) @@ -130,12 +177,12 @@ public sealed class BlacklistService : IExecOnMessage var blList = toBlacklist.ToList(); await uow.GetTable() - .Where(x => blList.Contains(x.UserId)) - .UpdateAsync(_ => new() - { - CurrencyAmount = 0 - }); + .Where(x => blList.Contains(x.UserId)) + .UpdateAsync(_ => new() + { + CurrencyAmount = 0 + }); - Reload(); + await Reload(); } } \ No newline at end of file diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index 54f6a45..8589812 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -1461,4 +1461,15 @@ translateflags: - transflags rakeback: - rakeback - - rb \ No newline at end of file + - rb +betstatsreset: + - betstatsreset + - bsr + - bsreset +gamblestatsreset: + - gamblestatsreset + - gsr + - gsreset +snipe: + - snipe + - sn \ 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 fd9ab58..2b5cbcc 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -2711,6 +2711,13 @@ gamblestats: - '' params: - { } +gamblestatsreset: + desc: |- + Resets the gamble stats. + ex: + - '' + params: + - { } slot: desc: |- Play Ellie slots by placing your bet. @@ -4648,6 +4655,16 @@ translateflags: - '' params: - { } +betstatsreset: + desc: |- + Reset all of your Bet Stats for a fee. + You can alternatively reset Bet Stats for the specified game. + ex: + - '' + - 'game' + params: + - game: + desc: 'The game to reset betstats for. Omit to reset all games' betstats: desc: |- Shows the current bet stats for yourself, or the targetted user. @@ -4677,4 +4694,12 @@ rakeback: ex: - '' params: - - {} \ No newline at end of file + - {} +snipe: + desc: |- + Snipe the message you replied to with this command. + Otherwise, if you don't reply to a message, it will snipe the last message sent in the channel (out of the last few messages) which has text or an image. + ex: + - '' + params: + - { } \ No newline at end of file diff --git a/src/EllieBot/data/strings/responses/responses.en-US.json b/src/EllieBot/data/strings/responses/responses.en-US.json index 4009aaa..130fcbe 100644 --- a/src/EllieBot/data/strings/responses/responses.en-US.json +++ b/src/EllieBot/data/strings/responses/responses.en-US.json @@ -1117,5 +1117,6 @@ "trfl_disabled": "Flag translation disabled.", "rakeback_claimed": "You've claimed {0} as rakeback!", "rakeback_none": "You don't have any rakeback to claim yet.", - "rakeback_available": "You have {0} rakeback available. Click the button to claim." + "rakeback_available": "You have {0} rakeback available. Click the button to claim.", + "sniped_by": "Sniped by {0}" }