mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 08:34:27 -05:00 
			
		
		
		
	More restructuring
This commit is contained in:
		@@ -0,0 +1,932 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using CommandLine;
 | 
			
		||||
using Humanizer.Localisation;
 | 
			
		||||
using NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration;
 | 
			
		||||
 | 
			
		||||
public partial class Administration
 | 
			
		||||
{
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class UserPunishCommands : NadekoModule<UserPunishService>
 | 
			
		||||
    {
 | 
			
		||||
        public enum AddRole
 | 
			
		||||
        {
 | 
			
		||||
            AddRole
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly MuteService _mute;
 | 
			
		||||
 | 
			
		||||
        public UserPunishCommands(MuteService mute)
 | 
			
		||||
        {
 | 
			
		||||
            _mute = mute;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<bool> CheckRoleHierarchy(IGuildUser target)
 | 
			
		||||
        {
 | 
			
		||||
            var curUser = ((SocketGuild)ctx.Guild).CurrentUser;
 | 
			
		||||
            var ownerId = ctx.Guild.OwnerId;
 | 
			
		||||
            var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
 | 
			
		||||
            var targetMaxRole = target.GetRoles().Max(r => r.Position);
 | 
			
		||||
            var botMaxRole = curUser.GetRoles().Max(r => r.Position);
 | 
			
		||||
            // bot can't punish a user who is higher in the hierarchy. Discord will return 403
 | 
			
		||||
            // moderator can be owner, in which case role hierarchy doesn't matter
 | 
			
		||||
            // otherwise, moderator has to have a higher role
 | 
			
		||||
            if (botMaxRole <= targetMaxRole
 | 
			
		||||
                || (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole)
 | 
			
		||||
                || target.Id == ownerId)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.hierarchy);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public Task Warn(IGuildUser user, [Leftover] string reason = null)
 | 
			
		||||
            => Warn(1, user, reason);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task Warn(int weight, IGuildUser user, [Leftover] string reason = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (weight <= 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (!await CheckRoleHierarchy(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await user.EmbedAsync(_eb.Create()
 | 
			
		||||
                                         .WithErrorColor()
 | 
			
		||||
                                         .WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
 | 
			
		||||
                                         .AddField(GetText(strs.moderator), ctx.User.ToString())
 | 
			
		||||
                                         .AddField(GetText(strs.reason), reason ?? "-"));
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                dmFailed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            WarningPunishment punishment;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, weight, reason);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex, "Exception occured while warning a user");
 | 
			
		||||
                var errorEmbed = _eb.Create().WithErrorColor().WithDescription(GetText(strs.cant_apply_punishment));
 | 
			
		||||
 | 
			
		||||
                if (dmFailed)
 | 
			
		||||
                    errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(errorEmbed);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = _eb.Create().WithOkColor();
 | 
			
		||||
            if (punishment is null)
 | 
			
		||||
                embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString()))));
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                embed.WithDescription(GetText(strs.user_warned_and_punished(Format.Bold(user.ToString()),
 | 
			
		||||
                    Format.Bold(punishment.Punishment.ToString()))));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                embed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        [NadekoOptions<WarnExpireOptions>]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task WarnExpire()
 | 
			
		||||
        {
 | 
			
		||||
            var expireDays = await _service.GetWarnExpire(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            if (expireDays == 0)
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warns_dont_expire);
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.warns_expire_in(expireDays));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        [NadekoOptions<WarnExpireOptions>]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        public async Task WarnExpire(int days, params string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            if (days is < 0 or > 366)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var opts = OptionsParser.ParseFrom<WarnExpireOptions>(args);
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
 | 
			
		||||
            await _service.WarnExpireAsync(ctx.Guild.Id, days, opts.Delete);
 | 
			
		||||
            if (days == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_expire_reset);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (opts.Delete)
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_expire_set_delete(Format.Bold(days.ToString())));
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_expire_set_clear(Format.Bold(days.ToString())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        public Task Warnlog(int page, [Leftover] IGuildUser user = null)
 | 
			
		||||
        {
 | 
			
		||||
            user ??= (IGuildUser)ctx.User;
 | 
			
		||||
 | 
			
		||||
            return Warnlog(page, user.Id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [Priority(3)]
 | 
			
		||||
        public Task Warnlog(IGuildUser user = null)
 | 
			
		||||
        {
 | 
			
		||||
            user ??= (IGuildUser)ctx.User;
 | 
			
		||||
 | 
			
		||||
            return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers
 | 
			
		||||
                ? Warnlog(user.Id)
 | 
			
		||||
                : Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public Task Warnlog(int page, ulong userId)
 | 
			
		||||
            => InternalWarnlog(userId, page - 1);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public Task Warnlog(ulong userId)
 | 
			
		||||
            => InternalWarnlog(userId, 0);
 | 
			
		||||
 | 
			
		||||
        private async Task InternalWarnlog(ulong userId, int inputPage)
 | 
			
		||||
        {
 | 
			
		||||
            if (inputPage < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var allWarnings = _service.UserWarnings(ctx.Guild.Id, userId);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(inputPage,
 | 
			
		||||
                page =>
 | 
			
		||||
                {
 | 
			
		||||
                    var warnings = allWarnings.Skip(page * 9).Take(9).ToArray();
 | 
			
		||||
 | 
			
		||||
                    var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
 | 
			
		||||
                    var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
 | 
			
		||||
 | 
			
		||||
                    if (!warnings.Any())
 | 
			
		||||
                        embed.WithDescription(GetText(strs.warnings_none));
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var descText = GetText(strs.warn_count(
 | 
			
		||||
                            Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
 | 
			
		||||
                            Format.Bold(warnings.Sum(x => x.Weight).ToString())));
 | 
			
		||||
 | 
			
		||||
                        embed.WithDescription(descText);
 | 
			
		||||
 | 
			
		||||
                        var i = page * 9;
 | 
			
		||||
                        foreach (var w in warnings)
 | 
			
		||||
                        {
 | 
			
		||||
                            i++;
 | 
			
		||||
                            var name = GetText(strs.warned_on_by(w.DateAdded?.ToString("dd.MM.yyy"),
 | 
			
		||||
                                w.DateAdded?.ToString("HH:mm"),
 | 
			
		||||
                                w.Moderator));
 | 
			
		||||
 | 
			
		||||
                            if (w.Forgiven)
 | 
			
		||||
                                name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            embed.AddField($"#`{i}` " + name,
 | 
			
		||||
                                Format.Code(GetText(strs.warn_weight(w.Weight))) + '\n' + w.Reason.TrimTo(1000));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                allWarnings.Length,
 | 
			
		||||
                9);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task WarnlogAll(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            var warnings = _service.WarnlogAll(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var ws = warnings.Skip(curPage * 15)
 | 
			
		||||
                                     .Take(15)
 | 
			
		||||
                                     .ToArray()
 | 
			
		||||
                                     .Select(x =>
 | 
			
		||||
                                     {
 | 
			
		||||
                                         var all = x.Count();
 | 
			
		||||
                                         var forgiven = x.Count(y => y.Forgiven);
 | 
			
		||||
                                         var total = all - forgiven;
 | 
			
		||||
                                         var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
 | 
			
		||||
                                         return (usr?.ToString() ?? x.Key.ToString())
 | 
			
		||||
                                                + $" | {total} ({all} - {forgiven})";
 | 
			
		||||
                                     });
 | 
			
		||||
 | 
			
		||||
                    return _eb.Create()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithTitle(GetText(strs.warnings_list))
 | 
			
		||||
                              .WithDescription(string.Join("\n", ws));
 | 
			
		||||
                },
 | 
			
		||||
                warnings.Length,
 | 
			
		||||
                15);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public Task Warnclear(IGuildUser user, int index = 0)
 | 
			
		||||
            => Warnclear(user.Id, index);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task Warnclear(ulong userId, int index = 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (index < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            var success = await _service.WarnClearAsync(ctx.Guild.Id, userId, index, ctx.User.ToString());
 | 
			
		||||
            var userStr = Format.Bold((ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString());
 | 
			
		||||
            if (index == 0)
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.warnings_cleared(userStr));
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (success)
 | 
			
		||||
                    await ReplyConfirmLocalizedAsync(strs.warning_cleared(Format.Bold(index.ToString()), userStr));
 | 
			
		||||
                else
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.warning_clear_fail);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task WarnPunish(
 | 
			
		||||
            int number,
 | 
			
		||||
            AddRole _,
 | 
			
		||||
            IRole role,
 | 
			
		||||
            StoopidTime time = null)
 | 
			
		||||
        {
 | 
			
		||||
            var punish = PunishmentAction.AddRole;
 | 
			
		||||
 | 
			
		||||
            if (ctx.Guild.OwnerId != ctx.User.Id
 | 
			
		||||
                && role.Position >= ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.role_too_high);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
 | 
			
		||||
 | 
			
		||||
            if (!success)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (time is null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()),
 | 
			
		||||
                    Format.Bold(number.ToString())));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
 | 
			
		||||
                    Format.Bold(number.ToString()),
 | 
			
		||||
                    Format.Bold(time.Input)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
 | 
			
		||||
        {
 | 
			
		||||
            // this should never happen. Addrole has its own method with higher priority
 | 
			
		||||
            // also disallow warn punishment for getting warned
 | 
			
		||||
            if (punish is PunishmentAction.AddRole or PunishmentAction.Warn)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            // you must specify the time for timeout
 | 
			
		||||
            if (punish is PunishmentAction.TimeOut && time is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);
 | 
			
		||||
 | 
			
		||||
            if (!success)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (time is null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()),
 | 
			
		||||
                    Format.Bold(number.ToString())));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
 | 
			
		||||
                    Format.Bold(number.ToString()),
 | 
			
		||||
                    Format.Bold(time.Input)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task WarnPunish(int number)
 | 
			
		||||
        {
 | 
			
		||||
            if (!_service.WarnPunishRemove(ctx.Guild.Id, number))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.warn_punish_rem(Format.Bold(number.ToString())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task WarnPunishList()
 | 
			
		||||
        {
 | 
			
		||||
            var ps = _service.WarnPunishList(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            string list;
 | 
			
		||||
            if (ps.Any())
 | 
			
		||||
            {
 | 
			
		||||
                list = string.Join("\n",
 | 
			
		||||
                    ps.Select(x
 | 
			
		||||
                        => $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time + "m")} "));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                list = GetText(strs.warnpl_none);
 | 
			
		||||
 | 
			
		||||
            await SendConfirmAsync(GetText(strs.warn_punish_list), list);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
 | 
			
		||||
            => Ban(time, user.Id, msg);
 | 
			
		||||
        
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (time.Time > TimeSpan.FromDays(49))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (guildUser is not null && !await CheckRoleHierarchy(guildUser))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
 | 
			
		||||
            if (guildUser is not null)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
 | 
			
		||||
                    var embed = _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
 | 
			
		||||
                    if (embed is not null)
 | 
			
		||||
                        await guildUser.SendAsync(embed);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    dmFailed = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var user = await ctx.Client.GetUserAsync(userId);
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
 | 
			
		||||
            var toSend = _eb.Create()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                            .AddField(GetText(strs.username),  user?.ToString() ?? userId.ToString(), true)
 | 
			
		||||
                            .AddField("ID", userId.ToString(), true)
 | 
			
		||||
                            .AddField(GetText(strs.duration),
 | 
			
		||||
                                time.Time.Humanize(3, minUnit: TimeUnit.Minute, culture: Culture),
 | 
			
		||||
                                true);
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task Ban(ulong userId, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
 | 
			
		||||
            if (user is null)
 | 
			
		||||
            {
 | 
			
		||||
                var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
                await ctx.Guild.AddBanAsync(userId, banPrune, (ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(_eb.Create()
 | 
			
		||||
                                                .WithOkColor()
 | 
			
		||||
                                                .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                                                .AddField("ID", userId.ToString(), true));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                await Ban(user, msg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        public async Task Ban(IGuildUser user, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await CheckRoleHierarchy(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
 | 
			
		||||
                var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
 | 
			
		||||
                if (embed is not null)
 | 
			
		||||
                    await user.SendAsync(embed);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                dmFailed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            await ctx.Guild.AddBanAsync(user, banPrune, (ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
            var toSend = _eb.Create()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                            .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
                            .AddField("ID", user.Id.ToString(), true);
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task BanPrune(int days)
 | 
			
		||||
        {
 | 
			
		||||
            if (days < 0 || days > 7)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.invalid_input);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            await _service.SetBanPruneAsync(ctx.Guild.Id, days);
 | 
			
		||||
            
 | 
			
		||||
            if (days == 0)
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.ban_prune_disabled);
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.ban_prune(days));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task BanMessage([Leftover] string message = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (message is null)
 | 
			
		||||
            {
 | 
			
		||||
                var template = _service.GetBanTemplate(ctx.Guild.Id);
 | 
			
		||||
                if (template is null)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyConfirmLocalizedAsync(strs.banmsg_default);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await SendConfirmAsync(template);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _service.SetBanTemplate(ctx.Guild.Id, message);
 | 
			
		||||
            await ctx.OkAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task BanMsgReset()
 | 
			
		||||
        {
 | 
			
		||||
            _service.SetBanTemplate(ctx.Guild.Id, null);
 | 
			
		||||
            await ctx.OkAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public Task BanMessageTest([Leftover] string reason = null)
 | 
			
		||||
            => InternalBanMessageTest(reason, null);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
 | 
			
		||||
            => InternalBanMessageTest(reason, duration.Time);
 | 
			
		||||
 | 
			
		||||
        private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
 | 
			
		||||
        {
 | 
			
		||||
            var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), reason));
 | 
			
		||||
            var embed = _service.GetBanUserDmEmbed(Context, (IGuildUser)ctx.User, defaultMessage, reason, duration);
 | 
			
		||||
 | 
			
		||||
            if (embed is null)
 | 
			
		||||
                await ConfirmLocalizedAsync(strs.banmsg_disabled);
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await ctx.User.SendAsync(embed);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.unable_to_dm_user);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await ctx.OkAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task Unban([Leftover] string user)
 | 
			
		||||
        {
 | 
			
		||||
            var bans = await ctx.Guild.GetBansAsync().FlattenAsync();
 | 
			
		||||
 | 
			
		||||
            var bun = bans.FirstOrDefault(x => x.User.ToString()!.ToLowerInvariant() == user.ToLowerInvariant());
 | 
			
		||||
 | 
			
		||||
            if (bun is null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.user_not_found);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await UnbanInternal(bun.User);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task Unban(ulong userId)
 | 
			
		||||
        {
 | 
			
		||||
            var bun = await ctx.Guild.GetBanAsync(userId);
 | 
			
		||||
 | 
			
		||||
            if (bun is null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.user_not_found);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await UnbanInternal(bun.User);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task UnbanInternal(IUser user)
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.Guild.RemoveBanAsync(user);
 | 
			
		||||
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.unbanned_user(Format.Bold(user.ToString())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public Task Softban(IGuildUser user, [Leftover] string msg = null)
 | 
			
		||||
            => SoftbanInternal(user, msg);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        public async Task Softban(ulong userId, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await SoftbanInternal(user, msg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task SoftbanInternal(IGuildUser user, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await CheckRoleHierarchy(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await user.SendErrorAsync(_eb, GetText(strs.sbdm(Format.Bold(ctx.Guild.Name), msg)));
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                dmFailed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            await ctx.Guild.AddBanAsync(user, banPrune, ("Softban | " + ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
            try { await ctx.Guild.RemoveBanAsync(user); }
 | 
			
		||||
            catch { await ctx.Guild.RemoveBanAsync(user); }
 | 
			
		||||
 | 
			
		||||
            var toSend = _eb.Create()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle("☣ " + GetText(strs.sb_user))
 | 
			
		||||
                            .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
                            .AddField("ID", user.Id.ToString(), true);
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.KickMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.KickMembers)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public Task Kick(IGuildUser user, [Leftover] string msg = null)
 | 
			
		||||
            => KickInternal(user, msg);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.KickMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.KickMembers)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task Kick(ulong userId, [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await KickInternal(user, msg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task KickInternal(IGuildUser user, string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await CheckRoleHierarchy(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await user.SendErrorAsync(_eb, GetText(strs.kickdm(Format.Bold(ctx.Guild.Name), msg)));
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                dmFailed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
            var toSend = _eb.Create()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle(GetText(strs.kicked_user))
 | 
			
		||||
                            .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
                            .AddField("ID", user.Id.ToString(), true);
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.ModerateMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.ModerateMembers)]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        public async Task Timeout(IUser globalUser, StoopidTime time,  [Leftover] string msg = null)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await ctx.Guild.GetUserAsync(globalUser.Id);
 | 
			
		||||
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
            if (!await CheckRoleHierarchy(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var dmFailed = false;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var dmMessage = GetText(strs.timeoutdm(Format.Bold(ctx.Guild.Name), msg));
 | 
			
		||||
                await user.EmbedAsync(_eb.Create(ctx)
 | 
			
		||||
                        .WithPendingColor()
 | 
			
		||||
                        .WithDescription(dmMessage));
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                dmFailed = true;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            await user.SetTimeOutAsync(time.Time);
 | 
			
		||||
 | 
			
		||||
            var toSend = _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle("⏳ " + GetText(strs.timedout_user))
 | 
			
		||||
                .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
                .AddField("ID", user.Id.ToString(), true);
 | 
			
		||||
 | 
			
		||||
            if (dmFailed)
 | 
			
		||||
                toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [Ratelimit(30)]
 | 
			
		||||
        public async Task MassBan(params string[] userStrings)
 | 
			
		||||
        {
 | 
			
		||||
            if (userStrings.Length == 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var missing = new List<string>();
 | 
			
		||||
            var banning = new HashSet<IUser>();
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
            foreach (var userStr in userStrings)
 | 
			
		||||
            {
 | 
			
		||||
                if (ulong.TryParse(userStr, out var userId))
 | 
			
		||||
                {
 | 
			
		||||
                    IUser user = await ctx.Guild.GetUserAsync(userId)
 | 
			
		||||
                                 ?? await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id,
 | 
			
		||||
                                     userId);
 | 
			
		||||
 | 
			
		||||
                    if (user is null)
 | 
			
		||||
                    {
 | 
			
		||||
                        // if IGuildUser is null, try to get IUser
 | 
			
		||||
                        user = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(userId);
 | 
			
		||||
 | 
			
		||||
                        // only add to missing if *still* null
 | 
			
		||||
                        if (user is null)
 | 
			
		||||
                        {
 | 
			
		||||
                            missing.Add(userStr);
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    //Hierachy checks only if the user is in the guild
 | 
			
		||||
                    if (user is IGuildUser gu && !await CheckRoleHierarchy(gu))
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    banning.Add(user);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    missing.Add(userStr);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var missStr = string.Join("\n", missing);
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(missStr))
 | 
			
		||||
                missStr = "-";
 | 
			
		||||
 | 
			
		||||
            var toSend = _eb.Create(ctx)
 | 
			
		||||
                            .WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
 | 
			
		||||
                            .AddField(GetText(strs.invalid(missing.Count)), missStr)
 | 
			
		||||
                            .WithPendingColor();
 | 
			
		||||
 | 
			
		||||
            var banningMessage = await ctx.Channel.EmbedAsync(toSend);
 | 
			
		||||
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            foreach (var toBan in banning)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await ctx.Guild.AddBanAsync(toBan.Id, banPrune, $"{ctx.User} | Massban");
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning(ex, "Error banning {User} user in {GuildId} server", toBan.Id, ctx.Guild.Id);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = _eb.Create()
 | 
			
		||||
                                                               .WithDescription(
 | 
			
		||||
                                                                   GetText(strs.mass_ban_completed(banning.Count())))
 | 
			
		||||
                                                               .AddField(GetText(strs.invalid(missing.Count)), missStr)
 | 
			
		||||
                                                               .WithOkColor()
 | 
			
		||||
                                                               .Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [BotPerm(GuildPerm.BanMembers)]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async Task MassKill([Leftover] string people)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(people))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var (bans, missing) = _service.MassKill((SocketGuild)ctx.Guild, people);
 | 
			
		||||
 | 
			
		||||
            var missStr = string.Join("\n", missing);
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(missStr))
 | 
			
		||||
                missStr = "-";
 | 
			
		||||
 | 
			
		||||
            //send a message but don't wait for it
 | 
			
		||||
            var banningMessageTask = ctx.Channel.EmbedAsync(_eb.Create()
 | 
			
		||||
                                                               .WithDescription(
 | 
			
		||||
                                                                   GetText(strs.mass_kill_in_progress(bans.Count())))
 | 
			
		||||
                                                               .AddField(GetText(strs.invalid(missing)), missStr)
 | 
			
		||||
                                                               .WithPendingColor());
 | 
			
		||||
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            //do the banning
 | 
			
		||||
            await Task.WhenAll(bans.Where(x => x.Id.HasValue)
 | 
			
		||||
                                   .Select(x => ctx.Guild.AddBanAsync(x.Id.Value,
 | 
			
		||||
                                       banPrune,
 | 
			
		||||
                                       x.Reason,
 | 
			
		||||
                                       new()
 | 
			
		||||
                                       {
 | 
			
		||||
                                           RetryMode = RetryMode.AlwaysRetry
 | 
			
		||||
                                       })));
 | 
			
		||||
 | 
			
		||||
            //wait for the message and edit it
 | 
			
		||||
            var banningMessage = await banningMessageTask;
 | 
			
		||||
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = _eb.Create()
 | 
			
		||||
                                                               .WithDescription(
 | 
			
		||||
                                                                   GetText(strs.mass_kill_completed(bans.Count())))
 | 
			
		||||
                                                               .AddField(GetText(strs.invalid(missing)), missStr)
 | 
			
		||||
                                                               .WithOkColor()
 | 
			
		||||
                                                               .Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class WarnExpireOptions : INadekoCommandOptions
 | 
			
		||||
        {
 | 
			
		||||
            [Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")]
 | 
			
		||||
            public bool Delete { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
            public void NormalizeOptions()
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,595 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using LinqToDB;
 | 
			
		||||
using LinqToDB.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Modules.Permissions.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration.Services;
 | 
			
		||||
 | 
			
		||||
public class UserPunishService : INService, IReadyExecutor
 | 
			
		||||
{
 | 
			
		||||
    private readonly MuteService _mute;
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly BlacklistService _blacklistService;
 | 
			
		||||
    private readonly BotConfigService _bcs;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
    public event Func<Warning, Task> OnUserWarned = static delegate { return Task.CompletedTask; };
 | 
			
		||||
 | 
			
		||||
    public UserPunishService(
 | 
			
		||||
        MuteService mute,
 | 
			
		||||
        DbService db,
 | 
			
		||||
        BlacklistService blacklistService,
 | 
			
		||||
        BotConfigService bcs,
 | 
			
		||||
        DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        _mute = mute;
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _blacklistService = blacklistService;
 | 
			
		||||
        _bcs = bcs;
 | 
			
		||||
        _client = client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task OnReadyAsync()
 | 
			
		||||
    {
 | 
			
		||||
        if (_client.ShardId != 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        using var expiryTimer = new PeriodicTimer(TimeSpan.FromHours(12));
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await CheckAllWarnExpiresAsync();
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Unexpected error while checking for warn expiries: {ErrorMessage}", ex.Message);
 | 
			
		||||
            }
 | 
			
		||||
        } while (await expiryTimer.WaitForNextTickAsync());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<WarningPunishment> Warn(
 | 
			
		||||
        IGuild guild,
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        IUser mod,
 | 
			
		||||
        long weight,
 | 
			
		||||
        string reason)
 | 
			
		||||
    {
 | 
			
		||||
        if (weight <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(weight));
 | 
			
		||||
 | 
			
		||||
        var modName = mod.ToString();
 | 
			
		||||
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(reason))
 | 
			
		||||
            reason = "-";
 | 
			
		||||
 | 
			
		||||
        var guildId = guild.Id;
 | 
			
		||||
 | 
			
		||||
        var warn = new Warning
 | 
			
		||||
        {
 | 
			
		||||
            UserId = userId,
 | 
			
		||||
            GuildId = guildId,
 | 
			
		||||
            Forgiven = false,
 | 
			
		||||
            Reason = reason,
 | 
			
		||||
            Moderator = modName,
 | 
			
		||||
            Weight = weight
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        long previousCount;
 | 
			
		||||
        List<WarningPunishment> ps;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
 | 
			
		||||
            previousCount = uow.Warnings.ForId(guildId, userId)
 | 
			
		||||
                                .Where(w => !w.Forgiven && w.UserId == userId)
 | 
			
		||||
                                .Sum(x => x.Weight);
 | 
			
		||||
 | 
			
		||||
            uow.Warnings.Add(warn);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _ = OnUserWarned(warn);
 | 
			
		||||
 | 
			
		||||
        var totalCount = previousCount + weight;
 | 
			
		||||
        
 | 
			
		||||
        var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount)
 | 
			
		||||
                  .MaxBy(x => x.Count);
 | 
			
		||||
 | 
			
		||||
        if (p is not null)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await guild.GetUserAsync(userId);
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            await ApplyPunishment(guild, user, mod, p.Punishment, p.Time, p.RoleId, "Warned too many times.");
 | 
			
		||||
            return p;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ApplyPunishment(
 | 
			
		||||
        IGuild guild,
 | 
			
		||||
        IGuildUser user,
 | 
			
		||||
        IUser mod,
 | 
			
		||||
        PunishmentAction p,
 | 
			
		||||
        int minutes,
 | 
			
		||||
        ulong? roleId,
 | 
			
		||||
        string reason)
 | 
			
		||||
    {
 | 
			
		||||
        if (!await CheckPermission(guild, p))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        int banPrune;
 | 
			
		||||
        switch (p)
 | 
			
		||||
        {
 | 
			
		||||
            case PunishmentAction.Mute:
 | 
			
		||||
                if (minutes == 0)
 | 
			
		||||
                    await _mute.MuteUser(user, mod, reason: reason);
 | 
			
		||||
                else
 | 
			
		||||
                    await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), reason: reason);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.VoiceMute:
 | 
			
		||||
                if (minutes == 0)
 | 
			
		||||
                    await _mute.MuteUser(user, mod, MuteType.Voice, reason);
 | 
			
		||||
                else
 | 
			
		||||
                    await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Voice, reason);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.ChatMute:
 | 
			
		||||
                if (minutes == 0)
 | 
			
		||||
                    await _mute.MuteUser(user, mod, MuteType.Chat, reason);
 | 
			
		||||
                else
 | 
			
		||||
                    await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Chat, reason);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.Kick:
 | 
			
		||||
                await user.KickAsync(reason);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.Ban:
 | 
			
		||||
                banPrune = await GetBanPruneAsync(user.GuildId) ?? 7;
 | 
			
		||||
                if (minutes == 0)
 | 
			
		||||
                    await guild.AddBanAsync(user, reason: reason, pruneDays: banPrune);
 | 
			
		||||
                else
 | 
			
		||||
                    await _mute.TimedBan(user.Guild, user.Id, TimeSpan.FromMinutes(minutes), reason, banPrune);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.Softban:
 | 
			
		||||
                banPrune = await GetBanPruneAsync(user.GuildId) ?? 7;
 | 
			
		||||
                await guild.AddBanAsync(user, banPrune, $"Softban | {reason}");
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await guild.RemoveBanAsync(user);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    await guild.RemoveBanAsync(user);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.RemoveRoles:
 | 
			
		||||
                await user.RemoveRolesAsync(user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole));
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.AddRole:
 | 
			
		||||
                if (roleId is null)
 | 
			
		||||
                    return;
 | 
			
		||||
                var role = guild.GetRole(roleId.Value);
 | 
			
		||||
                if (role is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (minutes == 0)
 | 
			
		||||
                        await user.AddRoleAsync(role);
 | 
			
		||||
                    else
 | 
			
		||||
                        await _mute.TimedRole(user, TimeSpan.FromMinutes(minutes), reason, role);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning("Can't find role {RoleId} on server {GuildId} to apply punishment",
 | 
			
		||||
                        roleId.Value,
 | 
			
		||||
                        guild.Id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.Warn:
 | 
			
		||||
                await Warn(guild, user.Id, mod, 1, reason);
 | 
			
		||||
                break;
 | 
			
		||||
            case PunishmentAction.TimeOut:
 | 
			
		||||
                await user.SetTimeOutAsync(TimeSpan.FromMinutes(minutes));
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Used to prevent the bot from hitting 403's when it needs to
 | 
			
		||||
    ///     apply punishments with insufficient permissions
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="guild">Guild the punishment is applied in</param>
 | 
			
		||||
    /// <param name="punish">Punishment to apply</param>
 | 
			
		||||
    /// <returns>Whether the bot has sufficient permissions</returns>
 | 
			
		||||
    private async Task<bool> CheckPermission(IGuild guild, PunishmentAction punish)
 | 
			
		||||
    {
 | 
			
		||||
        var botUser = await guild.GetCurrentUserAsync();
 | 
			
		||||
        switch (punish)
 | 
			
		||||
        {
 | 
			
		||||
            case PunishmentAction.Mute:
 | 
			
		||||
                return botUser.GuildPermissions.MuteMembers && botUser.GuildPermissions.ManageRoles;
 | 
			
		||||
            case PunishmentAction.Kick:
 | 
			
		||||
                return botUser.GuildPermissions.KickMembers;
 | 
			
		||||
            case PunishmentAction.Ban:
 | 
			
		||||
                return botUser.GuildPermissions.BanMembers;
 | 
			
		||||
            case PunishmentAction.Softban:
 | 
			
		||||
                return botUser.GuildPermissions.BanMembers; // ban + unban
 | 
			
		||||
            case PunishmentAction.RemoveRoles:
 | 
			
		||||
                return botUser.GuildPermissions.ManageRoles;
 | 
			
		||||
            case PunishmentAction.ChatMute:
 | 
			
		||||
                return botUser.GuildPermissions.ManageRoles; // adds nadeko-mute role
 | 
			
		||||
            case PunishmentAction.VoiceMute:
 | 
			
		||||
                return botUser.GuildPermissions.MuteMembers;
 | 
			
		||||
            case PunishmentAction.AddRole:
 | 
			
		||||
                return botUser.GuildPermissions.ManageRoles;
 | 
			
		||||
            case PunishmentAction.TimeOut:
 | 
			
		||||
                return botUser.GuildPermissions.ModerateMembers;
 | 
			
		||||
            default:
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task CheckAllWarnExpiresAsync()
 | 
			
		||||
    {
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var cleared = await uow.Warnings
 | 
			
		||||
                               .Where(x => uow.GuildConfigs
 | 
			
		||||
                                                 .Any(y => y.GuildId == x.GuildId
 | 
			
		||||
                                                           && y.WarnExpireHours > 0
 | 
			
		||||
                                                           && y.WarnExpireAction == WarnExpireAction.Clear)
 | 
			
		||||
                                           && x.Forgiven == false 
 | 
			
		||||
                                           && x.DateAdded
 | 
			
		||||
                                           < DateTime.UtcNow.AddHours(-uow.GuildConfigs
 | 
			
		||||
                                                                          .Where(y => x.GuildId == y.GuildId)
 | 
			
		||||
                                                                          .Select(y => y.WarnExpireHours)
 | 
			
		||||
                                                                          .First()))
 | 
			
		||||
                               .UpdateAsync(_ => new()
 | 
			
		||||
                               {
 | 
			
		||||
                                   Forgiven = true,
 | 
			
		||||
                                   ForgivenBy = "expiry"
 | 
			
		||||
                               });
 | 
			
		||||
 | 
			
		||||
        var deleted = await uow.Warnings
 | 
			
		||||
                               .Where(x => uow.GuildConfigs
 | 
			
		||||
                                              .Any(y => y.GuildId == x.GuildId
 | 
			
		||||
                                                        && y.WarnExpireHours > 0
 | 
			
		||||
                                                        && y.WarnExpireAction == WarnExpireAction.Delete)
 | 
			
		||||
                                           && x.DateAdded
 | 
			
		||||
                                           < DateTime.UtcNow.AddHours(-uow.GuildConfigs
 | 
			
		||||
                                                                          .Where(y => x.GuildId == y.GuildId)
 | 
			
		||||
                                                                          .Select(y => y.WarnExpireHours)
 | 
			
		||||
                                                                          .First()))
 | 
			
		||||
                               .DeleteAsync();
 | 
			
		||||
 | 
			
		||||
        if (cleared > 0 || deleted > 0)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Information("Cleared {ClearedWarnings} warnings and deleted {DeletedWarnings} warnings due to expiry",
 | 
			
		||||
                cleared,
 | 
			
		||||
                deleted);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task CheckWarnExpiresAsync(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, inc => inc);
 | 
			
		||||
 | 
			
		||||
        if (config.WarnExpireHours == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (config.WarnExpireAction == WarnExpireAction.Clear)
 | 
			
		||||
        {
 | 
			
		||||
            await uow.Warnings
 | 
			
		||||
                     .Where(x => x.GuildId == guildId
 | 
			
		||||
                                 && x.Forgiven == false
 | 
			
		||||
                                 && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
 | 
			
		||||
                     .UpdateAsync(_ => new()
 | 
			
		||||
                     {
 | 
			
		||||
                         Forgiven = true,
 | 
			
		||||
                         ForgivenBy = "expiry"
 | 
			
		||||
                     });
 | 
			
		||||
        }
 | 
			
		||||
        else if (config.WarnExpireAction == WarnExpireAction.Delete)
 | 
			
		||||
        {
 | 
			
		||||
            await uow.Warnings
 | 
			
		||||
                     .Where(x => x.GuildId == guildId
 | 
			
		||||
                                 && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
 | 
			
		||||
                     .DeleteAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<int> GetWarnExpire(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        return Task.FromResult(config.WarnExpireHours / 24);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, inc => inc);
 | 
			
		||||
 | 
			
		||||
            config.WarnExpireHours = days * 24;
 | 
			
		||||
            config.WarnExpireAction = delete ? WarnExpireAction.Delete : WarnExpireAction.Clear;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
            // no need to check for warn expires
 | 
			
		||||
            if (config.WarnExpireHours == 0)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CheckWarnExpiresAsync(guildId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IGrouping<ulong, Warning>[] WarnlogAll(ulong gid)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.Warnings.GetForGuild(gid).GroupBy(x => x.UserId).ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Warning[] UserWarnings(ulong gid, ulong userId)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.Warnings.ForId(gid, userId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> WarnClearAsync(
 | 
			
		||||
        ulong guildId,
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        int index,
 | 
			
		||||
        string moderator)
 | 
			
		||||
    {
 | 
			
		||||
        var toReturn = true;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        if (index == 0)
 | 
			
		||||
            await uow.Warnings.ForgiveAll(guildId, userId, moderator);
 | 
			
		||||
        else
 | 
			
		||||
            toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool WarnPunish(
 | 
			
		||||
        ulong guildId,
 | 
			
		||||
        int number,
 | 
			
		||||
        PunishmentAction punish,
 | 
			
		||||
        StoopidTime time,
 | 
			
		||||
        IRole role = null)
 | 
			
		||||
    {
 | 
			
		||||
        // these 3 don't make sense with time
 | 
			
		||||
        if (punish is PunishmentAction.Softban or PunishmentAction.Kick or PunishmentAction.RemoveRoles
 | 
			
		||||
            && time is not null)
 | 
			
		||||
            return false;
 | 
			
		||||
        if (number <= 0 || (time is not null && time.Time > TimeSpan.FromDays(49)))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (punish is PunishmentAction.AddRole && role is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (punish is PunishmentAction.TimeOut && time is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
        var toDelete = ps.Where(x => x.Count == number);
 | 
			
		||||
 | 
			
		||||
        uow.RemoveRange(toDelete);
 | 
			
		||||
 | 
			
		||||
        ps.Add(new()
 | 
			
		||||
        {
 | 
			
		||||
            Count = number,
 | 
			
		||||
            Punishment = punish,
 | 
			
		||||
            Time = (int?)time?.Time.TotalMinutes ?? 0,
 | 
			
		||||
            RoleId = punish == PunishmentAction.AddRole ? role!.Id : default(ulong?)
 | 
			
		||||
        });
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool WarnPunishRemove(ulong guildId, int number)
 | 
			
		||||
    {
 | 
			
		||||
        if (number <= 0)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
        var p = ps.FirstOrDefault(x => x.Count == number);
 | 
			
		||||
 | 
			
		||||
        if (p is not null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.Remove(p);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public WarningPunishment[] WarnPunishList(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
 | 
			
		||||
                  .WarnPunishments.OrderBy(x => x.Count)
 | 
			
		||||
                  .ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (IReadOnlyCollection<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
 | 
			
		||||
        SocketGuild guild,
 | 
			
		||||
        string people)
 | 
			
		||||
    {
 | 
			
		||||
        var gusers = guild.Users;
 | 
			
		||||
        //get user objects and reasons
 | 
			
		||||
        var bans = people.Split("\n")
 | 
			
		||||
                         .Select(x =>
 | 
			
		||||
                         {
 | 
			
		||||
                             var split = x.Trim().Split(" ");
 | 
			
		||||
 | 
			
		||||
                             var reason = string.Join(" ", split.Skip(1));
 | 
			
		||||
 | 
			
		||||
                             if (ulong.TryParse(split[0], out var id))
 | 
			
		||||
                                 return (Original: split[0], Id: id, Reason: reason);
 | 
			
		||||
 | 
			
		||||
                             return (Original: split[0],
 | 
			
		||||
                                 gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id,
 | 
			
		||||
                                 Reason: reason);
 | 
			
		||||
                         })
 | 
			
		||||
                         .ToArray();
 | 
			
		||||
 | 
			
		||||
        //if user is null, means that person couldn't be found
 | 
			
		||||
        var missing = bans.Count(x => !x.Id.HasValue);
 | 
			
		||||
 | 
			
		||||
        //get only data for found users
 | 
			
		||||
        var found = bans.Where(x => x.Id.HasValue).Select(x => x.Id.Value).ToList();
 | 
			
		||||
 | 
			
		||||
        _blacklistService.BlacklistUsers(found);
 | 
			
		||||
 | 
			
		||||
        return (bans, missing);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string GetBanTemplate(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId);
 | 
			
		||||
        return template?.Text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void SetBanTemplate(ulong guildId, string text)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId);
 | 
			
		||||
 | 
			
		||||
        if (text is null)
 | 
			
		||||
        {
 | 
			
		||||
            if (template is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            uow.Remove(template);
 | 
			
		||||
        }
 | 
			
		||||
        else if (template is null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.BanTemplates.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                GuildId = guildId,
 | 
			
		||||
                Text = text
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            template.Text = text;
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task SetBanPruneAsync(ulong guildId, int? pruneDays)
 | 
			
		||||
    {
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
        await ctx.BanTemplates
 | 
			
		||||
            .ToLinqToDBTable()
 | 
			
		||||
            .InsertOrUpdateAsync(() => new()
 | 
			
		||||
                {
 | 
			
		||||
                    GuildId = guildId,
 | 
			
		||||
                    Text = null,
 | 
			
		||||
                    DateAdded = DateTime.UtcNow,
 | 
			
		||||
                    PruneDays = pruneDays
 | 
			
		||||
                },
 | 
			
		||||
                old => new()
 | 
			
		||||
                {
 | 
			
		||||
                    PruneDays = pruneDays
 | 
			
		||||
                },
 | 
			
		||||
                () => new()
 | 
			
		||||
                {
 | 
			
		||||
                    GuildId = guildId
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<int?> GetBanPruneAsync(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
        return await ctx.BanTemplates
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .Select(x => x.PruneDays)
 | 
			
		||||
            .FirstOrDefaultAsyncLinqToDB();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SmartText GetBanUserDmEmbed(
 | 
			
		||||
        ICommandContext context,
 | 
			
		||||
        IGuildUser target,
 | 
			
		||||
        string defaultMessage,
 | 
			
		||||
        string banReason,
 | 
			
		||||
        TimeSpan? duration)
 | 
			
		||||
        => GetBanUserDmEmbed((DiscordSocketClient)context.Client,
 | 
			
		||||
            (SocketGuild)context.Guild,
 | 
			
		||||
            (IGuildUser)context.User,
 | 
			
		||||
            target,
 | 
			
		||||
            defaultMessage,
 | 
			
		||||
            banReason,
 | 
			
		||||
            duration);
 | 
			
		||||
 | 
			
		||||
    public SmartText GetBanUserDmEmbed(
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
        SocketGuild guild,
 | 
			
		||||
        IGuildUser moderator,
 | 
			
		||||
        IGuildUser target,
 | 
			
		||||
        string defaultMessage,
 | 
			
		||||
        string banReason,
 | 
			
		||||
        TimeSpan? duration)
 | 
			
		||||
    {
 | 
			
		||||
        var template = GetBanTemplate(guild.Id);
 | 
			
		||||
 | 
			
		||||
        banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason;
 | 
			
		||||
 | 
			
		||||
        var replacer = new ReplacementBuilder().WithServer(client, guild)
 | 
			
		||||
                                               .WithOverride("%ban.mod%", () => moderator.ToString())
 | 
			
		||||
                                               .WithOverride("%ban.mod.fullname%", () => moderator.ToString())
 | 
			
		||||
                                               .WithOverride("%ban.mod.name%", () => moderator.Username)
 | 
			
		||||
                                               .WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
 | 
			
		||||
                                               .WithOverride("%ban.user%", () => target.ToString())
 | 
			
		||||
                                               .WithOverride("%ban.user.fullname%", () => target.ToString())
 | 
			
		||||
                                               .WithOverride("%ban.user.name%", () => target.Username)
 | 
			
		||||
                                               .WithOverride("%ban.user.discrim%", () => target.Discriminator)
 | 
			
		||||
                                               .WithOverride("%reason%", () => banReason)
 | 
			
		||||
                                               .WithOverride("%ban.reason%", () => banReason)
 | 
			
		||||
                                               .WithOverride("%ban.duration%",
 | 
			
		||||
                                                   () => duration?.ToString(@"d\.hh\:mm") ?? "perma")
 | 
			
		||||
                                               .Build();
 | 
			
		||||
 | 
			
		||||
        // if template isn't set, use the old message style
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(template))
 | 
			
		||||
        {
 | 
			
		||||
            template = JsonConvert.SerializeObject(new
 | 
			
		||||
            {
 | 
			
		||||
                color = _bcs.Data.Color.Error.PackedValue >> 8,
 | 
			
		||||
                description = defaultMessage
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        // if template is set to "-" do not dm the user
 | 
			
		||||
        else if (template == "-")
 | 
			
		||||
            return default;
 | 
			
		||||
        // if template is an embed, send that embed with replacements
 | 
			
		||||
        // otherwise, treat template as a regular string with replacements
 | 
			
		||||
        else if (SmartText.CreateFrom(template) is not { IsEmbed: true } or { IsEmbedArray: true })
 | 
			
		||||
        {
 | 
			
		||||
            template = JsonConvert.SerializeObject(new
 | 
			
		||||
            {
 | 
			
		||||
                color = _bcs.Data.Color.Error.PackedValue >> 8,
 | 
			
		||||
                description = template
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var output = SmartText.CreateFrom(template);
 | 
			
		||||
        return replacer.Replace(output);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user