This repository has been archived on 2024-12-22. You can view files and clone it, but cannot push or open issues or pull requests.
elliebot/src/EllieBot/Modules/Xp/Xp.cs

592 lines
19 KiB
C#
Raw Normal View History

2024-06-18 11:56:02 +00:00
#nullable disable warnings
using EllieBot.Modules.Xp.Services;
using EllieBot.Db.Models;
using EllieBot.Modules.Patronage;
2024-06-27 09:07:29 +00:00
using EllieBot.Modules.Utility;
2024-06-18 11:56:02 +00:00
namespace EllieBot.Modules.Xp;
public partial class Xp : EllieModule<XpService>
{
public enum Channel
{
Channel
}
public enum NotifyPlace
{
Server = 0,
Guild = 0,
Global = 1
}
public enum Role
{
Role
}
public enum Server
{
Server
}
private readonly DownloadTracker _tracker;
private readonly ICurrencyProvider _gss;
public Xp(DownloadTracker tracker, ICurrencyProvider gss)
{
_tracker = tracker;
_gss = gss;
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Experience([Leftover] IUser user = null)
{
user ??= ctx.User;
await ctx.Channel.TriggerTypingAsync();
var (img, fmt) = await _service.GenerateXpImageAsync((IGuildUser)user);
await using (img)
{
await ctx.Channel.SendFileAsync(img, $"{ctx.Guild.Id}_{user.Id}_xp.{fmt.FileExtensions.FirstOrDefault()}");
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task XpNotify()
{
var globalSetting = _service.GetNotificationType(ctx.User);
var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id);
var embed = _sender.CreateEmbed()
.WithOkColor()
.AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting))
.AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting));
await Response().Embed(embed).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task XpNotify(NotifyPlace place, XpNotificationLocation type)
{
if (place == NotifyPlace.Guild)
await _service.ChangeNotificationType(ctx.User.Id, ctx.Guild.Id, type);
else
await _service.ChangeNotificationType(ctx.User, type);
await ctx.OkAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task XpExclude(Server _)
{
var ex = _service.ToggleExcludeServer(ctx.Guild.Id);
if (ex)
await Response().Confirm(strs.excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync();
else
await Response().Confirm(strs.not_excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync();
}
[Cmd]
[UserPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Role _, [Leftover] IRole role)
{
var ex = _service.ToggleExcludeRole(ctx.Guild.Id, role.Id);
if (ex)
await Response().Confirm(strs.excluded(Format.Bold(role.ToString()))).SendAsync();
else
await Response().Confirm(strs.not_excluded(Format.Bold(role.ToString()))).SendAsync();
}
[Cmd]
[UserPerm(GuildPerm.ManageChannels)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Channel _, [Leftover] IChannel channel = null)
{
if (channel is null)
channel = ctx.Channel;
var ex = _service.ToggleExcludeChannel(ctx.Guild.Id, channel.Id);
if (ex)
await Response().Confirm(strs.excluded(Format.Bold(channel.ToString()))).SendAsync();
else
await Response().Confirm(strs.not_excluded(Format.Bold(channel.ToString()))).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task XpExclusionList()
{
var serverExcluded = _service.IsServerExcluded(ctx.Guild.Id);
var roles = _service.GetExcludedRoles(ctx.Guild.Id)
.Select(x => ctx.Guild.GetRole(x))
.Where(x => x is not null)
.Select(x => $"`role` {x.Mention}")
.ToList();
var chans = (await _service.GetExcludedChannels(ctx.Guild.Id)
.Select(x => ctx.Guild.GetChannelAsync(x))
.WhenAll()).Where(x => x is not null)
.Select(x => $"`channel` <#{x.Id}>")
.ToList();
var rolesStr = roles.Any() ? string.Join("\n", roles) + "\n" : string.Empty;
var chansStr = chans.Count > 0 ? string.Join("\n", chans) + "\n" : string.Empty;
var desc = Format.Code(serverExcluded
? GetText(strs.server_is_excluded)
: GetText(strs.server_is_not_excluded));
desc += "\n\n" + rolesStr + chansStr;
var lines = desc.Split('\n');
await Response()
.Paginated()
.Items(lines)
.PageSize(15)
.CurrentPage(0)
.Page((items, _) =>
{
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.exclusion_list))
.WithDescription(string.Join('\n', items))
.WithOkColor();
return embed;
})
.SendAsync();
}
[Cmd]
[EllieOptions<LbOpts>]
[Priority(0)]
[RequireContext(ContextType.Guild)]
public Task XpLeaderboard(params string[] args)
=> XpLeaderboard(1, args);
[Cmd]
[EllieOptions<LbOpts>]
[Priority(1)]
[RequireContext(ContextType.Guild)]
public async Task XpLeaderboard(int page = 1, params string[] args)
{
if (--page < 0 || page > 100)
return;
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
await ctx.Channel.TriggerTypingAsync();
var socketGuild = (SocketGuild)ctx.Guild;
var allCleanUsers = new List<UserXpStats>();
if (opts.Clean)
{
await ctx.Channel.TriggerTypingAsync();
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
allCleanUsers = (await _service.GetTopUserXps(ctx.Guild.Id, 1000))
.Where(user => socketGuild.GetUser(user.UserId) is not null)
.ToList();
}
var res = opts.Clean
? Response()
.Paginated()
.Items(allCleanUsers)
: Response()
.Paginated()
.PageItems((curPage) => _service.GetUserXps(ctx.Guild.Id, curPage));
await res
.PageSize(9)
.CurrentPage(page)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
if (!users.Any())
return embed.WithDescription("-");
for (var i = 0; i < users.Count; i++)
{
var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp);
var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
var userXpData = users[i];
var awardStr = string.Empty;
if (userXpData.AwardedXp > 0)
awardStr = $"(+{userXpData.AwardedXp})";
else if (userXpData.AwardedXp < 0)
awardStr = $"({userXpData.AwardedXp})";
embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
}
return embed;
})
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task XpGlobalLeaderboard(int page = 1)
{
if (--page < 0 || page > 99)
return;
await Response()
.Paginated()
.PageItems(async curPage => await _service.GetUserXps(curPage))
.PageSize(9)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.global_leaderboard));
if (!users.Any())
{
embed.WithDescription("-");
return embed;
}
for (var i = 0; i < users.Count; i++)
{
var user = users[i];
embed.AddField($"#{i + 1 + (curPage * 9)} {user}",
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
}
return embed;
})
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(2)]
public async Task XpAdd(long amount, [Remainder] SocketRole role)
{
if (amount == 0)
return;
if (role.IsManaged)
return;
var count = await _service.AddXpToUsersAsync(ctx.Guild.Id, amount, role.Members.Select(x => x.Id).ToArray());
await Response()
.Confirm(
strs.xpadd_users(Format.Bold(amount.ToString()), Format.Bold(count.ToString())))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(3)]
public async Task XpAdd(int amount, ulong userId)
{
if (amount == 0)
return;
_service.AddXp(userId, ctx.Guild.Id, amount);
var usr = ((SocketGuild)ctx.Guild).GetUser(userId)?.ToString() ?? userId.ToString();
await Response().Confirm(strs.modified(Format.Bold(usr), Format.Bold(amount.ToString()))).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(4)]
public Task XpAdd(int amount, [Leftover] IGuildUser user)
=> XpAdd(amount, user.Id);
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task XpTemplateReload()
{
_service.ReloadXpTemplate();
await Task.Delay(1000);
await Response().Confirm(strs.template_reloaded).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task XpReset(IGuildUser user)
=> XpReset(user.Id);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task XpReset(ulong userId)
{
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.reset))
.WithDescription(GetText(strs.reset_user_confirm));
if (!await PromptUserConfirmAsync(embed))
return;
_service.XpReset(ctx.Guild.Id, userId);
await Response().Confirm(strs.reset_user(userId)).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task XpReset()
{
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.reset))
.WithDescription(GetText(strs.reset_server_confirm));
if (!await PromptUserConfirmAsync(embed))
return;
_service.XpReset(ctx.Guild.Id);
await Response().Confirm(strs.reset_server).SendAsync();
}
public enum XpShopInputType
{
Backgrounds = 0,
B = 0,
Bg = 0,
Bgs = 0,
Frames = 1,
F = 1,
Fr = 1,
Frs = 1,
Fs = 1,
}
[Cmd]
public async Task XpShop()
{
if (!_service.IsShopEnabled())
{
await Response().Error(strs.xp_shop_disabled).SendAsync();
return;
}
await Response()
.Confirm(GetText(strs.available_commands),
$"""
`{prefix}xpshop bgs`
`{prefix}xpshop frames`
*{GetText(strs.xpshop_website)}*
""")
.SendAsync();
}
[Cmd]
public async Task XpShop(XpShopInputType type, int page = 1)
{
--page;
if (page < 0)
return;
var allItems = type == XpShopInputType.Backgrounds
? await _service.GetShopBgs()
: await _service.GetShopFrames();
if (allItems is null)
{
await Response().Error(strs.xp_shop_disabled).SendAsync();
return;
}
if (allItems.Count == 0)
{
await Response().Error(strs.not_found).SendAsync();
return;
}
await Response()
.Paginated()
.Items(allItems)
.PageSize(1)
.CurrentPage(page)
.AddFooter(false)
.Page((items, _) =>
{
if (!items.Any())
return _sender.CreateEmbed()
.WithDescription(GetText(strs.not_found))
.WithErrorColor();
var (key, item) = items.FirstOrDefault();
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(item.Name)
.AddField(GetText(strs.price),
CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
true)
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
? item.Url
: item.Preview);
if (!string.IsNullOrWhiteSpace(item.Desc))
eb.AddField(GetText(strs.desc), item.Desc);
if (key == "default")
eb.WithDescription(GetText(strs.xpshop_website));
var tier = _service.GetXpShopTierRequirement(type);
if (tier != PatronTier.None)
{
eb.WithFooter(GetText(strs.xp_shop_buy_required_tier(tier.ToString())));
}
return eb;
})
.Interaction(async current =>
{
var (key, _) = allItems.Skip(current).First();
var itemType = type == XpShopInputType.Backgrounds
? XpShopItemType.Background
: XpShopItemType.Frame;
var ownedItem = await _service.GetUserItemAsync(ctx.User.Id, itemType, key);
if (ownedItem is not null)
{
var button = new ButtonBuilder(ownedItem.IsUsing
? GetText(strs.in_use)
: GetText(strs.use),
"xpshop:use",
emote: Emoji.Parse("👐"),
isDisabled: ownedItem.IsUsing);
var inter = _inter.Create(
ctx.User.Id,
button,
OnShopUse,
(key, itemType));
return inter;
}
else
{
var button = new ButtonBuilder(GetText(strs.buy),
"xpshop:buy",
emote: Emoji.Parse("💰"));
var inter = _inter.Create(
ctx.User.Id,
button,
OnShopBuy,
(key, itemType));
return inter;
}
})
.SendAsync();
}
[Cmd]
public async Task XpShopBuy(XpShopInputType type, string key)
{
var result = await _service.BuyShopItemAsync(ctx.User.Id, (XpShopItemType)type, key);
EllieInteractionBase GetUseInteraction()
{
return _inter.Create(ctx.User.Id,
new(label: "Use", customId: "xpshop:use_item", emote: Emoji.Parse("👐")),
async (_, state) => await XpShopUse(state.type, state.key),
(type, key)
);
}
if (result != BuyResult.Success)
{
var _ = result switch
{
BuyResult.XpShopDisabled => await Response().Error(strs.xp_shop_disabled).SendAsync(),
BuyResult.InsufficientFunds => await Response()
.Error(strs.not_enough(_gss.GetCurrencySign()))
.SendAsync(),
BuyResult.AlreadyOwned =>
await Response().Error(strs.xpshop_already_owned).Interaction(GetUseInteraction()).SendAsync(),
BuyResult.UnknownItem => await Response().Error(strs.xpshop_item_not_found).SendAsync(),
BuyResult.InsufficientPatronTier => await Response().Error(strs.patron_insuff_tier).SendAsync(),
_ => throw new ArgumentOutOfRangeException()
};
return;
}
await Response()
.Confirm(strs.xpshop_buy_success(type.ToString().ToLowerInvariant(),
key.ToLowerInvariant()))
.Interaction(GetUseInteraction())
.SendAsync();
}
[Cmd]
public async Task XpShopUse(XpShopInputType type, string key)
{
var result = await _service.UseShopItemAsync(ctx.User.Id, (XpShopItemType)type, key);
if (!result)
{
await Response().Confirm(strs.xp_shop_item_cant_use).SendAsync();
return;
}
await ctx.OkAsync();
}
private async Task OnShopUse(SocketMessageComponent smc, (string key, XpShopItemType type) state)
{
var (key, type) = state;
var result = await _service.UseShopItemAsync(ctx.User.Id, type, key);
if (!result)
{
await Response().Confirm(strs.xp_shop_item_cant_use).SendAsync();
}
}
private async Task OnShopBuy(SocketMessageComponent smc, (string key, XpShopItemType type) state)
{
var (key, type) = state;
var result = await _service.BuyShopItemAsync(ctx.User.Id, type, key);
if (result == BuyResult.InsufficientFunds)
{
await Response().Error(strs.not_enough(_gss.GetCurrencySign())).SendAsync();
}
}
private string GetNotifLocationString(XpNotificationLocation loc)
{
if (loc == XpNotificationLocation.Channel)
return GetText(strs.xpn_notif_channel);
if (loc == XpNotificationLocation.Dm)
return GetText(strs.xpn_notif_dm);
return GetText(strs.xpn_notif_disabled);
}
}