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:
Toastie (DCS Team) 2024-11-09 18:41:00 +13:00
parent 2fe1b94cea
commit 0b3582e476
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
22 changed files with 453 additions and 214 deletions

View file

@ -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;

View 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();
}
}
}

View file

@ -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
{ {

View file

@ -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 =
[ [

View file

@ -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;

View file

@ -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)

View file

@ -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
{ {

View file

@ -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)
{ {

View file

@ -58,11 +58,3 @@ public abstract class GamblingModule<TService> : EllieModule<TService>
return InternalCheckBet(amount); return InternalCheckBet(amount);
} }
} }
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
{
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
}
}

View file

@ -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;

View file

@ -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
{ {

View file

@ -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;

View 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();
}
}

View file

@ -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)

View file

@ -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
}; };

View file

@ -18,13 +18,13 @@ 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())
+ " " + " "

View file

@ -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();
}
} }

View file

@ -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

View file

@ -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 (bl.Type == BlacklistType.Channel && bl.ItemId == usrMsg.Channel.Id) if (blacklistedChannels.Contains(usrMsg.Channel.Id))
{ {
Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]", Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]",
usrMsg.Channel.Name, usrMsg.Channel.Name,
usrMsg.Channel.Id); usrMsg.Channel.Id.ToString());
return Task.FromResult(true);
} }
if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id)
if (blacklistedUsers.Contains(usrMsg.Author.Id))
{ {
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]", Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
usrMsg.Author.ToString(), usrMsg.Author.ToString(),
usrMsg.Author.Id); usrMsg.Author.Id.ToString());
return Task.FromResult(true); 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)
@ -105,7 +152,7 @@ public sealed class BlacklistService : IExecOnMessage
}); });
} }
Reload(); await Reload();
} }
public async Task UnBlacklist(BlacklistType type, ulong id) public async Task UnBlacklist(BlacklistType type, ulong id)
@ -115,7 +162,7 @@ public sealed class BlacklistService : IExecOnMessage
.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)
@ -136,6 +183,6 @@ public sealed class BlacklistService : IExecOnMessage
CurrencyAmount = 0 CurrencyAmount = 0
}); });
Reload(); await Reload();
} }
} }

View file

@ -1462,3 +1462,14 @@ translateflags:
rakeback: rakeback:
- rakeback - rakeback
- rb - rb
betstatsreset:
- betstatsreset
- bsr
- bsreset
gamblestatsreset:
- gamblestatsreset
- gsr
- gsreset
snipe:
- snipe
- sn

View file

@ -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.
@ -4678,3 +4695,11 @@ rakeback:
- '' - ''
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:
- { }

View file

@ -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}"
} }