added .snipe command
added .gsreset and .bsreset commands improved .timely rewards for patrons Improved how blacklist works under the hood
This commit is contained in:
parent
2fe1b94cea
commit
0b3582e476
22 changed files with 453 additions and 214 deletions
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
|
||||||
{
|
{
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
175
src/EllieBot/Modules/Gambling/BetStatsCommands.cs
Normal file
175
src/EllieBot/Modules/Gambling/BetStatsCommands.cs
Normal file
|
@ -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<UserBetStatsService>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
public partial class BlackJackCommands : GamblingModule<BlackJackService>
|
||||||
{
|
{
|
||||||
public enum BjAction
|
public enum BjAction
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
public partial class Connect4Commands : GamblingModule<GamblingService>
|
||||||
{
|
{
|
||||||
private static readonly string[] _numbers =
|
private static readonly string[] _numbers =
|
||||||
[
|
[
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class DrawCommands : GamblingSubmodule<IGamblingService>
|
public partial class DrawCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
public partial class CurrencyEventsCommands : GamblingModule<CurrencyEventsService>
|
||||||
{
|
{
|
||||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
||||||
: base(gamblingConf)
|
: base(gamblingConf)
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
|
public partial class FlipCoinCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
public enum BetFlipGuess : byte
|
public enum BetFlipGuess : byte
|
||||||
{
|
{
|
||||||
|
|
|
@ -80,100 +80,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
return N(bal);
|
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)
|
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||||
{
|
{
|
||||||
|
|
|
@ -57,12 +57,4 @@ public abstract class GamblingModule<TService> : EllieModule<TService>
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
return InternalCheckBet(amount);
|
return InternalCheckBet(amount);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
|
||||||
{
|
|
||||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
|
|
||||||
: base(gamblingConfService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
public partial class PlantPickCommands : GamblingModule<PlantPickService>
|
||||||
{
|
{
|
||||||
private readonly ILogCommandService _logService;
|
private readonly ILogCommandService _logService;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class ShopCommands : GamblingSubmodule<IShopService>
|
public partial class ShopCommands : GamblingModule<IShopService>
|
||||||
{
|
{
|
||||||
public enum List
|
public enum List
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ public enum GamblingError
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
public partial class SlotCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
|
|
55
src/EllieBot/Modules/Gambling/UserBetStatsService.cs
Normal file
55
src/EllieBot/Modules/Gambling/UserBetStatsService.cs
Normal file
|
@ -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<long> GetResetStatsPriceAsync(ulong userId, GamblingGame? game)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
|
var totalBet = await ctx.GetTable<UserBetStats>()
|
||||||
|
.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<bool> 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<UserBetStats>()
|
||||||
|
.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<GamblingStats>()
|
||||||
|
.DeleteAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
public partial class WaifuClaimCommands : GamblingModule<WaifuService>
|
||||||
{
|
{
|
||||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
||||||
: base(gamblingConfService)
|
: base(gamblingConfService)
|
||||||
|
|
|
@ -404,9 +404,9 @@ public sealed class PatronageService
|
||||||
{
|
{
|
||||||
>= 10_000 => 100,
|
>= 10_000 => 100,
|
||||||
>= 5000 => 50,
|
>= 5000 => 50,
|
||||||
>= 2000 => 20,
|
>= 2000 => 30,
|
||||||
>= 1000 => 10,
|
>= 1000 => 20,
|
||||||
>= 500 => 5,
|
>= 500 => 10,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,39 +18,39 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
var list = _service.GetBlacklist();
|
var list = await _service.GetBlacklist(type);
|
||||||
var allItems = await list.Where(x => x.Type == type)
|
var allItems = await list
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(i.Type switch
|
return Task.FromResult(type switch
|
||||||
{
|
{
|
||||||
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
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())
|
|
||||||
+ " "
|
+ " "
|
||||||
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
+ (_client.GetChannel(i.ItemId)?.ToString()
|
||||||
_ => Format.Code(i.ItemId.ToString())
|
?? ""),
|
||||||
});
|
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
||||||
}
|
+ " "
|
||||||
catch
|
+ ((_client.GetUser(i.ItemId))
|
||||||
{
|
?.ToString()
|
||||||
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
?? ""),
|
||||||
i.Type,
|
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
||||||
i.ItemId);
|
+ " "
|
||||||
|
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
||||||
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
_ => Format.Code(i.ItemId.ToString())
|
||||||
}
|
});
|
||||||
})
|
}
|
||||||
.WhenAll();
|
catch
|
||||||
|
{
|
||||||
|
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
||||||
|
i.Type,
|
||||||
|
i.ItemId);
|
||||||
|
|
||||||
|
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WhenAll();
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
|
@ -61,14 +61,14 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
if (pageItems.Count == 0)
|
if (pageItems.Count == 0)
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(GetText(strs.empty_page));
|
.WithDescription(GetText(strs.empty_page));
|
||||||
|
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(pageItems.Join('\n'))
|
.WithDescription(pageItems.Join('\n'))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -783,4 +783,28 @@ public partial class Utility : EllieModule
|
||||||
await Response().Error(ex.Message).SendAsync();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Services.Currency;
|
using EllieBot.Services.Currency;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Gambling;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace EllieBot.Services;
|
namespace EllieBot.Services;
|
||||||
|
@ -317,6 +318,8 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
GamblingGame.Race => 0.06m,
|
GamblingGame.Race => 0.06m,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class UserBetStats
|
public sealed class UserBetStats
|
||||||
|
|
|
@ -4,10 +4,11 @@ using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
public sealed class BlacklistService : IExecOnMessage
|
public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
{
|
{
|
||||||
public int Priority
|
public int Priority
|
||||||
=> int.MaxValue;
|
=> int.MaxValue;
|
||||||
|
@ -15,69 +16,115 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
private readonly IBotCreds _creds;
|
private readonly IBotCreds _creds;
|
||||||
private IReadOnlyList<BlacklistEntry> blacklist;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
private readonly TypedKey<BlacklistEntry[]> _blPubKey = new("blacklist.reload");
|
private FrozenSet<ulong> blacklistedGuilds = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedUsers = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedChannels = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
|
||||||
public BlacklistService(DbService db, IPubSub pubSub, IBotCreds creds)
|
private readonly TypedKey<bool> _blPubKey = new("blacklist.reload");
|
||||||
|
|
||||||
|
public BlacklistService(
|
||||||
|
DbService db,
|
||||||
|
IPubSub pubSub,
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
|
||||||
Reload(false);
|
_pubSub.Sub(_blPubKey, async _ => await Reload(false));
|
||||||
_pubSub.Sub(_blPubKey, OnReload);
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_client.JoinedGuild += async (g) =>
|
||||||
|
{
|
||||||
|
if (blacklistedGuilds.Contains(g.Id))
|
||||||
|
{
|
||||||
|
await g.LeaveAsync();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await Reload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
||||||
{
|
{
|
||||||
blacklist = newBlacklist;
|
if (newBlacklist is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
blacklistedGuilds =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Server).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
blacklistedChannels =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Channel).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
blacklistedUsers =
|
||||||
|
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.User).Select(x => x.ItemId))
|
||||||
|
.ToFrozenSet();
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg)
|
public Task<bool> 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,
|
||||||
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id);
|
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 (blacklistedUsers.Contains(usrMsg.Author.Id))
|
||||||
}
|
{
|
||||||
|
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
||||||
if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id)
|
usrMsg.Author.ToString(),
|
||||||
{
|
usrMsg.Author.Id.ToString());
|
||||||
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
return Task.FromResult(true);
|
||||||
usrMsg.Author.ToString(),
|
|
||||||
usrMsg.Author.Id);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<BlacklistEntry> GetBlacklist()
|
public async Task<IReadOnlyList<BlacklistEntry>> GetBlacklist(BlacklistType type)
|
||||||
=> blacklist;
|
|
||||||
|
|
||||||
public void Reload(bool publish = true)
|
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var toPublish = uow.GetTable<BlacklistEntry>().ToArray();
|
|
||||||
blacklist = toPublish;
|
return await uow
|
||||||
|
.GetTable<BlacklistEntry>()
|
||||||
|
.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<BlacklistEntry>()
|
||||||
|
.Where(x => x.Type != BlacklistType.Server
|
||||||
|
|| (x.Type == BlacklistType.Server
|
||||||
|
&& Linq2DbExpressions.GuildOnShard(x.ItemId, totalShards, _client.ShardId)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
|
||||||
if (publish)
|
if (publish)
|
||||||
_pubSub.Pub(_blPubKey, toPublish);
|
{
|
||||||
|
await _pubSub.Pub(_blPubKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await OnReload(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Blacklist(BlacklistType type, ulong id)
|
public async Task Blacklist(BlacklistType type, ulong id)
|
||||||
|
@ -88,34 +135,34 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
await uow
|
await uow
|
||||||
.GetTable<BlacklistEntry>()
|
.GetTable<BlacklistEntry>()
|
||||||
.InsertAsync(() => new()
|
.InsertAsync(() => new()
|
||||||
{
|
{
|
||||||
ItemId = id,
|
ItemId = id,
|
||||||
Type = type,
|
Type = type,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (type == BlacklistType.User)
|
if (type == BlacklistType.User)
|
||||||
{
|
{
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => x.UserId == id)
|
.Where(x => x.UserId == id)
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UnBlacklist(BlacklistType type, ulong id)
|
public async Task UnBlacklist(BlacklistType type, ulong id)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<BlacklistEntry>()
|
await uow.GetTable<BlacklistEntry>()
|
||||||
.Where(bi => bi.ItemId == id && bi.Type == type)
|
.Where(bi => bi.ItemId == id && bi.Type == type)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
||||||
|
@ -130,12 +177,12 @@ public sealed class BlacklistService : IExecOnMessage
|
||||||
|
|
||||||
var blList = toBlacklist.ToList();
|
var blList = toBlacklist.ToList();
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => blList.Contains(x.UserId))
|
.Where(x => blList.Contains(x.UserId))
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1461,4 +1461,15 @@ translateflags:
|
||||||
- transflags
|
- transflags
|
||||||
rakeback:
|
rakeback:
|
||||||
- rakeback
|
- rakeback
|
||||||
- rb
|
- rb
|
||||||
|
betstatsreset:
|
||||||
|
- betstatsreset
|
||||||
|
- bsr
|
||||||
|
- bsreset
|
||||||
|
gamblestatsreset:
|
||||||
|
- gamblestatsreset
|
||||||
|
- gsr
|
||||||
|
- gsreset
|
||||||
|
snipe:
|
||||||
|
- snipe
|
||||||
|
- sn
|
|
@ -2711,6 +2711,13 @@ gamblestats:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
||||||
|
gamblestatsreset:
|
||||||
|
desc: |-
|
||||||
|
Resets the gamble stats.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
slot:
|
slot:
|
||||||
desc: |-
|
desc: |-
|
||||||
Play Ellie slots by placing your bet.
|
Play Ellie slots by placing your bet.
|
||||||
|
@ -4648,6 +4655,16 @@ translateflags:
|
||||||
- ''
|
- ''
|
||||||
params:
|
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:
|
betstats:
|
||||||
desc: |-
|
desc: |-
|
||||||
Shows the current bet stats for yourself, or the targetted user.
|
Shows the current bet stats for yourself, or the targetted user.
|
||||||
|
@ -4677,4 +4694,12 @@ rakeback:
|
||||||
ex:
|
ex:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- {}
|
- {}
|
||||||
|
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:
|
||||||
|
- { }
|
|
@ -1117,5 +1117,6 @@
|
||||||
"trfl_disabled": "Flag translation disabled.",
|
"trfl_disabled": "Flag translation disabled.",
|
||||||
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
||||||
"rakeback_none": "You don't have any rakeback to claim yet.",
|
"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}"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue