Removed a lot of old useless/broken commands. Improved 'listserver.

Full changes in changelog
This commit is contained in:
Toastie 2024-08-07 11:32:51 +12:00
parent 811c126174
commit 9892de7c2e
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
17 changed files with 115 additions and 856 deletions

View file

@ -2,6 +2,28 @@
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
## Unreleased
### Added
- `'serverlist` is now paginated
### Changed
- `'listservers` renamed to `'serverlist`
### Removed
- Removed old bloat / semi broken / dumb commands
- `'memelist` / `'memegen` (too inconvenient to use)
- `'activity` (useless owner-only command)
- `'rafflecur` (Just use raffle and then award manually instead)
- `'rollduel` (we had this command?)
- You can no longer bet on `'connect4`
- `'economy` Removed.
- Was buggy and didn't really show the real state of the economy.
- It might come back improved in the future
- `'mal` Removed. Useless information / semi broken
## [5.1.5] - 01.08.2024
### Added

View file

@ -88,7 +88,7 @@ public sealed class Bot : IBot
public IReadOnlyList<ulong> GetCurrentGuildIds()
=> Client.Guilds.Select(x => x.Id).ToList();
=> Client.Guilds.Select(x => x.Id).ToList().ToList();
private void AddServices()
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -587,10 +587,6 @@ pokemon:
pokemonability:
- pokemonability
- pokeab
memelist:
- memelist
memegen:
- memegen
weather:
- weather
- we
@ -771,7 +767,7 @@ typedel:
- typedel
typelist:
- typelist
listservers:
serverlist:
- listservers
cleverbot:
- cleverbot
@ -804,8 +800,6 @@ autodisconnect:
define:
- define
- def
activity:
- activity
setstatus:
- setstatus
invitecreate:
@ -855,8 +849,6 @@ divorce:
waifuinfo:
- waifuinfo
- waifustats
mal:
- mal
setmusicchannel:
- setmusicchannel
- smch
@ -1148,8 +1140,6 @@ discordpermoverridelist:
- dpoli
discordpermoverridereset:
- dpor
rafflecur:
- rafflecur
timelyset:
- timelyset
timely:
@ -1166,8 +1156,6 @@ massban:
- massban
masskill:
- masskill
rollduel:
- rollduel
reroadd:
- reroadd
- reroa
@ -1209,8 +1197,6 @@ roleid:
agerestricttoggle:
- nsfwtoggle
- artoggle
economy:
- economy
purgeuser:
- purgeuser
imageonlychannel:

View file

@ -1987,23 +1987,6 @@ pokemonability:
params:
- ability:
desc: "The type of the Pokémon's special power or trait that can be used in battle."
memelist:
desc: Shows a list of template keys (and their respective names) used for `{0}memegen`.
ex:
- ''
params:
- page:
desc: "The number of pages in the list to be displayed."
memegen:
desc: Generates a meme from memelist with specified text. Separate multiple text values with semicolons. Provide no meme text to see an example meme with that template.
ex:
- biw gets iced coffee;in the winter
- ntot
params:
- meme:
desc: "The caption or punchline of the meme, which can be a single sentence or multiple sentences separated by semicolons."
memeText:
desc: "The user-provided text to be displayed on the generated meme."
weather:
desc: Shows current weather data for the specified city.
ex:
@ -2550,7 +2533,7 @@ typelist:
params:
- page:
desc: "The current page number for the list of typing articles."
listservers:
serverlist:
desc: Lists servers the bot is on with some basic info. 15 per page.
ex:
- 3
@ -2645,13 +2628,6 @@ define:
params:
- word:
desc: "The word being searched for."
activity:
desc: Checks for spammers.
ex:
- ''
params:
- page:
desc: "The number of pages to scan for spam."
setstatus:
desc: Sets the bot's status. (Online/Idle/Dnd/Invisible)
ex:
@ -2808,15 +2784,6 @@ waifuinfo:
desc: "The user being targeted, whose waifu information will be displayed."
- targetId:
desc: "The ID of the person whose waifu stats are being displayed."
mal:
desc: Shows basic info from a MyAnimeList profile.
ex:
- straysocks
params:
- name:
desc: "The username or identifier for the MyAnimeList account being queried."
- usr:
desc: "The user's guild membership information is used to fetch their anime list and other relevant data."
setmusicchannel:
desc: Sets the current channel as the default music output channel. This will output playing, finished, paused and removed songs to that channel instead of the channel where the first song was queued in. Persistent server setting.
ex:
@ -3871,20 +3838,6 @@ discordpermoverridereset:
- ''
params:
- {}
rafflecur:
desc: Starts or joins a currency raffle with a specified amount. Users who join the raffle will lose the amount of currency specified and add it to the pot. After 30 seconds, random winner will be selected who will receive the whole pot. There is also a `mixed` mode in which the users will be able to join the game with any amount of currency, and have their chances be proportional to the amount they've bet.
ex:
- 20
- mixed 15
params:
- _:
desc: "The type of game mode to use, either \"fixed\" or \"mixed\"."
amount:
desc: "The minimum or maximum amount of currency that can be used for betting."
- amount:
desc: "The minimum or maximum amount of currency that can be used for betting."
mixed:
desc: "The parameter determines whether the raffle operates in \"fixed\" or \"proportional\" mode."
autodisconnect:
desc: Toggles whether the bot should disconnect from the voice channel once it's done playing all of the songs and queue repeat option is set to `none`.
ex:
@ -3956,18 +3909,6 @@ masskill:
params:
- people:
desc: "The list of user IDs or usernames to ban from the server and blacklist from the bot."
rollduel:
desc: Challenge someone to a roll duel by specifying the amount and the user you wish to challenge as the parameters. To accept the challenge, just specify the name of the user who challenged you, without the amount.
ex:
- 50 @Someone
- '@Challenger'
params:
- u:
desc: "The user being challenged or accepting the challenge."
- amount:
desc: "The stakes for the roll duel."
u:
desc: "The user being challenged or accepting the challenge."
reroadd:
desc: |-
Specify a message id, emote and a role name to have the bot assign the specified role to the user who reacts to the specified message (in this channel) with the specified emoji.
@ -4158,12 +4099,6 @@ agerestricttoggle:
- ''
params:
- {}
economy:
desc: Breakdown of the current state of the bot's economy. Updates every 3 minutes.
ex:
- ''
params:
- {}
purgeuser:
desc: Purge user from the database completely. This includes currency, xp, clubs that user owns, waifu info
ex:

View file

@ -977,14 +977,7 @@
"reset_user_confirm": "Are you sure that you want to reset specified user's XP on this server?",
"reset_user": "User with id {0} has had their XP reset on this server.",
"reset_server": "XP of all users on the server has been reset.",
"economy_state": "State of the economy",
"currency_owned": "Total currency owned by users",
"currency_one_percent": "% of currency owned by top 1%",
"currency_planted": "Currency currently planted",
"owned_waifus_total": "Total value of owned waifus",
"bot_currency": "Currency owned by the bot",
"total": "Total",
"bank_accounts": "Bank Accounts",
"no_invites": "No invites on this page.",
"invite_deleted": "Invite {0} has been deleted.",
"group_name_added": "Group #{0} now has a name: {1}",