added .fishlb
improve vote auth added discord and dbl stat reporting updated check lb quest to require fishlb too
This commit is contained in:
parent
07df2ed450
commit
aa06f62258
10 changed files with 310 additions and 32 deletions
src
EllieBot.VotesApi/Common
EllieBot
Modules
Gambling
Games
_common/Services
data
strings
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
@ -25,20 +26,58 @@ namespace EllieBot.VotesApi
|
||||||
: base(options, logger, encoder)
|
: base(options, logger, encoder)
|
||||||
=> _conf = conf;
|
=> _conf = conf;
|
||||||
|
|
||||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
|
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Authorization header missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
var authToken = authHeader.ToString().Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(authToken))
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Authorization token empty");
|
||||||
|
}
|
||||||
|
|
||||||
var claims = new List<Claim>();
|
var claims = new List<Claim>();
|
||||||
|
|
||||||
if (_conf[ConfKeys.DISCORDS_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
var discsKey = _conf[ConfKeys.DISCORDS_KEY]?.Trim();
|
||||||
claims.Add(new(DiscordsClaim, "true"));
|
var topggKey = _conf[ConfKeys.TOPGG_KEY]?.Trim();
|
||||||
|
var dblKey = _conf[ConfKeys.DISCORDBOTLIST_KEY]?.Trim();
|
||||||
|
|
||||||
if (_conf[ConfKeys.TOPGG_KEY] == Request.Headers["Authorization"].ToString().Trim())
|
if (!string.IsNullOrWhiteSpace(discsKey)
|
||||||
|
&& System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
|
||||||
|
Encoding.UTF8.GetBytes(discsKey),
|
||||||
|
Encoding.UTF8.GetBytes(authToken)))
|
||||||
|
{
|
||||||
|
claims.Add(new Claim(DiscordsClaim, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(topggKey)
|
||||||
|
&& System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
|
||||||
|
Encoding.UTF8.GetBytes(topggKey),
|
||||||
|
Encoding.UTF8.GetBytes(authToken)))
|
||||||
|
{
|
||||||
claims.Add(new Claim(TopggClaim, "true"));
|
claims.Add(new Claim(TopggClaim, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
if (_conf[ConfKeys.DISCORDBOTLIST_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
if (!string.IsNullOrWhiteSpace(dblKey)
|
||||||
|
&& System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
|
||||||
|
Encoding.UTF8.GetBytes(dblKey),
|
||||||
|
Encoding.UTF8.GetBytes(authToken)))
|
||||||
|
{
|
||||||
claims.Add(new Claim(DiscordbotlistClaim, "true"));
|
claims.Add(new Claim(DiscordbotlistClaim, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(AuthenticateResult.Success(new(new(new ClaimsIdentity(claims)), SchemeName)));
|
if (claims.Count == 0)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Invalid authorization token");
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthenticateResult.Success(
|
||||||
|
new AuthenticationTicket(
|
||||||
|
new ClaimsPrincipal(new ClaimsIdentity(claims)),
|
||||||
|
SchemeName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,99 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net.Http.Json;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.GrpcVotesApi;
|
using EllieBot.GrpcVotesApi;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
public sealed class ServerCountRewardService(
|
||||||
|
IBotCreds creds,
|
||||||
|
IHttpClientFactory httpFactory,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
ShardData shardData
|
||||||
|
)
|
||||||
|
: IEService, IReadyExecutor
|
||||||
|
{
|
||||||
|
|
||||||
|
private Task dblTask = Task.CompletedTask;
|
||||||
|
private Task discordsTask = Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
if (creds.Votes is null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(creds.Votes.DblApiKey))
|
||||||
|
{
|
||||||
|
dblTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var dblApiKey = creds.Votes.DblApiKey;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var httpClient = httpFactory.CreateClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Clear();
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", dblApiKey);
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
|
||||||
|
await httpClient.PostAsJsonAsync(
|
||||||
|
$"https://discordbotlist.com/api/v1/bots/{116275390695079945}/stats",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
users = client.Guilds.Sum(x => x.MemberCount),
|
||||||
|
shard_id = shardData.ShardId,
|
||||||
|
guilds = client.Guilds.Count,
|
||||||
|
voice_connections = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Unable to send server count to DBL");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromHours(12));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shardData.ShardId != 0)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(creds.Votes.DiscordsApiKey))
|
||||||
|
{
|
||||||
|
discordsTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var discordsApiKey = creds.Votes.DiscordsApiKey;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var httpClient = httpFactory.CreateClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Clear();
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", discordsApiKey);
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type",
|
||||||
|
"application/json");
|
||||||
|
await httpClient.PostAsJsonAsync(
|
||||||
|
$"https://discords.com/bots/api/bot/{client.CurrentUser.Id}/setservers",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
server_count = client.Guilds.Count * shardData.TotalShards,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Unable to send server count to Discords");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromHours(12));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class VoteRewardService(
|
public class VoteRewardService(
|
||||||
ShardData shardData,
|
ShardData shardData,
|
||||||
GamblingConfigService gcs,
|
GamblingConfigService gcs,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using AngleSharp.Common;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Modules.Administration;
|
using EllieBot.Modules.Administration;
|
||||||
|
@ -468,6 +469,26 @@ public sealed class FishService(
|
||||||
return catches;
|
return catches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<(ulong UserId, int Catches)>> GetFishLbAsync(int page)
|
||||||
|
{
|
||||||
|
await using var ctx = db.GetDbContext();
|
||||||
|
|
||||||
|
var result = await ctx.GetTable<FishCatch>()
|
||||||
|
.GroupBy(x => x.UserId)
|
||||||
|
.OrderByDescending(x => x.Sum(x => x.Count))
|
||||||
|
.Skip(page * 10)
|
||||||
|
.Take(10)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
UserId = x.Key,
|
||||||
|
Catches = x.Sum(x => x.Count)
|
||||||
|
})
|
||||||
|
.ToListAsyncLinqToDB()
|
||||||
|
.Fmap(x => x.Map(y => (y.UserId, y.Catches)));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public string GetStarText(int resStars, int fishStars)
|
public string GetStarText(int resStars, int fishStars)
|
||||||
{
|
{
|
||||||
if (resStars == fishStars)
|
if (resStars == fishStars)
|
||||||
|
@ -494,3 +515,9 @@ public sealed class FishService(
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class IUserFishCatch
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using EllieBot.Modules.Games.Fish;
|
using EllieBot.Modules.Games.Fish;
|
||||||
|
using EllieBot.Modules.Xp.Services;
|
||||||
using Format = Discord.Format;
|
using Format = Discord.Format;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games;
|
namespace EllieBot.Modules.Games;
|
||||||
|
@ -10,6 +11,7 @@ public partial class Games
|
||||||
FishItemService fis,
|
FishItemService fis,
|
||||||
FishConfigService fcs,
|
FishConfigService fcs,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
|
UserService us,
|
||||||
CaptchaService captchaService) : EllieModule
|
CaptchaService captchaService) : EllieModule
|
||||||
{
|
{
|
||||||
private static readonly EllieRandom _rng = new();
|
private static readonly EllieRandom _rng = new();
|
||||||
|
@ -18,6 +20,7 @@ public partial class Games
|
||||||
=> new($"fishingwhitelist:{userId}");
|
=> new($"fishingwhitelist:{userId}");
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Fish()
|
public async Task Fish()
|
||||||
{
|
{
|
||||||
var cRes = await cache.GetAsync(FishingWhitelistKey(ctx.User.Id));
|
var cRes = await cache.GetAsync(FishingWhitelistKey(ctx.User.Id));
|
||||||
|
@ -120,6 +123,7 @@ public partial class Games
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task FishSpot()
|
public async Task FishSpot()
|
||||||
{
|
{
|
||||||
var ws = fs.GetWeatherForPeriods(7);
|
var ws = fs.GetWeatherForPeriods(7);
|
||||||
|
@ -139,6 +143,7 @@ public partial class Games
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task FishList(int page = 1)
|
public async Task FishList(int page = 1)
|
||||||
{
|
{
|
||||||
if (--page < 0)
|
if (--page < 0)
|
||||||
|
@ -153,8 +158,8 @@ public partial class Games
|
||||||
|
|
||||||
var items = await fis.GetEquippedItemsAsync(ctx.User.Id);
|
var items = await fis.GetEquippedItemsAsync(ctx.User.Id);
|
||||||
var desc = $"""
|
var desc = $"""
|
||||||
🧠 {skill} / {maxSkill}
|
🧠 {skill} / {maxSkill}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
foreach (var itemType in Enum.GetValues<FishItemType>())
|
foreach (var itemType in Enum.GetValues<FishItemType>())
|
||||||
{
|
{
|
||||||
|
@ -204,6 +209,43 @@ public partial class Games
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task FishLb(int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.PageItems(async p => await fs.GetFishLbAsync(p))
|
||||||
|
.PageSize(9)
|
||||||
|
.Page(async (items, page) =>
|
||||||
|
{
|
||||||
|
var users = await us.GetUsersAsync(items.Select(x => x.UserId).ToArray());
|
||||||
|
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithTitle(GetText(strs.fish_lb_title))
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var data = items[i];
|
||||||
|
var user = users.TryGetValue(data.UserId, out var ud)
|
||||||
|
? ud.ToString()
|
||||||
|
: data.UserId.ToString();
|
||||||
|
|
||||||
|
eb.AddField("#" + (page * 9 + i + 1) + " | " + user,
|
||||||
|
GetText(strs.fish_catches(Format.Bold(data.Catches.ToString()))),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private string GetFishEmoji(FishData? fish, int count)
|
private string GetFishEmoji(FishData? fish, int count)
|
||||||
{
|
{
|
||||||
if (fish is null)
|
if (fish is null)
|
||||||
|
|
|
@ -9,7 +9,7 @@ public sealed class CheckLeaderboardsQuest : IQuest
|
||||||
=> "Leaderboard Enthusiast";
|
=> "Leaderboard Enthusiast";
|
||||||
|
|
||||||
public string Desc
|
public string Desc
|
||||||
=> "Check lb, xplb and waifulb";
|
=> "Check lb, xplb, fishlb and waifulb";
|
||||||
|
|
||||||
public string ProgDesc
|
public string ProgDesc
|
||||||
=> "";
|
=> "";
|
||||||
|
@ -18,7 +18,7 @@ public sealed class CheckLeaderboardsQuest : IQuest
|
||||||
=> QuestEventType.CommandUsed;
|
=> QuestEventType.CommandUsed;
|
||||||
|
|
||||||
public long RequiredAmount
|
public long RequiredAmount
|
||||||
=> 0b111;
|
=> 0b1111;
|
||||||
|
|
||||||
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
{
|
{
|
||||||
|
@ -28,11 +28,13 @@ public sealed class CheckLeaderboardsQuest : IQuest
|
||||||
var progress = oldProgress;
|
var progress = oldProgress;
|
||||||
|
|
||||||
if (name == "leaderboard")
|
if (name == "leaderboard")
|
||||||
progress |= 0b001;
|
progress |= 0b0001;
|
||||||
else if (name == "xpleaderboard")
|
else if (name == "xpleaderboard")
|
||||||
progress |= 0b010;
|
progress |= 0b0010;
|
||||||
else if (name == "waifulb")
|
else if (name == "waifulb")
|
||||||
progress |= 0b100;
|
progress |= 0b0100;
|
||||||
|
else if (name == "fishlb")
|
||||||
|
progress |= 0b1000;
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
@ -42,23 +44,29 @@ public sealed class CheckLeaderboardsQuest : IQuest
|
||||||
var msg = "";
|
var msg = "";
|
||||||
|
|
||||||
var emoji = IQuest.INCOMPLETE;
|
var emoji = IQuest.INCOMPLETE;
|
||||||
if ((progress & 0b001) == 0b001)
|
if ((progress & 0b0001) == 0b0001)
|
||||||
emoji = IQuest.COMPLETED;
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
msg += emoji + " flower lb seen\n";
|
msg += emoji + " flower lb seen\n";
|
||||||
|
|
||||||
emoji = IQuest.INCOMPLETE;
|
emoji = IQuest.INCOMPLETE;
|
||||||
if ((progress & 0b010) == 0b010)
|
if ((progress & 0b0010) == 0b0010)
|
||||||
emoji = IQuest.COMPLETED;
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
msg += emoji + " xp lb seen\n";
|
msg += emoji + " xp lb seen\n";
|
||||||
|
|
||||||
emoji = IQuest.INCOMPLETE;
|
emoji = IQuest.INCOMPLETE;
|
||||||
if ((progress & 0b100) == 0b100)
|
if ((progress & 0b0100) == 0b0100)
|
||||||
emoji = IQuest.COMPLETED;
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
msg += emoji + " waifu lb seen";
|
msg += emoji + " waifu lb seen";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b1000) == 0b1000)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += "\n" + emoji + " fish lb seen";
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,22 +3,67 @@ using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Xp.Services;
|
namespace EllieBot.Modules.Xp.Services;
|
||||||
|
|
||||||
public sealed class UserService : IUserService, IEService
|
public sealed class UserService(DbService db, DiscordSocketClient client) : IUserService, IEService
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
|
||||||
|
|
||||||
public UserService(DbService db)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DiscordUser?> GetUserAsync(ulong userId)
|
public async Task<DiscordUser?> GetUserAsync(ulong userId)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
var user = await uow
|
var user = await uow
|
||||||
.GetTable<DiscordUser>()
|
.GetTable<DiscordUser>()
|
||||||
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<ulong, IUserData>> GetUsersAsync(IReadOnlyCollection<ulong> userIds)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<ulong, IUserData>();
|
||||||
|
|
||||||
|
var cachedUsers = userIds
|
||||||
|
.Select(userId => (userId, user: client.GetUser(userId)))
|
||||||
|
.Where(x => x.user is not null)
|
||||||
|
.ToDictionary(x => x.userId, x => (IUserData)new UserData(
|
||||||
|
x.user.Id,
|
||||||
|
x.user.Username,
|
||||||
|
x.user.GetAvatarUrl() ?? x.user.GetDefaultAvatarUrl()));
|
||||||
|
|
||||||
|
foreach (var (userId, userData) in cachedUsers)
|
||||||
|
result[userId] = userData;
|
||||||
|
|
||||||
|
var remainingIds = userIds.Except(cachedUsers.Keys).ToList();
|
||||||
|
if (remainingIds.Count == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Try to get remaining users from database
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
var dbUsers = await uow
|
||||||
|
.GetTable<DiscordUser>()
|
||||||
|
.Where(u => remainingIds.Contains(u.UserId))
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
foreach (var dbUser in dbUsers)
|
||||||
|
{
|
||||||
|
result[dbUser.UserId] = new UserData(
|
||||||
|
dbUser.UserId,
|
||||||
|
dbUser.Username,
|
||||||
|
dbUser.AvatarId);
|
||||||
|
remainingIds.Remove(dbUser.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IUserData
|
||||||
|
{
|
||||||
|
ulong Id { get; }
|
||||||
|
string? Username { get; }
|
||||||
|
string? AvatarUrl { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct UserData(ulong Id, string? Username, string? AvatarUrl) : IUserData
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
=> Username ?? Id.ToString();
|
||||||
}
|
}
|
|
@ -4150,6 +4150,20 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".fishlb",
|
||||||
|
".filb"
|
||||||
|
],
|
||||||
|
"Description": "Shows the top anglers.",
|
||||||
|
"Usage": [
|
||||||
|
".fishlb"
|
||||||
|
],
|
||||||
|
"Submodule": "FishingCommands",
|
||||||
|
"Module": "Games",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".fishshop",
|
".fishshop",
|
||||||
|
@ -5920,7 +5934,9 @@
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".questlog",
|
".questlog",
|
||||||
".qlog",
|
".qlog",
|
||||||
".myquests"
|
".quest",
|
||||||
|
".quests",
|
||||||
|
".dailies"
|
||||||
],
|
],
|
||||||
"Description": "Shows your active quests and progress.",
|
"Description": "Shows your active quests and progress.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
|
|
|
@ -1660,7 +1660,6 @@ massping:
|
||||||
questlog:
|
questlog:
|
||||||
- questlog
|
- questlog
|
||||||
- qlog
|
- qlog
|
||||||
- myquests
|
|
||||||
- quest
|
- quest
|
||||||
- quests
|
- quests
|
||||||
- dailies
|
- dailies
|
||||||
|
@ -1686,3 +1685,6 @@ fishinv:
|
||||||
- fishinv
|
- fishinv
|
||||||
- finv
|
- finv
|
||||||
- fiinv
|
- fiinv
|
||||||
|
fishlb:
|
||||||
|
- fishlb
|
||||||
|
- filb
|
|
@ -5259,3 +5259,11 @@ fishunequip:
|
||||||
params:
|
params:
|
||||||
- index:
|
- index:
|
||||||
desc: "The index of the item to unequip."
|
desc: "The index of the item to unequip."
|
||||||
|
fishlb:
|
||||||
|
desc: |-
|
||||||
|
Shows the top anglers.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- page:
|
||||||
|
desc: "The optional page to display."
|
|
@ -1258,5 +1258,7 @@
|
||||||
"fish_unequip_success": "Item unequipped successfully!",
|
"fish_unequip_success": "Item unequipped successfully!",
|
||||||
"fish_unequip_error": "Could not unequip item.",
|
"fish_unequip_error": "Could not unequip item.",
|
||||||
"fish_inv_title": "Fishing Inventory",
|
"fish_inv_title": "Fishing Inventory",
|
||||||
"fish_cant_uneq_potion": "You can't unequip a potion."
|
"fish_cant_uneq_potion": "You can't unequip a potion.",
|
||||||
|
"fish_lb_title": "Fishing Leaderboard",
|
||||||
|
"fish_catches": "{0} catches"
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue