Removed module projects because it can't work like that atm. Commented out package commands.

This commit is contained in:
Master Kwoth
2017-10-15 09:39:46 +02:00
parent 90e71a3a30
commit 696a0eb2a7
180 changed files with 21625 additions and 1058 deletions

View File

@ -0,0 +1,349 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Core.Services.Database.Models;
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;
private readonly DiscordSocketClient _client;
public Club(XpService xps, DiscordSocketClient client)
{
_xps = xps;
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubAdmin([Remainder]IUser toAdmin)
{
bool admin;
try
{
admin = _service.ToggleAdmin(Context.User, toAdmin);
}
catch (InvalidOperationException)
{
await ReplyErrorLocalized("club_admin_error").ConfigureAwait(false);
return;
}
if(admin)
await ReplyConfirmLocalized("club_admin_add", Format.Bold(toAdmin.ToString())).ConfigureAwait(false);
else
await ReplyConfirmLocalized("club_admin_remove", Format.Bold(toAdmin.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubCreate([Remainder]string clubName)
{
if (string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20)
return;
if (!_service.CreateClub(Context.User, clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_create_error").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalized("club_created", Format.Bold(club.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubIcon([Remainder]string url = null)
{
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url != null)
|| !_service.SetClubIcon(Context.User.Id, url))
{
return ReplyErrorLocalized("club_icon_error");
}
return ReplyConfirmLocalized("club_icon_set");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public async Task ClubInformation(IUser user = null)
{
user = user ?? Context.User;
var club = _service.GetClubByMember(user);
if (club == null)
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
await ClubInformation(club.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public async Task ClubInformation([Remainder]string clubName = null)
{
if (string.IsNullOrWhiteSpace(clubName))
{
await ClubInformation(Context.User).ConfigureAwait(false);
return;
}
if (!_service.GetClubByName(clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
var lvl = new LevelStats(club.Xp);
await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (page) =>
{
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle($"{club.ToString()}")
.WithDescription(GetText("level_x", lvl.Level) + $" ({club.Xp} xp)")
.AddField("Owner", club.Owner.ToString(), true)
.AddField("Level Req.", club.MinimumLevelReq.ToString(), true)
.AddField("Members", string.Join("\n", club.Users
.OrderByDescending(x => {
if (club.OwnerId == x.Id)
return 2;
else if (x.IsClubAdmin)
return 1;
else
return 0;
})
.Skip(page * 10)
.Take(10)
.Select(x =>
{
if (club.OwnerId == x.Id)
return x.ToString() + "🌟";
else if (x.IsClubAdmin)
return x.ToString() + "⭐";
return x.ToString();
})), false);
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
return embed.WithThumbnailUrl(club.ImageUrl);
return embed;
}, club.Users.Count / 10);
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubBans(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
var club = _service.GetClubWithBansAndApplications(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists_owner");
var bans = club
.Bans
.Select(x => x.User)
.ToArray();
return Context.Channel.SendPaginatedConfirmAsync(_client, 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(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists_owner");
var apps = club
.Applicants
.Select(x => x.User)
.ToArray();
return Context.Channel.SendPaginatedConfirmAsync(_client, 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([Remainder]string clubName)
{
if (string.IsNullOrWhiteSpace(clubName))
return;
if (!_service.GetClubByName(clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
if (_service.ApplyToClub(Context.User, club))
{
await ReplyConfirmLocalized("club_applied", Format.Bold(club.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("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([Remainder]string userName)
{
if (_service.AcceptApplication(Context.User.Id, userName, out var discordUser))
{
await ReplyConfirmLocalized("club_accepted", Format.Bold(discordUser.ToString())).ConfigureAwait(false);
}
else
await ReplyErrorLocalized("club_accept_error").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Clubleave()
{
if (_service.LeaveClub(Context.User))
await ReplyConfirmLocalized("club_left").ConfigureAwait(false);
else
await ReplyErrorLocalized("club_not_in_club").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubKick([Remainder]IUser user)
=> ClubKick(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubKick([Remainder]string userName)
{
if (_service.Kick(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_kick", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_kick_fail");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubBan([Remainder]IUser user)
=> ClubBan(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubBan([Remainder]string userName)
{
if (_service.Ban(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_banned", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_ban_fail");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubUnBan([Remainder]IUser user)
=> ClubUnBan(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubUnBan([Remainder]string userName)
{
if (_service.UnBan(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_unbanned", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_unban_fail");
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubLevelReq(int level)
{
if (_service.ChangeClubLevelReq(Context.User.Id, level))
{
await ReplyConfirmLocalized("club_level_req_changed", Format.Bold(level.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("club_level_req_change_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubDisband()
{
if (_service.Disband(Context.User.Id, out ClubInfo club))
{
await ReplyConfirmLocalized("club_disbanded", Format.Bold(club.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("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 Context.Channel.EmbedAsync(embed);
}
}
}
}

View File

@ -0,0 +1,26 @@
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Modules.Xp.Common
{
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)
{
this.User = usr;
this.Global = global;
this.Guild = guild;
this.GlobalRanking = globalRanking;
this.GuildRanking = guildRanking;
this.FullGuildStats = fullGuildStats;
}
}
}

View File

@ -0,0 +1,43 @@
using NadekoBot.Modules.Xp.Services;
using System;
namespace NadekoBot.Modules.Xp.Common
{
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;
}
public static LevelStats FromXp(int xp) => new LevelStats(xp);
}
}

View File

@ -0,0 +1,34 @@
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Core.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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);
}
}
}

View File

@ -0,0 +1,322 @@
using NadekoBot.Core.Services;
using System;
using NadekoBot.Core.Services.Database.Models;
using Discord;
using NadekoBot.Modules.Xp.Common;
using System.Linq;
namespace NadekoBot.Modules.Xp.Services
{
public class ClubService : INService
{
private readonly DbService _db;
public ClubService(DbService db)
{
_db = db;
}
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.UnitOfWork)
{
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>().Where(x => x.UserId == du.Id));
club = du.Club;
uow.Complete();
}
return true;
}
public bool ToggleAdmin(IUser owner, IUser toAdmin)
{
bool newState;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(owner.Id);
var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin);
if (club.OwnerId == adminUser.Id)
return true;
if (club == null || club.Owner.UserId != owner.Id ||
!club.Users.Contains(adminUser))
throw new InvalidOperationException();
newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
uow.Complete();
}
return newState;
}
public ClubInfo GetClubByMember(IUser user)
{
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetByMember(user.Id);
}
}
public bool SetClubIcon(ulong ownerUserId, string url)
{
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(ownerUserId, set => set);
if (club == null)
return false;
club.ImageUrl = url;
uow.Complete();
}
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.UnitOfWork)
{
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.UnitOfWork)
{
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.Complete();
}
return true;
}
public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
{
discordUser = null;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
if (club == null)
return false;
var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
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>().Where(x => x.UserId == applicant.User.Id));
discordUser = applicant.User;
uow.Complete();
}
return true;
}
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
{
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
}
}
public bool LeaveClub(IUser user)
{
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
if (du.Club == null || du.Club.OwnerId == du.Id)
return false;
du.Club = null;
du.IsClubAdmin = false;
uow.Complete();
}
return true;
}
public bool ChangeClubLevelReq(ulong userId, int level)
{
if (level < 5)
return false;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(userId);
if (club == null)
return false;
club.MinimumLevelReq = level;
uow.Complete();
}
return true;
}
public bool Disband(ulong userId, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(userId);
if (club == null)
return false;
uow.Clubs.Remove(club);
uow.Complete();
}
return true;
}
public bool Ban(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;
var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant())
?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant())?.User;
if (usr == null)
return false;
if (club.OwnerId == usr.Id) // 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.Complete();
}
return true;
}
public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
if (ban == null)
return false;
club.Bans.Remove(ban);
uow.Complete();
}
return true;
}
public bool Kick(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;
var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
if (usr == null)
return false;
if (club.OwnerId == usr.Id)
return false;
club.Users.Remove(usr);
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
if (app != null)
club.Applicants.Remove(app);
uow.Complete();
}
return true;
}
public ClubInfo[] GetClubLeaderboardPage(int page)
{
if (page < 0)
throw new ArgumentOutOfRangeException(nameof(page));
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetClubLeaderboardPage(page);
}
}
}
}

View File

@ -0,0 +1,21 @@
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 override int GetHashCode()
{
return User.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is UserCacheItem uci && uci.User == User;
}
}
}

View File

@ -0,0 +1,780 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Core.Services.Impl;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ImageSharp;
using Image = ImageSharp.Image;
using SixLabors.Fonts;
using System.IO;
using SixLabors.Primitives;
using System.Net.Http;
using SixLabors.Shapes;
using System.Numerics;
using ImageSharp.Drawing.Pens;
using ImageSharp.Drawing.Brushes;
namespace NadekoBot.Modules.Xp.Services
{
public class XpService : INService, IUnloadableService
{
private enum NotifOf { Server, Global } // is it a server level-up or global level-up notification
private readonly DbService _db;
private readonly CommandHandler _cmd;
private readonly IBotConfigProvider _bc;
private readonly IImagesService _images;
private readonly Logger _log;
private readonly NadekoStrings _strings;
private readonly IDataCache _cache;
private readonly FontCollection _fonts = new FontCollection();
public const int XP_REQUIRED_LVL_1 = 36;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentHashSet<ulong> _excludedServers
= new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _rewardedUsers
= new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp
= new ConcurrentQueue<UserCacheItem>();
private readonly Timer _updateXpTimer;
private readonly CancellationTokenSource _clearRewardTimerTokenSource;
private readonly Task _clearRewardTimer;
private readonly HttpClient http = new HttpClient();
private FontFamily _usernameFontFamily;
private FontFamily _clubFontFamily;
private Font _levelFont;
private Font _xpFont;
private Font _awardedFont;
private Font _rankFont;
private Font _timeFont;
public XpService(CommandHandler cmd, IBotConfigProvider bc,
NadekoBot bot, IImagesService images,
DbService db, NadekoStrings strings, IDataCache cache)
{
_db = db;
_cmd = cmd;
_bc = bc;
_images = images;
_log = LogManager.GetCurrentClassLogger();
_strings = strings;
_cache = cache;
//load settings
var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null);
_excludedChannels = allGuildConfigs
.ToDictionary(
x => x.GuildId,
x => new ConcurrentHashSet<ulong>(x.XpSettings
.ExclusionList
.Where(ex => ex.ItemType == ExcludedItemType.Channel)
.Select(ex => ex.ItemId)
.Distinct()))
.ToConcurrent();
_excludedRoles = allGuildConfigs
.ToDictionary(
x => x.GuildId,
x => new ConcurrentHashSet<ulong>(x.XpSettings
.ExclusionList
.Where(ex => ex.ItemType == ExcludedItemType.Role)
.Select(ex => ex.ItemId)
.Distinct()))
.ToConcurrent();
_excludedServers = new ConcurrentHashSet<ulong>(
allGuildConfigs.Where(x => x.XpSettings.ServerExcluded)
.Select(x => x.GuildId));
//todo 60 move to font provider or somethign
_fonts = new FontCollection();
if (Directory.Exists("data/fonts"))
foreach (var file in Directory.GetFiles("data/fonts"))
{
_fonts.Install(file);
}
InitializeFonts();
_cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
_updateXpTimer = new Timer(async _ =>
{
try
{
var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr))
toAddTo.Add(usr);
var group = toAddTo.GroupBy(x => (GuildId: x.Guild.Id, User: x.User));
if (toAddTo.Count == 0)
return;
using (var uow = _db.UnitOfWork)
{
foreach (var item in group)
{
var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum();
var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id);
var du = uow.DiscordUsers.GetOrCreate(item.Key.User);
if (du.LastXpGain + TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout) > DateTime.UtcNow)
continue;
du.LastXpGain = DateTime.UtcNow;
var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp);
var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
usr.Xp += xp;
du.TotalXp += xp;
if (du.Club != null)
du.Club.Xp += xp;
var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
if (oldGlobalLevelData.Level < newGlobalLevelData.Level)
{
du.LastLevelUp = DateTime.UtcNow;
var first = item.First();
if (du.NotifyOnLevelUp != XpNotificationType.None)
toNotify.Add((first.Channel, first.User, newGlobalLevelData.Level, du.NotifyOnLevelUp, NotifOf.Global));
}
if (oldGuildLevelData.Level < newGuildLevelData.Level)
{
usr.LastLevelUp = DateTime.UtcNow;
//send level up notification
var first = item.First();
if (usr.NotifyOnLevelUp != XpNotificationType.None)
toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server));
//give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rewards))
{
rewards = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rewards);
}
var rew = rewards.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (rew != null)
{
var role = first.User.Guild.GetRole(rew.RoleId);
if (role != null)
{
var __ = first.User.AddRoleAsync(role);
}
}
}
}
uow.Complete();
}
await Task.WhenAll(toNotify.Select(async x =>
{
if (x.NotifOf == NotifOf.Server)
{
if (x.NotifyType == XpNotificationType.Dm)
{
var chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false);
if (chan != null)
await chan.SendConfirmAsync(_strings.GetText("level_up_dm",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString()),
Format.Bold((x.MessageChannel as ITextChannel)?.Guild.ToString() ?? "-")))
.ConfigureAwait(false);
}
else // channel
{
await x.MessageChannel.SendConfirmAsync(_strings.GetText("level_up_channel",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString())))
.ConfigureAwait(false);
}
}
else
{
IMessageChannel chan;
if (x.NotifyType == XpNotificationType.Dm)
{
chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false);
}
else // channel
{
chan = x.MessageChannel;
}
await chan.SendConfirmAsync(_strings.GetText("level_up_global",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString())))
.ConfigureAwait(false);
}
}));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
_clearRewardTimerTokenSource = new CancellationTokenSource();
var token = _clearRewardTimerTokenSource.Token;
//just a first line, in order to prevent queries. But since other shards can try to do this too,
//i'll check in the db too.
_clearRewardTimer = Task.Run(async () =>
{
while (!token.IsCancellationRequested)
{
_rewardedUsers.Clear();
await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout));
}
}, token);
}
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.XpSettingsFor(id)
.RoleRewards
.ToArray();
}
}
public void SetRoleReward(ulong guildId, int level, ulong? roleId)
{
using (var uow = _db.UnitOfWork)
{
var settings = uow.GuildConfigs.XpSettingsFor(guildId);
if (roleId == null)
{
var toRemove = settings.RoleRewards.FirstOrDefault(x => x.Level == level);
if (toRemove != null)
{
uow._context.Remove(toRemove);
settings.RoleRewards.Remove(toRemove);
}
}
else
{
var rew = settings.RoleRewards.FirstOrDefault(x => x.Level == level);
if (rew != null)
rew.RoleId = roleId.Value;
else
settings.RoleRewards.Add(new XpRoleReward()
{
Level = level,
RoleId = roleId.Value,
});
}
uow.Complete();
}
}
public UserXpStats[] GetUserXps(ulong guildId, int page)
{
using (var uow = _db.UnitOfWork)
{
return uow.Xp.GetUsersFor(guildId, page);
}
}
public DiscordUser[] GetUserXps(int page)
{
using (var uow = _db.UnitOfWork)
{
return uow.DiscordUsers.GetUsersXpLeaderboardFor(page);
}
}
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationType type)
{
using (var uow = _db.UnitOfWork)
{
var user = uow.Xp.GetOrCreateUser(guildId, userId);
user.NotifyOnLevelUp = type;
await uow.CompleteAsync().ConfigureAwait(false);
}
}
public async Task ChangeNotificationType(IUser user, XpNotificationType type)
{
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
du.NotifyOnLevelUp = type;
await uow.CompleteAsync().ConfigureAwait(false);
}
}
private Task _cmd_OnMessageNoTrigger(IUserMessage arg)
{
if (!(arg.Author is SocketGuildUser user) || user.IsBot)
return Task.CompletedTask;
var _ = Task.Run(() =>
{
if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) &&
chans.Contains(arg.Channel.Id))
return;
if (_excludedServers.Contains(user.Guild.Id))
return;
if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) &&
user.Roles.Any(x => roles.Contains(x.Id)))
return;
if (!arg.Content.Contains(' ') && arg.Content.Length < 5)
return;
if (!SetUserRewarded(user.Id))
return;
_addMessageXp.Enqueue(new UserCacheItem { Guild = user.Guild, Channel = arg.Channel, User = user });
});
return Task.CompletedTask;
}
public void AddXp(ulong userId, ulong guildId, int amount)
{
using (var uow = _db.UnitOfWork)
{
var usr = uow.Xp.GetOrCreateUser(guildId, userId);
usr.AwardedXp += amount;
uow.Complete();
}
}
public bool IsServerExcluded(ulong id)
{
return _excludedServers.Contains(id);
}
public IEnumerable<ulong> GetExcludedRoles(ulong id)
{
if (_excludedRoles.TryGetValue(id, out var val))
return val.ToArray();
return Enumerable.Empty<ulong>();
}
public IEnumerable<ulong> GetExcludedChannels(ulong id)
{
if (_excludedChannels.TryGetValue(id, out var val))
return val.ToArray();
return Enumerable.Empty<ulong>();
}
private bool SetUserRewarded(ulong userId)
{
return _rewardedUsers.Add(userId);
}
public FullUserStats GetUserStats(IGuildUser user)
{
DiscordUser du;
UserXpStats stats;
int totalXp;
int globalRank;
int guildRank;
using (var uow = _db.UnitOfWork)
{
du = uow.DiscordUsers.GetOrCreate(user);
stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id);
totalXp = du.TotalXp;
globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id);
guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId);
}
return new FullUserStats(du,
stats,
new LevelStats(totalXp),
new LevelStats(stats.Xp + stats.AwardedXp),
globalRank,
guildRank);
}
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(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);
}
public bool ToggleExcludeServer(ulong id)
{
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(id);
if (_excludedServers.Add(id))
{
xpSetting.ServerExcluded = true;
uow.Complete();
return true;
}
_excludedServers.TryRemove(id);
xpSetting.ServerExcluded = false;
uow.Complete();
return false;
}
}
public bool ToggleExcludeRole(ulong guildId, ulong rId)
{
var roles = _excludedRoles.GetOrAdd(guildId, _ => new ConcurrentHashSet<ulong>());
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId);
var excludeObj = new ExcludedItem
{
ItemId = rId,
ItemType = ExcludedItemType.Role,
};
if (roles.Add(rId))
{
if (xpSetting.ExclusionList.Add(excludeObj))
{
uow.Complete();
}
return true;
}
else
{
roles.TryRemove(rId);
if (xpSetting.ExclusionList.Remove(excludeObj))
{
uow.Complete();
}
return false;
}
}
}
public bool ToggleExcludeChannel(ulong guildId, ulong chId)
{
var channels = _excludedChannels.GetOrAdd(guildId, _ => new ConcurrentHashSet<ulong>());
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId);
var excludeObj = new ExcludedItem
{
ItemId = chId,
ItemType = ExcludedItemType.Channel,
};
if (channels.Add(chId))
{
if (xpSetting.ExclusionList.Add(excludeObj))
{
uow.Complete();
}
return true;
}
else
{
channels.TryRemove(chId);
if (xpSetting.ExclusionList.Remove(excludeObj))
{
uow.Complete();
}
return false;
}
}
}
public Task<MemoryStream> GenerateImageAsync(IGuildUser user)
{
return GenerateImageAsync(GetUserStats(user));
}
private void InitializeFonts()
{
_usernameFontFamily = _fonts.Find("Whitney-Bold");
_clubFontFamily = _fonts.Find("Whitney-Bold");
_levelFont = _fonts.Find("Whitney-Bold").CreateFont(45);
_xpFont = _fonts.Find("Whitney-Bold").CreateFont(50);
_awardedFont = _fonts.Find("Whitney-Bold").CreateFont(25);
_rankFont = _fonts.Find("Uni Sans Thin CAPS").CreateFont(30);
_timeFont = _fonts.Find("Whitney-Bold").CreateFont(20);
}
public Task<MemoryStream> GenerateImageAsync(FullUserStats stats) => Task.Run(async () =>
{
using (var img = Image.Load(_images.XpCard.ToArray()))
{
var username = stats.User.ToString();
var usernameFont = _usernameFontFamily
.CreateFont(username.Length <= 6
? 50
: 50 - username.Length);
img.DrawText("@" + username, usernameFont, Rgba32.White,
new PointF(130, 5));
// level
img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 137));
img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 285));
//club name
var clubName = stats.User.Club?.ToString() ?? "-";
var clubFont = _clubFontFamily
.CreateFont(clubName.Length <= 8
? 35
: 35 - (clubName.Length / 2));
img.DrawText(clubName, clubFont, Rgba32.White,
new PointF(650 - clubName.Length * 10, 40));
var pen = new Pen<Rgba32>(Rgba32.Black, 1);
var brush = Brushes.Solid<Rgba32>(Rgba32.White);
var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f));
var global = stats.Global;
var guild = stats.Guild;
//xp bar
img.FillPolygon(xpBgBrush, new[] {
new PointF(321, 104),
new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104),
new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235),
new PointF(286, 235),
});
img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen,
new PointF(430, 130));
img.FillPolygon(xpBgBrush, new[] {
new PointF(282, 248),
new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248),
new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379),
new PointF(247, 379),
});
img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen,
new PointF(400, 270));
if (stats.FullGuildStats.AwardedXp != 0)
{
var sign = stats.FullGuildStats.AwardedXp > 0
? "+ "
: "";
img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen,
new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335));
}
//ranking
img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 170));
img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 317));
//time on this level
string GetTimeSpent(DateTime time)
{
var offset = DateTime.UtcNow - time;
return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m";
}
img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 197));
img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 344));
//avatar
if (stats.User.AvatarId != null)
{
try
{
var avatarUrl = stats.User.RealAvatarUrl();
var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl);
if (!succ)
{
using (var temp = await http.GetStreamAsync(avatarUrl))
using (var tempDraw = Image.Load(temp).Resize(69, 70))
{
ApplyRoundedCorners(tempDraw, 35);
data = tempDraw.ToStream().ToArray();
}
await _cache.SetImageDataAsync(avatarUrl, data);
}
var toDraw = Image.Load(data);
img.DrawImage(toDraw,
1,
new Size(69, 70),
new Point(32, 10));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
//club image
await DrawClubImage(img, stats).ConfigureAwait(false);
return img.Resize(432, 211).ToStream();
}
});
private async Task DrawClubImage(Image<Rgba32> img, FullUserStats stats)
{
if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl))
{
var imgUrl = stats.User.Club.ImageUrl;
try
{
var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl);
if (!succ)
{
using (var temp = await http.GetAsync(imgUrl, HttpCompletionOption.ResponseHeadersRead))
{
if (temp.Content.Headers.ContentType.MediaType != "image/png"
&& temp.Content.Headers.ContentType.MediaType != "image/jpeg"
&& temp.Content.Headers.ContentType.MediaType != "image/gif")
return;
using (var tempDraw = Image.Load(await temp.Content.ReadAsStreamAsync()).Resize(45, 45))
{
ApplyRoundedCorners(tempDraw, 22.5f);
data = tempDraw.ToStream().ToArray();
}
}
await _cache.SetImageDataAsync(imgUrl, data);
}
var toDraw = Image.Load(data);
img.DrawImage(toDraw,
1,
new Size(45, 45),
new Point(722, 25));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
}
// https://github.com/SixLabors/ImageSharp/tree/master/samples/AvatarWithRoundedCorner
public static void ApplyRoundedCorners(Image<Rgba32> img, float cornerRadius)
{
var corners = BuildCorners(img.Width, img.Height, cornerRadius);
// now we have our corners time to draw them
img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true)
{
BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background
});
}
public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
{
// first create a square
var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
// then cut out of the square a circle so we are left with a corner
var cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
// corner is now a corner shape positions top left
//lets make 3 more positioned correctly, we can do that by translating the orgional around the center of the image
var center = new Vector2(imageWidth / 2, imageHeight / 2);
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
// move it across the width of the image - the width of the shape
var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
}
public Task Unload()
{
_cmd.OnMessageNoTrigger -= _cmd_OnMessageNoTrigger;
if (!_clearRewardTimerTokenSource.IsCancellationRequested)
_clearRewardTimerTokenSource.Cancel();
_updateXpTimer.Change(Timeout.Infinite, Timeout.Infinite);
_clearRewardTimerTokenSource.Dispose();
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,283 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Xp
{
public partial class Xp : NadekoTopLevelModule<XpService>
{
private readonly DiscordSocketClient _client;
private readonly DbService _db;
public Xp(DiscordSocketClient client,DbService db)
{
_client = client;
_db = db;
}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//public async Task Populate()
//{
// var rng = new NadekoRandom();
// using (var uow = _db.UnitOfWork)
// {
// for (var i = 0ul; i < 1000000; i++)
// {
// uow.DiscordUsers.Add(new DiscordUser()
// {
// AvatarId = i.ToString(),
// Discriminator = "1234",
// UserId = i,
// Username = i.ToString(),
// Club = null,
// });
// var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i);
// xp.Xp = rng.Next(100, 100000);
// }
// uow.Complete();
// }
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
//[Ratelimit(30)]
public async Task Experience([Remainder]IUser user = null)
{
user = user ?? Context.User;
var sw = Stopwatch.StartNew();
await Context.Channel.TriggerTypingAsync();
var img = await _service.GenerateImageAsync((IGuildUser)user);
sw.Stop();
_log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds);
sw.Restart();
await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png")
.ConfigureAwait(false);
sw.Stop();
_log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task XpRoleRewards(int page = 1)
{
page--;
if (page < 0 || page > 100)
return Task.CompletedTask;
var roles = _service.GetRoleRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Skip(page * 9)
.Take(9);
var embed = new EmbedBuilder()
.WithTitle(GetText("role_rewards"))
.WithOkColor();
if (!roles.Any())
return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_role_rewards")));
foreach (var rolerew in roles)
{
var role = Context.Guild.GetRole(rolerew.RoleId);
if (role == null)
continue;
embed.AddField(GetText("level_x", Format.Bold(rolerew.Level.ToString())), role.ToString());
}
return Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task XpRoleReward(int level, [Remainder] IRole role = null)
{
if (level < 1)
return;
_service.SetRoleReward(Context.Guild.Id, level, role?.Id);
if(role == null)
await ReplyConfirmLocalized("role_reward_cleared", level).ConfigureAwait(false);
else
await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false);
}
public enum NotifyPlace
{
Server = 0,
Guild = 0,
Global = 1,
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task XpNotify(NotifyPlace place = NotifyPlace.Guild, XpNotificationType type = XpNotificationType.Channel)
{
if (place == NotifyPlace.Guild)
await _service.ChangeNotificationType(Context.User.Id, Context.Guild.Id, type);
else
await _service.ChangeNotificationType(Context.User, type);
await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false);
}
public enum Server { Server };
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task XpExclude(Server _)
{
var ex = _service.ToggleExcludeServer(Context.Guild.Id);
await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(Context.Guild.ToString())).ConfigureAwait(false);
}
public enum Role { Role };
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Role _, [Remainder] IRole role)
{
var ex = _service.ToggleExcludeRole(Context.Guild.Id, role.Id);
await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(role.ToString())).ConfigureAwait(false);
}
public enum Channel { Channel };
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Channel _, [Remainder] ITextChannel channel = null)
{
if (channel == null)
channel = (ITextChannel)Context.Channel;
var ex = _service.ToggleExcludeChannel(Context.Guild.Id, channel.Id);
await ReplyConfirmLocalized((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(Context.Guild.Id);
var roles = _service.GetExcludedRoles(Context.Guild.Id)
.Select(x => Context.Guild.GetRole(x)?.Name)
.Where(x => x != null);
var chans = (await Task.WhenAll(_service.GetExcludedChannels(Context.Guild.Id)
.Select(x => Context.Guild.GetChannelAsync(x)))
.ConfigureAwait(false))
.Where(x => x != null)
.Select(x => x.Name);
var embed = new EmbedBuilder()
.WithTitle(GetText("exclusion_list"))
.WithDescription((serverExcluded ? GetText("server_is_excluded") : GetText("server_is_not_excluded")))
.AddField(GetText("excluded_roles"), roles.Any() ? string.Join("\n", roles) : "-", false)
.AddField(GetText("excluded_channels"), chans.Any() ? string.Join("\n", chans) : "-", false)
.WithOkColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task XpLeaderboard(int page = 1)
{
if (--page < 0 || page > 100)
return Task.CompletedTask;
return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) =>
{
var users = _service.GetUserXps(Context.Guild.Id, curPage);
var embed = new EmbedBuilder()
.WithTitle(GetText("server_leaderboard"))
.WithOkColor();
if (!users.Any())
return embed.WithDescription("-");
else
{
for (int i = 0; i < users.Length; i++)
{
var levelStats = LevelStats.FromXp(users[i].Xp + users[i].AwardedXp);
var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false);
var userXpData = users[i];
var awardStr = "";
if (userXpData.AwardedXp > 0)
awardStr = $"(+{userXpData.AwardedXp})";
else if (userXpData.AwardedXp < 0)
awardStr = $"({userXpData.AwardedXp.ToString()})";
embed.AddField(
$"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}",
$"{GetText("level_x", levelStats.Level)} - {levelStats.TotalXp}xp {awardStr}");
}
return embed;
}
}, addPaginatedFooter: false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task XpGlobalLeaderboard(int page = 1)
{
if (--page < 0 || page > 100)
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", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp");
}
}
await Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task XpAdd(int amount, [Remainder] IGuildUser user)
{
if (amount == 0)
return;
_service.AddXp(user.Id, Context.Guild.Id, amount);
await ReplyConfirmLocalized("modified", Format.Bold(user.ToString()), Format.Bold(amount.ToString())).ConfigureAwait(false);
}
}
}