Added Xp module
This commit is contained in:
parent
e124621f90
commit
3e0ddb8eb5
24 changed files with 3868 additions and 0 deletions
483
src/EllieBot/Modules/Xp/Club/Club.cs
Normal file
483
src/EllieBot/Modules/Xp/Club/Club.cs
Normal file
|
@ -0,0 +1,483 @@
|
|||
#nullable disable
|
||||
using EllieBot.Db;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Xp.Services;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public partial class Xp
|
||||
{
|
||||
[Group]
|
||||
public partial class Club : EllieModule<IClubService>
|
||||
{
|
||||
private readonly XpService _xps;
|
||||
|
||||
public Club(XpService xps)
|
||||
=> _xps = xps;
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubTransfer([Leftover] IUser newOwner)
|
||||
{
|
||||
var result = _service.TransferClub(ctx.User, newOwner);
|
||||
|
||||
if (!result.TryPickT0(out var club, out var error))
|
||||
{
|
||||
if (error == ClubTransferError.NotOwner)
|
||||
await Response().Error(strs.club_owner_only).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.club_target_not_member).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response()
|
||||
.Confirm(
|
||||
strs.club_transfered(
|
||||
Format.Bold(club.Name),
|
||||
Format.Bold(newOwner.ToString())
|
||||
)
|
||||
)
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubAdmin([Leftover] IUser toAdmin)
|
||||
{
|
||||
var result = await _service.ToggleAdminAsync(ctx.User, toAdmin);
|
||||
|
||||
if (result == ToggleAdminResult.AddedAdmin)
|
||||
await Response().Confirm(strs.club_admin_add(Format.Bold(toAdmin.ToString()))).SendAsync();
|
||||
else if (result == ToggleAdminResult.RemovedAdmin)
|
||||
await Response().Confirm(strs.club_admin_remove(Format.Bold(toAdmin.ToString()))).SendAsync();
|
||||
else if (result == ToggleAdminResult.NotOwner)
|
||||
await Response().Error(strs.club_owner_only).SendAsync();
|
||||
else if (result == ToggleAdminResult.CantTargetThyself)
|
||||
await Response().Error(strs.club_admin_invalid_target).SendAsync();
|
||||
else if (result == ToggleAdminResult.TargetNotMember)
|
||||
await Response().Error(strs.club_target_not_member).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubCreate([Leftover] string clubName)
|
||||
{
|
||||
var result = await _service.CreateClubAsync(ctx.User, clubName);
|
||||
|
||||
if (result == ClubCreateResult.NameTooLong)
|
||||
{
|
||||
await Response().Error(strs.club_name_too_long).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == ClubCreateResult.NameTaken)
|
||||
{
|
||||
await Response().Error(strs.club_name_taken).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == ClubCreateResult.InsufficientLevel)
|
||||
{
|
||||
await Response().Error(strs.club_create_insuff_lvl).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == ClubCreateResult.AlreadyInAClub)
|
||||
{
|
||||
await Response().Error(strs.club_already_in).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await Response().Confirm(strs.club_created(Format.Bold(clubName))).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubIcon([Leftover] string url = null)
|
||||
{
|
||||
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url is not null))
|
||||
{
|
||||
await Response().Error(strs.club_icon_url_format).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _service.SetClubIconAsync(ctx.User.Id, url);
|
||||
if (result == SetClubIconResult.Success)
|
||||
await Response().Confirm(strs.club_icon_set).SendAsync();
|
||||
else if (result == SetClubIconResult.NotOwner)
|
||||
await Response().Error(strs.club_owner_only).SendAsync();
|
||||
else if (result == SetClubIconResult.TooLarge)
|
||||
await Response().Error(strs.club_icon_too_large).SendAsync();
|
||||
else if (result == SetClubIconResult.InvalidFileType)
|
||||
await Response().Error(strs.club_icon_invalid_filetype).SendAsync();
|
||||
}
|
||||
|
||||
private async Task InternalClubInfoAsync(ClubInfo club)
|
||||
{
|
||||
var lvl = new LevelStats(club.Xp);
|
||||
var allUsers = club.Members.OrderByDescending(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp).Level;
|
||||
if (club.OwnerId == x.Id)
|
||||
return int.MaxValue;
|
||||
if (x.IsClubAdmin)
|
||||
return (int.MaxValue / 2) + l;
|
||||
return l;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(allUsers)
|
||||
.PageSize(10)
|
||||
.Page((users, _) =>
|
||||
{
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{club}")
|
||||
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
|
||||
.AddField(GetText(strs.desc),
|
||||
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
|
||||
.AddField(GetText(strs.owner), club.Owner.ToString(), true)
|
||||
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
|
||||
.AddField(GetText(strs.members),
|
||||
string.Join("\n",
|
||||
users
|
||||
.Select(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp);
|
||||
var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
|
||||
if (club.OwnerId == x.Id)
|
||||
return x + "🌟" + lvlStr;
|
||||
if (x.IsClubAdmin)
|
||||
return x + "⭐" + lvlStr;
|
||||
return x + lvlStr;
|
||||
})));
|
||||
|
||||
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
|
||||
return embed.WithThumbnailUrl(club.ImageUrl);
|
||||
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public async Task ClubInformation(IUser user = null)
|
||||
{
|
||||
user ??= ctx.User;
|
||||
var club = _service.GetClubByMember(user);
|
||||
if (club is null)
|
||||
{
|
||||
await Response().Error(strs.club_user_not_in_club(Format.Bold(user.ToString()))).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await InternalClubInfoAsync(club);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async Task ClubInformation([Leftover] string clubName = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clubName))
|
||||
{
|
||||
await ClubInformation(ctx.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_service.GetClubByName(clubName, out var club))
|
||||
{
|
||||
await Response().Error(strs.club_not_exists).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await InternalClubInfoAsync(club);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public Task ClubBans(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
if (club is null)
|
||||
return Response().Error(strs.club_admin_perms).SendAsync();
|
||||
|
||||
var bans = club.Bans.Select(x => x.User).ToArray();
|
||||
|
||||
return Response()
|
||||
.Paginated()
|
||||
.Items(bans)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var toShow = string.Join("\n", items.Select(x => x.ToString()));
|
||||
|
||||
return _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.club_bans_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public Task ClubApps(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
if (club is null)
|
||||
return Response().Error(strs.club_admin_perms).SendAsync();
|
||||
|
||||
var apps = club.Applicants.Select(x => x.User).ToArray();
|
||||
|
||||
return Response()
|
||||
.Paginated()
|
||||
.Items(apps)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var toShow = string.Join("\n", items.Select(x => x.ToString()));
|
||||
|
||||
return _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.club_apps_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubApply([Leftover] string clubName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clubName))
|
||||
return;
|
||||
|
||||
if (!_service.GetClubByName(clubName, out var club))
|
||||
{
|
||||
await Response().Error(strs.club_not_exists).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = _service.ApplyToClub(ctx.User, club);
|
||||
if (result == ClubApplyResult.Success)
|
||||
await Response().Confirm(strs.club_applied(Format.Bold(club.ToString()))).SendAsync();
|
||||
else if (result == ClubApplyResult.Banned)
|
||||
await Response().Error(strs.club_join_banned).SendAsync();
|
||||
else if (result == ClubApplyResult.AlreadyApplied)
|
||||
await Response().Error(strs.club_already_applied).SendAsync();
|
||||
else if (result == ClubApplyResult.AlreadyInAClub)
|
||||
await Response().Error(strs.club_already_in).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public Task ClubAccept(IUser user)
|
||||
=> ClubAccept(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async Task ClubAccept([Leftover] string userName)
|
||||
{
|
||||
var result = _service.AcceptApplication(ctx.User.Id, userName, out var discordUser);
|
||||
if (result == ClubAcceptResult.Accepted)
|
||||
await Response().Confirm(strs.club_accepted(Format.Bold(discordUser.ToString()))).SendAsync();
|
||||
else if (result == ClubAcceptResult.NoSuchApplicant)
|
||||
await Response().Error(strs.club_accept_invalid_applicant).SendAsync();
|
||||
else if (result == ClubAcceptResult.NotOwnerOrAdmin)
|
||||
await Response().Error(strs.club_admin_perms).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public Task ClubReject(IUser user)
|
||||
=> ClubReject(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async Task ClubReject([Leftover] string userName)
|
||||
{
|
||||
var result = _service.RejectApplication(ctx.User.Id, userName, out var discordUser);
|
||||
if (result == ClubDenyResult.Rejected)
|
||||
await Response().Confirm(strs.club_rejected(Format.Bold(discordUser.ToString()))).SendAsync();
|
||||
else if (result == ClubDenyResult.NoSuchApplicant)
|
||||
await Response().Error(strs.club_accept_invalid_applicant).SendAsync();
|
||||
else if (result == ClubDenyResult.NotOwnerOrAdmin)
|
||||
await Response().Error(strs.club_admin_perms).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubLeave()
|
||||
{
|
||||
var res = _service.LeaveClub(ctx.User);
|
||||
|
||||
if (res == ClubLeaveResult.Success)
|
||||
await Response().Confirm(strs.club_left).SendAsync();
|
||||
else if (res == ClubLeaveResult.NotInAClub)
|
||||
await Response().Error(strs.club_not_in_a_club).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.club_owner_cant_leave).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public Task ClubKick([Leftover] IUser user)
|
||||
=> ClubKick(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public Task ClubKick([Leftover] string userName)
|
||||
{
|
||||
var result = _service.Kick(ctx.User.Id, userName, out var club);
|
||||
if (result == ClubKickResult.Success)
|
||||
{
|
||||
return Response()
|
||||
.Confirm(strs.club_user_kick(Format.Bold(userName),
|
||||
Format.Bold(club.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
if (result == ClubKickResult.Hierarchy)
|
||||
return Response().Error(strs.club_kick_hierarchy).SendAsync();
|
||||
|
||||
if (result == ClubKickResult.NotOwnerOrAdmin)
|
||||
return Response().Error(strs.club_admin_perms).SendAsync();
|
||||
|
||||
return Response().Error(strs.club_target_not_member).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public Task ClubBan([Leftover] IUser user)
|
||||
=> ClubBan(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public Task ClubBan([Leftover] string userName)
|
||||
{
|
||||
var result = _service.Ban(ctx.User.Id, userName, out var club);
|
||||
if (result == ClubBanResult.Success)
|
||||
{
|
||||
return Response()
|
||||
.Confirm(strs.club_user_banned(Format.Bold(userName),
|
||||
Format.Bold(club.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
if (result == ClubBanResult.Unbannable)
|
||||
return Response().Error(strs.club_ban_fail_unbannable).SendAsync();
|
||||
|
||||
if (result == ClubBanResult.WrongUser)
|
||||
return Response().Error(strs.club_ban_fail_user_not_found).SendAsync();
|
||||
|
||||
return Response().Error(strs.club_admin_perms).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public Task ClubUnBan([Leftover] IUser user)
|
||||
=> ClubUnBan(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public Task ClubUnBan([Leftover] string userName)
|
||||
{
|
||||
var result = _service.UnBan(ctx.User.Id, userName, out var club);
|
||||
|
||||
if (result == ClubUnbanResult.Success)
|
||||
{
|
||||
return Response()
|
||||
.Confirm(strs.club_user_unbanned(Format.Bold(userName),
|
||||
Format.Bold(club.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
if (result == ClubUnbanResult.WrongUser)
|
||||
{
|
||||
return Response().Error(strs.club_unban_fail_user_not_found).SendAsync();
|
||||
}
|
||||
|
||||
return Response().Error(strs.club_admin_perms).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubDescription([Leftover] string desc = null)
|
||||
{
|
||||
if (_service.SetDescription(ctx.User.Id, desc))
|
||||
{
|
||||
desc = string.IsNullOrWhiteSpace(desc)
|
||||
? "-"
|
||||
: desc;
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithAuthor(ctx.User)
|
||||
.WithTitle(GetText(strs.club_desc_update))
|
||||
.WithOkColor()
|
||||
.WithDescription(desc);
|
||||
|
||||
await Response().Embed(eb).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Error(strs.club_desc_update_failed).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubDisband()
|
||||
{
|
||||
if (_service.Disband(ctx.User.Id, out var club))
|
||||
await Response().Confirm(strs.club_disbanded(Format.Bold(club.Name))).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.club_disband_error).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public Task ClubLeaderboard(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var clubs = _service.GetClubLeaderboardPage(page);
|
||||
|
||||
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.club_leaderboard(page + 1))).WithOkColor();
|
||||
|
||||
var i = page * 9;
|
||||
foreach (var club in clubs)
|
||||
embed.AddField($"#{++i} " + club, club.Xp + " xp");
|
||||
|
||||
return Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ClubRename([Leftover] string clubName)
|
||||
{
|
||||
var res = await _service.RenameClubAsync(ctx.User.Id, clubName);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
case ClubRenameResult.NameTooLong:
|
||||
await Response().Error(strs.club_name_too_long).SendAsync();
|
||||
return;
|
||||
case ClubRenameResult.Success:
|
||||
{
|
||||
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.club_renamed(clubName))).WithOkColor();
|
||||
await Response().Embed(embed).SendAsync();
|
||||
return;
|
||||
}
|
||||
case ClubRenameResult.NameTaken:
|
||||
await Response().Error(strs.club_name_taken).SendAsync();
|
||||
return;
|
||||
case ClubRenameResult.NotOwnerOrAdmin:
|
||||
await Response().Error(strs.club_admin_perms).SendAsync();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
367
src/EllieBot/Modules/Xp/Club/ClubService.cs
Normal file
367
src/EllieBot/Modules/Xp/Club/ClubService.cs
Normal file
|
@ -0,0 +1,367 @@
|
|||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Db;
|
||||
using EllieBot.Db.Models;
|
||||
using OneOf;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public class ClubService : IEService, IClubService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public ClubService(DbService db, IHttpClientFactory httpFactory)
|
||||
{
|
||||
_db = db;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
public async Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName)
|
||||
{
|
||||
if (!CheckClubName(clubName))
|
||||
return ClubCreateResult.NameTooLong;
|
||||
|
||||
//must be lvl 5 and must not be in a club already
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(user);
|
||||
var xp = new LevelStats(du.TotalXp);
|
||||
|
||||
if (xp.Level < 5)
|
||||
return ClubCreateResult.InsufficientLevel;
|
||||
|
||||
if (du.ClubId is not null)
|
||||
return ClubCreateResult.AlreadyInAClub;
|
||||
|
||||
if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName))
|
||||
return ClubCreateResult.NameTaken;
|
||||
|
||||
du.IsClubAdmin = true;
|
||||
du.Club = new()
|
||||
{
|
||||
Name = clubName,
|
||||
Owner = du
|
||||
};
|
||||
uow.Set<ClubInfo>().Add(du.Club);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
await uow.GetTable<ClubApplicants>()
|
||||
.DeleteAsync(x => x.UserId == du.Id);
|
||||
|
||||
return ClubCreateResult.Success;
|
||||
}
|
||||
|
||||
public OneOf<ClubInfo, ClubTransferError> TransferClub(IUser from, IUser newOwner)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwner(from.Id);
|
||||
var newOwnerUser = uow.GetOrCreateUser(newOwner);
|
||||
|
||||
if (club is null || club.Owner.UserId != from.Id)
|
||||
return ClubTransferError.NotOwner;
|
||||
|
||||
if (!club.Members.Contains(newOwnerUser))
|
||||
return ClubTransferError.TargetNotMember;
|
||||
|
||||
club.Owner.IsClubAdmin = true; // old owner will stay as admin
|
||||
newOwnerUser.IsClubAdmin = true;
|
||||
club.Owner = newOwnerUser;
|
||||
uow.SaveChanges();
|
||||
return club;
|
||||
}
|
||||
|
||||
public async Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin)
|
||||
{
|
||||
if (owner.Id == toAdmin.Id)
|
||||
return ToggleAdminResult.CantTargetThyself;
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwner(owner.Id);
|
||||
var adminUser = uow.GetOrCreateUser(toAdmin);
|
||||
|
||||
if (club is null)
|
||||
return ToggleAdminResult.NotOwner;
|
||||
|
||||
if(!club.Members.Contains(adminUser))
|
||||
return ToggleAdminResult.TargetNotMember;
|
||||
|
||||
var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
|
||||
await uow.SaveChangesAsync();
|
||||
return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubByMember(IUser user)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var member = uow.Set<ClubInfo>().GetByMember(user.Id);
|
||||
return member;
|
||||
}
|
||||
|
||||
public async Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string url)
|
||||
{
|
||||
if (url is not null)
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
if (!temp.IsImage())
|
||||
return SetClubIconResult.InvalidFileType;
|
||||
|
||||
if (temp.GetContentLength() > 5.Megabytes())
|
||||
return SetClubIconResult.TooLarge;
|
||||
}
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwner(ownerUserId);
|
||||
|
||||
if (club is null)
|
||||
return SetClubIconResult.NotOwner;
|
||||
|
||||
club.ImageUrl = url;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return SetClubIconResult.Success;
|
||||
}
|
||||
|
||||
public bool GetClubByName(string clubName, out ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
club = uow.Set<ClubInfo>().GetByName(clubName);
|
||||
|
||||
return club is not null;
|
||||
}
|
||||
|
||||
public ClubApplyResult ApplyToClub(IUser user, ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(user);
|
||||
uow.SaveChanges();
|
||||
|
||||
//user banned or a member of a club, or already applied,
|
||||
// or doesn't min minumum level requirement, can't apply
|
||||
if (du.ClubId is not null)
|
||||
return ClubApplyResult.AlreadyInAClub;
|
||||
|
||||
if (club.Bans.Any(x => x.UserId == du.Id))
|
||||
return ClubApplyResult.Banned;
|
||||
|
||||
if (club.Applicants.Any(x => x.UserId == du.Id))
|
||||
return ClubApplyResult.AlreadyApplied;
|
||||
|
||||
var app = new ClubApplicants
|
||||
{
|
||||
ClubId = club.Id,
|
||||
UserId = du.Id
|
||||
};
|
||||
|
||||
uow.Set<ClubApplicants>().Add(app);
|
||||
uow.SaveChanges();
|
||||
return ClubApplyResult.Success;
|
||||
}
|
||||
|
||||
|
||||
public ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
|
||||
{
|
||||
discordUser = null;
|
||||
using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(clubOwnerUserId);
|
||||
if (club is null)
|
||||
return ClubAcceptResult.NotOwnerOrAdmin;
|
||||
|
||||
var applicant =
|
||||
club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (applicant is null)
|
||||
return ClubAcceptResult.NoSuchApplicant;
|
||||
|
||||
applicant.User.Club = club;
|
||||
applicant.User.IsClubAdmin = false;
|
||||
club.Applicants.Remove(applicant);
|
||||
|
||||
//remove that user's all other applications
|
||||
uow.Set<ClubApplicants>()
|
||||
.RemoveRange(uow.Set<ClubApplicants>().AsQueryable().Where(x => x.UserId == applicant.User.Id));
|
||||
|
||||
discordUser = applicant.User;
|
||||
uow.SaveChanges();
|
||||
return ClubAcceptResult.Accepted;
|
||||
}
|
||||
|
||||
public ClubDenyResult RejectApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
|
||||
{
|
||||
discordUser = null;
|
||||
using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(clubOwnerUserId);
|
||||
if (club is null)
|
||||
return ClubDenyResult.NotOwnerOrAdmin;
|
||||
|
||||
var applicant =
|
||||
club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (applicant is null)
|
||||
return ClubDenyResult.NoSuchApplicant;
|
||||
|
||||
club.Applicants.Remove(applicant);
|
||||
|
||||
discordUser = applicant.User;
|
||||
uow.SaveChanges();
|
||||
return ClubDenyResult.Rejected;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<ClubInfo>().GetByOwnerOrAdmin(ownerUserId);
|
||||
}
|
||||
|
||||
public ClubLeaveResult LeaveClub(IUser user)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club));
|
||||
if (du.Club is null)
|
||||
return ClubLeaveResult.NotInAClub;
|
||||
if (du.Club.OwnerId == du.Id)
|
||||
return ClubLeaveResult.OwnerCantLeave;
|
||||
|
||||
du.Club = null;
|
||||
du.IsClubAdmin = false;
|
||||
uow.SaveChanges();
|
||||
return ClubLeaveResult.Success;
|
||||
}
|
||||
|
||||
public bool SetDescription(ulong userId, string desc)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var club = uow.Set<ClubInfo>().GetByOwner(userId);
|
||||
if (club is null)
|
||||
return false;
|
||||
|
||||
club.Description = desc?.TrimTo(150, true);
|
||||
uow.SaveChanges();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Disband(ulong userId, out ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
club = uow.Set<ClubInfo>().GetByOwner(userId);
|
||||
if (club is null)
|
||||
return false;
|
||||
|
||||
uow.Set<ClubInfo>().Remove(club);
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(bannerId);
|
||||
if (club is null)
|
||||
return ClubBanResult.NotOwnerOrAdmin;
|
||||
|
||||
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
|
||||
?? club.Applicants
|
||||
.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
|
||||
?.User;
|
||||
if (usr is null)
|
||||
return ClubBanResult.WrongUser;
|
||||
|
||||
if (club.OwnerId == usr.Id
|
||||
|| (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
|
||||
return ClubBanResult.Unbannable;
|
||||
|
||||
club.Bans.Add(new()
|
||||
{
|
||||
Club = club,
|
||||
User = usr
|
||||
});
|
||||
club.Members.Remove(usr);
|
||||
|
||||
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
|
||||
if (app is not null)
|
||||
club.Applicants.Remove(app);
|
||||
|
||||
uow.SaveChanges();
|
||||
|
||||
return ClubBanResult.Success;
|
||||
}
|
||||
|
||||
public ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(ownerUserId);
|
||||
if (club is null)
|
||||
return ClubUnbanResult.NotOwnerOrAdmin;
|
||||
|
||||
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (ban is null)
|
||||
return ClubUnbanResult.WrongUser;
|
||||
|
||||
club.Bans.Remove(ban);
|
||||
uow.SaveChanges();
|
||||
|
||||
return ClubUnbanResult.Success;
|
||||
}
|
||||
|
||||
|
||||
public ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(kickerId);
|
||||
if (club is null)
|
||||
return ClubKickResult.NotOwnerOrAdmin;
|
||||
|
||||
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (usr is null)
|
||||
return ClubKickResult.TargetNotAMember;
|
||||
|
||||
if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
|
||||
return ClubKickResult.Hierarchy;
|
||||
|
||||
club.Members.Remove(usr);
|
||||
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
|
||||
if (app is not null)
|
||||
club.Applicants.Remove(app);
|
||||
uow.SaveChanges();
|
||||
|
||||
return ClubKickResult.Success;
|
||||
}
|
||||
|
||||
public List<ClubInfo> GetClubLeaderboardPage(int page)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<ClubInfo>().GetClubLeaderboardPage(page);
|
||||
}
|
||||
|
||||
public async Task<ClubRenameResult> RenameClubAsync(ulong userId, string clubName)
|
||||
{
|
||||
if (!CheckClubName(clubName))
|
||||
return ClubRenameResult.NameTooLong;
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(userId);
|
||||
|
||||
if (club is null)
|
||||
return ClubRenameResult.NotOwnerOrAdmin;
|
||||
|
||||
if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName))
|
||||
return ClubRenameResult.NameTaken;
|
||||
|
||||
club.Name = clubName;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return ClubRenameResult.Success;
|
||||
}
|
||||
|
||||
private static bool CheckClubName(string clubName)
|
||||
{
|
||||
return !(string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20);
|
||||
}
|
||||
}
|
34
src/EllieBot/Modules/Xp/Club/IClubService.cs
Normal file
34
src/EllieBot/Modules/Xp/Club/IClubService.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using EllieBot.Db.Models;
|
||||
using OneOf;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public interface IClubService
|
||||
{
|
||||
Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName);
|
||||
OneOf<ClubInfo,ClubTransferError> TransferClub(IUser from, IUser newOwner);
|
||||
Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin);
|
||||
ClubInfo? GetClubByMember(IUser user);
|
||||
Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string? url);
|
||||
bool GetClubByName(string clubName, out ClubInfo club);
|
||||
ClubApplyResult ApplyToClub(IUser user, ClubInfo club);
|
||||
ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubDenyResult RejectApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubInfo? GetClubWithBansAndApplications(ulong ownerUserId);
|
||||
ClubLeaveResult LeaveClub(IUser user);
|
||||
bool SetDescription(ulong userId, string? desc);
|
||||
bool Disband(ulong userId, out ClubInfo club);
|
||||
ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club);
|
||||
ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club);
|
||||
ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||
List<ClubInfo> GetClubLeaderboardPage(int page);
|
||||
Task<ClubRenameResult> RenameClubAsync(ulong userId, string clubName);
|
||||
}
|
||||
|
||||
public enum ClubApplyResult
|
||||
{
|
||||
Success,
|
||||
AlreadyInAClub,
|
||||
Banned,
|
||||
AlreadyApplied
|
||||
}
|
15
src/EllieBot/Modules/Xp/Club/Results/ClubAcceptResult.cs
Normal file
15
src/EllieBot/Modules/Xp/Club/Results/ClubAcceptResult.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubAcceptResult
|
||||
{
|
||||
Accepted,
|
||||
NotOwnerOrAdmin,
|
||||
NoSuchApplicant,
|
||||
}
|
||||
|
||||
public enum ClubDenyResult
|
||||
{
|
||||
Rejected,
|
||||
NoSuchApplicant,
|
||||
NotOwnerOrAdmin
|
||||
}
|
10
src/EllieBot/Modules/Xp/Club/Results/ClubBanResult.cs
Normal file
10
src/EllieBot/Modules/Xp/Club/Results/ClubBanResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubBanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser,
|
||||
Unbannable,
|
||||
|
||||
}
|
10
src/EllieBot/Modules/Xp/Club/Results/ClubCreateResult.cs
Normal file
10
src/EllieBot/Modules/Xp/Club/Results/ClubCreateResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubCreateResult
|
||||
{
|
||||
Success,
|
||||
AlreadyInAClub,
|
||||
NameTaken,
|
||||
InsufficientLevel,
|
||||
NameTooLong
|
||||
}
|
9
src/EllieBot/Modules/Xp/Club/Results/ClubKickResult.cs
Normal file
9
src/EllieBot/Modules/Xp/Club/Results/ClubKickResult.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubKickResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
TargetNotAMember,
|
||||
Hierarchy
|
||||
}
|
8
src/EllieBot/Modules/Xp/Club/Results/ClubLeaveResult.cs
Normal file
8
src/EllieBot/Modules/Xp/Club/Results/ClubLeaveResult.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubLeaveResult
|
||||
{
|
||||
Success,
|
||||
OwnerCantLeave,
|
||||
NotInAClub
|
||||
}
|
9
src/EllieBot/Modules/Xp/Club/Results/ClubRenameResult.cs
Normal file
9
src/EllieBot/Modules/Xp/Club/Results/ClubRenameResult.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubRenameResult
|
||||
{
|
||||
NotOwnerOrAdmin,
|
||||
Success,
|
||||
NameTaken,
|
||||
NameTooLong
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubTransferError
|
||||
{
|
||||
NotOwner,
|
||||
TargetNotMember
|
||||
}
|
8
src/EllieBot/Modules/Xp/Club/Results/ClubUnbanResult.cs
Normal file
8
src/EllieBot/Modules/Xp/Club/Results/ClubUnbanResult.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubUnbanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum SetClubIconResult
|
||||
{
|
||||
Success,
|
||||
InvalidFileType,
|
||||
TooLarge,
|
||||
NotOwner,
|
||||
}
|
10
src/EllieBot/Modules/Xp/Club/Results/ToggleAdminResult.cs
Normal file
10
src/EllieBot/Modules/Xp/Club/Results/ToggleAdminResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public enum ToggleAdminResult
|
||||
{
|
||||
AddedAdmin,
|
||||
RemovedAdmin,
|
||||
NotOwner,
|
||||
TargetNotMember,
|
||||
CantTargetThyself,
|
||||
}
|
591
src/EllieBot/Modules/Xp/Xp.cs
Normal file
591
src/EllieBot/Modules/Xp/Xp.cs
Normal file
|
@ -0,0 +1,591 @@
|
|||
#nullable disable warnings
|
||||
using EllieBot.Modules.Xp.Services;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Patronage;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
109
src/EllieBot/Modules/Xp/XpConfig.cs
Normal file
109
src/EllieBot/Modules/Xp/XpConfig.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
#nullable disable warnings
|
||||
using Cloneable;
|
||||
using EllieBot.Common.Yml;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Patronage;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class XpConfig : ICloneable<XpConfig>
|
||||
{
|
||||
[Comment("""DO NOT CHANGE""")]
|
||||
public int Version { get; set; } = 5;
|
||||
|
||||
[Comment("""How much XP will the users receive per message""")]
|
||||
public int XpPerMessage { get; set; } = 3;
|
||||
|
||||
[Comment("""How often can the users receive XP in minutes""")]
|
||||
public int MessageXpCooldown { get; set; } = 5;
|
||||
|
||||
[Comment("""Amount of xp users gain from posting an image""")]
|
||||
public int XpFromImage { get; set; } = 0;
|
||||
|
||||
[Comment("""Average amount of xp earned per minute in VC""")]
|
||||
public double VoiceXpPerMinute { get; set; } = 0;
|
||||
|
||||
[Comment("""The maximum amount of minutes the bot will keep track of a user in a voice channel""")]
|
||||
public int VoiceMaxMinutes { get; set; } = 720;
|
||||
|
||||
[Comment("""The amount of currency users will receive for each point of global xp that they earn""")]
|
||||
public float CurrencyPerXp { get; set; } = 0;
|
||||
|
||||
[Comment("""Xp Shop config""")]
|
||||
public ShopConfig Shop { get; set; } = new();
|
||||
|
||||
public sealed class ShopConfig
|
||||
{
|
||||
[Comment("""
|
||||
Whether the xp shop is enabled
|
||||
True -> Users can access the xp shop using .xpshop command
|
||||
False -> Users can't access the xp shop
|
||||
""")]
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
[Comment("""
|
||||
Which patron tier do users need in order to use the .xpshop bgs command
|
||||
Leave at 'None' if patron system is disabled or you don't want any restrictions
|
||||
""")]
|
||||
public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
|
||||
|
||||
[Comment("""
|
||||
Which patron tier do users need in order to use the .xpshop frames command
|
||||
Leave at 'None' if patron system is disabled or you don't want any restrictions
|
||||
""")]
|
||||
public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
|
||||
|
||||
[Comment("""
|
||||
Frames available for sale. Keys are unique IDs.
|
||||
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
|
||||
Removing a key which previously existed means that all previous purchases will also be unusable.
|
||||
To remove an item from the shop, but keep previous purchases, set the price to -1
|
||||
""")]
|
||||
public Dictionary<string, ShopItemInfo>? Frames { get; set; } = new()
|
||||
{
|
||||
{"default", new() {Name = "No frame", Price = 0, Url = string.Empty}}
|
||||
};
|
||||
|
||||
[Comment("""
|
||||
Backgrounds available for sale. Keys are unique IDs.
|
||||
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
|
||||
Removing a key which previously existed means that all previous purchases will also be unusable.
|
||||
To remove an item from the shop, but keep previous purchases, set the price to -1
|
||||
""")]
|
||||
public Dictionary<string, ShopItemInfo>? Bgs { get; set; } = new()
|
||||
{
|
||||
{"default", new() {Name = "Default Background", Price = 0, Url = string.Empty}}
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class ShopItemInfo
|
||||
{
|
||||
[Comment("""Visible name of the item""")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Comment("""Price of the item. Set to -1 if you no longer want to sell the item but want the users to be able to keep their old purchase""")]
|
||||
public int Price { get; set; }
|
||||
|
||||
[Comment("""Direct url to the .png image which will be applied to the user's XP card""")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[Comment("""Optional preview url which will show instead of the real URL in the shop """)]
|
||||
public string Preview { get; set; }
|
||||
|
||||
[Comment("""Optional description of the item""")]
|
||||
public string Desc { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public static class XpShopConfigExtensions
|
||||
{
|
||||
public static string? GetItemUrl(this XpConfig.ShopConfig sc, XpShopItemType type, string key)
|
||||
=> (type switch
|
||||
{
|
||||
XpShopItemType.Background => sc.Bgs,
|
||||
_ => sc.Frames
|
||||
})?.TryGetValue(key, out var item) ?? false
|
||||
? item.Url
|
||||
: null;
|
||||
}
|
63
src/EllieBot/Modules/Xp/XpConfigService.cs
Normal file
63
src/EllieBot/Modules/Xp/XpConfigService.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
#nullable disable
|
||||
using EllieBot.Common.Configs;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
public sealed class XpConfigService : ConfigServiceBase<XpConfig>
|
||||
{
|
||||
private const string FILE_PATH = "data/xp.yml";
|
||||
private static readonly TypedKey<XpConfig> _changeKey = new("config.xp.updated");
|
||||
|
||||
public override string Name
|
||||
=> "xp";
|
||||
|
||||
public XpConfigService(IConfigSeria serializer, IPubSub pubSub)
|
||||
: base(FILE_PATH, serializer, pubSub, _changeKey)
|
||||
{
|
||||
AddParsedProp("txt.cooldown",
|
||||
conf => conf.MessageXpCooldown,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
x => x > 0);
|
||||
AddParsedProp("txt.per_msg", conf => conf.XpPerMessage, int.TryParse, ConfigPrinters.ToString, x => x >= 0);
|
||||
AddParsedProp("txt.per_image", conf => conf.XpFromImage, int.TryParse, ConfigPrinters.ToString, x => x > 0);
|
||||
|
||||
AddParsedProp("voice.per_minute",
|
||||
conf => conf.VoiceXpPerMinute,
|
||||
double.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
x => x >= 0);
|
||||
AddParsedProp("voice.max_minutes",
|
||||
conf => conf.VoiceMaxMinutes,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
x => x > 0);
|
||||
|
||||
AddParsedProp("shop.is_enabled",
|
||||
conf => conf.Shop.IsEnabled,
|
||||
bool.TryParse,
|
||||
ConfigPrinters.ToString);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (data.Version < 2)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 2;
|
||||
c.XpFromImage = 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.Version < 6)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 6;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
141
src/EllieBot/Modules/Xp/XpRewards.cs
Normal file
141
src/EllieBot/Modules/Xp/XpRewards.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
using EllieBot.Modules.Xp.Services;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public partial class Xp
|
||||
{
|
||||
public partial class XpRewards : EllieModule<XpService>
|
||||
{
|
||||
private readonly ICurrencyProvider _cp;
|
||||
|
||||
public XpRewards(ICurrencyProvider cp)
|
||||
=> _cp = cp;
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task XpRewsReset()
|
||||
{
|
||||
var promptEmbed = _sender.CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.xprewsreset_confirm));
|
||||
|
||||
var reply = await PromptUserConfirmAsync(promptEmbed);
|
||||
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
await _service.ResetXpRewards(ctx.Guild.Id);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task XpLevelUpRewards(int page = 1)
|
||||
{
|
||||
page--;
|
||||
|
||||
if (page is < 0 or > 100)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var allRewards = _service.GetRoleRewards(ctx.Guild.Id)
|
||||
.OrderBy(x => x.Level)
|
||||
.Select(x =>
|
||||
{
|
||||
var sign = !x.Remove ? "✅ " : "❌ ";
|
||||
|
||||
var str = ctx.Guild.GetRole(x.RoleId)?.ToString();
|
||||
|
||||
if (str is null)
|
||||
str = GetText(strs.role_not_found(Format.Code(x.RoleId.ToString())));
|
||||
else
|
||||
{
|
||||
if (!x.Remove)
|
||||
str = GetText(strs.xp_receive_role(Format.Bold(str)));
|
||||
else
|
||||
str = GetText(strs.xp_lose_role(Format.Bold(str)));
|
||||
}
|
||||
|
||||
return (x.Level, Text: sign + str);
|
||||
})
|
||||
.Concat(_service.GetCurrencyRewards(ctx.Guild.Id)
|
||||
.OrderBy(x => x.Level)
|
||||
.Select(x => (x.Level,
|
||||
Format.Bold(x.Amount + _cp.GetCurrencySign()))))
|
||||
.GroupBy(x => x.Level)
|
||||
.OrderBy(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
return Response()
|
||||
.Paginated()
|
||||
.Items(allRewards)
|
||||
.PageSize(9)
|
||||
.CurrentPage(page)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.level_up_rewards)).WithOkColor();
|
||||
|
||||
if (!items.Any())
|
||||
return embed.WithDescription(GetText(strs.no_level_up_rewards));
|
||||
|
||||
foreach (var reward in items)
|
||||
embed.AddField(GetText(strs.level_x(reward.Key)),
|
||||
string.Join("\n", reward.Select(y => y.Item2)));
|
||||
|
||||
return embed;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(2)]
|
||||
public async Task XpRoleReward(int level)
|
||||
{
|
||||
_service.ResetRoleReward(ctx.Guild.Id, level);
|
||||
await Response().Confirm(strs.xp_role_reward_cleared(level)).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task XpRoleReward(int level, AddRemove action, [Leftover] IRole role)
|
||||
{
|
||||
if (level < 1)
|
||||
return;
|
||||
|
||||
_service.SetRoleReward(ctx.Guild.Id, level, role.Id, action == AddRemove.Remove);
|
||||
if (action == AddRemove.Add)
|
||||
await Response().Confirm(strs.xp_role_reward_add_role(level, Format.Bold(role.ToString()))).SendAsync();
|
||||
else
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.xp_role_reward_remove_role(Format.Bold(level.ToString()),
|
||||
Format.Bold(role.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task XpCurrencyReward(int level, int amount = 0)
|
||||
{
|
||||
if (level < 1 || amount < 0)
|
||||
return;
|
||||
|
||||
_service.SetCurrencyReward(ctx.Guild.Id, level, amount);
|
||||
if (amount == 0)
|
||||
await Response().Confirm(strs.cur_reward_cleared(level, _cp.GetCurrencySign())).SendAsync();
|
||||
else
|
||||
await Response()
|
||||
.Confirm(strs.cur_reward_added(level,
|
||||
Format.Bold(amount + _cp.GetCurrencySign())))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
1617
src/EllieBot/Modules/Xp/XpService.cs
Normal file
1617
src/EllieBot/Modules/Xp/XpService.cs
Normal file
File diff suppressed because it is too large
Load diff
31
src/EllieBot/Modules/Xp/_common/FullUserStats.cs
Normal file
31
src/EllieBot/Modules/Xp/_common/FullUserStats.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
#nullable disable
|
||||
using EllieBot.Db;
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public class FullUserStats
|
||||
{
|
||||
public DiscordUser User { get; }
|
||||
public UserXpStats FullGuildStats { get; }
|
||||
public LevelStats Global { get; }
|
||||
public LevelStats Guild { get; }
|
||||
public int GlobalRanking { get; }
|
||||
public int GuildRanking { get; }
|
||||
|
||||
public FullUserStats(
|
||||
DiscordUser usr,
|
||||
UserXpStats fullGuildStats,
|
||||
LevelStats global,
|
||||
LevelStats guild,
|
||||
int globalRanking,
|
||||
int guildRanking)
|
||||
{
|
||||
User = usr;
|
||||
Global = global;
|
||||
Guild = guild;
|
||||
GlobalRanking = globalRanking;
|
||||
GuildRanking = guildRanking;
|
||||
FullGuildStats = fullGuildStats;
|
||||
}
|
||||
}
|
6
src/EllieBot/Modules/Xp/_common/IXpCleanupService.cs
Normal file
6
src/EllieBot/Modules/Xp/_common/IXpCleanupService.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public interface IXpCleanupService
|
||||
{
|
||||
Task DeleteXp();
|
||||
}
|
13
src/EllieBot/Modules/Xp/_common/UserCacheItem.cs
Normal file
13
src/EllieBot/Modules/Xp/_common/UserCacheItem.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#nullable disable warnings
|
||||
using Cloneable;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class UserXpGainData : ICloneable<UserXpGainData>
|
||||
{
|
||||
public IGuildUser User { get; init; }
|
||||
public IGuild Guild { get; init; }
|
||||
public IMessageChannel Channel { get; init; }
|
||||
public int XpAmount { get; set; }
|
||||
}
|
31
src/EllieBot/Modules/Xp/_common/XpCleanupService.cs
Normal file
31
src/EllieBot/Modules/Xp/_common/XpCleanupService.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using LinqToDB;
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public sealed class XpCleanupService : IXpCleanupService, IEService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public XpCleanupService(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task DeleteXp()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.Set<DiscordUser>().UpdateAsync(_ => new DiscordUser()
|
||||
{
|
||||
ClubId = null,
|
||||
// IsClubAdmin = false,
|
||||
TotalXp = 0
|
||||
});
|
||||
|
||||
await uow.Set<UserXpStats>().DeleteAsync();
|
||||
await uow.Set<ClubApplicants>().DeleteAsync();
|
||||
await uow.Set<ClubBans>().DeleteAsync();
|
||||
await uow.Set<ClubInfo>().DeleteAsync();
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
271
src/EllieBot/Modules/Xp/_common/XpTemplate.cs
Normal file
271
src/EllieBot/Modules/Xp/_common/XpTemplate.cs
Normal file
|
@ -0,0 +1,271 @@
|
|||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public class XpTemplate
|
||||
{
|
||||
public int Version { get; set; } = 0;
|
||||
|
||||
[JsonProperty("output_size")]
|
||||
public XpTemplatePos OutputSize { get; set; } = new()
|
||||
{
|
||||
X = 800,
|
||||
Y = 392
|
||||
};
|
||||
|
||||
public XpTemplateUser User { get; set; } = new()
|
||||
{
|
||||
Name = new()
|
||||
{
|
||||
FontSize = 50,
|
||||
Show = true,
|
||||
Pos = new()
|
||||
{
|
||||
X = 130,
|
||||
Y = 17
|
||||
}
|
||||
},
|
||||
Icon = new()
|
||||
{
|
||||
Show = true,
|
||||
Pos = new()
|
||||
{
|
||||
X = 14,
|
||||
Y = 14
|
||||
},
|
||||
Size = new()
|
||||
{
|
||||
X = 72,
|
||||
Y = 71
|
||||
}
|
||||
},
|
||||
GuildLevel = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 45,
|
||||
Pos = new()
|
||||
{
|
||||
X = 47,
|
||||
Y = 308
|
||||
}
|
||||
},
|
||||
GlobalLevel = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 45,
|
||||
Pos = new()
|
||||
{
|
||||
X = 47,
|
||||
Y = 160
|
||||
}
|
||||
},
|
||||
GuildRank = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 30,
|
||||
Pos = new()
|
||||
{
|
||||
X = 148,
|
||||
Y = 326
|
||||
}
|
||||
},
|
||||
GlobalRank = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 30,
|
||||
Pos = new()
|
||||
{
|
||||
X = 148,
|
||||
Y = 179
|
||||
}
|
||||
},
|
||||
Xp = new()
|
||||
{
|
||||
Bar = new()
|
||||
{
|
||||
Show = true,
|
||||
Global = new()
|
||||
{
|
||||
Direction = XpTemplateDirection.Right,
|
||||
Length = 450,
|
||||
Color = new(0, 0, 0, 0.4f),
|
||||
PointA = new()
|
||||
{
|
||||
X = 321,
|
||||
Y = 104
|
||||
},
|
||||
PointB = new()
|
||||
{
|
||||
X = 286,
|
||||
Y = 235
|
||||
}
|
||||
},
|
||||
Guild = new()
|
||||
{
|
||||
Direction = XpTemplateDirection.Right,
|
||||
Length = 450,
|
||||
Color = new(0, 0, 0, 0.4f),
|
||||
PointA = new()
|
||||
{
|
||||
X = 282,
|
||||
Y = 248
|
||||
},
|
||||
PointB = new()
|
||||
{
|
||||
X = 247,
|
||||
Y = 379
|
||||
}
|
||||
}
|
||||
},
|
||||
Global = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 50,
|
||||
Pos = new()
|
||||
{
|
||||
X = 528,
|
||||
Y = 170
|
||||
}
|
||||
},
|
||||
Guild = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 50,
|
||||
Pos = new()
|
||||
{
|
||||
X = 490,
|
||||
Y = 313
|
||||
}
|
||||
},
|
||||
Awarded = new()
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 25,
|
||||
Pos = new()
|
||||
{
|
||||
X = 490,
|
||||
Y = 345
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public XpTemplateClub Club { get; set; } = new()
|
||||
{
|
||||
Icon = new()
|
||||
{
|
||||
Show = true,
|
||||
Pos = new()
|
||||
{
|
||||
X = 722,
|
||||
Y = 25
|
||||
},
|
||||
Size = new()
|
||||
{
|
||||
X = 45,
|
||||
Y = 45
|
||||
}
|
||||
},
|
||||
Name = new()
|
||||
{
|
||||
FontSize = 35,
|
||||
Pos = new()
|
||||
{
|
||||
X = 650,
|
||||
Y = 49
|
||||
},
|
||||
Show = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public class XpTemplateIcon
|
||||
{
|
||||
public bool Show { get; set; }
|
||||
public XpTemplatePos Pos { get; set; }
|
||||
public XpTemplatePos Size { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplatePos
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateUser
|
||||
{
|
||||
public XpTemplateText Name { get; set; }
|
||||
public XpTemplateIcon Icon { get; set; }
|
||||
public XpTemplateText GlobalLevel { get; set; }
|
||||
public XpTemplateText GuildLevel { get; set; }
|
||||
public XpTemplateText GlobalRank { get; set; }
|
||||
public XpTemplateText GuildRank { get; set; }
|
||||
public XpTemplateXp Xp { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateClub
|
||||
{
|
||||
public XpTemplateIcon Icon { get; set; }
|
||||
public XpTemplateText Name { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateText
|
||||
{
|
||||
[JsonConverter(typeof(XpRgba32Converter))]
|
||||
public Rgba32 Color { get; set; } = SixLabors.ImageSharp.Color.White;
|
||||
|
||||
public bool Show { get; set; }
|
||||
public int FontSize { get; set; }
|
||||
public XpTemplatePos Pos { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateXp
|
||||
{
|
||||
public XpTemplateXpBar Bar { get; set; }
|
||||
public XpTemplateText Global { get; set; }
|
||||
public XpTemplateText Guild { get; set; }
|
||||
public XpTemplateText Awarded { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateXpBar
|
||||
{
|
||||
public bool Show { get; set; }
|
||||
public XpBar Global { get; set; }
|
||||
public XpBar Guild { get; set; }
|
||||
}
|
||||
|
||||
public class XpBar
|
||||
{
|
||||
[JsonConverter(typeof(XpRgba32Converter))]
|
||||
public Rgba32 Color { get; set; }
|
||||
|
||||
public XpTemplatePos PointA { get; set; }
|
||||
public XpTemplatePos PointB { get; set; }
|
||||
public int Length { get; set; }
|
||||
public XpTemplateDirection Direction { get; set; }
|
||||
}
|
||||
|
||||
public enum XpTemplateDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
public class XpRgba32Converter : JsonConverter<Rgba32>
|
||||
{
|
||||
public override Rgba32 ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
Rgba32 existingValue,
|
||||
bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
=> Color.ParseHex(reader.Value?.ToString());
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Rgba32 value, JsonSerializer serializer)
|
||||
=> writer.WriteValue(value.ToHex().ToLowerInvariant());
|
||||
}
|
16
src/EllieBot/Modules/Xp/_common/db/XpShopOwnedItem.cs
Normal file
16
src/EllieBot/Modules/Xp/_common/db/XpShopOwnedItem.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
#nullable disable warnings
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class XpShopOwnedItem : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public XpShopItemType ItemType { get; set; }
|
||||
public bool IsUsing { get; set; }
|
||||
public string ItemKey { get; set; }
|
||||
}
|
||||
|
||||
public enum XpShopItemType
|
||||
{
|
||||
Background = 0,
|
||||
Frame = 1,
|
||||
}
|
Loading…
Reference in a new issue