forked from EllieBotDevs/elliebot
Removed a lot of old useless/broken commands. Improved 'listserver.
Full changes in changelog
This commit is contained in:
parent
811c126174
commit
9892de7c2e
17 changed files with 115 additions and 856 deletions
src/EllieBot/Modules
|
@ -38,11 +38,11 @@ public sealed class Connect4Game : IDisposable
|
|||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
||||
public ImmutableArray<Field> GameState
|
||||
=> _gameState.ToImmutableArray();
|
||||
public IReadOnlyList<Field> GameState
|
||||
=> _gameState.AsReadOnly();
|
||||
|
||||
public ImmutableArray<(ulong UserId, string Username)?> Players
|
||||
=> _players.ToImmutableArray();
|
||||
public IReadOnlyCollection<(ulong UserId, string Username)?> Players
|
||||
=> _players.AsReadOnly();
|
||||
|
||||
public (ulong UserId, string Username) CurrentPlayer
|
||||
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
|
||||
|
@ -56,7 +56,6 @@ public sealed class Connect4Game : IDisposable
|
|||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly Options _options;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly EllieRandom _rng;
|
||||
|
||||
private Timer playerTimeoutTimer;
|
||||
|
@ -73,12 +72,11 @@ public sealed class Connect4Game : IDisposable
|
|||
public Connect4Game(
|
||||
ulong userId,
|
||||
string userName,
|
||||
Options options,
|
||||
ICurrencyService cs)
|
||||
Options options
|
||||
)
|
||||
{
|
||||
_players[0] = (userId, userName);
|
||||
_options = options;
|
||||
_cs = cs;
|
||||
|
||||
_rng = new();
|
||||
for (var i = 0; i < NUMBER_OF_COLUMNS * NUMBER_OF_ROWS; i++)
|
||||
|
@ -99,14 +97,13 @@ public sealed class Connect4Game : IDisposable
|
|||
{
|
||||
_ = OnGameFailedToStart?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
await _cs.AddAsync(_players[0].Value.UserId, _options.Bet, new("connect4", "refund"));
|
||||
}
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> Join(ulong userId, string userName, int bet)
|
||||
public async Task<bool> Join(ulong userId, string userName)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
|
@ -117,11 +114,6 @@ public sealed class Connect4Game : IDisposable
|
|||
if (_players[0].Value.UserId == userId) // same user can't join own game
|
||||
return false;
|
||||
|
||||
if (bet != _options.Bet) // can't join if bet amount is not the same
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(userId, bet, new("connect4", "bet"))) // user doesn't have enough money to gamble
|
||||
return false;
|
||||
|
||||
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
||||
{
|
||||
|
@ -133,14 +125,14 @@ public sealed class Connect4Game : IDisposable
|
|||
|
||||
CurrentPhase = Phase.P1Move; //start the game
|
||||
playerTimeoutTimer = new(async _ =>
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
},
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(_options.TurnTimer),
|
||||
TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
|
@ -351,13 +343,8 @@ public sealed class Connect4Game : IDisposable
|
|||
|
||||
if (result == Result.Draw)
|
||||
{
|
||||
_cs.AddAsync(CurrentPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
||||
_cs.AddAsync(OtherPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (winId is not null)
|
||||
_cs.AddAsync(winId.Value, (long)(_options.Bet * 1.98), new("connect4", "win"));
|
||||
}
|
||||
|
||||
private Field GetPlayerPiece(ulong userId)
|
||||
|
@ -394,16 +381,10 @@ public sealed class Connect4Game : IDisposable
|
|||
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
|
||||
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
|
||||
public int Bet { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer is < 5 or > 60)
|
||||
TurnTimer = 15;
|
||||
|
||||
if (Bet < 0)
|
||||
Bet = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,17 +29,15 @@ public partial class Gambling
|
|||
}
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
private IUserMessage msg;
|
||||
|
||||
private int repostCounter;
|
||||
|
||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
||||
public Connect4Commands(DiscordSocketClient client, GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -48,10 +46,8 @@ public partial class Gambling
|
|||
public async Task Connect4(params string[] args)
|
||||
{
|
||||
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
||||
if (!await CheckBetOptional(options.Bet))
|
||||
return;
|
||||
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options);
|
||||
Connect4Game game;
|
||||
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
||||
{
|
||||
|
@ -60,31 +56,17 @@ public partial class Gambling
|
|||
|
||||
newGame.Dispose();
|
||||
//means game already exists, try to join
|
||||
await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet);
|
||||
await game.Join(ctx.User.Id, ctx.User.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.Bet > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, options.Bet, new("connect4", "bet")))
|
||||
{
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
|
||||
game.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||
game.OnGameFailedToStart += GameOnGameFailedToStart;
|
||||
game.OnGameEnded += GameOnGameEnded;
|
||||
_client.MessageReceived += ClientMessageReceived;
|
||||
|
||||
game.Initialize();
|
||||
if (options.Bet == 0)
|
||||
await Response().Confirm(strs.connect4_created).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.connect4_created_bet(N(options.Bet))).SendAsync();
|
||||
await Response().Confirm(strs.connect4_created).SendAsync();
|
||||
|
||||
Task ClientMessageReceived(SocketMessage arg)
|
||||
{
|
||||
|
@ -99,7 +81,8 @@ public partial class Gambling
|
|||
|
||||
if (success)
|
||||
{
|
||||
try { await arg.DeleteAsync(); }
|
||||
try
|
||||
{ await arg.DeleteAsync(); }
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
|
@ -109,7 +92,8 @@ public partial class Gambling
|
|||
RepostCounter++;
|
||||
if (RepostCounter == 0)
|
||||
{
|
||||
try { msg = await Response().Embed(msg.Embeds.First().ToEmbedBuilder()).SendAsync(); }
|
||||
try
|
||||
{ msg = await Response().Embed(msg.Embeds.First().ToEmbedBuilder()).SendAsync(); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
@ -151,19 +135,19 @@ public partial class Gambling
|
|||
title = GetText(strs.connect4_draw);
|
||||
|
||||
return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||
{
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
|
||||
|
||||
if (msg is null)
|
||||
|
@ -198,7 +182,7 @@ public partial class Gambling
|
|||
|
||||
for (var i = 0; i < Connect4Game.NUMBER_OF_COLUMNS; i++)
|
||||
sb.Append(_numbers[i]);
|
||||
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||
private readonly IPatronageService _ps;
|
||||
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
public Gambling(
|
||||
IGamblingService gs,
|
||||
DbService db,
|
||||
|
@ -104,34 +102,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
await Response().Embed(eb).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Economy()
|
||||
{
|
||||
var ec = await _service.GetEconomyAsync();
|
||||
decimal onePercent = 0;
|
||||
|
||||
// This stops the top 1% from owning more than 100% of the money
|
||||
if (ec.Cash > 0)
|
||||
{
|
||||
onePercent = ec.OnePercent / (ec.Cash - ec.Bot);
|
||||
}
|
||||
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
||||
.WithOkColor();
|
||||
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||
{
|
||||
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
|
||||
|
@ -601,117 +571,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel(IUser u)
|
||||
{
|
||||
if (ctx.User.Id == u.Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//since the challenge is created by another user, we need to reverse the ids
|
||||
//if it gets removed, means challenge is accepted
|
||||
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
|
||||
{
|
||||
await game.StartGame();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, IUser u)
|
||||
{
|
||||
if (ctx.User.Id == u.Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.roll_duel));
|
||||
|
||||
var description = string.Empty;
|
||||
|
||||
var game = new RollDuelGame(_cs, _client.CurrentUser.Id, ctx.User.Id, u.Id, amount);
|
||||
//means challenge is just created
|
||||
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
|
||||
{
|
||||
if (other.Amount != amount)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_already_challenged).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await RollDuel(u);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
|
||||
{
|
||||
game.OnGameTick += GameOnGameTick;
|
||||
game.OnEnded += GameOnEnded;
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(u.ToString()),
|
||||
Format.Bold(N(amount))))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
async Task GameOnGameTick(RollDuelGame arg)
|
||||
{
|
||||
var rolls = arg.Rolls.Last();
|
||||
description += $@"{Format.Bold(ctx.User.ToString())} rolled **{rolls.Item1}**
|
||||
{Format.Bold(u.ToString())} rolled **{rolls.Item2}**
|
||||
--
|
||||
";
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
if (rdMsg is null)
|
||||
{
|
||||
rdMsg = await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); });
|
||||
}
|
||||
}
|
||||
|
||||
async Task GameOnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reason == RollDuelGame.Reason.Normal)
|
||||
{
|
||||
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
|
||||
description += $"\n**{winner}** Won {N((long)(rdGame.Amount * 2 * 0.98))}";
|
||||
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
|
||||
}
|
||||
else if (reason == RollDuelGame.Reason.Timeout)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_timeout).SendAsync();
|
||||
}
|
||||
else if (reason == RollDuelGame.Reason.NoFunds)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_no_funds).SendAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_service.Duels.TryRemove((u.Id, ctx.User.Id), out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
{
|
||||
|
|
|
@ -128,38 +128,6 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
|
||||
private static readonly TypedKey<EconomyResult> _ecoKey = new("ellie:economy");
|
||||
|
||||
public async Task<EconomyResult> GetEconomyAsync()
|
||||
{
|
||||
var data = await _cache.GetOrAddAsync(_ecoKey,
|
||||
async () =>
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cash = uow.Set<DiscordUser>().GetTotalCurrency();
|
||||
var onePercent = uow.Set<DiscordUser>().GetTopOnePercentCurrency(_client.CurrentUser.Id);
|
||||
decimal planted = uow.Set<PlantedCurrency>().AsQueryable().Sum(x => x.Amount);
|
||||
var waifus = uow.Set<WaifuInfo>().GetTotalValue();
|
||||
var bot = await uow.Set<DiscordUser>().GetUserCurrencyAsync(_client.CurrentUser.Id);
|
||||
decimal bank = await uow.GetTable<BankUser>()
|
||||
.SumAsyncLinqToDB(x => x.Balance);
|
||||
|
||||
var result = new EconomyResult
|
||||
{
|
||||
Cash = cash,
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent,
|
||||
Bank = bank
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
TimeSpan.FromMinutes(3));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
private static readonly SemaphoreSlim _timelyLock = new(1, 1);
|
||||
|
||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Common.TypeReaders;
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
using EllieBot.Modules.Gambling.Services;
|
||||
|
||||
namespace EllieBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
|
||||
{
|
||||
public enum Mixed { Mixed }
|
||||
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task RaffleCur(Mixed _, [OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
=> RaffleCur(amount, true);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RaffleCur([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, bool mixed = false)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
async Task OnEnded(IUser arg, long won)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(GetText(strs.rafflecur_ended(CurrencyName,
|
||||
Format.Bold(arg.ToString()),
|
||||
won + CurrencySign)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded);
|
||||
|
||||
if (res.Item1 is not null)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
|
||||
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({N(x.Amount)})")),
|
||||
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
|
||||
await Response().Error(strs.rafflecur_already_joined).SendAsync();
|
||||
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#nullable disable
|
||||
namespace EllieBot.Modules.Gambling.Common;
|
||||
|
||||
public class CurrencyRaffleGame
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Mixed,
|
||||
Normal
|
||||
}
|
||||
|
||||
public IEnumerable<User> Users
|
||||
=> _users;
|
||||
|
||||
public Type GameType { get; }
|
||||
|
||||
private readonly HashSet<User> _users = new();
|
||||
|
||||
public CurrencyRaffleGame(Type type)
|
||||
=> GameType = type;
|
||||
|
||||
public bool AddUser(IUser usr, long amount)
|
||||
{
|
||||
// if game type is normal, and someone already joined the game
|
||||
// (that's the user who created it)
|
||||
if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount)
|
||||
return false;
|
||||
|
||||
if (!_users.Add(new()
|
||||
{
|
||||
DiscordUser = usr,
|
||||
Amount = amount
|
||||
}))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public User GetWinner()
|
||||
{
|
||||
var rng = new EllieRandom();
|
||||
if (GameType == Type.Mixed)
|
||||
{
|
||||
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
|
||||
var sum = 0L;
|
||||
foreach (var u in Users)
|
||||
{
|
||||
sum += u.Amount;
|
||||
if (sum > num)
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
var usrs = _users.ToArray();
|
||||
return usrs[rng.Next(0, usrs.Length)];
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
=> DiscordUser.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is User u ? u.DiscordUser == DiscordUser : false;
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Services;
|
||||
|
||||
public class CurrencyRaffleService : IEService
|
||||
{
|
||||
public enum JoinErrorType
|
||||
{
|
||||
NotEnoughCurrency,
|
||||
AlreadyJoinedOrInvalidAmount
|
||||
}
|
||||
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public CurrencyRaffleService(ICurrencyService cs)
|
||||
=> _cs = cs;
|
||||
|
||||
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
|
||||
ulong channelId,
|
||||
IUser user,
|
||||
long amount,
|
||||
bool mixed,
|
||||
Func<IUser, long, Task> onEnded)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
var newGame = false;
|
||||
if (!Games.TryGetValue(channelId, out var crg))
|
||||
{
|
||||
newGame = true;
|
||||
crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal);
|
||||
Games.Add(channelId, crg);
|
||||
}
|
||||
|
||||
//remove money, and stop the game if this
|
||||
// user created it and doesn't have the money
|
||||
if (!await _cs.RemoveAsync(user.Id, amount, new("raffle", "join")))
|
||||
{
|
||||
if (newGame)
|
||||
Games.Remove(channelId);
|
||||
return (null, JoinErrorType.NotEnoughCurrency);
|
||||
}
|
||||
|
||||
if (!crg.AddUser(user, amount))
|
||||
{
|
||||
await _cs.AddAsync(user.Id, amount, new("raffle", "refund"));
|
||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
||||
}
|
||||
|
||||
if (newGame)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(60000);
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
var winner = crg.GetWinner();
|
||||
var won = crg.Users.Sum(x => x.Amount);
|
||||
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, won, new("raffle", "win"));
|
||||
Games.Remove(channelId, out _);
|
||||
_ = onEnded(winner.DiscordUser, won);
|
||||
}
|
||||
catch { }
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
return (crg, null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,9 +23,6 @@ public partial class Gambling
|
|||
[Group]
|
||||
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
||||
{
|
||||
private static decimal totalBet;
|
||||
private static decimal totalPaidOut;
|
||||
|
||||
private readonly IImageCache _images;
|
||||
private readonly FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
|
@ -69,17 +66,19 @@ public partial class Gambling
|
|||
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithAuthor(ctx.User)
|
||||
.WithDescription(Format.Bold(text))
|
||||
.WithImageUrl($"attachment://result.png")
|
||||
.WithOkColor();
|
||||
.WithAuthor(ctx.User)
|
||||
.WithDescription(Format.Bold(text))
|
||||
.WithImageUrl($"attachment://result.png")
|
||||
.WithOkColor();
|
||||
|
||||
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
|
||||
var inter = _inter.Create(ctx.User.Id, bb, smc =>
|
||||
{
|
||||
smc.DeferAsync();
|
||||
return Slot(amount);
|
||||
});
|
||||
var inter = _inter.Create(ctx.User.Id,
|
||||
bb,
|
||||
smc =>
|
||||
{
|
||||
smc.DeferAsync();
|
||||
return Slot(amount);
|
||||
});
|
||||
|
||||
var msg = await ctx.Channel.SendFileAsync(imgStream,
|
||||
"result.png",
|
||||
|
@ -159,12 +158,6 @@ public partial class Gambling
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_slotStatsLock)
|
||||
{
|
||||
totalBet += amount;
|
||||
totalPaidOut += result.Won;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -211,7 +204,7 @@ public partial class Gambling
|
|||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Origin = new(393, 480)
|
||||
Origin = new(393, 480)
|
||||
},
|
||||
ownedAmount.ToString(),
|
||||
fontColor));
|
||||
|
|
|
@ -10,133 +10,6 @@ public partial class Searches
|
|||
[Group]
|
||||
public partial class AnimeSearchCommands : EllieModule<AnimeSearchService>
|
||||
{
|
||||
// [EllieCommand, Aliases]
|
||||
// public async Task Novel([Leftover] string query)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
//
|
||||
// var novelData = await _service.GetNovelData(query);
|
||||
//
|
||||
// if (novelData is null)
|
||||
// {
|
||||
// await Response().Error(strs.failed_finding_novel).SendAsync();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var embed = _sender.CreateEmbed()
|
||||
// .WithOkColor()
|
||||
// .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
// .WithTitle(novelData.Title)
|
||||
// .WithUrl(novelData.Link)
|
||||
// .WithImageUrl(novelData.ImageUrl)
|
||||
// .AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
||||
// .AddField(GetText(strs.status), novelData.Status, true)
|
||||
// .AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
||||
// .WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
||||
//
|
||||
// await Response().Embed(embed).SendAsync();
|
||||
// }
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async Task Mal([Leftover] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
var imageElem =
|
||||
document.QuerySelector(
|
||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
|
||||
var imageUrl = ((IHtmlImageElement)imageElem)?.Source
|
||||
?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
|
||||
|
||||
var stats = document
|
||||
.QuerySelectorAll(
|
||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span")
|
||||
.Select(x => x.InnerHtml)
|
||||
.ToList();
|
||||
|
||||
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
|
||||
var favAnime = GetText(strs.anime_no_fav);
|
||||
if (favorites.Length > 0 && favorites[0].QuerySelector("p") is null)
|
||||
{
|
||||
favAnime = string.Join("\n",
|
||||
favorites[0]
|
||||
.QuerySelectorAll("ul > li > div.di-tc.va-t > a")
|
||||
.Shuffle()
|
||||
.Take(3)
|
||||
.Select(x =>
|
||||
{
|
||||
var elem = (IHtmlAnchorElement)x;
|
||||
return $"[{elem.InnerHtml}]({elem.Href})";
|
||||
}));
|
||||
}
|
||||
|
||||
var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
|
||||
.Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
|
||||
.ToList();
|
||||
|
||||
var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
|
||||
.Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
|
||||
.ToArray();
|
||||
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.mal_profile(name)))
|
||||
.AddField("💚 " + GetText(strs.watching), stats[0], true)
|
||||
.AddField("💙 " + GetText(strs.completed), stats[1], true);
|
||||
if (info.Count < 3)
|
||||
embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
|
||||
embed.AddField("💔 " + GetText(strs.dropped), stats[3], true)
|
||||
.AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
|
||||
.AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
|
||||
.AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
|
||||
.AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
|
||||
.AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
|
||||
if (info.Count > 2)
|
||||
embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
|
||||
|
||||
embed.WithDescription($@"
|
||||
** https://myanimelist.net/animelist/{name} **
|
||||
|
||||
**{GetText(strs.top_3_fav_anime)}**
|
||||
{favAnime}")
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
private static string MalInfoToEmoji(string info)
|
||||
{
|
||||
info = info.Trim().ToLowerInvariant();
|
||||
switch (info)
|
||||
{
|
||||
case "gender":
|
||||
return "🚁";
|
||||
case "location":
|
||||
return "🗺";
|
||||
case "last online":
|
||||
return "👥";
|
||||
case "birthday":
|
||||
return "📆";
|
||||
default:
|
||||
return "❔";
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task Mal(IGuildUser usr)
|
||||
=> Mal(usr.Username);
|
||||
|
||||
[Cmd]
|
||||
public async Task Anime([Leftover] string query)
|
||||
{
|
||||
|
@ -152,19 +25,19 @@ public partial class Searches
|
|||
}
|
||||
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(animeData.Synopsis.Replace("<br>",
|
||||
Environment.NewLine,
|
||||
StringComparison.InvariantCulture))
|
||||
.WithTitle(animeData.TitleEnglish)
|
||||
.WithUrl(animeData.Link)
|
||||
.WithImageUrl(animeData.ImageUrlLarge)
|
||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||
.WithOkColor()
|
||||
.WithDescription(animeData.Synopsis.Replace("<br>",
|
||||
Environment.NewLine,
|
||||
StringComparison.InvariantCulture))
|
||||
.WithTitle(animeData.TitleEnglish)
|
||||
.WithUrl(animeData.Link)
|
||||
.WithImageUrl(animeData.ImageUrlLarge)
|
||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
|
@ -184,19 +57,19 @@ public partial class Searches
|
|||
}
|
||||
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(mangaData.Synopsis.Replace("<br>",
|
||||
Environment.NewLine,
|
||||
StringComparison.InvariantCulture))
|
||||
.WithTitle(mangaData.TitleEnglish)
|
||||
.WithUrl(mangaData.Link)
|
||||
.WithImageUrl(mangaData.ImageUrlLge)
|
||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||
.WithOkColor()
|
||||
.WithDescription(mangaData.Synopsis.Replace("<br>",
|
||||
Environment.NewLine,
|
||||
StringComparison.InvariantCulture))
|
||||
.WithTitle(mangaData.TitleEnglish)
|
||||
.WithUrl(mangaData.Link)
|
||||
.WithImageUrl(mangaData.ImageUrlLge)
|
||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
|
||||
namespace EllieBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public partial class MemegenCommands : EllieModule
|
||||
{
|
||||
private static readonly ImmutableDictionary<char, string> _map = new Dictionary<char, string>
|
||||
{
|
||||
{ '?', "~q" },
|
||||
{ '%', "~p" },
|
||||
{ '#', "~h" },
|
||||
{ '/', "~s" },
|
||||
{ ' ', "-" },
|
||||
{ '-', "--" },
|
||||
{ '_', "__" },
|
||||
{ '"', "''" }
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public MemegenCommands(IHttpClientFactory factory)
|
||||
=> _httpFactory = factory;
|
||||
|
||||
[Cmd]
|
||||
public async Task Memelist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
using var http = _httpFactory.CreateClient("memelist");
|
||||
using var res = await http.GetAsync("https://api.memegen.link/templates/");
|
||||
|
||||
var rawJson = await res.Content.ReadAsStringAsync();
|
||||
|
||||
var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson)!;
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(data)
|
||||
.PageSize(15)
|
||||
.CurrentPage(page)
|
||||
.Page((items, curPage) =>
|
||||
{
|
||||
var templates = string.Empty;
|
||||
foreach (var template in items)
|
||||
templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
|
||||
var embed = _sender.CreateEmbed().WithOkColor().WithDescription(templates);
|
||||
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Memegen(string meme, [Leftover] string memeText = null)
|
||||
{
|
||||
var memeUrl = $"http://api.memegen.link/{meme}";
|
||||
if (!string.IsNullOrWhiteSpace(memeText))
|
||||
{
|
||||
var memeTextArray = memeText.Split(';');
|
||||
foreach (var text in memeTextArray)
|
||||
{
|
||||
var newText = Replace(text);
|
||||
memeUrl += $"/{newText}";
|
||||
}
|
||||
}
|
||||
|
||||
memeUrl += ".png";
|
||||
await Response().Text(memeUrl).SendAsync();
|
||||
}
|
||||
|
||||
private static string Replace(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (_map.TryGetValue(c, out var tmp))
|
||||
sb.Append(tmp);
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private class MemegenTemplate
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -176,39 +176,5 @@ public partial class Utility
|
|||
|
||||
return joinedAt;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Activity(int page = 1)
|
||||
{
|
||||
const int activityPerPage = 10;
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
var startCount = page * activityPerPage;
|
||||
|
||||
var str = new StringBuilder();
|
||||
foreach (var kvp in _cmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value)
|
||||
.Skip(page * activityPerPage)
|
||||
.Take(activityPerPage))
|
||||
{
|
||||
str.AppendLine(GetText(strs.activity_line(++startCount,
|
||||
Format.Bold(kvp.Key.ToString()),
|
||||
kvp.Value / _stats.GetUptime().TotalSeconds,
|
||||
kvp.Value)));
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.activity_page(page + 1)))
|
||||
.WithOkColor()
|
||||
.WithFooter(GetText(
|
||||
strs.activity_users_total(_cmdHandler.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -557,29 +557,38 @@ public partial class Utility : EllieModule
|
|||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task ListServers(int page = 1)
|
||||
public async Task ServerList(int page = 1)
|
||||
{
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
var guilds = _client.Guilds.OrderBy(g => g.Name)
|
||||
.Skip(page * 15)
|
||||
.Take(15)
|
||||
var allGuilds = _client.Guilds
|
||||
.OrderBy(g => g.Name)
|
||||
.ToList();
|
||||
|
||||
if (!guilds.Any())
|
||||
{
|
||||
await Response().Error(strs.listservers_none).SendAsync();
|
||||
return;
|
||||
}
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(allGuilds)
|
||||
.PageSize(9)
|
||||
.Page((guilds, _) =>
|
||||
{
|
||||
if (!guilds.Any())
|
||||
{
|
||||
return _sender.CreateEmbed()
|
||||
.WithDescription(GetText(strs.listservers_none))
|
||||
.WithErrorColor();
|
||||
}
|
||||
|
||||
var embed = _sender.CreateEmbed().WithOkColor();
|
||||
foreach (var guild in guilds)
|
||||
embed.AddField(guild.Name, GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)));
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor();
|
||||
foreach (var guild in guilds)
|
||||
embed.AddField(guild.Name, GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)));
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
|
Reference in a new issue