mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Killed history
This commit is contained in:
389
NadekoBot.Core/Modules/Xp/Club.cs
Normal file
389
NadekoBot.Core/Modules/Xp/Club.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Xp
|
||||
{
|
||||
public partial class Xp
|
||||
{
|
||||
[Group]
|
||||
public class Club : NadekoSubmodule<ClubService>
|
||||
{
|
||||
private readonly XpService _xps;
|
||||
|
||||
public Club(XpService xps)
|
||||
{
|
||||
_xps = xps;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubTransfer([Leftover] IUser newOwner)
|
||||
{
|
||||
var club = _service.TransferClub(ctx.User, newOwner);
|
||||
|
||||
if (club != null)
|
||||
await ReplyConfirmLocalizedAsync("club_transfered",
|
||||
Format.Bold(club.Name),
|
||||
Format.Bold(newOwner.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalizedAsync("club_transfer_failed").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubAdmin([Leftover] IUser toAdmin)
|
||||
{
|
||||
bool admin;
|
||||
try
|
||||
{
|
||||
admin = _service.ToggleAdmin(ctx.User, toAdmin);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_admin_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (admin)
|
||||
await ReplyConfirmLocalizedAsync("club_admin_add", Format.Bold(toAdmin.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync("club_admin_remove", Format.Bold(toAdmin.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubCreate([Leftover] string clubName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_name_too_long").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_service.CreateClub(ctx.User, clubName, out ClubInfo club))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_create_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync("club_created", Format.Bold(club.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubIcon([Leftover] string url = null)
|
||||
{
|
||||
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url != null)
|
||||
|| !await _service.SetClubIcon(ctx.User.Id, url == null ? null : new Uri(url)))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_icon_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync("club_icon_set").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task ClubInformation(IUser user = null)
|
||||
{
|
||||
user = user ?? ctx.User;
|
||||
var club = _service.GetClubByMember(user);
|
||||
if (club == null)
|
||||
{
|
||||
await ErrorLocalizedAsync("club_user_not_in_club", Format.Bold(user.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
await ClubInformation(club.ToString()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task ClubInformation([Leftover] string clubName = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clubName))
|
||||
{
|
||||
await ClubInformation(ctx.User).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_service.GetClubByName(clubName, out ClubInfo club))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_not_exists").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var lvl = new LevelStats(club.Xp);
|
||||
var users = club.Users
|
||||
.OrderByDescending(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp).Level;
|
||||
if (club.OwnerId == x.Id)
|
||||
return int.MaxValue;
|
||||
else if (x.IsClubAdmin)
|
||||
return int.MaxValue / 2 + l;
|
||||
else
|
||||
return l;
|
||||
});
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0, (page) =>
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{club.ToString()}")
|
||||
.WithDescription(GetText("level_x", lvl.Level) + $" ({club.Xp} xp)")
|
||||
.AddField(GetText("desc"), string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description,
|
||||
false)
|
||||
.AddField(GetText("owner"), club.Owner.ToString(), true)
|
||||
.AddField(GetText("level_req"), club.MinimumLevelReq.ToString(), true)
|
||||
.AddField(GetText("members"), string.Join("\n", users
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.Select(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp);
|
||||
var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
|
||||
if (club.OwnerId == x.Id)
|
||||
return x.ToString() + "🌟" + lvlStr;
|
||||
else if (x.IsClubAdmin)
|
||||
return x.ToString() + "⭐" + lvlStr;
|
||||
return x.ToString() + lvlStr;
|
||||
})), false);
|
||||
|
||||
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
|
||||
return embed.WithThumbnailUrl(club.ImageUrl);
|
||||
|
||||
return embed;
|
||||
}, club.Users.Count, 10).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task ClubBans(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
if (club == null)
|
||||
return ReplyErrorLocalizedAsync("club_not_exists_owner");
|
||||
|
||||
var bans = club
|
||||
.Bans
|
||||
.Select(x => x.User)
|
||||
.ToArray();
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var toShow = string.Join("\n", bans
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.Select(x => x.ToString()));
|
||||
|
||||
return new EmbedBuilder()
|
||||
.WithTitle(GetText("club_bans_for", club.ToString()))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
}, bans.Length, 10);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task ClubApps(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
if (club == null)
|
||||
return ReplyErrorLocalizedAsync("club_not_exists_owner");
|
||||
|
||||
var apps = club
|
||||
.Applicants
|
||||
.Select(x => x.User)
|
||||
.ToArray();
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var toShow = string.Join("\n", apps
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.Select(x => x.ToString()));
|
||||
|
||||
return new EmbedBuilder()
|
||||
.WithTitle(GetText("club_apps_for", club.ToString()))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
}, apps.Length, 10);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubApply([Leftover] string clubName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clubName))
|
||||
return;
|
||||
|
||||
if (!_service.GetClubByName(clubName, out ClubInfo club))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_not_exists").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_service.ApplyToClub(ctx.User, club))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync("club_applied", Format.Bold(club.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_apply_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public Task ClubAccept(IUser user)
|
||||
=> ClubAccept(user.ToString());
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task ClubAccept([Leftover] string userName)
|
||||
{
|
||||
if (_service.AcceptApplication(ctx.User.Id, userName, out var discordUser))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync("club_accepted", Format.Bold(discordUser.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ReplyErrorLocalizedAsync("club_accept_error").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Clubleave()
|
||||
{
|
||||
if (_service.LeaveClub(ctx.User))
|
||||
await ReplyConfirmLocalizedAsync("club_left").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalizedAsync("club_not_in_club").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public Task ClubKick([Leftover] IUser user)
|
||||
=> ClubKick(user.ToString());
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public Task ClubKick([Leftover] string userName)
|
||||
{
|
||||
if (_service.Kick(ctx.User.Id, userName, out var club))
|
||||
return ReplyConfirmLocalizedAsync("club_user_kick", Format.Bold(userName),
|
||||
Format.Bold(club.ToString()));
|
||||
else
|
||||
return ReplyErrorLocalizedAsync("club_user_kick_fail");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public Task ClubBan([Leftover] IUser user)
|
||||
=> ClubBan(user.ToString());
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public Task ClubBan([Leftover] string userName)
|
||||
{
|
||||
if (_service.Ban(ctx.User.Id, userName, out var club))
|
||||
return ReplyConfirmLocalizedAsync("club_user_banned", Format.Bold(userName),
|
||||
Format.Bold(club.ToString()));
|
||||
else
|
||||
return ReplyErrorLocalizedAsync("club_user_ban_fail");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public Task ClubUnBan([Leftover] IUser user)
|
||||
=> ClubUnBan(user.ToString());
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public Task ClubUnBan([Leftover] string userName)
|
||||
{
|
||||
if (_service.UnBan(ctx.User.Id, userName, out var club))
|
||||
return ReplyConfirmLocalizedAsync("club_user_unbanned", Format.Bold(userName),
|
||||
Format.Bold(club.ToString()));
|
||||
else
|
||||
return ReplyErrorLocalizedAsync("club_user_unban_fail");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubLevelReq(int level)
|
||||
{
|
||||
if (_service.ChangeClubLevelReq(ctx.User.Id, level))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync("club_level_req_changed", Format.Bold(level.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_level_req_change_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubDescription([Leftover] string desc = null)
|
||||
{
|
||||
if (_service.ChangeClubDescription(ctx.User.Id, desc))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync("club_desc_updated", Format.Bold(desc ?? "-"))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_desc_update_failed").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClubDisband()
|
||||
{
|
||||
if (_service.Disband(ctx.User.Id, out ClubInfo club))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync("club_disbanded", Format.Bold(club.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync("club_disband_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task ClubLeaderboard(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var clubs = _service.GetClubLeaderboardPage(page);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("club_leaderboard", page + 1))
|
||||
.WithOkColor();
|
||||
|
||||
var i = page * 9;
|
||||
foreach (var club in clubs)
|
||||
{
|
||||
embed.AddField($"#{++i} " + club.ToString(), club.Xp.ToString() + " xp", false);
|
||||
}
|
||||
|
||||
return ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
NadekoBot.Core/Modules/Xp/Common/FullUserStats.cs
Normal file
25
NadekoBot.Core/Modules/Xp/Common/FullUserStats.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.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;
|
||||
}
|
||||
}
|
||||
}
|
40
NadekoBot.Core/Modules/Xp/Common/LevelStats.cs
Normal file
40
NadekoBot.Core/Modules/Xp/Common/LevelStats.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Xp
|
||||
{
|
||||
public class LevelStats
|
||||
{
|
||||
public int Level { get; }
|
||||
public int LevelXp { get; }
|
||||
public int RequiredXp { get; }
|
||||
public int TotalXp { get; }
|
||||
|
||||
public LevelStats(int xp)
|
||||
{
|
||||
if (xp < 0)
|
||||
xp = 0;
|
||||
|
||||
TotalXp = xp;
|
||||
|
||||
const int baseXp = XpService.XP_REQUIRED_LVL_1;
|
||||
|
||||
var required = baseXp;
|
||||
var totalXp = 0;
|
||||
var lvl = 1;
|
||||
while (true)
|
||||
{
|
||||
required = (int)(baseXp + baseXp / 4.0 * (lvl - 1));
|
||||
|
||||
if (required + totalXp > xp)
|
||||
break;
|
||||
|
||||
totalXp += required;
|
||||
lvl++;
|
||||
}
|
||||
|
||||
Level = lvl - 1;
|
||||
LevelXp = xp - totalXp;
|
||||
RequiredXp = required;
|
||||
}
|
||||
}
|
||||
}
|
25
NadekoBot.Core/Modules/Xp/Common/XpConfig.cs
Normal file
25
NadekoBot.Core/Modules/Xp/Common/XpConfig.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using NadekoBot.Common.Yml;
|
||||
|
||||
namespace NadekoBot.Modules.Xp
|
||||
{
|
||||
public sealed class XpConfig
|
||||
{
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
297
NadekoBot.Core/Modules/Xp/Common/XpTemplate.cs
Normal file
297
NadekoBot.Core/Modules/Xp/Common/XpTemplate.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace NadekoBot.Core.Modules.Xp
|
||||
{
|
||||
public class XpTemplate
|
||||
{
|
||||
[JsonProperty("output_size")]
|
||||
public XpTemplatePos OutputSize { get; set; } = new XpTemplatePos
|
||||
{
|
||||
X = 450,
|
||||
Y = 220,
|
||||
};
|
||||
public XpTemplateUser User { get; set; } = new XpTemplateUser
|
||||
{
|
||||
Name = new XpTemplateText
|
||||
{
|
||||
FontSize = 50,
|
||||
Show = true,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 130,
|
||||
Y = 17,
|
||||
}
|
||||
},
|
||||
Icon = new XpTemplateIcon
|
||||
{
|
||||
Show = true,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 32,
|
||||
Y = 10,
|
||||
},
|
||||
Size = new XpTemplatePos
|
||||
{
|
||||
X = 69,
|
||||
Y = 70,
|
||||
}
|
||||
},
|
||||
GuildLevel = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 45,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 47,
|
||||
Y = 297,
|
||||
}
|
||||
},
|
||||
GlobalLevel = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 45,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 47,
|
||||
Y = 149,
|
||||
}
|
||||
},
|
||||
GuildRank = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 30,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 148,
|
||||
Y = 326,
|
||||
}
|
||||
},
|
||||
GlobalRank = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 30,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 148,
|
||||
Y = 179,
|
||||
}
|
||||
},
|
||||
TimeOnLevel = new XpTemplateTimeOnLevel
|
||||
{
|
||||
Format = "{0}d{1}h{2}m",
|
||||
Global = new XpTemplateText
|
||||
{
|
||||
FontSize = 20,
|
||||
Show = true,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 50,
|
||||
Y = 204
|
||||
}
|
||||
},
|
||||
Guild = new XpTemplateText
|
||||
{
|
||||
FontSize = 20,
|
||||
Show = true,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 50,
|
||||
Y = 351
|
||||
}
|
||||
}
|
||||
},
|
||||
Xp = new XpTemplateXP
|
||||
{
|
||||
Bar = new XpTemplateXpBar
|
||||
{
|
||||
Show = true,
|
||||
Global = new XpBar
|
||||
{
|
||||
Direction = XpTemplateDirection.Right,
|
||||
Length = 450,
|
||||
Color = new Rgba32(0, 0, 0, 0.4f),
|
||||
PointA = new XpTemplatePos
|
||||
{
|
||||
X = 321,
|
||||
Y = 104
|
||||
},
|
||||
PointB = new XpTemplatePos
|
||||
{
|
||||
X = 286,
|
||||
Y = 235
|
||||
}
|
||||
},
|
||||
Guild = new XpBar
|
||||
{
|
||||
Direction = XpTemplateDirection.Right,
|
||||
Length = 450,
|
||||
Color = new Rgba32(0, 0, 0, 0.4f),
|
||||
PointA = new XpTemplatePos
|
||||
{
|
||||
X = 282,
|
||||
Y = 248
|
||||
},
|
||||
PointB = new XpTemplatePos
|
||||
{
|
||||
X = 247,
|
||||
Y = 379
|
||||
}
|
||||
}
|
||||
},
|
||||
Global = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 50,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 430,
|
||||
Y = 142
|
||||
}
|
||||
},
|
||||
Guild = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 50,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 400,
|
||||
Y = 282
|
||||
}
|
||||
},
|
||||
Awarded = new XpTemplateText
|
||||
{
|
||||
Show = true,
|
||||
FontSize = 25,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 445,
|
||||
Y = 347
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
public XpTemplateClub Club { get; set; } = new XpTemplateClub
|
||||
{
|
||||
Icon = new XpTemplateIcon
|
||||
{
|
||||
Show = true,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
X = 722,
|
||||
Y = 25,
|
||||
},
|
||||
Size = new XpTemplatePos
|
||||
{
|
||||
X = 45,
|
||||
Y = 45,
|
||||
}
|
||||
},
|
||||
Name = new XpTemplateText
|
||||
{
|
||||
FontSize = 35,
|
||||
Pos = new XpTemplatePos
|
||||
{
|
||||
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 XpTemplateTimeOnLevel TimeOnLevel { get; set; }
|
||||
public XpTemplateXP Xp { get; set; }
|
||||
}
|
||||
|
||||
public class XpTemplateTimeOnLevel
|
||||
{
|
||||
public string Format { get; set; }
|
||||
public XpTemplateText Global { get; set; }
|
||||
public XpTemplateText Guild { 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)
|
||||
{
|
||||
return Color.ParseHex(reader.Value.ToString());
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Rgba32 value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.ToHex().ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
}
|
29
NadekoBot.Core/Modules/Xp/Extensions/Extensions.cs
Normal file
29
NadekoBot.Core/Modules/Xp/Extensions/Extensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Extensions
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this UserXpStats stats)
|
||||
{
|
||||
var baseXp = XpService.XP_REQUIRED_LVL_1;
|
||||
|
||||
var required = baseXp;
|
||||
var totalXp = 0;
|
||||
var lvl = 1;
|
||||
while (true)
|
||||
{
|
||||
required = (int)(baseXp + baseXp / 4.0 * (lvl - 1));
|
||||
|
||||
if (required + totalXp > stats.Xp)
|
||||
break;
|
||||
|
||||
totalXp += required;
|
||||
lvl++;
|
||||
}
|
||||
|
||||
return (lvl - 1, stats.Xp - totalXp, required);
|
||||
}
|
||||
}
|
||||
}
|
55
NadekoBot.Core/Modules/Xp/ResetCommands.cs
Normal file
55
NadekoBot.Core/Modules/Xp/ResetCommands.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Xp
|
||||
{
|
||||
public partial class Xp
|
||||
{
|
||||
public class ResetCommands : NadekoSubmodule<XpService>
|
||||
{
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task XpReset(IGuildUser user)
|
||||
=> XpReset(user.Id);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task XpReset(ulong userId)
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("reset"))
|
||||
.WithDescription(GetText("reset_user_confirm"));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
_service.XpReset(ctx.Guild.Id, userId);
|
||||
|
||||
await ReplyConfirmLocalizedAsync("reset_user", userId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task XpReset()
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("reset"))
|
||||
.WithDescription(GetText("reset_server_confirm"));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
_service.XpReset(ctx.Guild.Id);
|
||||
|
||||
await ReplyConfirmLocalizedAsync("reset_server").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
376
NadekoBot.Core/Modules/Xp/Services/ClubService.cs
Normal file
376
NadekoBot.Core/Modules/Xp/Services/ClubService.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Discord;
|
||||
using System.Linq;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services
|
||||
{
|
||||
public class ClubService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public ClubService(DbService db, IHttpClientFactory _httpFactory)
|
||||
{
|
||||
_db = db;
|
||||
this._httpFactory = _httpFactory;
|
||||
}
|
||||
|
||||
public bool CreateClub(IUser user, string clubName, out ClubInfo club)
|
||||
{
|
||||
//must be lvl 5 and must not be in a club already
|
||||
|
||||
club = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var du = uow.DiscordUsers.GetOrCreate(user);
|
||||
uow._context.SaveChanges();
|
||||
var xp = new LevelStats(du.TotalXp);
|
||||
|
||||
if (xp.Level >= 5 && du.Club == null)
|
||||
{
|
||||
du.IsClubAdmin = true;
|
||||
du.Club = new ClubInfo()
|
||||
{
|
||||
Name = clubName,
|
||||
Discrim = uow.Clubs.GetNextDiscrim(clubName),
|
||||
Owner = du,
|
||||
};
|
||||
uow.Clubs.Add(du.Club);
|
||||
uow._context.SaveChanges();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
uow._context.Set<ClubApplicants>()
|
||||
.RemoveRange(uow._context.Set<ClubApplicants>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.UserId == du.Id));
|
||||
club = du.Club;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClubInfo TransferClub(IUser from, IUser newOwner)
|
||||
{
|
||||
ClubInfo club;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
club = uow.Clubs.GetByOwner(from.Id);
|
||||
var newOwnerUser = uow.DiscordUsers.GetOrCreate(newOwner);
|
||||
|
||||
if (club == null ||
|
||||
club.Owner.UserId != from.Id ||
|
||||
!club.Users.Contains(newOwnerUser))
|
||||
return null;
|
||||
|
||||
club.Owner.IsClubAdmin = true; // old owner will stay as admin
|
||||
newOwnerUser.IsClubAdmin = true;
|
||||
club.Owner = newOwnerUser;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return club;
|
||||
}
|
||||
|
||||
public bool ToggleAdmin(IUser owner, IUser toAdmin)
|
||||
{
|
||||
bool newState;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var club = uow.Clubs.GetByOwner(owner.Id);
|
||||
var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin);
|
||||
|
||||
if (club == null || club.Owner.UserId != owner.Id ||
|
||||
!club.Users.Contains(adminUser))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (club.OwnerId == adminUser.Id)
|
||||
return true;
|
||||
|
||||
newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubByMember(IUser user)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.Clubs.GetByMember(user.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetClubIcon(ulong ownerUserId, Uri url)
|
||||
{
|
||||
if (url != null)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
using (var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (!temp.IsImage() || temp.GetImageSize() > 11)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var club = uow.Clubs.GetByOwner(ownerUserId, set => set);
|
||||
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
club.ImageUrl = url.ToString();
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GetClubByName(string clubName, out ClubInfo club)
|
||||
{
|
||||
club = null;
|
||||
var arr = clubName.Split('#');
|
||||
if (arr.Length < 2 || !int.TryParse(arr[arr.Length - 1], out var discrim))
|
||||
return false;
|
||||
|
||||
//incase club has # in it
|
||||
var name = string.Concat(arr.Except(new[] { arr[arr.Length - 1] }));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
club = uow.Clubs.GetByName(name, discrim);
|
||||
if (club == null)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ApplyToClub(IUser user, ClubInfo club)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var du = uow.DiscordUsers.GetOrCreate(user);
|
||||
uow._context.SaveChanges();
|
||||
|
||||
if (du.Club != null
|
||||
|| new LevelStats(du.TotalXp).Level < club.MinimumLevelReq
|
||||
|| club.Bans.Any(x => x.UserId == du.Id)
|
||||
|| club.Applicants.Any(x => x.UserId == du.Id))
|
||||
{
|
||||
//user banned or a member of a club, or already applied,
|
||||
// or doesn't min minumum level requirement, can't apply
|
||||
return false;
|
||||
}
|
||||
|
||||
var app = new ClubApplicants
|
||||
{
|
||||
ClubId = club.Id,
|
||||
UserId = du.Id,
|
||||
};
|
||||
|
||||
uow._context.Set<ClubApplicants>().Add(app);
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
|
||||
{
|
||||
discordUser = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (applicant == null)
|
||||
return false;
|
||||
|
||||
applicant.User.Club = club;
|
||||
applicant.User.IsClubAdmin = false;
|
||||
club.Applicants.Remove(applicant);
|
||||
|
||||
//remove that user's all other applications
|
||||
uow._context.Set<ClubApplicants>()
|
||||
.RemoveRange(uow._context.Set<ClubApplicants>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.UserId == applicant.User.Id));
|
||||
|
||||
discordUser = applicant.User;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool LeaveClub(IUser user)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var du = uow.DiscordUsers.GetOrCreate(user);
|
||||
if (du.Club == null || du.Club.OwnerId == du.Id)
|
||||
return false;
|
||||
|
||||
du.Club = null;
|
||||
du.IsClubAdmin = false;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeClubLevelReq(ulong userId, int level)
|
||||
{
|
||||
if (level < 5)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var club = uow.Clubs.GetByOwner(userId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
club.MinimumLevelReq = level;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeClubDescription(ulong userId, string desc)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var club = uow.Clubs.GetByOwner(userId);
|
||||
if (club == 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.Clubs.GetByOwner(userId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
uow.Clubs.Remove(club);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Ban(ulong bannerId, string userName, out ClubInfo club)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(bannerId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
|
||||
?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())?.User;
|
||||
if (usr == null)
|
||||
return false;
|
||||
|
||||
if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
|
||||
return false;
|
||||
|
||||
club.Bans.Add(new ClubBans
|
||||
{
|
||||
Club = club,
|
||||
User = usr,
|
||||
});
|
||||
club.Users.Remove(usr);
|
||||
|
||||
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
|
||||
if (app != null)
|
||||
club.Applicants.Remove(app);
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (ban == null)
|
||||
return false;
|
||||
|
||||
club.Bans.Remove(ban);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Kick(ulong kickerId, string userName, out ClubInfo club)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(kickerId);
|
||||
if (club == null)
|
||||
return false;
|
||||
|
||||
var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (usr == null)
|
||||
return false;
|
||||
|
||||
if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
|
||||
return false;
|
||||
|
||||
club.Users.Remove(usr);
|
||||
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
|
||||
if (app != null)
|
||||
club.Applicants.Remove(app);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClubInfo[] GetClubLeaderboardPage(int page)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.Clubs.GetClubLeaderboardPage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
NadekoBot.Core/Modules/Xp/Services/UserCacheItem.cs
Normal file
22
NadekoBot.Core/Modules/Xp/Services/UserCacheItem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Discord;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services
|
||||
{
|
||||
public class UserCacheItem
|
||||
{
|
||||
public IGuildUser User { get; set; }
|
||||
public IGuild Guild { get; set; }
|
||||
public IMessageChannel Channel { get; set; }
|
||||
public int XpAmount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return User.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UserCacheItem uci && uci.User == User;
|
||||
}
|
||||
}
|
||||
}
|
70
NadekoBot.Core/Modules/Xp/Services/XpConfigMigrator.cs
Normal file
70
NadekoBot.Core/Modules/Xp/Services/XpConfigMigrator.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services
|
||||
{
|
||||
public sealed class XpConfigMigrator : IConfigMigrator
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly XpConfigService _gss;
|
||||
|
||||
public XpConfigMigrator(DbService dbService, XpConfigService gss)
|
||||
{
|
||||
_db = dbService;
|
||||
_gss = gss;
|
||||
}
|
||||
|
||||
public void EnsureMigrated()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
using var conn = uow._context.Database.GetDbConnection();
|
||||
Migrate(conn);
|
||||
}
|
||||
|
||||
private void Migrate(DbConnection conn)
|
||||
{
|
||||
using (var checkTableCommand = conn.CreateCommand())
|
||||
{
|
||||
// make sure table still exists
|
||||
checkTableCommand.CommandText =
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='BotConfig';";
|
||||
var checkReader = checkTableCommand.ExecuteReader();
|
||||
if (!checkReader.HasRows)
|
||||
return;
|
||||
}
|
||||
|
||||
using (var checkMigratedCommand = conn.CreateCommand())
|
||||
{
|
||||
checkMigratedCommand.CommandText =
|
||||
"UPDATE BotConfig SET HasMigratedXpSettings = 1 WHERE HasMigratedXpSettings = 0;";
|
||||
var changedRows = checkMigratedCommand.ExecuteNonQuery();
|
||||
if (changedRows == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("Migrating Xp settings...");
|
||||
|
||||
using var com = conn.CreateCommand();
|
||||
com.CommandText = $@"SELECT XpPerMessage, XpMinutesTimeout, VoiceXpPerMinute, MaxXpMinutes
|
||||
FROM BotConfig";
|
||||
|
||||
using var reader = com.ExecuteReader();
|
||||
if (!reader.Read())
|
||||
return;
|
||||
|
||||
_gss.ModifyConfig(ModifyAction(reader));
|
||||
}
|
||||
|
||||
private static Action<XpConfig> ModifyAction(DbDataReader reader)
|
||||
=> config =>
|
||||
{
|
||||
config.XpPerMessage = (int) (long) reader["XpPerMessage"];
|
||||
config.MessageXpCooldown = (int) (long) reader["XpMinutesTimeout"];
|
||||
config.VoiceMaxMinutes = (int) (long) reader["MaxXpMinutes"];
|
||||
config.VoiceXpPerMinute = (double) reader["VoiceXpPerMinute"];
|
||||
};
|
||||
}
|
||||
}
|
44
NadekoBot.Core/Modules/Xp/Services/XpConfigService.cs
Normal file
44
NadekoBot.Core/Modules/Xp/Services/XpConfigService.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using NadekoBot.Core.Common;
|
||||
using NadekoBot.Core.Common.Configs;
|
||||
using NadekoBot.Core.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services
|
||||
{
|
||||
public sealed class XpConfigService : ConfigServiceBase<XpConfig>
|
||||
{
|
||||
public override string Name { get; } = "xp";
|
||||
private const string FilePath = "data/xp.yml";
|
||||
private static TypedKey<XpConfig> changeKey = new TypedKey<XpConfig>("config.xp.updated");
|
||||
|
||||
public XpConfigService(IConfigSeria serializer, IPubSub pubSub)
|
||||
: base(FilePath, 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);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (_data.Version <= 1)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 2;
|
||||
c.XpFromImage = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1228
NadekoBot.Core/Modules/Xp/Services/XpService.cs
Normal file
1228
NadekoBot.Core/Modules/Xp/Services/XpService.cs
Normal file
File diff suppressed because it is too large
Load Diff
432
NadekoBot.Core/Modules/Xp/Xp.cs
Normal file
432
NadekoBot.Core/Modules/Xp/Xp.cs
Normal file
@@ -0,0 +1,432 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Common;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Core.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Xp
|
||||
{
|
||||
public partial class Xp : NadekoModule<XpService>
|
||||
{
|
||||
private readonly DownloadTracker _tracker;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public Xp(DownloadTracker tracker, GamblingConfigService gss)
|
||||
{
|
||||
_tracker = tracker;
|
||||
_gss = gss;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Experience([Leftover] IUser user = null)
|
||||
{
|
||||
user = user ?? ctx.User;
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var (img, fmt) = await _service.GenerateXpImageAsync((IGuildUser)user).ConfigureAwait(false);
|
||||
using (img)
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(img, $"{ctx.Guild.Id}_{user.Id}_xp.{fmt.FileExtensions.FirstOrDefault()}")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task XpRewsReset()
|
||||
{
|
||||
var reply = await PromptUserConfirmAsync(new EmbedBuilder()
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText("xprewsreset_confirm")));
|
||||
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
await _service.ResetXpRewards(ctx.Guild.Id);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task XpLevelUpRewards(int page = 1)
|
||||
{
|
||||
page--;
|
||||
|
||||
if (page < 0 || page > 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("role_not_found", Format.Code(x.RoleId.ToString()));
|
||||
else
|
||||
{
|
||||
if (!x.Remove)
|
||||
str = GetText("xp_receive_role", Format.Bold(str));
|
||||
else
|
||||
str = GetText("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 + _gss.Data.Currency.Sign))))
|
||||
.GroupBy(x => x.Level)
|
||||
.OrderBy(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
return Context.SendPaginatedConfirmAsync(page, cur =>
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("level_up_rewards"))
|
||||
.WithOkColor();
|
||||
|
||||
var localRewards = allRewards
|
||||
.Skip(cur * 9)
|
||||
.Take(9)
|
||||
.ToList();
|
||||
|
||||
if (!localRewards.Any())
|
||||
return embed.WithDescription(GetText("no_level_up_rewards"));
|
||||
|
||||
foreach (var reward in localRewards)
|
||||
{
|
||||
embed.AddField(GetText("level_x", reward.Key),
|
||||
string.Join("\n", reward.Select(y => y.Item2)));
|
||||
}
|
||||
|
||||
return embed;
|
||||
}, allRewards.Count, 9);
|
||||
}
|
||||
|
||||
public enum AddRemove
|
||||
{
|
||||
Add = 0,
|
||||
Remove = 1,
|
||||
Rm = 1,
|
||||
Rem = 1,
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(2)]
|
||||
public async Task XpRoleReward(int level)
|
||||
{
|
||||
_service.ResetRoleReward(ctx.Guild.Id, level);
|
||||
await ReplyConfirmLocalizedAsync("xp_role_reward_cleared", level).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[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 ReplyConfirmLocalizedAsync("xp_role_reward_add_role",
|
||||
level,
|
||||
Format.Bold(role.ToString()));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync("xp_role_reward_remove_role",
|
||||
Format.Bold(level.ToString()),
|
||||
Format.Bold(role.ToString()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[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);
|
||||
var config = _gss.Data;
|
||||
|
||||
if (amount == 0)
|
||||
await ReplyConfirmLocalizedAsync("cur_reward_cleared", level, config.Currency.Sign)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync("cur_reward_added",
|
||||
level, Format.Bold(amount + config.Currency.Sign))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum NotifyPlace
|
||||
{
|
||||
Server = 0,
|
||||
Guild = 0,
|
||||
Global = 1,
|
||||
}
|
||||
|
||||
private string GetNotifLocationString(XpNotificationLocation loc)
|
||||
{
|
||||
if (loc == XpNotificationLocation.Channel)
|
||||
{
|
||||
return GetText("xpn_notif_channel");
|
||||
}
|
||||
|
||||
if (loc == XpNotificationLocation.Dm)
|
||||
{
|
||||
return GetText("xpn_notif_dm");
|
||||
}
|
||||
|
||||
return GetText("xpn_notif_disabled");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[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 = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.AddField(GetText("xpn_setting_global"), GetNotifLocationString(globalSetting))
|
||||
.AddField(GetText("xpn_setting_server"), GetNotifLocationString(serverSetting));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[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).ConfigureAwait(false);
|
||||
else
|
||||
await _service.ChangeNotificationType(ctx.User, type).ConfigureAwait(false);
|
||||
|
||||
await ctx.OkAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum Server { Server };
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task XpExclude(Server _)
|
||||
{
|
||||
var ex = _service.ToggleExcludeServer(ctx.Guild.Id);
|
||||
|
||||
await ReplyConfirmLocalizedAsync((ex ? "excluded" : "not_excluded"), Format.Bold(ctx.Guild.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum Role { Role };
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpExclude(Role _, [Leftover] IRole role)
|
||||
{
|
||||
var ex = _service.ToggleExcludeRole(ctx.Guild.Id, role.Id);
|
||||
|
||||
await ReplyConfirmLocalizedAsync((ex ? "excluded" : "not_excluded"), Format.Bold(role.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum Channel { Channel };
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpExclude(Channel _, [Leftover] IChannel channel = null)
|
||||
{
|
||||
if (channel == null)
|
||||
channel = ctx.Channel;
|
||||
|
||||
var ex = _service.ToggleExcludeChannel(ctx.Guild.Id, channel.Id);
|
||||
|
||||
await ReplyConfirmLocalizedAsync((ex ? "excluded" : "not_excluded"), Format.Bold(channel.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[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 != null)
|
||||
.Select(x => $"`role` {x.Mention}")
|
||||
.ToList();
|
||||
|
||||
var chans = (await Task.WhenAll(_service.GetExcludedChannels(ctx.Guild.Id)
|
||||
.Select(x => ctx.Guild.GetChannelAsync(x)))
|
||||
.ConfigureAwait(false))
|
||||
.Where(x => x != 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("server_is_excluded")
|
||||
: GetText("server_is_not_excluded"));
|
||||
|
||||
desc += "\n\n" + rolesStr + chansStr;
|
||||
|
||||
var lines = desc.Split('\n');
|
||||
await ctx.SendPaginatedConfirmAsync(0, curpage =>
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("exclusion_list"))
|
||||
.WithDescription(string.Join('\n', lines.Skip(15 * curpage).Take(15)))
|
||||
.WithOkColor();
|
||||
|
||||
return embed;
|
||||
}, lines.Length, 15);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[NadekoOptions(typeof(LbOpts))]
|
||||
[Priority(0)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task XpLeaderboard(params string[] args)
|
||||
=> XpLeaderboard(1, args);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[NadekoOptions(typeof(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 Context.Channel.TriggerTypingAsync();
|
||||
|
||||
var socketGuild = ((SocketGuild)ctx.Guild);
|
||||
List<UserXpStats> allUsers = new List<UserXpStats>();
|
||||
if (opts.Clean)
|
||||
{
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild).ConfigureAwait(false);
|
||||
|
||||
allUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
|
||||
.Where(user => !(socketGuild.GetUser(user.UserId) is null))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, (curPage) =>
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("server_leaderboard"))
|
||||
.WithOkColor();
|
||||
|
||||
List<UserXpStats> users;
|
||||
if (opts.Clean)
|
||||
{
|
||||
users = allUsers.Skip(curPage * 9).Take(9).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
users = _service.GetUserXps(ctx.Guild.Id, curPage);
|
||||
}
|
||||
|
||||
if (!users.Any())
|
||||
return embed.WithDescription("-");
|
||||
else
|
||||
{
|
||||
for (int 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 = "";
|
||||
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("level_x", levelStats.Level)} - {levelStats.TotalXp}xp {awardStr}");
|
||||
}
|
||||
return embed;
|
||||
}
|
||||
}, 900, 9, addPaginatedFooter: false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpGlobalLeaderboard(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > 99)
|
||||
return;
|
||||
var users = _service.GetUserXps(page);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("global_leaderboard"))
|
||||
.WithOkColor();
|
||||
|
||||
if (!users.Any())
|
||||
embed.WithDescription("-");
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < users.Length; i++)
|
||||
{
|
||||
var user = users[i];
|
||||
embed.AddField(
|
||||
$"#{i + 1 + page * 9} {(user.ToString())}",
|
||||
$"{GetText("level_x", new LevelStats(users[i].TotalXp).Level)} - {users[i].TotalXp}xp");
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
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 ReplyConfirmLocalizedAsync("modified", Format.Bold(usr), Format.Bold(amount.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task XpAdd(int amount, [Leftover] IGuildUser user)
|
||||
=> XpAdd(amount, user.Id);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task XpTemplateReload()
|
||||
{
|
||||
_service.ReloadXpTemplate();
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync("template_reloaded").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user