mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Using declarations and other code reformats
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Class)]
 | 
			
		||||
sealed class NadekoModuleAttribute : GroupAttribute
 | 
			
		||||
internal sealed class NadekoModuleAttribute : GroupAttribute
 | 
			
		||||
{
 | 
			
		||||
    public NadekoModuleAttribute(string moduleName) : base(moduleName)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class NadekoRandom : Random
 | 
			
		||||
{
 | 
			
		||||
    readonly RandomNumberGenerator _rng;
 | 
			
		||||
    private readonly RandomNumberGenerator _rng;
 | 
			
		||||
 | 
			
		||||
    public NadekoRandom() : base()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,13 @@ public static class OptionsParser
 | 
			
		||||
 | 
			
		||||
    public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
 | 
			
		||||
    {
 | 
			
		||||
        using (var p = new Parser(x =>
 | 
			
		||||
               {
 | 
			
		||||
                   x.HelpWriter = null;
 | 
			
		||||
               }))
 | 
			
		||||
        using var p = new Parser(x =>
 | 
			
		||||
        {
 | 
			
		||||
            var res = p.ParseArguments<T>(args);
 | 
			
		||||
            options = res.MapResult(x => x, x => options);
 | 
			
		||||
            options.NormalizeOptions();
 | 
			
		||||
            return (options, res.Tag == ParserResultType.Parsed);
 | 
			
		||||
        }
 | 
			
		||||
            x.HelpWriter = null;
 | 
			
		||||
        });
 | 
			
		||||
        var res = p.ParseArguments<T>(args);
 | 
			
		||||
        options = res.MapResult(x => x, x => options);
 | 
			
		||||
        options.NormalizeOptions();
 | 
			
		||||
        return (options, res.Tag == ParserResultType.Parsed);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,13 +32,11 @@ public class AdministrationService : INService
 | 
			
		||||
 | 
			
		||||
    public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId,
 | 
			
		||||
                set => set.Include(x => x.DelMsgOnCmdChannels));
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId,
 | 
			
		||||
            set => set.Include(x => x.DelMsgOnCmdChannels));
 | 
			
		||||
 | 
			
		||||
            return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
 | 
			
		||||
        }
 | 
			
		||||
        return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
 | 
			
		||||
@@ -70,13 +68,11 @@ public class AdministrationService : INService
 | 
			
		||||
    public bool ToggleDeleteMessageOnCommand(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        bool enabled;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,8 @@ DELETE FROM Clubs;";
 | 
			
		||||
    public async Task<int> ExecuteSql(string sql)
 | 
			
		||||
    {
 | 
			
		||||
        int res;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            res = await uow.Database.ExecuteSqlRawAsync(sql);
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        res = await uow.Database.ExecuteSqlRawAsync(sql);
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -61,30 +59,25 @@ DELETE FROM Clubs;";
 | 
			
		||||
            Results = new(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conn = uow.Database.GetDbConnection();
 | 
			
		||||
        using var cmd = conn.CreateCommand();
 | 
			
		||||
        cmd.CommandText = sql;
 | 
			
		||||
        using var reader = cmd.ExecuteReader();
 | 
			
		||||
        if (reader.HasRows)
 | 
			
		||||
        {
 | 
			
		||||
            var conn = uow.Database.GetDbConnection();
 | 
			
		||||
            using (var cmd = conn.CreateCommand())
 | 
			
		||||
            for (var i = 0; i < reader.FieldCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                cmd.CommandText = sql;
 | 
			
		||||
                using (var reader = cmd.ExecuteReader())
 | 
			
		||||
                {
 | 
			
		||||
                    if (reader.HasRows)
 | 
			
		||||
                    {
 | 
			
		||||
                        for (var i = 0; i < reader.FieldCount; i++)
 | 
			
		||||
                        {
 | 
			
		||||
                            result.ColumnNames.Add(reader.GetName(i));
 | 
			
		||||
                        }
 | 
			
		||||
                        while (reader.Read())
 | 
			
		||||
                        {
 | 
			
		||||
                            var obj = new object[reader.FieldCount];
 | 
			
		||||
                            reader.GetValues(obj);
 | 
			
		||||
                            result.Results.Add(obj.Select(x => x.ToString()).ToArray());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                result.ColumnNames.Add(reader.GetName(i));
 | 
			
		||||
            }
 | 
			
		||||
            while (reader.Read())
 | 
			
		||||
            {
 | 
			
		||||
                var obj = new object[reader.FieldCount];
 | 
			
		||||
                reader.GetValues(obj);
 | 
			
		||||
                result.Results.Add(obj.Select(x => x.ToString()).ToArray());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public class DiscordPermOverrideService : INService, ILateBlocker
 | 
			
		||||
            .ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
 | 
			
		||||
            .ToConcurrent();
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm)
 | 
			
		||||
    {
 | 
			
		||||
        commandName = commandName.ToLowerInvariant();
 | 
			
		||||
@@ -48,95 +48,82 @@ public class DiscordPermOverrideService : INService, ILateBlocker
 | 
			
		||||
    public async Task AddOverride(ulong guildId, string commandName, GuildPerm perm)
 | 
			
		||||
    {
 | 
			
		||||
        commandName = commandName.ToLowerInvariant();
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var over = await uow
 | 
			
		||||
            .Set<DiscordPermOverride>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
 | 
			
		||||
 | 
			
		||||
        if (over is null)
 | 
			
		||||
        {
 | 
			
		||||
            var over = await uow
 | 
			
		||||
                .Set<DiscordPermOverride>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
 | 
			
		||||
 | 
			
		||||
            if (over is null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Set<DiscordPermOverride>()
 | 
			
		||||
                    .Add(over = new()
 | 
			
		||||
                    {
 | 
			
		||||
                        Command = commandName,
 | 
			
		||||
                        Perm = perm,
 | 
			
		||||
                        GuildId = guildId,
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                over.Perm = perm;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _overrides[(guildId, commandName)] = over;
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            uow.Set<DiscordPermOverride>()
 | 
			
		||||
                .Add(over = new() { Command = commandName, Perm = perm, GuildId = guildId, });
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            over.Perm = perm;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _overrides[(guildId, commandName)] = over;
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ClearAllOverrides(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var overrides = await uow
 | 
			
		||||
                .Set<DiscordPermOverride>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                .ToListAsync();
 | 
			
		||||
                
 | 
			
		||||
            uow.RemoveRange(overrides);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var overrides = await uow
 | 
			
		||||
            .Set<DiscordPermOverride>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
 | 
			
		||||
            foreach (var over in overrides)
 | 
			
		||||
            {
 | 
			
		||||
                _overrides.TryRemove((guildId, over.Command), out _);
 | 
			
		||||
            }
 | 
			
		||||
        uow.RemoveRange(overrides);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        foreach (var over in overrides)
 | 
			
		||||
        {
 | 
			
		||||
            _overrides.TryRemove((guildId, over.Command), out _);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    public async Task RemoveOverride(ulong guildId, string commandName)
 | 
			
		||||
    {
 | 
			
		||||
        commandName = commandName.ToLowerInvariant();
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var over = await uow
 | 
			
		||||
                .Set<DiscordPermOverride>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var over = await uow
 | 
			
		||||
            .Set<DiscordPermOverride>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
 | 
			
		||||
 | 
			
		||||
            if (over is null)
 | 
			
		||||
                return;
 | 
			
		||||
                
 | 
			
		||||
            uow.Remove(over);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        if (over is null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
            _overrides.TryRemove((guildId, commandName), out _);
 | 
			
		||||
        }
 | 
			
		||||
        uow.Remove(over);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        _overrides.TryRemove((guildId, commandName), out _);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
 | 
			
		||||
    public async Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow
 | 
			
		||||
                .Set<DiscordPermOverride>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                .ToListAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        return await uow
 | 
			
		||||
            .Set<DiscordPermOverride>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command)
 | 
			
		||||
    {
 | 
			
		||||
        if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && perm is not null)
 | 
			
		||||
        {
 | 
			
		||||
            var result = await new RequireUserPermissionAttribute((GuildPermission) perm)
 | 
			
		||||
            var result = await new RequireUserPermissionAttribute((GuildPermission)perm)
 | 
			
		||||
                .CheckPermissionsAsync(context, command, _services);
 | 
			
		||||
 | 
			
		||||
            return !result.IsSuccess;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,25 +56,23 @@ public class GameVoiceChannelService : INService
 | 
			
		||||
    public ulong? ToggleGameVoiceChannel(ulong guildId, ulong vchId)
 | 
			
		||||
    {
 | 
			
		||||
        ulong? id;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
 | 
			
		||||
        if (gc.GameVoiceChannel == vchId)
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
 | 
			
		||||
            if (gc.GameVoiceChannel == vchId)
 | 
			
		||||
            {
 | 
			
		||||
                GameVoiceChannels.TryRemove(vchId);
 | 
			
		||||
                id = gc.GameVoiceChannel = null;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (gc.GameVoiceChannel != null)
 | 
			
		||||
                    GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
 | 
			
		||||
                GameVoiceChannels.Add(vchId);
 | 
			
		||||
                id = gc.GameVoiceChannel = vchId;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            GameVoiceChannels.TryRemove(vchId);
 | 
			
		||||
            id = gc.GameVoiceChannel = null;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (gc.GameVoiceChannel != null)
 | 
			
		||||
                GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
 | 
			
		||||
            GameVoiceChannels.Add(vchId);
 | 
			
		||||
            id = gc.GameVoiceChannel = vchId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,18 +59,16 @@ public class GuildTimezoneService : INService
 | 
			
		||||
 | 
			
		||||
    public void SetTimeZone(ulong guildId, TimeZoneInfo tz)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
 | 
			
		||||
            gc.TimeZoneId = tz?.Id;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        gc.TimeZoneId = tz?.Id;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
            if (tz is null)
 | 
			
		||||
                _timezones.TryRemove(guildId, out tz);
 | 
			
		||||
            else
 | 
			
		||||
                _timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
 | 
			
		||||
        }
 | 
			
		||||
        if (tz is null)
 | 
			
		||||
            _timezones.TryRemove(guildId, out tz);
 | 
			
		||||
        else
 | 
			
		||||
            _timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
 | 
			
		||||
 
 | 
			
		||||
@@ -198,30 +198,28 @@ public sealed class LogCommandService : ILogCommandService
 | 
			
		||||
 | 
			
		||||
    public async Task LogServer(ulong guildId, ulong channelId, bool value)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var logSetting = uow.LogSettingsFor(guildId);
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var logSetting = uow.LogSettingsFor(guildId);
 | 
			
		||||
                
 | 
			
		||||
            logSetting.LogOtherId =
 | 
			
		||||
                logSetting.MessageUpdatedId =
 | 
			
		||||
                    logSetting.MessageDeletedId =
 | 
			
		||||
                        logSetting.UserJoinedId =
 | 
			
		||||
                            logSetting.UserLeftId =
 | 
			
		||||
                                logSetting.UserBannedId =
 | 
			
		||||
                                    logSetting.UserUnbannedId =
 | 
			
		||||
                                        logSetting.UserUpdatedId =
 | 
			
		||||
                                            logSetting.ChannelCreatedId =
 | 
			
		||||
                                                logSetting.ChannelDestroyedId =
 | 
			
		||||
                                                    logSetting.ChannelUpdatedId =
 | 
			
		||||
                                                        logSetting.LogUserPresenceId =
 | 
			
		||||
                                                            logSetting.LogVoicePresenceId =
 | 
			
		||||
                                                                logSetting.UserMutedId =
 | 
			
		||||
                                                                    logSetting.LogVoicePresenceTTSId =
 | 
			
		||||
                                                                        value ? channelId : null;
 | 
			
		||||
            ;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            GuildLogSettings.AddOrUpdate(guildId, id => logSetting, (id, old) => logSetting);
 | 
			
		||||
        }
 | 
			
		||||
        logSetting.LogOtherId =
 | 
			
		||||
            logSetting.MessageUpdatedId =
 | 
			
		||||
                logSetting.MessageDeletedId =
 | 
			
		||||
                    logSetting.UserJoinedId =
 | 
			
		||||
                        logSetting.UserLeftId =
 | 
			
		||||
                            logSetting.UserBannedId =
 | 
			
		||||
                                logSetting.UserUnbannedId =
 | 
			
		||||
                                    logSetting.UserUpdatedId =
 | 
			
		||||
                                        logSetting.ChannelCreatedId =
 | 
			
		||||
                                            logSetting.ChannelDestroyedId =
 | 
			
		||||
                                                logSetting.ChannelUpdatedId =
 | 
			
		||||
                                                    logSetting.LogUserPresenceId =
 | 
			
		||||
                                                        logSetting.LogVoicePresenceId =
 | 
			
		||||
                                                            logSetting.UserMutedId =
 | 
			
		||||
                                                                logSetting.LogVoicePresenceTTSId =
 | 
			
		||||
                                                                    value ? channelId : null;
 | 
			
		||||
        ;
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        GuildLogSettings.AddOrUpdate(guildId, id => logSetting, (id, old) => logSetting);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
 | 
			
		||||
@@ -1193,60 +1191,58 @@ public sealed class LogCommandService : ILogCommandService
 | 
			
		||||
 | 
			
		||||
    private void UnsetLogSetting(ulong guildId, LogType logChannelType)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var newLogSetting = uow.LogSettingsFor(guildId);
 | 
			
		||||
        switch (logChannelType)
 | 
			
		||||
        {
 | 
			
		||||
            var newLogSetting = uow.LogSettingsFor(guildId);
 | 
			
		||||
            switch (logChannelType)
 | 
			
		||||
            {
 | 
			
		||||
                case LogType.Other:
 | 
			
		||||
                    newLogSetting.LogOtherId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.MessageUpdated:
 | 
			
		||||
                    newLogSetting.MessageUpdatedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.MessageDeleted:
 | 
			
		||||
                    newLogSetting.MessageDeletedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserJoined:
 | 
			
		||||
                    newLogSetting.UserJoinedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserLeft:
 | 
			
		||||
                    newLogSetting.UserLeftId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserBanned:
 | 
			
		||||
                    newLogSetting.UserBannedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserUnbanned:
 | 
			
		||||
                    newLogSetting.UserUnbannedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserUpdated:
 | 
			
		||||
                    newLogSetting.UserUpdatedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserMuted:
 | 
			
		||||
                    newLogSetting.UserMutedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.ChannelCreated:
 | 
			
		||||
                    newLogSetting.ChannelCreatedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.ChannelDestroyed:
 | 
			
		||||
                    newLogSetting.ChannelDestroyedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.ChannelUpdated:
 | 
			
		||||
                    newLogSetting.ChannelUpdatedId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.UserPresence:
 | 
			
		||||
                    newLogSetting.LogUserPresenceId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.VoicePresence:
 | 
			
		||||
                    newLogSetting.LogVoicePresenceId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.VoicePresenceTTS:
 | 
			
		||||
                    newLogSetting.LogVoicePresenceTTSId = null;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            case LogType.Other:
 | 
			
		||||
                newLogSetting.LogOtherId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.MessageUpdated:
 | 
			
		||||
                newLogSetting.MessageUpdatedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.MessageDeleted:
 | 
			
		||||
                newLogSetting.MessageDeletedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserJoined:
 | 
			
		||||
                newLogSetting.UserJoinedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserLeft:
 | 
			
		||||
                newLogSetting.UserLeftId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserBanned:
 | 
			
		||||
                newLogSetting.UserBannedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserUnbanned:
 | 
			
		||||
                newLogSetting.UserUnbannedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserUpdated:
 | 
			
		||||
                newLogSetting.UserUpdatedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserMuted:
 | 
			
		||||
                newLogSetting.UserMutedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.ChannelCreated:
 | 
			
		||||
                newLogSetting.ChannelCreatedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.ChannelDestroyed:
 | 
			
		||||
                newLogSetting.ChannelDestroyedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.ChannelUpdated:
 | 
			
		||||
                newLogSetting.ChannelUpdatedId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserPresence:
 | 
			
		||||
                newLogSetting.LogUserPresenceId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.VoicePresence:
 | 
			
		||||
                newLogSetting.LogVoicePresenceId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.VoicePresenceTTS:
 | 
			
		||||
                newLogSetting.LogVoicePresenceTTSId = null;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -163,13 +163,11 @@ public class MuteService : INService
 | 
			
		||||
 | 
			
		||||
    public async Task SetMuteRoleAsync(ulong guildId, string name)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            config.MuteRoleName = name;
 | 
			
		||||
            GuildMuteRoles.AddOrUpdate(guildId, name, (id, old) => name);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        config.MuteRoleName = name;
 | 
			
		||||
        GuildMuteRoles.AddOrUpdate(guildId, name, (id, old) => name);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task MuteUser(IGuildUser usr, IUser mod, MuteType type = MuteType.All, string reason = "")
 | 
			
		||||
@@ -445,24 +443,22 @@ public class MuteService : INService
 | 
			
		||||
 | 
			
		||||
    private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        object toDelete;
 | 
			
		||||
        if (type == TimerType.Mute)
 | 
			
		||||
        {
 | 
			
		||||
            object toDelete;
 | 
			
		||||
            if (type == TimerType.Mute)
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
                toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnbanTimer));
 | 
			
		||||
                toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId);
 | 
			
		||||
            }
 | 
			
		||||
            if (toDelete != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(toDelete);
 | 
			
		||||
            }
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
            toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnbanTimer));
 | 
			
		||||
            toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId);
 | 
			
		||||
        }
 | 
			
		||||
        if (toDelete != null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.Remove(toDelete);
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -290,13 +290,11 @@ public class ProtectionService : INService
 | 
			
		||||
 | 
			
		||||
        _antiRaidGuilds.AddOrUpdate(guildId, stats, (key, old) => stats);
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
 | 
			
		||||
 | 
			
		||||
            gc.AntiRaidSetting = stats.AntiRaidSettings;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        gc.AntiRaidSetting = stats.AntiRaidSettings;
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return stats;
 | 
			
		||||
    }
 | 
			
		||||
@@ -305,13 +303,11 @@ public class ProtectionService : INService
 | 
			
		||||
    {
 | 
			
		||||
        if (_antiRaidGuilds.TryRemove(guildId, out _))
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
 | 
			
		||||
            using var uow = _db.GetDbContext();
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
 | 
			
		||||
 | 
			
		||||
                gc.AntiRaidSetting = null;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            gc.AntiRaidSetting = null;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -322,14 +318,12 @@ public class ProtectionService : INService
 | 
			
		||||
        if (_antiSpamGuilds.TryRemove(guildId, out var removed))
 | 
			
		||||
        {
 | 
			
		||||
            removed.UserStats.ForEach(x => x.Value.Dispose());
 | 
			
		||||
            using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting)
 | 
			
		||||
                    .ThenInclude(x => x.IgnoredChannels));
 | 
			
		||||
            using var uow = _db.GetDbContext();
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting)
 | 
			
		||||
                .ThenInclude(x => x.IgnoredChannels));
 | 
			
		||||
 | 
			
		||||
                gc.AntiSpamSetting = null;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            gc.AntiSpamSetting = null;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -361,23 +355,21 @@ public class ProtectionService : INService
 | 
			
		||||
            return stats;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
 | 
			
		||||
 | 
			
		||||
            if (gc.AntiSpamSetting != null)
 | 
			
		||||
            {
 | 
			
		||||
                gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
 | 
			
		||||
                gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
 | 
			
		||||
                gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
 | 
			
		||||
                gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                gc.AntiSpamSetting = stats.AntiSpamSettings;
 | 
			
		||||
            }
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        if (gc.AntiSpamSetting != null)
 | 
			
		||||
        {
 | 
			
		||||
            gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
 | 
			
		||||
            gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
 | 
			
		||||
            gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
 | 
			
		||||
            gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            gc.AntiSpamSetting = stats.AntiSpamSettings;
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return stats;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -388,34 +380,32 @@ public class ProtectionService : INService
 | 
			
		||||
            ChannelId = channelId
 | 
			
		||||
        };
 | 
			
		||||
        bool added;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
 | 
			
		||||
        var spam = gc.AntiSpamSetting;
 | 
			
		||||
        if (spam is null)
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
 | 
			
		||||
            var spam = gc.AntiSpamSetting;
 | 
			
		||||
            if (spam is null)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful
 | 
			
		||||
            {
 | 
			
		||||
                if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
 | 
			
		||||
                    temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
 | 
			
		||||
                added = true;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId);
 | 
			
		||||
                uow.Set<AntiSpamIgnore>().Remove(toRemove); // remove from db
 | 
			
		||||
                if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
 | 
			
		||||
                {
 | 
			
		||||
                    temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache
 | 
			
		||||
                }
 | 
			
		||||
                added = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful
 | 
			
		||||
        {
 | 
			
		||||
            if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
 | 
			
		||||
                temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
 | 
			
		||||
            added = true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId);
 | 
			
		||||
            uow.Set<AntiSpamIgnore>().Remove(toRemove); // remove from db
 | 
			
		||||
            if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
 | 
			
		||||
            {
 | 
			
		||||
                temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache
 | 
			
		||||
            }
 | 
			
		||||
            added = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return added;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -163,19 +163,17 @@ public class RoleCommandsService : INService
 | 
			
		||||
 | 
			
		||||
    public void Remove(ulong id, int index)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(id,
 | 
			
		||||
                set => set.Include(x => x.ReactionRoleMessages)
 | 
			
		||||
                    .ThenInclude(x => x.ReactionRoles));
 | 
			
		||||
            uow.Set<ReactionRole>()
 | 
			
		||||
                .RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
 | 
			
		||||
            gc.ReactionRoleMessages.RemoveAt(index);
 | 
			
		||||
            _models.AddOrUpdate(id,
 | 
			
		||||
                gc.ReactionRoleMessages,
 | 
			
		||||
                delegate { return gc.ReactionRoleMessages; });
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(id,
 | 
			
		||||
            set => set.Include(x => x.ReactionRoleMessages)
 | 
			
		||||
                .ThenInclude(x => x.ReactionRoles));
 | 
			
		||||
        uow.Set<ReactionRole>()
 | 
			
		||||
            .RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
 | 
			
		||||
        gc.ReactionRoleMessages.RemoveAt(index);
 | 
			
		||||
        _models.AddOrUpdate(id,
 | 
			
		||||
            gc.ReactionRoleMessages,
 | 
			
		||||
            delegate { return gc.ReactionRoleMessages; });
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,34 +33,30 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
 | 
			
		||||
    public bool AddNew(ulong guildId, IRole role, int group)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
        if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
 | 
			
		||||
        {
 | 
			
		||||
            var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
            if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uow.SelfAssignableRoles.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                Group = group,
 | 
			
		||||
                RoleId = role.Id,
 | 
			
		||||
                GuildId = role.Guild.Id
 | 
			
		||||
            });
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uow.SelfAssignableRoles.Add(new()
 | 
			
		||||
        {
 | 
			
		||||
            Group = @group,
 | 
			
		||||
            RoleId = role.Id,
 | 
			
		||||
            GuildId = role.Guild.Id
 | 
			
		||||
        });
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ToggleAdSarm(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        bool newval;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return newval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -129,33 +125,31 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
    public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
 | 
			
		||||
    {
 | 
			
		||||
        var set = false;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
 | 
			
		||||
        var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == @group);
 | 
			
		||||
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
 | 
			
		||||
            var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
            {
 | 
			
		||||
                if (toUpdate != null)
 | 
			
		||||
                    gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
 | 
			
		||||
            }
 | 
			
		||||
            else if (toUpdate is null)
 | 
			
		||||
            {
 | 
			
		||||
                gc.SelfAssignableRoleGroupNames.Add(new()
 | 
			
		||||
                {
 | 
			
		||||
                    Name = name,
 | 
			
		||||
                    Number = group,
 | 
			
		||||
                });
 | 
			
		||||
                set = true;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                toUpdate.Name = name;
 | 
			
		||||
                set = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            if (toUpdate != null)
 | 
			
		||||
                gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
 | 
			
		||||
        }
 | 
			
		||||
        else if (toUpdate is null)
 | 
			
		||||
        {
 | 
			
		||||
            gc.SelfAssignableRoleGroupNames.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                Name = name,
 | 
			
		||||
                Number = @group,
 | 
			
		||||
            });
 | 
			
		||||
            set = true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            toUpdate.Name = name;
 | 
			
		||||
            set = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return set;
 | 
			
		||||
    }
 | 
			
		||||
@@ -187,42 +181,36 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
    public bool RemoveSar(ulong guildId, ulong roleId)
 | 
			
		||||
    {
 | 
			
		||||
        bool success;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            success = uow.SelfAssignableRoles.DeleteByGuildAndRoleId(guildId, roleId);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        success = uow.SelfAssignableRoles.DeleteByGuildAndRoleId(guildId, roleId);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
 | 
			
		||||
            var exclusive = gc.ExclusiveSelfAssignedRoles;
 | 
			
		||||
            var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
 | 
			
		||||
        var exclusive = gc.ExclusiveSelfAssignedRoles;
 | 
			
		||||
        var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
 | 
			
		||||
            return (autoDelete, exclusive, roles);
 | 
			
		||||
        }
 | 
			
		||||
        return (autoDelete, exclusive, roles);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool SetLevelReq(ulong guildId, IRole role, int level)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
        var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
 | 
			
		||||
        if (sar != null)
 | 
			
		||||
        {
 | 
			
		||||
            var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
 | 
			
		||||
            var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
 | 
			
		||||
            if (sar != null)
 | 
			
		||||
            {
 | 
			
		||||
                sar.LevelRequirement = level;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            sar.LevelRequirement = level;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
@@ -231,13 +219,11 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
    public bool ToggleEsar(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        bool areExclusive;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
 | 
			
		||||
            areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return areExclusive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -273,20 +273,18 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
 | 
			
		||||
 | 
			
		||||
    public bool RemoveStartupCommand(int index, out AutoCommand cmd)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            cmd = uow.AutoCommands
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.Interval == 0)
 | 
			
		||||
                .Skip(index)
 | 
			
		||||
                .FirstOrDefault();
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        cmd = uow.AutoCommands
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.Interval == 0)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (cmd != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(cmd);
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        if (cmd != null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.Remove(cmd);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -294,23 +292,21 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
 | 
			
		||||
        
 | 
			
		||||
    public bool RemoveAutoCommand(int index, out AutoCommand cmd)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            cmd = uow.AutoCommands
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.Interval >= 5)
 | 
			
		||||
                .Skip(index)
 | 
			
		||||
                .FirstOrDefault();
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        cmd = uow.AutoCommands
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.Interval >= 5)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (cmd != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(cmd);
 | 
			
		||||
                if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
 | 
			
		||||
                    if (autos.TryRemove(cmd.Id, out var timer))
 | 
			
		||||
                        timer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        if (cmd != null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.Remove(cmd);
 | 
			
		||||
            if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
 | 
			
		||||
                if (autos.TryRemove(cmd.Id, out var timer))
 | 
			
		||||
                    timer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -326,35 +322,29 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
 | 
			
		||||
 | 
			
		||||
        var uri = new Uri(img);
 | 
			
		||||
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using (var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
 | 
			
		||||
        {
 | 
			
		||||
            if (!sr.IsImage())
 | 
			
		||||
                return false;
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        using var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
 | 
			
		||||
        if (!sr.IsImage())
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            // i can't just do ReadAsStreamAsync because dicord.net's image poops itself
 | 
			
		||||
            var imgData = await sr.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
 | 
			
		||||
            await using (var imgStream = imgData.ToStream())
 | 
			
		||||
            {
 | 
			
		||||
                await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // i can't just do ReadAsStreamAsync because dicord.net's image poops itself
 | 
			
		||||
        var imgData = await sr.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
 | 
			
		||||
        await using var imgStream = imgData.ToStream();
 | 
			
		||||
        await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void ClearStartupCommands()
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var toRemove = uow
 | 
			
		||||
                .AutoCommands
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.Interval == 0);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var toRemove = uow
 | 
			
		||||
            .AutoCommands
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.Interval == 0);
 | 
			
		||||
 | 
			
		||||
            uow.AutoCommands.RemoveRange(toRemove);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.AutoCommands.RemoveRange(toRemove);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task ReloadImagesAsync() 
 | 
			
		||||
 
 | 
			
		||||
@@ -198,54 +198,50 @@ public class UserPunishService : INService
 | 
			
		||||
 | 
			
		||||
    public async Task CheckAllWarnExpiresAsync()
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var cleared = await uow.Database.ExecuteSqlRawAsync($@"UPDATE Warnings
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var cleared = await uow.Database.ExecuteSqlRawAsync($@"UPDATE Warnings
 | 
			
		||||
SET Forgiven = 1,
 | 
			
		||||
    ForgivenBy = 'Expiry'
 | 
			
		||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 0)
 | 
			
		||||
	AND Forgiven = 0
 | 
			
		||||
	AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
 | 
			
		||||
 | 
			
		||||
            var deleted = await uow.Database.ExecuteSqlRawAsync($@"DELETE FROM Warnings
 | 
			
		||||
        var deleted = await uow.Database.ExecuteSqlRawAsync($@"DELETE FROM Warnings
 | 
			
		||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 1)
 | 
			
		||||
	AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
 | 
			
		||||
 | 
			
		||||
            if(cleared > 0 || deleted > 0)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
 | 
			
		||||
            }
 | 
			
		||||
        if(cleared > 0 || deleted > 0)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task CheckWarnExpiresAsync(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GuildConfigsForId(guildId, inc => inc);
 | 
			
		||||
 | 
			
		||||
        if (config.WarnExpireHours == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var hours = $"{-config.WarnExpireHours} hours";
 | 
			
		||||
        if (config.WarnExpireAction == WarnExpireAction.Clear)
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GuildConfigsForId(guildId, inc => inc);
 | 
			
		||||
 | 
			
		||||
            if (config.WarnExpireHours == 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var hours = $"{-config.WarnExpireHours} hours";
 | 
			
		||||
            if (config.WarnExpireAction == WarnExpireAction.Clear)
 | 
			
		||||
            {
 | 
			
		||||
                await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
 | 
			
		||||
            await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
 | 
			
		||||
SET Forgiven = 1,
 | 
			
		||||
    ForgivenBy = 'Expiry'
 | 
			
		||||
WHERE GuildId={guildId}
 | 
			
		||||
    AND Forgiven = 0
 | 
			
		||||
    AND DateAdded < datetime('now', {hours})");
 | 
			
		||||
            }
 | 
			
		||||
            else if (config.WarnExpireAction == WarnExpireAction.Delete)
 | 
			
		||||
            {
 | 
			
		||||
                await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
 | 
			
		||||
        }
 | 
			
		||||
        else if (config.WarnExpireAction == WarnExpireAction.Delete)
 | 
			
		||||
        {
 | 
			
		||||
            await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
 | 
			
		||||
WHERE GuildId={guildId}
 | 
			
		||||
    AND DateAdded < datetime('now', {hours})");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<int> GetWarnExpire(ulong guildId)
 | 
			
		||||
@@ -275,35 +271,29 @@ WHERE GuildId={guildId}
 | 
			
		||||
 | 
			
		||||
    public IGrouping<ulong, Warning>[] WarnlogAll(ulong gid)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.Warnings.GetForGuild(gid).GroupBy(x => x.UserId).ToArray();
 | 
			
		||||
        }
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
        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())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        if (index == 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (index == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await uow.Warnings.ForgiveAll(guildId, userId, moderator);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
 | 
			
		||||
            }
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            await uow.Warnings.ForgiveAll(guildId, userId, moderator);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -315,22 +305,20 @@ WHERE GuildId={guildId}
 | 
			
		||||
        if (number <= 0 || (time != null && time.Time > TimeSpan.FromDays(49)))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        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()
 | 
			
		||||
        {
 | 
			
		||||
            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();
 | 
			
		||||
        }
 | 
			
		||||
            Count = number,
 | 
			
		||||
            Punishment = punish,
 | 
			
		||||
            Time = (int?)time?.Time.TotalMinutes ?? 0,
 | 
			
		||||
            RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?),
 | 
			
		||||
        });
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -339,29 +327,26 @@ WHERE GuildId={guildId}
 | 
			
		||||
        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);
 | 
			
		||||
        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 != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(p);
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
        if (p != 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();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
 | 
			
		||||
            .WarnPunishments
 | 
			
		||||
            .OrderBy(x => x.Count)
 | 
			
		||||
            .ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(SocketGuild guild, string people)
 | 
			
		||||
@@ -403,45 +388,41 @@ WHERE GuildId={guildId}
 | 
			
		||||
 | 
			
		||||
    public string GetBanTemplate(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var template = uow.BanTemplates
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .FirstOrDefault(x => x.GuildId == guildId);
 | 
			
		||||
            return template?.Text;
 | 
			
		||||
        }
 | 
			
		||||
        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())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var template = uow.BanTemplates
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .FirstOrDefault(x => x.GuildId == guildId);
 | 
			
		||||
 | 
			
		||||
        if (text is null)
 | 
			
		||||
        {
 | 
			
		||||
            var template = uow.BanTemplates
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .FirstOrDefault(x => x.GuildId == guildId);
 | 
			
		||||
 | 
			
		||||
            if (text is null)
 | 
			
		||||
            {
 | 
			
		||||
                if (template is null)
 | 
			
		||||
                    return;
 | 
			
		||||
            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();
 | 
			
		||||
            uow.Remove(template);
 | 
			
		||||
        }
 | 
			
		||||
        else if (template is null)
 | 
			
		||||
        {
 | 
			
		||||
            uow.BanTemplates.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                GuildId = guildId,
 | 
			
		||||
                Text = text,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            template.Text = text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SmartText GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage,
 | 
			
		||||
 
 | 
			
		||||
@@ -117,12 +117,10 @@ public class VcRoleService : INService
 | 
			
		||||
 | 
			
		||||
        if (missingRoles.Any())
 | 
			
		||||
        {
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
 | 
			
		||||
                uow.RemoveRange(missingRoles);
 | 
			
		||||
                await uow.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
            await using var uow = _db.GetDbContext();
 | 
			
		||||
            Log.Warning($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
 | 
			
		||||
            uow.RemoveRange(missingRoles);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -134,21 +132,19 @@ public class VcRoleService : INService
 | 
			
		||||
        var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
 | 
			
		||||
 | 
			
		||||
        guildVcRoles.AddOrUpdate(vcId, role, (key, old) => role);
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
        var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one
 | 
			
		||||
        if(toDelete != null)
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
            var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one
 | 
			
		||||
            if(toDelete != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(toDelete);
 | 
			
		||||
            }
 | 
			
		||||
            conf.VcRoleInfos.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                VoiceChannelId = vcId,
 | 
			
		||||
                RoleId = role.Id,
 | 
			
		||||
            }); // add new one
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            uow.Remove(toDelete);
 | 
			
		||||
        }
 | 
			
		||||
        conf.VcRoleInfos.Add(new()
 | 
			
		||||
        {
 | 
			
		||||
            VoiceChannelId = vcId,
 | 
			
		||||
            RoleId = role.Id,
 | 
			
		||||
        }); // add new one
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool RemoveVcRole(ulong guildId, ulong vcId)
 | 
			
		||||
@@ -159,13 +155,11 @@ public class VcRoleService : INService
 | 
			
		||||
        if (!guildVcRoles.TryRemove(vcId, out _))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
            var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
 | 
			
		||||
            uow.RemoveRange(toRemove);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
        var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
 | 
			
		||||
        uow.RemoveRange(toRemove);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -30,15 +30,13 @@ public partial class Gambling
 | 
			
		||||
            var num1 = gen / 10;
 | 
			
		||||
            var num2 = gen % 10;
 | 
			
		||||
 | 
			
		||||
            using (var img1 = GetDice(num1))
 | 
			
		||||
            using (var img2 = GetDice(num2))
 | 
			
		||||
            using (var img = new[] { img1, img2 }.Merge(out var format))
 | 
			
		||||
            await using (var ms = img.ToStream(format))
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(ms,
 | 
			
		||||
                    $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                    Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
 | 
			
		||||
            }
 | 
			
		||||
            using var img1 = GetDice(num1);
 | 
			
		||||
            using var img2 = GetDice(num2);
 | 
			
		||||
            using var img = new[] { img1, img2 }.Merge(out var format);
 | 
			
		||||
            await using var ms = img.ToStream(format);
 | 
			
		||||
            await ctx.Channel.SendFileAsync(ms,
 | 
			
		||||
                $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
@@ -108,21 +106,19 @@ public partial class Gambling
 | 
			
		||||
                values.Insert(toInsert, randomNumber);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var bitmap = dice.Merge(out var format))
 | 
			
		||||
            await using (var ms = bitmap.ToStream(format))
 | 
			
		||||
            using var bitmap = dice.Merge(out var format);
 | 
			
		||||
            await using var ms = bitmap.ToStream(format);
 | 
			
		||||
            foreach (var d in dice)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var d in dice)
 | 
			
		||||
                {
 | 
			
		||||
                    d.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                    Format.Bold(ctx.User.ToString()) + " " +
 | 
			
		||||
                    GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) +
 | 
			
		||||
                    " " + GetText(strs.total_average(
 | 
			
		||||
                        Format.Bold(values.Sum().ToString()),
 | 
			
		||||
                        Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
 | 
			
		||||
                d.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                Format.Bold(ctx.User.ToString()) + " " +
 | 
			
		||||
                GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) +
 | 
			
		||||
                " " + GetText(strs.total_average(
 | 
			
		||||
                    Format.Bold(values.Sum().ToString()),
 | 
			
		||||
                    Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task InternallDndRoll(string arg, bool ordered)
 | 
			
		||||
@@ -211,11 +207,9 @@ public partial class Gambling
 | 
			
		||||
            if (num == 10)
 | 
			
		||||
            {
 | 
			
		||||
                var images = _images.Dice;
 | 
			
		||||
                using (var imgOne = Image.Load(images[1]))
 | 
			
		||||
                using (var imgZero = Image.Load(images[0]))
 | 
			
		||||
                {
 | 
			
		||||
                    return new[] { imgOne, imgZero }.Merge();
 | 
			
		||||
                }
 | 
			
		||||
                using var imgOne = Image.Load(images[1]);
 | 
			
		||||
                using var imgZero = Image.Load(images[0]);
 | 
			
		||||
                return new[] { imgOne, imgZero }.Merge();
 | 
			
		||||
            }
 | 
			
		||||
            return Image.Load(_images.Dice[num]);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -44,22 +44,21 @@ public partial class Gambling
 | 
			
		||||
                cardObjects.Add(currentCard);
 | 
			
		||||
                images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_'))));
 | 
			
		||||
            }
 | 
			
		||||
            using (var img = images.Merge())
 | 
			
		||||
 | 
			
		||||
            using var img = images.Merge();
 | 
			
		||||
            foreach (var i in images)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var i in images)
 | 
			
		||||
                {
 | 
			
		||||
                    i.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var toSend = $"{Format.Bold(ctx.User.ToString())}";
 | 
			
		||||
                if (cardObjects.Count == 5)
 | 
			
		||||
                    toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
 | 
			
		||||
 | 
			
		||||
                if (guildId != null)
 | 
			
		||||
                    toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
 | 
			
		||||
 | 
			
		||||
                return (img.ToStream(), toSend);
 | 
			
		||||
                i.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var toSend = $"{Format.Bold(ctx.User.ToString())}";
 | 
			
		||||
            if (cardObjects.Count == 5)
 | 
			
		||||
                toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
 | 
			
		||||
 | 
			
		||||
            if (guildId != null)
 | 
			
		||||
                toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
 | 
			
		||||
 | 
			
		||||
            return (img.ToStream(), toSend);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
 
 | 
			
		||||
@@ -47,20 +47,19 @@ public partial class Gambling
 | 
			
		||||
                    tailCount++;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            using (var img = imgs.Merge(out var format))
 | 
			
		||||
            await using (var stream = img.ToStream(format))
 | 
			
		||||
 | 
			
		||||
            using var img = imgs.Merge(out var format);
 | 
			
		||||
            await using var stream = img.ToStream(format);
 | 
			
		||||
            foreach (var i in imgs)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var i in imgs)
 | 
			
		||||
                {
 | 
			
		||||
                    i.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
                var msg = count != 1
 | 
			
		||||
                    ? Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_results(count, headCount, tailCount))
 | 
			
		||||
                    : Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flipped(headCount > 0
 | 
			
		||||
                        ? Format.Bold(GetText(strs.heads))
 | 
			
		||||
                        : Format.Bold(GetText(strs.tails))));
 | 
			
		||||
                await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
 | 
			
		||||
                i.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            var msg = count != 1
 | 
			
		||||
                ? Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_results(count, headCount, tailCount))
 | 
			
		||||
                : Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flipped(headCount > 0
 | 
			
		||||
                    ? Format.Bold(GetText(strs.heads))
 | 
			
		||||
                    : Format.Bold(GetText(strs.tails))));
 | 
			
		||||
            await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public enum BetFlipGuess
 | 
			
		||||
 
 | 
			
		||||
@@ -538,10 +538,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, page).ToList();
 | 
			
		||||
            }
 | 
			
		||||
            await using var uow = _db.GetDbContext();
 | 
			
		||||
            cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, page).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page, curPage =>
 | 
			
		||||
@@ -553,10 +551,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            List<DiscordUser> toSend;
 | 
			
		||||
            if (!opts.Clean)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
 | 
			
		||||
                }
 | 
			
		||||
                using var uow = _db.GetDbContext();
 | 
			
		||||
                toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,21 +41,20 @@ public class GamblingService : INService
 | 
			
		||||
                if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
 | 
			
		||||
                using var uow = _db.GetDbContext();
 | 
			
		||||
                var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
 | 
			
		||||
                        
 | 
			
		||||
                    if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
 | 
			
		||||
                        return;
 | 
			
		||||
                if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
 | 
			
		||||
                    return;
 | 
			
		||||
                        
 | 
			
		||||
                    Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
 | 
			
		||||
                                    $"| max: {maxDecay} " +
 | 
			
		||||
                                    $"| threshold: {config.Decay.MinThreshold}");
 | 
			
		||||
                Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
 | 
			
		||||
                                $"| max: {maxDecay} " +
 | 
			
		||||
                                $"| threshold: {config.Decay.MinThreshold}");
 | 
			
		||||
                         
 | 
			
		||||
                    if (maxDecay == 0)
 | 
			
		||||
                        maxDecay = int.MaxValue;
 | 
			
		||||
                if (maxDecay == 0)
 | 
			
		||||
                    maxDecay = int.MaxValue;
 | 
			
		||||
                         
 | 
			
		||||
                    uow.Database.ExecuteSqlInterpolated($@"
 | 
			
		||||
                uow.Database.ExecuteSqlInterpolated($@"
 | 
			
		||||
UPDATE DiscordUser
 | 
			
		||||
SET CurrencyAmount=
 | 
			
		||||
    CASE WHEN
 | 
			
		||||
@@ -67,9 +66,8 @@ SET CurrencyAmount=
 | 
			
		||||
    END
 | 
			
		||||
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
 | 
			
		||||
 | 
			
		||||
                    _cache.SetLastCurrencyDecay();
 | 
			
		||||
                    uow.SaveChanges();
 | 
			
		||||
                }
 | 
			
		||||
                _cache.SetLastCurrencyDecay();
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -44,18 +44,16 @@ public class PlantPickService : INService
 | 
			
		||||
        _gss = gss;
 | 
			
		||||
 | 
			
		||||
        cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
 | 
			
		||||
        using (var uow = db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var guildIds = client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
            var configs = uow.Set<GuildConfig>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Include(x => x.GenerateCurrencyChannelIds)
 | 
			
		||||
                .Where(x => guildIds.Contains(x.GuildId))
 | 
			
		||||
                .ToList();
 | 
			
		||||
        using var uow = db.GetDbContext();
 | 
			
		||||
        var guildIds = client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
        var configs = uow.Set<GuildConfig>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Include(x => x.GenerateCurrencyChannelIds)
 | 
			
		||||
            .Where(x => guildIds.Contains(x.GuildId))
 | 
			
		||||
            .ToList();
 | 
			
		||||
                
 | 
			
		||||
            _generationChannels = new(configs
 | 
			
		||||
                .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
 | 
			
		||||
        }
 | 
			
		||||
        _generationChannels = new(configs
 | 
			
		||||
            .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string GetText(ulong gid, LocStr str)
 | 
			
		||||
@@ -64,39 +62,35 @@ public class PlantPickService : INService
 | 
			
		||||
    public bool ToggleCurrencyGeneration(ulong gid, ulong cid)
 | 
			
		||||
    {
 | 
			
		||||
        bool enabled;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
 | 
			
		||||
 | 
			
		||||
            var toAdd = new GCChannelId() { ChannelId = cid };
 | 
			
		||||
            if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
 | 
			
		||||
            {
 | 
			
		||||
                guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
 | 
			
		||||
                _generationChannels.Add(cid);
 | 
			
		||||
                enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
 | 
			
		||||
                if (toDelete != null)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Remove(toDelete);
 | 
			
		||||
                }
 | 
			
		||||
                _generationChannels.TryRemove(cid);
 | 
			
		||||
                enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        var toAdd = new GCChannelId() { ChannelId = cid };
 | 
			
		||||
        if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
 | 
			
		||||
        {
 | 
			
		||||
            guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
 | 
			
		||||
            _generationChannels.Add(cid);
 | 
			
		||||
            enabled = true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
 | 
			
		||||
            if (toDelete != null)
 | 
			
		||||
            {
 | 
			
		||||
                uow.Remove(toDelete);
 | 
			
		||||
            }
 | 
			
		||||
            _generationChannels.TryRemove(cid);
 | 
			
		||||
            enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<GuildConfigExtensions.GeneratingChannel> GetAllGeneratingChannels()
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var chs = uow.GuildConfigs.GetGeneratingChannels();
 | 
			
		||||
            return chs;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var chs = uow.GuildConfigs.GetGeneratingChannels();
 | 
			
		||||
        return chs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -139,31 +133,29 @@ public class PlantPickService : INService
 | 
			
		||||
    {
 | 
			
		||||
        // draw lower, it looks better
 | 
			
		||||
        pass = pass.TrimTo(10, true).ToLowerInvariant();
 | 
			
		||||
        using (var img = Image.Load<Rgba32>(curImg, out var format))
 | 
			
		||||
        using var img = Image.Load<Rgba32>(curImg, out var format);
 | 
			
		||||
        // choose font size based on the image height, so that it's visible
 | 
			
		||||
        var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
 | 
			
		||||
        img.Mutate(x =>
 | 
			
		||||
        {
 | 
			
		||||
            // choose font size based on the image height, so that it's visible
 | 
			
		||||
            var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
 | 
			
		||||
            img.Mutate(x =>
 | 
			
		||||
            {
 | 
			
		||||
                // measure the size of the text to be drawing
 | 
			
		||||
                var size = TextMeasurer.Measure(pass, new(font, new PointF(0, 0)));
 | 
			
		||||
            // measure the size of the text to be drawing
 | 
			
		||||
            var size = TextMeasurer.Measure(pass, new(font, new PointF(0, 0)));
 | 
			
		||||
 | 
			
		||||
                // fill the background with black, add 5 pixels on each side to make it look better
 | 
			
		||||
                x.FillPolygon(Color.ParseHex("00000080"),
 | 
			
		||||
                    new PointF(0, 0),
 | 
			
		||||
                    new PointF(size.Width + 5, 0),
 | 
			
		||||
                    new PointF(size.Width + 5, size.Height + 10),
 | 
			
		||||
                    new PointF(0, size.Height + 10));
 | 
			
		||||
            // fill the background with black, add 5 pixels on each side to make it look better
 | 
			
		||||
            x.FillPolygon(Color.ParseHex("00000080"),
 | 
			
		||||
                new PointF(0, 0),
 | 
			
		||||
                new PointF(size.Width + 5, 0),
 | 
			
		||||
                new PointF(size.Width + 5, size.Height + 10),
 | 
			
		||||
                new PointF(0, size.Height + 10));
 | 
			
		||||
 | 
			
		||||
                // draw the password over the background
 | 
			
		||||
                x.DrawText(pass,
 | 
			
		||||
                    font,
 | 
			
		||||
                    SixLabors.ImageSharp.Color.White,
 | 
			
		||||
                    new(0, 0));
 | 
			
		||||
            });
 | 
			
		||||
            // return image as a stream for easy sending
 | 
			
		||||
            return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
 | 
			
		||||
        }
 | 
			
		||||
            // draw the password over the background
 | 
			
		||||
            x.DrawText(pass,
 | 
			
		||||
                font,
 | 
			
		||||
                SixLabors.ImageSharp.Color.White,
 | 
			
		||||
                new(0, 0));
 | 
			
		||||
        });
 | 
			
		||||
        // return image as a stream for easy sending
 | 
			
		||||
        return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task PotentialFlowerGeneration(IUserMessage imsg)
 | 
			
		||||
@@ -309,13 +301,11 @@ public class PlantPickService : INService
 | 
			
		||||
                msgToSend += " " + GetText(gid, strs.pick_sn(prefix));
 | 
			
		||||
 | 
			
		||||
            //get the image
 | 
			
		||||
            await using (var stream = GetRandomCurrencyImage(pass, out var ext))
 | 
			
		||||
            {
 | 
			
		||||
                // send it
 | 
			
		||||
                var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
 | 
			
		||||
                // return sent message's id (in order to be able to delete it when it's picked)
 | 
			
		||||
                return msg.Id;
 | 
			
		||||
            }
 | 
			
		||||
            await using var stream = GetRandomCurrencyImage(pass, out var ext);
 | 
			
		||||
            // send it
 | 
			
		||||
            var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
 | 
			
		||||
            // return sent message's id (in order to be able to delete it when it's picked)
 | 
			
		||||
            return msg.Id;
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
@@ -353,18 +343,16 @@ public class PlantPickService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task AddPlantToDatabase(ulong gid, ulong cid, ulong uid, ulong mid, long amount, string pass)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        uow.PlantedCurrency.Add(new()
 | 
			
		||||
        {
 | 
			
		||||
            uow.PlantedCurrency.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                Amount = amount,
 | 
			
		||||
                GuildId = gid,
 | 
			
		||||
                ChannelId = cid,
 | 
			
		||||
                Password = pass,
 | 
			
		||||
                UserId = uid,
 | 
			
		||||
                MessageId = mid,
 | 
			
		||||
            });
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
            Amount = amount,
 | 
			
		||||
            GuildId = gid,
 | 
			
		||||
            ChannelId = cid,
 | 
			
		||||
            Password = pass,
 | 
			
		||||
            UserId = uid,
 | 
			
		||||
            MessageId = mid,
 | 
			
		||||
        });
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -37,49 +37,47 @@ public class WaifuService : INService
 | 
			
		||||
 | 
			
		||||
        var settings = _gss.Data;
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
 | 
			
		||||
            var ownerUser = uow.GetOrCreateUser(owner);
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
 | 
			
		||||
        var ownerUser = uow.GetOrCreateUser(owner);
 | 
			
		||||
 | 
			
		||||
            // owner has to be the owner of the waifu
 | 
			
		||||
            if (waifu is null || waifu.ClaimerId != ownerUser.Id)
 | 
			
		||||
                return false;
 | 
			
		||||
        // owner has to be the owner of the waifu
 | 
			
		||||
        if (waifu is null || waifu.ClaimerId != ownerUser.Id)
 | 
			
		||||
            return false;
 | 
			
		||||
                
 | 
			
		||||
            // if waifu likes the person, gotta pay the penalty
 | 
			
		||||
            if (waifu.AffinityId == ownerUser.Id)
 | 
			
		||||
        // if waifu likes the person, gotta pay the penalty
 | 
			
		||||
        if (waifu.AffinityId == ownerUser.Id)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await _cs.RemoveAsync(owner.Id,
 | 
			
		||||
                    "Waifu Transfer - affinity penalty", 
 | 
			
		||||
                    (int)(waifu.Price * 0.6),
 | 
			
		||||
                    true))
 | 
			
		||||
            {
 | 
			
		||||
                if (!await _cs.RemoveAsync(owner.Id,
 | 
			
		||||
                        "Waifu Transfer - affinity penalty", 
 | 
			
		||||
                        (int)(waifu.Price * 0.6),
 | 
			
		||||
                        true))
 | 
			
		||||
                {
 | 
			
		||||
                    // unable to pay 60% penalty
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
 | 
			
		||||
                if (waifu.Price < settings.Waifu.MinPrice)
 | 
			
		||||
                    waifu.Price = settings.Waifu.MinPrice;
 | 
			
		||||
            }
 | 
			
		||||
            else // if not, pay 10% fee
 | 
			
		||||
            {
 | 
			
		||||
                if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
 | 
			
		||||
                if (waifu.Price < settings.Waifu.MinPrice)
 | 
			
		||||
                    waifu.Price = settings.Waifu.MinPrice;
 | 
			
		||||
                // unable to pay 60% penalty
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //new claimerId is the id of the new owner
 | 
			
		||||
            var newOwnerUser = uow.GetOrCreateUser(newOwner);
 | 
			
		||||
            waifu.ClaimerId = newOwnerUser.Id;
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
 | 
			
		||||
            if (waifu.Price < settings.Waifu.MinPrice)
 | 
			
		||||
                waifu.Price = settings.Waifu.MinPrice;
 | 
			
		||||
        }
 | 
			
		||||
        else // if not, pay 10% fee
 | 
			
		||||
        {
 | 
			
		||||
            if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
 | 
			
		||||
            if (waifu.Price < settings.Waifu.MinPrice)
 | 
			
		||||
                waifu.Price = settings.Waifu.MinPrice;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //new claimerId is the id of the new owner
 | 
			
		||||
        var newOwnerUser = uow.GetOrCreateUser(newOwner);
 | 
			
		||||
        waifu.ClaimerId = newOwnerUser.Id;
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -87,67 +85,63 @@ public class WaifuService : INService
 | 
			
		||||
    public int GetResetPrice(IUser user)
 | 
			
		||||
    {
 | 
			
		||||
        var settings = _gss.Data;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
 | 
			
		||||
 | 
			
		||||
            if (waifu is null)
 | 
			
		||||
                return settings.Waifu.MinPrice;
 | 
			
		||||
        if (waifu is null)
 | 
			
		||||
            return settings.Waifu.MinPrice;
 | 
			
		||||
 | 
			
		||||
            var divorces = uow.WaifuUpdates.Count(x => x.Old != null &&
 | 
			
		||||
                                                       x.Old.UserId == user.Id &&
 | 
			
		||||
                                                       x.UpdateType == WaifuUpdateType.Claimed &&
 | 
			
		||||
                                                       x.New == null);
 | 
			
		||||
            var affs = uow.WaifuUpdates
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged &&
 | 
			
		||||
                            w.New != null)
 | 
			
		||||
                .ToList()
 | 
			
		||||
                .GroupBy(x => x.New)
 | 
			
		||||
                .Count();
 | 
			
		||||
        var divorces = uow.WaifuUpdates.Count(x => x.Old != null &&
 | 
			
		||||
                                                   x.Old.UserId == user.Id &&
 | 
			
		||||
                                                   x.UpdateType == WaifuUpdateType.Claimed &&
 | 
			
		||||
                                                   x.New == null);
 | 
			
		||||
        var affs = uow.WaifuUpdates
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged &&
 | 
			
		||||
                        w.New != null)
 | 
			
		||||
            .ToList()
 | 
			
		||||
            .GroupBy(x => x.New)
 | 
			
		||||
            .Count();
 | 
			
		||||
 | 
			
		||||
            return (int) Math.Ceiling(waifu.Price * 1.25f) +
 | 
			
		||||
                   ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
 | 
			
		||||
        }
 | 
			
		||||
        return (int) Math.Ceiling(waifu.Price * 1.25f) +
 | 
			
		||||
               ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> TryReset(IUser user)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var price = GetResetPrice(user);
 | 
			
		||||
            if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
 | 
			
		||||
                return false;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var price = GetResetPrice(user);
 | 
			
		||||
        if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            var affs = uow.WaifuUpdates
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(w => w.User.UserId == user.Id
 | 
			
		||||
                            && w.UpdateType == WaifuUpdateType.AffinityChanged
 | 
			
		||||
                            && w.New != null);
 | 
			
		||||
        var affs = uow.WaifuUpdates
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Where(w => w.User.UserId == user.Id
 | 
			
		||||
                        && w.UpdateType == WaifuUpdateType.AffinityChanged
 | 
			
		||||
                        && w.New != null);
 | 
			
		||||
 | 
			
		||||
            var divorces = uow.WaifuUpdates
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => x.Old != null &&
 | 
			
		||||
                            x.Old.UserId == user.Id &&
 | 
			
		||||
                            x.UpdateType == WaifuUpdateType.Claimed &&
 | 
			
		||||
                            x.New == null);
 | 
			
		||||
        var divorces = uow.WaifuUpdates
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Where(x => x.Old != null &&
 | 
			
		||||
                        x.Old.UserId == user.Id &&
 | 
			
		||||
                        x.UpdateType == WaifuUpdateType.Claimed &&
 | 
			
		||||
                        x.New == null);
 | 
			
		||||
 | 
			
		||||
            //reset changes of heart to 0
 | 
			
		||||
            uow.WaifuUpdates.RemoveRange(affs);
 | 
			
		||||
            //reset divorces to 0
 | 
			
		||||
            uow.WaifuUpdates.RemoveRange(divorces);
 | 
			
		||||
            var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
 | 
			
		||||
            //reset price, remove items
 | 
			
		||||
            //remove owner, remove affinity
 | 
			
		||||
            waifu.Price = 50;
 | 
			
		||||
            waifu.Items.Clear();
 | 
			
		||||
            waifu.ClaimerId = null;
 | 
			
		||||
            waifu.AffinityId = null;
 | 
			
		||||
        //reset changes of heart to 0
 | 
			
		||||
        uow.WaifuUpdates.RemoveRange(affs);
 | 
			
		||||
        //reset divorces to 0
 | 
			
		||||
        uow.WaifuUpdates.RemoveRange(divorces);
 | 
			
		||||
        var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
 | 
			
		||||
        //reset price, remove items
 | 
			
		||||
        //remove owner, remove affinity
 | 
			
		||||
        waifu.Price = 50;
 | 
			
		||||
        waifu.Items.Clear();
 | 
			
		||||
        waifu.ClaimerId = null;
 | 
			
		||||
        waifu.AffinityId = null;
 | 
			
		||||
 | 
			
		||||
            //wives stay though
 | 
			
		||||
        //wives stay though
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -302,10 +296,8 @@ public class WaifuService : INService
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<WaifuLbResult> GetTopWaifusAtPage(int page)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.WaifuInfo.GetTop(9, page * 9);
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.WaifuInfo.GetTop(9, page * 9);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ulong GetWaifuUserId(ulong ownerId, string name)
 | 
			
		||||
@@ -372,85 +364,79 @@ public class WaifuService : INService
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, 
 | 
			
		||||
            set => set.Include(x => x.Items)
 | 
			
		||||
                .Include(x => x.Claimer));
 | 
			
		||||
        if (w is null)
 | 
			
		||||
        {
 | 
			
		||||
            var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, 
 | 
			
		||||
                set => set.Include(x => x.Items)
 | 
			
		||||
                    .Include(x => x.Claimer));
 | 
			
		||||
            if (w is null)
 | 
			
		||||
            uow.WaifuInfo.Add(w = new()
 | 
			
		||||
            {
 | 
			
		||||
                uow.WaifuInfo.Add(w = new()
 | 
			
		||||
                {
 | 
			
		||||
                    Affinity = null,
 | 
			
		||||
                    Claimer = null,
 | 
			
		||||
                    Price = 1,
 | 
			
		||||
                    Waifu = uow.GetOrCreateUser(giftedWaifu),
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
                Affinity = null,
 | 
			
		||||
                Claimer = null,
 | 
			
		||||
                Price = 1,
 | 
			
		||||
                Waifu = uow.GetOrCreateUser(giftedWaifu),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (!itemObj.Negative)
 | 
			
		||||
        if (!itemObj.Negative)
 | 
			
		||||
        {
 | 
			
		||||
            w.Items.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                w.Items.Add(new()
 | 
			
		||||
                {
 | 
			
		||||
                    Name = itemObj.Name.ToLowerInvariant(),
 | 
			
		||||
                    ItemEmoji = itemObj.ItemEmoji,
 | 
			
		||||
                });
 | 
			
		||||
                Name = itemObj.Name.ToLowerInvariant(),
 | 
			
		||||
                ItemEmoji = itemObj.ItemEmoji,
 | 
			
		||||
            });
 | 
			
		||||
                    
 | 
			
		||||
                if (w.Claimer?.UserId == from.Id)
 | 
			
		||||
                {
 | 
			
		||||
                    w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    w.Price += itemObj.Price / 2;
 | 
			
		||||
                }
 | 
			
		||||
            if (w.Claimer?.UserId == @from.Id)
 | 
			
		||||
            {
 | 
			
		||||
                w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
 | 
			
		||||
                if (w.Price < 1)
 | 
			
		||||
                    w.Price = 1;
 | 
			
		||||
                w.Price += itemObj.Price / 2;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
 | 
			
		||||
            if (w.Price < 1)
 | 
			
		||||
                w.Price = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var wi = uow.GetWaifuInfo(targetId);
 | 
			
		||||
        if (wi is null)
 | 
			
		||||
        {
 | 
			
		||||
            var wi = uow.GetWaifuInfo(targetId);
 | 
			
		||||
            if (wi is null)
 | 
			
		||||
            wi = new()
 | 
			
		||||
            {
 | 
			
		||||
                wi = new()
 | 
			
		||||
                {
 | 
			
		||||
                    AffinityCount = 0,
 | 
			
		||||
                    AffinityName = null,
 | 
			
		||||
                    ClaimCount = 0,
 | 
			
		||||
                    ClaimerName = null,
 | 
			
		||||
                    Claims = new(),
 | 
			
		||||
                    Fans = new(),
 | 
			
		||||
                    DivorceCount = 0,
 | 
			
		||||
                    FullName = null,
 | 
			
		||||
                    Items = new(),
 | 
			
		||||
                    Price = 1
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return wi;
 | 
			
		||||
                AffinityCount = 0,
 | 
			
		||||
                AffinityName = null,
 | 
			
		||||
                ClaimCount = 0,
 | 
			
		||||
                ClaimerName = null,
 | 
			
		||||
                Claims = new(),
 | 
			
		||||
                Fans = new(),
 | 
			
		||||
                DivorceCount = 0,
 | 
			
		||||
                FullName = null,
 | 
			
		||||
                Items = new(),
 | 
			
		||||
                Price = 1
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return wi;
 | 
			
		||||
    }
 | 
			
		||||
    public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var du = uow.GetOrCreateUser(target);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var du = uow.GetOrCreateUser(target);
 | 
			
		||||
                
 | 
			
		||||
            return GetFullWaifuInfoAsync(target.Id);
 | 
			
		||||
        }
 | 
			
		||||
        return GetFullWaifuInfoAsync(target.Id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string GetClaimTitle(int count)
 | 
			
		||||
 
 | 
			
		||||
@@ -41,18 +41,18 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
        public sealed class SlotMachine
 | 
			
		||||
        {
 | 
			
		||||
            public const int MaxValue = 5;
 | 
			
		||||
            public const int MAX_VALUE = 5;
 | 
			
		||||
 | 
			
		||||
            static readonly List<Func<int[], int>> _winningCombos = new()
 | 
			
		||||
            private static readonly List<Func<int[], int>> _winningCombos = new()
 | 
			
		||||
            {
 | 
			
		||||
                //three flowers
 | 
			
		||||
                arr => arr.All(a=>a==MaxValue) ? 30 : 0,
 | 
			
		||||
                arr => arr.All(a=>a==MAX_VALUE) ? 30 : 0,
 | 
			
		||||
                //three of the same
 | 
			
		||||
                arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
 | 
			
		||||
                //two flowers
 | 
			
		||||
                arr => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
 | 
			
		||||
                arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
 | 
			
		||||
                //one flower
 | 
			
		||||
                arr => arr.Any(a => a == MaxValue) ? 1 : 0,
 | 
			
		||||
                arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            public static SlotResult Pull()
 | 
			
		||||
@@ -60,7 +60,7 @@ public partial class Gambling
 | 
			
		||||
                var numbers = new int[3];
 | 
			
		||||
                for (var i = 0; i < numbers.Length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
 | 
			
		||||
                    numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
 | 
			
		||||
                }
 | 
			
		||||
                var multi = 0;
 | 
			
		||||
                foreach (var t in _winningCombos)
 | 
			
		||||
@@ -84,6 +84,8 @@ public partial class Gambling
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Task Test() => Task.CompletedTask; 
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
@@ -124,7 +126,6 @@ public partial class Gambling
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            const int bet = 1;
 | 
			
		||||
            var payout = 0;
 | 
			
		||||
            foreach (var key in dict.Keys.OrderByDescending(x => x))
 | 
			
		||||
            {
 | 
			
		||||
@@ -132,7 +133,7 @@ public partial class Gambling
 | 
			
		||||
                payout += key * dict[key];
 | 
			
		||||
            }
 | 
			
		||||
            await SendConfirmAsync("Slot Test Results", sb.ToString(),
 | 
			
		||||
                footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
 | 
			
		||||
                footer: $"Total Bet: {tests} | Payout: {payout} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
@@ -170,7 +171,7 @@ public partial class Gambling
 | 
			
		||||
                        .FirstOrDefault(x => x.UserId == ctx.User.Id)
 | 
			
		||||
                        ?.CurrencyAmount ?? 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                
 | 
			
		||||
                using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
 | 
			
		||||
                {
 | 
			
		||||
                    var numbers = new int[3];
 | 
			
		||||
@@ -216,22 +217,20 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                    for (var i = 0; i < 3; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
 | 
			
		||||
                        {
 | 
			
		||||
                            bgImage.Mutate(x => x.DrawImage(img, new Point(148 + (105 * i), 217), 1f));
 | 
			
		||||
                        }
 | 
			
		||||
                        using var img = Image.Load(_images.SlotEmojis[numbers[i]]);
 | 
			
		||||
                        bgImage.Mutate(x => x.DrawImage(img, new Point(148 + (105 * i), 217), 1f));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var msg = GetText(strs.better_luck);
 | 
			
		||||
                    if (result.Multiplier > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (result.Multiplier == 1f)
 | 
			
		||||
                        if (Math.Abs(result.Multiplier - 1f) <= float.Epsilon)
 | 
			
		||||
                            msg = GetText(strs.slot_single(CurrencySign, 1));
 | 
			
		||||
                        else if (result.Multiplier == 4f)
 | 
			
		||||
                        else if (Math.Abs(result.Multiplier - 4f) < float.Epsilon)
 | 
			
		||||
                            msg = GetText(strs.slot_two(CurrencySign, 4));
 | 
			
		||||
                        else if (result.Multiplier == 10f)
 | 
			
		||||
                        else if (Math.Abs(result.Multiplier - 10f) <= float.Epsilon)
 | 
			
		||||
                            msg = GetText(strs.slot_three(10));
 | 
			
		||||
                        else if (result.Multiplier == 30f)
 | 
			
		||||
                        else if (Math.Abs(result.Multiplier - 30f) <= float.Epsilon)
 | 
			
		||||
                            msg = GetText(strs.slot_jackpot(30));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,9 @@ public class ChatterBotSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    public async Task<string> Think(string message)
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var res = await http.GetStringAsync(string.Format(ApiEndpoint, message)).ConfigureAwait(false);
 | 
			
		||||
            var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
 | 
			
		||||
            return cbr.BotSay.Replace("<br/>", "\n", StringComparison.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var res = await http.GetStringAsync(string.Format(ApiEndpoint, message)).ConfigureAwait(false);
 | 
			
		||||
        var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
 | 
			
		||||
        return cbr.BotSay.Replace("<br/>", "\n", StringComparison.InvariantCulture);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,22 +21,20 @@ public class OfficialCleverbotSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    public async Task<string> Think(string input)
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var dataString = await http.GetStringAsync(string.Format(QueryString, input, _cs ?? "")).ConfigureAwait(false);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var dataString = await http.GetStringAsync(string.Format(QueryString, input, _cs ?? "")).ConfigureAwait(false);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var data = JsonConvert.DeserializeObject<CleverbotResponse>(dataString);
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<CleverbotResponse>(dataString);
 | 
			
		||||
 | 
			
		||||
                _cs = data?.Cs;
 | 
			
		||||
                return data?.Output;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning("Unexpected cleverbot response received: ");
 | 
			
		||||
                Log.Warning(dataString);
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            _cs = data?.Cs;
 | 
			
		||||
            return data?.Output;
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning("Unexpected cleverbot response received: ");
 | 
			
		||||
            Log.Warning(dataString);
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -62,41 +60,37 @@ public class CleverbotIOSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    private async Task<string> GetNick()
 | 
			
		||||
    {
 | 
			
		||||
        using (var _http = _httpFactory.CreateClient())
 | 
			
		||||
        using (var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
               {
 | 
			
		||||
                   new KeyValuePair<string, string>("user", _user),
 | 
			
		||||
                   new KeyValuePair<string, string>("key", _key),
 | 
			
		||||
               }))
 | 
			
		||||
        using (var data = await _http.PostAsync(_createEndpoint, msg).ConfigureAwait(false))
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        using var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
        {
 | 
			
		||||
            var str = await data.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
            var obj = JsonConvert.DeserializeObject<CleverbotIOCreateResponse>(str);
 | 
			
		||||
            if (obj.Status != "success")
 | 
			
		||||
                throw new OperationCanceledException(obj.Status);
 | 
			
		||||
            new KeyValuePair<string, string>("user", _user),
 | 
			
		||||
            new KeyValuePair<string, string>("key", _key),
 | 
			
		||||
        });
 | 
			
		||||
        using var data = await _http.PostAsync(_createEndpoint, msg).ConfigureAwait(false);
 | 
			
		||||
        var str = await data.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIOCreateResponse>(str);
 | 
			
		||||
        if (obj.Status != "success")
 | 
			
		||||
            throw new OperationCanceledException(obj.Status);
 | 
			
		||||
 | 
			
		||||
            return obj.Nick;
 | 
			
		||||
        }
 | 
			
		||||
        return obj.Nick;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> Think(string input)
 | 
			
		||||
    {
 | 
			
		||||
        using (var _http = _httpFactory.CreateClient())
 | 
			
		||||
        using (var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
               {
 | 
			
		||||
                   new KeyValuePair<string, string>("user", _user),
 | 
			
		||||
                   new KeyValuePair<string, string>("key", _key),
 | 
			
		||||
                   new KeyValuePair<string, string>("nick", await _nick),
 | 
			
		||||
                   new KeyValuePair<string, string>("text", input),
 | 
			
		||||
               }))
 | 
			
		||||
        using (var data = await _http.PostAsync(_askEndpoint, msg).ConfigureAwait(false))
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        using var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
        {
 | 
			
		||||
            var str = await data.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
            var obj = JsonConvert.DeserializeObject<CleverbotIOAskResponse>(str);
 | 
			
		||||
            if (obj.Status != "success")
 | 
			
		||||
                throw new OperationCanceledException(obj.Status);
 | 
			
		||||
            new KeyValuePair<string, string>("user", _user),
 | 
			
		||||
            new KeyValuePair<string, string>("key", _key),
 | 
			
		||||
            new KeyValuePair<string, string>("nick", await _nick),
 | 
			
		||||
            new KeyValuePair<string, string>("text", input),
 | 
			
		||||
        });
 | 
			
		||||
        using var data = await _http.PostAsync(_askEndpoint, msg).ConfigureAwait(false);
 | 
			
		||||
        var str = await data.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIOAskResponse>(str);
 | 
			
		||||
        if (obj.Status != "success")
 | 
			
		||||
            throw new OperationCanceledException(obj.Status);
 | 
			
		||||
 | 
			
		||||
            return obj.Response;
 | 
			
		||||
        }
 | 
			
		||||
        return obj.Response;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -30,33 +30,31 @@ public class GirlRating
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var img = Image.Load(_images.RategirlMatrix))
 | 
			
		||||
                using var img = Image.Load(_images.RategirlMatrix);
 | 
			
		||||
                const int minx = 35;
 | 
			
		||||
                const int miny = 385;
 | 
			
		||||
                const int length = 345;
 | 
			
		||||
 | 
			
		||||
                var pointx = (int)(minx + (length * (Hot / 10)));
 | 
			
		||||
                var pointy = (int)(miny - (length * ((Crazy - 4) / 6)));
 | 
			
		||||
 | 
			
		||||
                using (var pointImg = Image.Load(_images.RategirlDot))
 | 
			
		||||
                {
 | 
			
		||||
                    const int minx = 35;
 | 
			
		||||
                    const int miny = 385;
 | 
			
		||||
                    const int length = 345;
 | 
			
		||||
 | 
			
		||||
                    var pointx = (int)(minx + (length * (Hot / 10)));
 | 
			
		||||
                    var pointy = (int)(miny - (length * ((Crazy - 4) / 6)));
 | 
			
		||||
 | 
			
		||||
                    using (var pointImg = Image.Load(_images.RategirlDot))
 | 
			
		||||
                    {
 | 
			
		||||
                        img.Mutate(x => x.DrawImage(pointImg, new(pointx - 10, pointy - 10), new GraphicsOptions()));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var imgStream = new MemoryStream();
 | 
			
		||||
                    img.SaveAsPng(imgStream);
 | 
			
		||||
                    return imgStream;
 | 
			
		||||
                    //using (var byteContent = new ByteArrayContent(imgStream.ToArray()))
 | 
			
		||||
                    //{
 | 
			
		||||
                    //    http.AddFakeHeaders();
 | 
			
		||||
 | 
			
		||||
                    //    using (var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent).ConfigureAwait(false))
 | 
			
		||||
                    //    {
 | 
			
		||||
                    //        url = await reponse.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
                    //    }
 | 
			
		||||
                    //}
 | 
			
		||||
                    img.Mutate(x => x.DrawImage(pointImg, new(pointx - 10, pointy - 10), new GraphicsOptions()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var imgStream = new MemoryStream();
 | 
			
		||||
                img.SaveAsPng(imgStream);
 | 
			
		||||
                return imgStream;
 | 
			
		||||
                //using (var byteContent = new ByteArrayContent(imgStream.ToArray()))
 | 
			
		||||
                //{
 | 
			
		||||
                //    http.AddFakeHeaders();
 | 
			
		||||
 | 
			
		||||
                //    using (var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent).ConfigureAwait(false))
 | 
			
		||||
                //    {
 | 
			
		||||
                //        url = await reponse.Content.ReadAsStringAsync().ConfigureAwait(false);
 | 
			
		||||
                //    }
 | 
			
		||||
                //}
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -51,12 +51,10 @@ public class PollRunner
 | 
			
		||||
        }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var trackedPoll = uow.Poll.FirstOrDefault(x => x.Id == Poll.Id);
 | 
			
		||||
            trackedPoll.Votes.Add(voteObj);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var trackedPoll = uow.Poll.FirstOrDefault(x => x.Id == Poll.Id);
 | 
			
		||||
        trackedPoll.Votes.Add(voteObj);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ public class TicTacToe
 | 
			
		||||
        public int TurnTimer { get; set; } = 15;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum Phase
 | 
			
		||||
    private enum Phase
 | 
			
		||||
    {
 | 
			
		||||
        Starting,
 | 
			
		||||
        Started,
 | 
			
		||||
 
 | 
			
		||||
@@ -57,24 +57,22 @@ public partial class Games : NadekoModule<GamesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using (var imgStream = new MemoryStream())
 | 
			
		||||
        await using var imgStream = new MemoryStream();
 | 
			
		||||
        lock (gr)
 | 
			
		||||
        {
 | 
			
		||||
            lock (gr)
 | 
			
		||||
            {
 | 
			
		||||
                originalStream.Position = 0;
 | 
			
		||||
                originalStream.CopyTo(imgStream);
 | 
			
		||||
            }
 | 
			
		||||
            imgStream.Position = 0;
 | 
			
		||||
            await ctx.Channel.SendFileAsync(stream: imgStream,
 | 
			
		||||
                filename: $"girl_{usr}.png",
 | 
			
		||||
                text: Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"),
 | 
			
		||||
                embed: _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .AddField("Hot", gr.Hot.ToString("F2"), true)
 | 
			
		||||
                    .AddField("Crazy", gr.Crazy.ToString("F2"), true)
 | 
			
		||||
                    .AddField("Advice", gr.Advice, false)
 | 
			
		||||
                    .Build()).ConfigureAwait(false);
 | 
			
		||||
            originalStream.Position = 0;
 | 
			
		||||
            originalStream.CopyTo(imgStream);
 | 
			
		||||
        }
 | 
			
		||||
        imgStream.Position = 0;
 | 
			
		||||
        await ctx.Channel.SendFileAsync(stream: imgStream,
 | 
			
		||||
            filename: $"girl_{usr}.png",
 | 
			
		||||
            text: Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"),
 | 
			
		||||
            embed: _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .AddField("Hot", gr.Hot.ToString("F2"), true)
 | 
			
		||||
                .AddField("Crazy", gr.Crazy.ToString("F2"), true)
 | 
			
		||||
                .AddField("Advice", gr.Advice, false)
 | 
			
		||||
                .Build()).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private double NextDouble(double x, double y)
 | 
			
		||||
 
 | 
			
		||||
@@ -76,11 +76,9 @@ public class GamesService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task<RatingTexts> GetRatingTexts()
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var text = await http.GetStringAsync("https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json");
 | 
			
		||||
            return JsonConvert.DeserializeObject<RatingTexts>(text);
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var text = await http.GetStringAsync("https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json");
 | 
			
		||||
        return JsonConvert.DeserializeObject<RatingTexts>(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void AddTypingArticle(IUser user, string text)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,17 +22,15 @@ public class PollService : IEarlyBehavior
 | 
			
		||||
        _strs = strs;
 | 
			
		||||
        _eb = eb;
 | 
			
		||||
 | 
			
		||||
        using (var uow = db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            ActivePolls = uow.Poll.GetAllPolls()
 | 
			
		||||
                .ToDictionary(x => x.GuildId, x =>
 | 
			
		||||
                {
 | 
			
		||||
                    var pr = new PollRunner(db, x);
 | 
			
		||||
                    pr.OnVoted += Pr_OnVoted;
 | 
			
		||||
                    return pr;
 | 
			
		||||
                })
 | 
			
		||||
                .ToConcurrent();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = db.GetDbContext();
 | 
			
		||||
        ActivePolls = uow.Poll.GetAllPolls()
 | 
			
		||||
            .ToDictionary(x => x.GuildId, x =>
 | 
			
		||||
            {
 | 
			
		||||
                var pr = new PollRunner(db, x);
 | 
			
		||||
                pr.OnVoted += Pr_OnVoted;
 | 
			
		||||
                return pr;
 | 
			
		||||
            })
 | 
			
		||||
            .ToConcurrent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Poll CreatePoll(ulong guildId, ulong channelId, string input)
 | 
			
		||||
 
 | 
			
		||||
@@ -42,10 +42,8 @@ public class RadioResolver : IRadioResolver
 | 
			
		||||
        string file = null;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                file = await http.GetStringAsync(query).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            using var http = new HttpClient();
 | 
			
		||||
            file = await http.GetStringAsync(query).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -64,18 +64,16 @@ public sealed partial class Music
 | 
			
		||||
            var success = false;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    var pl = uow.MusicPlaylists.FirstOrDefault(x => x.Id == id);
 | 
			
		||||
                await using var uow = _db.GetDbContext();
 | 
			
		||||
                var pl = uow.MusicPlaylists.FirstOrDefault(x => x.Id == id);
 | 
			
		||||
 | 
			
		||||
                    if (pl != null)
 | 
			
		||||
                if (pl != null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_creds.IsOwner(ctx.User) || pl.AuthorId == ctx.User.Id)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_creds.IsOwner(ctx.User) || pl.AuthorId == ctx.User.Id)
 | 
			
		||||
                        {
 | 
			
		||||
                            uow.MusicPlaylists.Remove(pl);
 | 
			
		||||
                            await uow.SaveChangesAsync();
 | 
			
		||||
                            success = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        uow.MusicPlaylists.Remove(pl);
 | 
			
		||||
                        await uow.SaveChangesAsync();
 | 
			
		||||
                        success = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -34,26 +34,24 @@ public sealed class FilterService : IEarlyBehavior
 | 
			
		||||
 | 
			
		||||
    public void ClearFilteredWords(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId,
 | 
			
		||||
            set => set.Include(x => x.FilteredWords)
 | 
			
		||||
                .Include(x => x.FilterWordsChannelIds));
 | 
			
		||||
 | 
			
		||||
        WordFilteringServers.TryRemove(guildId);
 | 
			
		||||
        ServerFilteredWords.TryRemove(guildId, out _);
 | 
			
		||||
 | 
			
		||||
        foreach (var c in gc.FilterWordsChannelIds)
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId,
 | 
			
		||||
                set => set.Include(x => x.FilteredWords)
 | 
			
		||||
                    .Include(x => x.FilterWordsChannelIds));
 | 
			
		||||
 | 
			
		||||
            WordFilteringServers.TryRemove(guildId);
 | 
			
		||||
            ServerFilteredWords.TryRemove(guildId, out _);
 | 
			
		||||
 | 
			
		||||
            foreach (var c in gc.FilterWordsChannelIds)
 | 
			
		||||
            {
 | 
			
		||||
                WordFilteringChannels.TryRemove(c.ChannelId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            gc.FilterWords = false;
 | 
			
		||||
            gc.FilteredWords.Clear();
 | 
			
		||||
            gc.FilterWordsChannelIds.Clear();
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            WordFilteringChannels.TryRemove(c.ChannelId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        gc.FilterWords = false;
 | 
			
		||||
        gc.FilteredWords.Clear();
 | 
			
		||||
        gc.FilterWordsChannelIds.Clear();
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ConcurrentHashSet<string> FilteredWordsForServer(ulong guildId)
 | 
			
		||||
 
 | 
			
		||||
@@ -29,18 +29,16 @@ public class PermissionService : ILateBlocker, INService
 | 
			
		||||
        _strings = strings;
 | 
			
		||||
        _eb = eb;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => x.Id)
 | 
			
		||||
                     .ToList()))
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => x.Id)
 | 
			
		||||
                         .ToList()))
 | 
			
		||||
            Cache.TryAdd(x.GuildId, new()
 | 
			
		||||
            {
 | 
			
		||||
                Cache.TryAdd(x.GuildId, new()
 | 
			
		||||
                {
 | 
			
		||||
                    Verbose = x.VerbosePermissions,
 | 
			
		||||
                    PermRole = x.PermissionRole,
 | 
			
		||||
                    Permissions = new(x.Permissions)
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
                Verbose = x.VerbosePermissions,
 | 
			
		||||
                PermRole = x.PermissionRole,
 | 
			
		||||
                Permissions = new(x.Permissions)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -63,19 +61,17 @@ public class PermissionService : ILateBlocker, INService
 | 
			
		||||
 | 
			
		||||
    public async Task AddPermissions(ulong guildId, params Permissionv2[] perms)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GcWithPermissionsv2For(guildId);
 | 
			
		||||
        //var orderedPerms = new PermissionsCollection<Permissionv2>(config.Permissions);
 | 
			
		||||
        var max = config.Permissions.Max(x => x.Index); //have to set its index to be the highest
 | 
			
		||||
        foreach (var perm in perms)
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GcWithPermissionsv2For(guildId);
 | 
			
		||||
            //var orderedPerms = new PermissionsCollection<Permissionv2>(config.Permissions);
 | 
			
		||||
            var max = config.Permissions.Max(x => x.Index); //have to set its index to be the highest
 | 
			
		||||
            foreach (var perm in perms)
 | 
			
		||||
            {
 | 
			
		||||
                perm.Index = ++max;
 | 
			
		||||
                config.Permissions.Add(perm);
 | 
			
		||||
            }
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            UpdateCache(config);
 | 
			
		||||
            perm.Index = ++max;
 | 
			
		||||
            config.Permissions.Add(perm);
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        UpdateCache(config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void UpdateCache(GuildConfig config)
 | 
			
		||||
@@ -170,12 +166,10 @@ public class PermissionService : ILateBlocker, INService
 | 
			
		||||
 | 
			
		||||
    public async Task Reset(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var config = uow.GcWithPermissionsv2For(guildId);
 | 
			
		||||
            config.Permissions = Permissionv2.GetDefaultPermlist;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            UpdateCache(config);
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var config = uow.GcWithPermissionsv2For(guildId);
 | 
			
		||||
        config.Permissions = Permissionv2.GetDefaultPermlist;
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        UpdateCache(config);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -47,64 +47,62 @@ public partial class Searches
 | 
			
		||||
            var fullQueryLink = "https://myanimelist.net/profile/" + name;
 | 
			
		||||
 | 
			
		||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
            using (var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false))
 | 
			
		||||
            {
 | 
			
		||||
                var imageElem = document.QuerySelector("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
 | 
			
		||||
                var imageUrl = ((IHtmlImageElement)imageElem)?.Source ?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
 | 
			
		||||
            using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false);
 | 
			
		||||
            var imageElem = document.QuerySelector("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
 | 
			
		||||
            var imageUrl = ((IHtmlImageElement)imageElem)?.Source ?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
 | 
			
		||||
 | 
			
		||||
                var stats = document.QuerySelectorAll("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span").Select(x => x.InnerHtml).ToList();
 | 
			
		||||
            var stats = document.QuerySelectorAll("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span").Select(x => x.InnerHtml).ToList();
 | 
			
		||||
 | 
			
		||||
                var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
 | 
			
		||||
            var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
 | 
			
		||||
 | 
			
		||||
                var favAnime = GetText(strs.anime_no_fav);
 | 
			
		||||
                if (favorites.Length > 0 && favorites[0].QuerySelector("p") is null)
 | 
			
		||||
                    favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
 | 
			
		||||
                        .Shuffle()
 | 
			
		||||
                        .Take(3)
 | 
			
		||||
                        .Select(x =>
 | 
			
		||||
                        {
 | 
			
		||||
                            var elem = (IHtmlAnchorElement)x;
 | 
			
		||||
                            return $"[{elem.InnerHtml}]({elem.Href})";
 | 
			
		||||
                        }));
 | 
			
		||||
            var favAnime = GetText(strs.anime_no_fav);
 | 
			
		||||
            if (favorites.Length > 0 && favorites[0].QuerySelector("p") is null)
 | 
			
		||||
                favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
 | 
			
		||||
                    .Shuffle()
 | 
			
		||||
                    .Take(3)
 | 
			
		||||
                    .Select(x =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var elem = (IHtmlAnchorElement)x;
 | 
			
		||||
                        return $"[{elem.InnerHtml}]({elem.Href})";
 | 
			
		||||
                    }));
 | 
			
		||||
 | 
			
		||||
                var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
 | 
			
		||||
                    .Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
 | 
			
		||||
                    .ToList();
 | 
			
		||||
            var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
 | 
			
		||||
                .Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
 | 
			
		||||
                .ToList();
 | 
			
		||||
 | 
			
		||||
                var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
 | 
			
		||||
                    .Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
            var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
 | 
			
		||||
                .Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
 | 
			
		||||
                .ToArray();
 | 
			
		||||
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle(GetText(strs.mal_profile(name)))
 | 
			
		||||
                    .AddField("💚 " + GetText(strs.watching), stats[0], true)
 | 
			
		||||
                    .AddField("💙 " + GetText(strs.completed), stats[1], true);
 | 
			
		||||
                if (info.Count < 3)
 | 
			
		||||
                    embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
 | 
			
		||||
                embed
 | 
			
		||||
                    .AddField("💔 " + GetText(strs.dropped), stats[3], true)
 | 
			
		||||
                    .AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
 | 
			
		||||
                    .AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
 | 
			
		||||
                    .AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
 | 
			
		||||
                    .AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
 | 
			
		||||
                    .AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
 | 
			
		||||
                if (info.Count > 2)
 | 
			
		||||
                    embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle(GetText(strs.mal_profile(name)))
 | 
			
		||||
                .AddField("💚 " + GetText(strs.watching), stats[0], true)
 | 
			
		||||
                .AddField("💙 " + GetText(strs.completed), stats[1], true);
 | 
			
		||||
            if (info.Count < 3)
 | 
			
		||||
                embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
 | 
			
		||||
            embed
 | 
			
		||||
                .AddField("💔 " + GetText(strs.dropped), stats[3], true)
 | 
			
		||||
                .AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
 | 
			
		||||
                .AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
 | 
			
		||||
                .AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
 | 
			
		||||
                .AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
 | 
			
		||||
                .AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
 | 
			
		||||
            if (info.Count > 2)
 | 
			
		||||
                embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
 | 
			
		||||
 | 
			
		||||
                embed
 | 
			
		||||
                    .WithDescription($@"
 | 
			
		||||
            embed
 | 
			
		||||
                .WithDescription($@"
 | 
			
		||||
** https://myanimelist.net/animelist/{ name } **
 | 
			
		||||
 | 
			
		||||
**{GetText(strs.top_3_fav_anime)}**
 | 
			
		||||
{favAnime}"
 | 
			
		||||
 | 
			
		||||
                    )
 | 
			
		||||
                    .WithUrl(fullQueryLink)
 | 
			
		||||
                    .WithImageUrl(imageUrl);
 | 
			
		||||
                )
 | 
			
		||||
                .WithUrl(fullQueryLink)
 | 
			
		||||
                .WithImageUrl(imageUrl);
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string MalInfoToEmoji(string info)
 | 
			
		||||
 
 | 
			
		||||
@@ -53,34 +53,32 @@ public class PicartoProvider : Provider
 | 
			
		||||
        if (logins.Count == 0)
 | 
			
		||||
            return new();
 | 
			
		||||
 | 
			
		||||
        using (var http = _httpClientFactory.CreateClient())
 | 
			
		||||
        using var http = _httpClientFactory.CreateClient();
 | 
			
		||||
        var toReturn = new List<StreamData>();
 | 
			
		||||
        foreach (var login in logins)
 | 
			
		||||
        {
 | 
			
		||||
            var toReturn = new List<StreamData>();
 | 
			
		||||
            foreach (var login in logins)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    http.DefaultRequestHeaders.Accept.Add(new("application/json"));
 | 
			
		||||
                    // get id based on the username
 | 
			
		||||
                    var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
 | 
			
		||||
                http.DefaultRequestHeaders.Accept.Add(new("application/json"));
 | 
			
		||||
                // get id based on the username
 | 
			
		||||
                var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
 | 
			
		||||
                        
 | 
			
		||||
                    if (!res.IsSuccessStatusCode)
 | 
			
		||||
                        continue;
 | 
			
		||||
                if (!res.IsSuccessStatusCode)
 | 
			
		||||
                    continue;
 | 
			
		||||
                        
 | 
			
		||||
                    var userData = JsonConvert.DeserializeObject<PicartoChannelResponse>(await res.Content.ReadAsStringAsync())!;
 | 
			
		||||
                var userData = JsonConvert.DeserializeObject<PicartoChannelResponse>(await res.Content.ReadAsStringAsync())!;
 | 
			
		||||
 | 
			
		||||
                    toReturn.Add(ToStreamData(userData));
 | 
			
		||||
                    _failingStreams.TryRemove(login, out _);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning(ex, $"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
 | 
			
		||||
                    _failingStreams.TryAdd(login, DateTime.UtcNow);
 | 
			
		||||
                }
 | 
			
		||||
                toReturn.Add(ToStreamData(userData));
 | 
			
		||||
                _failingStreams.TryRemove(login, out _);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex, $"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
 | 
			
		||||
                _failingStreams.TryAdd(login, DateTime.UtcNow);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private StreamData ToStreamData(PicartoChannelResponse stream)
 | 
			
		||||
 
 | 
			
		||||
@@ -53,63 +53,61 @@ public class TwitchProvider : Provider
 | 
			
		||||
        if (logins.Count == 0)
 | 
			
		||||
            return new();
 | 
			
		||||
 | 
			
		||||
        using (var http = _httpClientFactory.CreateClient())
 | 
			
		||||
        using var http = _httpClientFactory.CreateClient();
 | 
			
		||||
        http.DefaultRequestHeaders.Add("Client-Id", "67w6z9i09xv2uoojdm9l0wsyph4hxo6");
 | 
			
		||||
        http.DefaultRequestHeaders.Add("Accept", "application/vnd.twitchtv.v5+json");
 | 
			
		||||
 | 
			
		||||
        var toReturn = new List<StreamData>();
 | 
			
		||||
        foreach (var login in logins)
 | 
			
		||||
        {
 | 
			
		||||
            http.DefaultRequestHeaders.Add("Client-Id", "67w6z9i09xv2uoojdm9l0wsyph4hxo6");
 | 
			
		||||
            http.DefaultRequestHeaders.Add("Accept", "application/vnd.twitchtv.v5+json");
 | 
			
		||||
 | 
			
		||||
            var toReturn = new List<StreamData>();
 | 
			
		||||
            foreach (var login in logins)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                // get id based on the username
 | 
			
		||||
                var idsStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/users?login={login}");
 | 
			
		||||
                var userData = JsonConvert.DeserializeObject<TwitchUsersResponseV5>(idsStr);
 | 
			
		||||
                var user = userData?.Users.FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                // if user can't be found, skip, it means there is no such user
 | 
			
		||||
                if (user is null)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                // get stream data
 | 
			
		||||
                var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}");
 | 
			
		||||
                var resObj =
 | 
			
		||||
                    JsonConvert.DeserializeAnonymousType(str, new {Stream = new TwitchResponseV5.Stream()});
 | 
			
		||||
 | 
			
		||||
                // if stream is null, user is not streaming
 | 
			
		||||
                if (resObj?.Stream is null)
 | 
			
		||||
                {
 | 
			
		||||
                    // get id based on the username
 | 
			
		||||
                    var idsStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/users?login={login}");
 | 
			
		||||
                    var userData = JsonConvert.DeserializeObject<TwitchUsersResponseV5>(idsStr);
 | 
			
		||||
                    var user = userData?.Users.FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                    // if user can't be found, skip, it means there is no such user
 | 
			
		||||
                    if (user is null)
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
                    // get stream data
 | 
			
		||||
                    var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}");
 | 
			
		||||
                    var resObj =
 | 
			
		||||
                        JsonConvert.DeserializeAnonymousType(str, new {Stream = new TwitchResponseV5.Stream()});
 | 
			
		||||
 | 
			
		||||
                    // if stream is null, user is not streaming
 | 
			
		||||
                    if (resObj?.Stream is null)
 | 
			
		||||
                    {
 | 
			
		||||
                        // if user is not streaming, get his offline banner
 | 
			
		||||
                        var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}");
 | 
			
		||||
                        var ch = JsonConvert.DeserializeObject<TwitchResponseV5.Channel>(chStr)!;
 | 
			
		||||
                    // if user is not streaming, get his offline banner
 | 
			
		||||
                    var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}");
 | 
			
		||||
                    var ch = JsonConvert.DeserializeObject<TwitchResponseV5.Channel>(chStr)!;
 | 
			
		||||
                            
 | 
			
		||||
                        toReturn.Add(new()
 | 
			
		||||
                        {
 | 
			
		||||
                            StreamType = FollowedStream.FType.Twitch,
 | 
			
		||||
                            Name = ch.DisplayName,
 | 
			
		||||
                            UniqueName = ch.Name,
 | 
			
		||||
                            Title = ch.Status,
 | 
			
		||||
                            IsLive = false,
 | 
			
		||||
                            AvatarUrl = ch.Logo,
 | 
			
		||||
                            StreamUrl = $"https://twitch.tv/{ch.Name}",
 | 
			
		||||
                            Preview = ch.VideoBanner // set video banner as the preview,
 | 
			
		||||
                        });
 | 
			
		||||
                        continue; // move on
 | 
			
		||||
                    }
 | 
			
		||||
                    toReturn.Add(new()
 | 
			
		||||
                    {
 | 
			
		||||
                        StreamType = FollowedStream.FType.Twitch,
 | 
			
		||||
                        Name = ch.DisplayName,
 | 
			
		||||
                        UniqueName = ch.Name,
 | 
			
		||||
                        Title = ch.Status,
 | 
			
		||||
                        IsLive = false,
 | 
			
		||||
                        AvatarUrl = ch.Logo,
 | 
			
		||||
                        StreamUrl = $"https://twitch.tv/{ch.Name}",
 | 
			
		||||
                        Preview = ch.VideoBanner // set video banner as the preview,
 | 
			
		||||
                    });
 | 
			
		||||
                    continue; // move on
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                    toReturn.Add(ToStreamData(resObj.Stream));
 | 
			
		||||
                    _failingStreams.TryRemove(login, out _);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning($"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
 | 
			
		||||
                    _failingStreams.TryAdd(login, DateTime.UtcNow);
 | 
			
		||||
                }
 | 
			
		||||
                toReturn.Add(ToStreamData(resObj.Stream));
 | 
			
		||||
                _failingStreams.TryRemove(login, out _);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning($"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
 | 
			
		||||
                _failingStreams.TryAdd(login, DateTime.UtcNow);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private StreamData ToStreamData(TwitchResponseV5.Stream stream)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,29 +39,27 @@ public partial class Searches
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using (var http = _httpFactory.CreateClient("memelist"))
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetAsync("https://api.memegen.link/templates/")
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
            using var http = _httpFactory.CreateClient("memelist");
 | 
			
		||||
            var res = await http.GetAsync("https://api.memegen.link/templates/")
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var rawJson = await res.Content.ReadAsStringAsync();
 | 
			
		||||
            var rawJson = await res.Content.ReadAsStringAsync();
 | 
			
		||||
                    
 | 
			
		||||
                var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson);
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson);
 | 
			
		||||
 | 
			
		||||
                await ctx.SendPaginatedConfirmAsync(page, curPage =>
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page, curPage =>
 | 
			
		||||
            {
 | 
			
		||||
                var templates = string.Empty;
 | 
			
		||||
                foreach (var template in data.Skip(curPage * 15).Take(15))
 | 
			
		||||
                {
 | 
			
		||||
                    var templates = string.Empty;
 | 
			
		||||
                    foreach (var template in data.Skip(curPage * 15).Take(15))
 | 
			
		||||
                    {
 | 
			
		||||
                        templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
 | 
			
		||||
                    }
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithDescription(templates);
 | 
			
		||||
                    templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
 | 
			
		||||
                }
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithDescription(templates);
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                }, data.Count, 15).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
                return embed;
 | 
			
		||||
            }, data.Count, 15).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
 
 | 
			
		||||
@@ -23,101 +23,97 @@ public partial class Searches
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(user))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var modeNumber = string.IsNullOrWhiteSpace(mode)
 | 
			
		||||
                ? 0
 | 
			
		||||
                : ResolveGameMode(mode);
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var modeNumber = string.IsNullOrWhiteSpace(mode)
 | 
			
		||||
                    ? 0
 | 
			
		||||
                    : ResolveGameMode(mode);
 | 
			
		||||
 | 
			
		||||
                try
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyErrorLocalizedAsync(strs.osu_api_key).ConfigureAwait(false);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var smode = ResolveGameMode(modeNumber);
 | 
			
		||||
                    var userReq = $"https://osu.ppy.sh/api/get_user?k={_creds.OsuApiKey}&u={user}&m={modeNumber}";
 | 
			
		||||
                    var userResString = await http.GetStringAsync(userReq)
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
                    var objs = JsonConvert.DeserializeObject<List<OsuUserData>>(userResString);
 | 
			
		||||
 | 
			
		||||
                    if (objs.Count == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var obj = objs[0];
 | 
			
		||||
                    var userId = obj.UserId;
 | 
			
		||||
 | 
			
		||||
                    await ctx.Channel.EmbedAsync(_eb.Create()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithTitle($"osu! {smode} profile for {user}")
 | 
			
		||||
                        .WithThumbnailUrl($"https://a.ppy.sh/{userId}")
 | 
			
		||||
                        .WithDescription($"https://osu.ppy.sh/u/{userId}")
 | 
			
		||||
                        .AddField("Official Rank", $"#{obj.PpRank}", true)
 | 
			
		||||
                        .AddField("Country Rank", $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", true)
 | 
			
		||||
                        .AddField("Total PP", Math.Round(obj.PpRaw, 2), true)
 | 
			
		||||
                        .AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true)
 | 
			
		||||
                        .AddField("Playcount", obj.Playcount, true)
 | 
			
		||||
                        .AddField("Level", Math.Round(obj.Level), true)
 | 
			
		||||
                    );
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.osu_api_key).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                catch (ArgumentOutOfRangeException)
 | 
			
		||||
 | 
			
		||||
                var smode = ResolveGameMode(modeNumber);
 | 
			
		||||
                var userReq = $"https://osu.ppy.sh/api/get_user?k={_creds.OsuApiKey}&u={user}&m={modeNumber}";
 | 
			
		||||
                var userResString = await http.GetStringAsync(userReq)
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
                var objs = JsonConvert.DeserializeObject<List<OsuUserData>>(userResString);
 | 
			
		||||
 | 
			
		||||
                if (objs.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.osu_failed).ConfigureAwait(false);
 | 
			
		||||
                    Log.Warning(ex, "Osu command failed");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var obj = objs[0];
 | 
			
		||||
                var userId = obj.UserId;
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(_eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle($"osu! {smode} profile for {user}")
 | 
			
		||||
                    .WithThumbnailUrl($"https://a.ppy.sh/{userId}")
 | 
			
		||||
                    .WithDescription($"https://osu.ppy.sh/u/{userId}")
 | 
			
		||||
                    .AddField("Official Rank", $"#{obj.PpRank}", true)
 | 
			
		||||
                    .AddField("Country Rank", $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", true)
 | 
			
		||||
                    .AddField("Total PP", Math.Round(obj.PpRaw, 2), true)
 | 
			
		||||
                    .AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true)
 | 
			
		||||
                    .AddField("Playcount", obj.Playcount, true)
 | 
			
		||||
                    .AddField("Level", Math.Round(obj.Level), true)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            catch (ArgumentOutOfRangeException)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.osu_failed).ConfigureAwait(false);
 | 
			
		||||
                Log.Warning(ex, "Osu command failed");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
        public async Task Gatari(string user, [Leftover] string mode = null)
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var modeNumber = string.IsNullOrWhiteSpace(mode)
 | 
			
		||||
                ? 0
 | 
			
		||||
                : ResolveGameMode(mode);
 | 
			
		||||
 | 
			
		||||
            var modeStr = ResolveGameMode(modeNumber);
 | 
			
		||||
            var resString = await http
 | 
			
		||||
                .GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}")
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var statsResponse = JsonConvert.DeserializeObject<GatariUserStatsResponse>(resString);
 | 
			
		||||
            if (statsResponse.Code != 200 || statsResponse.Stats.Id == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var modeNumber = string.IsNullOrWhiteSpace(mode)
 | 
			
		||||
                    ? 0
 | 
			
		||||
                    : ResolveGameMode(mode);
 | 
			
		||||
 | 
			
		||||
                var modeStr = ResolveGameMode(modeNumber);
 | 
			
		||||
                var resString = await http
 | 
			
		||||
                    .GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}")
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var statsResponse = JsonConvert.DeserializeObject<GatariUserStatsResponse>(resString);
 | 
			
		||||
                if (statsResponse.Code != 200 || statsResponse.Stats.Id == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var usrResString = await http.GetStringAsync($"https://api.gatari.pw/users/get?u={user}")
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var userData = JsonConvert.DeserializeObject<GatariUserResponse>(usrResString).Users[0];
 | 
			
		||||
                var userStats = statsResponse.Stats;
 | 
			
		||||
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle($"osu!Gatari {modeStr} profile for {user}")
 | 
			
		||||
                    .WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}")
 | 
			
		||||
                    .WithDescription($"https://osu.gatari.pw/u/{userStats.Id}")
 | 
			
		||||
                    .AddField("Official Rank", $"#{userStats.Rank}", true)
 | 
			
		||||
                    .AddField("Country Rank", $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", true)
 | 
			
		||||
                    .AddField("Total PP", userStats.Pp, true)
 | 
			
		||||
                    .AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true)
 | 
			
		||||
                    .AddField("Playcount", userStats.Playcount, true)
 | 
			
		||||
                    .AddField("Level", userStats.Level, true);
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var usrResString = await http.GetStringAsync($"https://api.gatari.pw/users/get?u={user}")
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var userData = JsonConvert.DeserializeObject<GatariUserResponse>(usrResString).Users[0];
 | 
			
		||||
            var userStats = statsResponse.Stats;
 | 
			
		||||
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle($"osu!Gatari {modeStr} profile for {user}")
 | 
			
		||||
                .WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}")
 | 
			
		||||
                .WithDescription($"https://osu.gatari.pw/u/{userStats.Id}")
 | 
			
		||||
                .AddField("Official Rank", $"#{userStats.Rank}", true)
 | 
			
		||||
                .AddField("Country Rank", $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", true)
 | 
			
		||||
                .AddField("Total PP", userStats.Pp, true)
 | 
			
		||||
                .AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true)
 | 
			
		||||
                .AddField("Playcount", userStats.Playcount, true)
 | 
			
		||||
                .AddField("Level", userStats.Level, true);
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Aliases]
 | 
			
		||||
@@ -135,62 +131,60 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var m = 0;
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(mode))
 | 
			
		||||
            {
 | 
			
		||||
                var m = 0;
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(mode))
 | 
			
		||||
                {
 | 
			
		||||
                    m = ResolveGameMode(mode);
 | 
			
		||||
                }
 | 
			
		||||
                m = ResolveGameMode(mode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                var reqString = $"https://osu.ppy.sh/api/get_user_best" +
 | 
			
		||||
                                $"?k={_creds.OsuApiKey}" +
 | 
			
		||||
                                $"&u={Uri.EscapeDataString(user)}" +
 | 
			
		||||
                                $"&type=string" +
 | 
			
		||||
                                $"&limit=5" +
 | 
			
		||||
                                $"&m={m}";
 | 
			
		||||
            var reqString = $"https://osu.ppy.sh/api/get_user_best" +
 | 
			
		||||
                            $"?k={_creds.OsuApiKey}" +
 | 
			
		||||
                            $"&u={Uri.EscapeDataString(user)}" +
 | 
			
		||||
                            $"&type=string" +
 | 
			
		||||
                            $"&limit=5" +
 | 
			
		||||
                            $"&m={m}";
 | 
			
		||||
 | 
			
		||||
                var resString = await http.GetStringAsync(reqString).ConfigureAwait(false);
 | 
			
		||||
                var obj = JsonConvert.DeserializeObject<List<OsuUserBests>>(resString);
 | 
			
		||||
            var resString = await http.GetStringAsync(reqString).ConfigureAwait(false);
 | 
			
		||||
            var obj = JsonConvert.DeserializeObject<List<OsuUserBests>>(resString);
 | 
			
		||||
 | 
			
		||||
                var mapTasks = obj.Select(async item =>
 | 
			
		||||
                {
 | 
			
		||||
                    var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps" +
 | 
			
		||||
                                       $"?k={_creds.OsuApiKey}" +
 | 
			
		||||
                                       $"&b={item.BeatmapId}";
 | 
			
		||||
            var mapTasks = obj.Select(async item =>
 | 
			
		||||
            {
 | 
			
		||||
                var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps" +
 | 
			
		||||
                                   $"?k={_creds.OsuApiKey}" +
 | 
			
		||||
                                   $"&b={item.BeatmapId}";
 | 
			
		||||
 | 
			
		||||
                    var mapResString = await http.GetStringAsync(mapReqString).ConfigureAwait(false);
 | 
			
		||||
                    var map = JsonConvert.DeserializeObject<List<OsuMapData>>(mapResString).FirstOrDefault();
 | 
			
		||||
                    if (map is null)
 | 
			
		||||
                        return default;
 | 
			
		||||
                    var pp = Math.Round(item.Pp, 2);
 | 
			
		||||
                    var acc = CalculateAcc(item, m);
 | 
			
		||||
                    var mods = ResolveMods(item.EnabledMods);
 | 
			
		||||
                var mapResString = await http.GetStringAsync(mapReqString).ConfigureAwait(false);
 | 
			
		||||
                var map = JsonConvert.DeserializeObject<List<OsuMapData>>(mapResString).FirstOrDefault();
 | 
			
		||||
                if (map is null)
 | 
			
		||||
                    return default;
 | 
			
		||||
                var pp = Math.Round(item.Pp, 2);
 | 
			
		||||
                var acc = CalculateAcc(item, m);
 | 
			
		||||
                var mods = ResolveMods(item.EnabledMods);
 | 
			
		||||
 | 
			
		||||
                    var title = $"{map.Artist}-{map.Title} ({map.Version})";
 | 
			
		||||
                    var desc = $@"[/b/{item.BeatmapId}](https://osu.ppy.sh/b/{item.BeatmapId})
 | 
			
		||||
                var title = $"{map.Artist}-{map.Title} ({map.Version})";
 | 
			
		||||
                var desc = $@"[/b/{item.BeatmapId}](https://osu.ppy.sh/b/{item.BeatmapId})
 | 
			
		||||
{pp + "pp",-7} | {acc + "%",-7}
 | 
			
		||||
";
 | 
			
		||||
                    if (mods != "+")
 | 
			
		||||
                    {
 | 
			
		||||
                        desc += Format.Bold(mods);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return (title, desc);
 | 
			
		||||
                });
 | 
			
		||||
                    
 | 
			
		||||
                var eb = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle($"Top 5 plays for {user}");
 | 
			
		||||
                    
 | 
			
		||||
                var mapData = await Task.WhenAll(mapTasks);
 | 
			
		||||
                foreach (var (title, desc) in mapData.Where(x => x != default))
 | 
			
		||||
                if (mods != "+")
 | 
			
		||||
                {
 | 
			
		||||
                    eb.AddField(title, desc, false);
 | 
			
		||||
                    desc += Format.Bold(mods);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(eb).ConfigureAwait(false);
 | 
			
		||||
                return (title, desc);
 | 
			
		||||
            });
 | 
			
		||||
                    
 | 
			
		||||
            var eb = _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle($"Top 5 plays for {user}");
 | 
			
		||||
                    
 | 
			
		||||
            var mapData = await Task.WhenAll(mapTasks);
 | 
			
		||||
            foreach (var (title, desc) in mapData.Where(x => x != default))
 | 
			
		||||
            {
 | 
			
		||||
                eb.AddField(title, desc, false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(eb).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //https://osu.ppy.sh/wiki/Accuracy
 | 
			
		||||
 
 | 
			
		||||
@@ -41,11 +41,9 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                {
 | 
			
		||||
                    var res = await http.GetStringAsync($"{_poeURL}{usr}").ConfigureAwait(false);
 | 
			
		||||
                    characters = JsonConvert.DeserializeObject<List<Account>>(res);
 | 
			
		||||
                }
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                var res = await http.GetStringAsync($"{_poeURL}{usr}").ConfigureAwait(false);
 | 
			
		||||
                characters = JsonConvert.DeserializeObject<List<Account>>(res);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
@@ -102,11 +100,9 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                {
 | 
			
		||||
                    var res = await http.GetStringAsync("http://api.pathofexile.com/leagues?type=main&compact=1").ConfigureAwait(false);
 | 
			
		||||
                    leagues = JsonConvert.DeserializeObject<List<Leagues>>(res);
 | 
			
		||||
                }
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                var res = await http.GetStringAsync("http://api.pathofexile.com/leagues?type=main&compact=1").ConfigureAwait(false);
 | 
			
		||||
                leagues = JsonConvert.DeserializeObject<List<Leagues>>(res);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
@@ -159,48 +155,46 @@ public partial class Searches
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var res = $"{_ponURL}{leagueName}";
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                var obj = JObject.Parse(await http.GetStringAsync(res).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
                var chaosEquivalent = 0.0F;
 | 
			
		||||
                var conversionEquivalent = 0.0F;
 | 
			
		||||
 | 
			
		||||
                //	poe.ninja API does not include a "chaosEquivalent" property for Chaos Orbs.
 | 
			
		||||
                if (cleanCurrency == "Chaos Orb")
 | 
			
		||||
                {
 | 
			
		||||
                    var obj = JObject.Parse(await http.GetStringAsync(res).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
                    var chaosEquivalent = 0.0F;
 | 
			
		||||
                    var conversionEquivalent = 0.0F;
 | 
			
		||||
 | 
			
		||||
                    //	poe.ninja API does not include a "chaosEquivalent" property for Chaos Orbs.
 | 
			
		||||
                    if (cleanCurrency == "Chaos Orb")
 | 
			
		||||
                    {
 | 
			
		||||
                        chaosEquivalent = 1.0F;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var currencyInput = obj["lines"].Values<JObject>()
 | 
			
		||||
                            .Where(i => i["currencyTypeName"].Value<string>() == cleanCurrency)
 | 
			
		||||
                            .FirstOrDefault();
 | 
			
		||||
                        chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (cleanConvert == "Chaos Orb")
 | 
			
		||||
                    {
 | 
			
		||||
                        conversionEquivalent = 1.0F;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var currencyOutput = obj["lines"].Values<JObject>()
 | 
			
		||||
                            .Where(i => i["currencyTypeName"].Value<string>() == cleanConvert)
 | 
			
		||||
                            .FirstOrDefault();
 | 
			
		||||
                        conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                        .WithAuthor($"{leagueName} Currency Exchange",
 | 
			
		||||
                            "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                            "http://poe.ninja")
 | 
			
		||||
                        .AddField("Currency Type", cleanCurrency, true)
 | 
			
		||||
                        .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
 | 
			
		||||
                        .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
                    chaosEquivalent = 1.0F;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var currencyInput = obj["lines"].Values<JObject>()
 | 
			
		||||
                        .Where(i => i["currencyTypeName"].Value<string>() == cleanCurrency)
 | 
			
		||||
                        .FirstOrDefault();
 | 
			
		||||
                    chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (cleanConvert == "Chaos Orb")
 | 
			
		||||
                {
 | 
			
		||||
                    conversionEquivalent = 1.0F;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var currencyOutput = obj["lines"].Values<JObject>()
 | 
			
		||||
                        .Where(i => i["currencyTypeName"].Value<string>() == cleanConvert)
 | 
			
		||||
                        .FirstOrDefault();
 | 
			
		||||
                    conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithAuthor($"{leagueName} Currency Exchange",
 | 
			
		||||
                        "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                        "http://poe.ninja")
 | 
			
		||||
                    .AddField("Currency Type", cleanCurrency, true)
 | 
			
		||||
                    .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -39,15 +39,13 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        var av = usr.RealAvatarUrl();
 | 
			
		||||
        if (av is null)
 | 
			
		||||
            return;
 | 
			
		||||
        await using (var picStream = await _service.GetRipPictureAsync(usr.Nickname ?? usr.Username, av).ConfigureAwait(false))
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.Channel.SendFileAsync(
 | 
			
		||||
                    picStream,
 | 
			
		||||
                    "rip.png",
 | 
			
		||||
                    $"Rip {Format.Bold(usr.ToString())} \n\t- " +
 | 
			
		||||
                    Format.Italics(ctx.User.ToString()))
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        await using var picStream = await _service.GetRipPictureAsync(usr.Nickname ?? usr.Username, av).ConfigureAwait(false);
 | 
			
		||||
        await ctx.Channel.SendFileAsync(
 | 
			
		||||
                picStream,
 | 
			
		||||
                "rip.png",
 | 
			
		||||
                $"Rip {Format.Bold(usr.ToString())} \n\t- " +
 | 
			
		||||
                Format.Italics(ctx.User.ToString()))
 | 
			
		||||
            .ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [NadekoCommand, Aliases]
 | 
			
		||||
@@ -224,30 +222,28 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
 | 
			
		||||
            var fullQueryLink = $"http://imgur.com/search?q={ query }";
 | 
			
		||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
            using (var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false))
 | 
			
		||||
            {
 | 
			
		||||
                var elems = document.QuerySelectorAll("a.image-list-link").ToList();
 | 
			
		||||
            using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false);
 | 
			
		||||
            var elems = document.QuerySelectorAll("a.image-list-link").ToList();
 | 
			
		||||
 | 
			
		||||
                if (!elems.Any())
 | 
			
		||||
                    return;
 | 
			
		||||
            if (!elems.Any())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
                var img = elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement;
 | 
			
		||||
            var img = elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement;
 | 
			
		||||
 | 
			
		||||
                if (img?.Source is null)
 | 
			
		||||
                    return;
 | 
			
		||||
            if (img?.Source is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
                var source = img.Source.Replace("b.", ".", StringComparison.InvariantCulture);
 | 
			
		||||
            var source = img.Source.Replace("b.", ".", StringComparison.InvariantCulture);
 | 
			
		||||
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50),
 | 
			
		||||
                        "http://s.imgur.com/images/logo-1200-630.jpg?",
 | 
			
		||||
                        fullQueryLink)
 | 
			
		||||
                    .WithDescription(source)
 | 
			
		||||
                    .WithImageUrl(source)
 | 
			
		||||
                    .WithTitle(ctx.User.ToString());
 | 
			
		||||
                await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(GetText(strs.image_search_for) + " " + oterms.TrimTo(50),
 | 
			
		||||
                    "http://s.imgur.com/images/logo-1200-630.jpg?",
 | 
			
		||||
                    fullQueryLink)
 | 
			
		||||
                .WithDescription(source)
 | 
			
		||||
                .WithImageUrl(source)
 | 
			
		||||
                .WithTitle(ctx.User.ToString());
 | 
			
		||||
            await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -280,28 +276,24 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var _http = _httpFactory.CreateClient())
 | 
			
		||||
                using (var req = new HttpRequestMessage(HttpMethod.Post, "https://goolnk.com/api/v1/shorten"))
 | 
			
		||||
                using var _http = _httpFactory.CreateClient();
 | 
			
		||||
                using var req = new HttpRequestMessage(HttpMethod.Post, "https://goolnk.com/api/v1/shorten");
 | 
			
		||||
                var formData = new MultipartFormDataContent
 | 
			
		||||
                {
 | 
			
		||||
                    var formData = new MultipartFormDataContent
 | 
			
		||||
                    {
 | 
			
		||||
                        { new StringContent(query), "url" }
 | 
			
		||||
                    };
 | 
			
		||||
                    req.Content = formData;
 | 
			
		||||
                    { new StringContent(query), "url" }
 | 
			
		||||
                };
 | 
			
		||||
                req.Content = formData;
 | 
			
		||||
 | 
			
		||||
                    using (var res = await _http.SendAsync(req).ConfigureAwait(false))
 | 
			
		||||
                    {
 | 
			
		||||
                        var content = await res.Content.ReadAsStringAsync();
 | 
			
		||||
                        var data = JsonConvert.DeserializeObject<ShortenData>(content);
 | 
			
		||||
                using var res = await _http.SendAsync(req).ConfigureAwait(false);
 | 
			
		||||
                var content = await res.Content.ReadAsStringAsync();
 | 
			
		||||
                var data = JsonConvert.DeserializeObject<ShortenData>(content);
 | 
			
		||||
 | 
			
		||||
                        if (!string.IsNullOrWhiteSpace(data?.ResultUrl))
 | 
			
		||||
                            cachedShortenedLinks.TryAdd(query, data.ResultUrl);
 | 
			
		||||
                        else
 | 
			
		||||
                            return;
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(data?.ResultUrl))
 | 
			
		||||
                    cachedShortenedLinks.TryAdd(query, data.ResultUrl);
 | 
			
		||||
                else
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                        shortLink = data.ResultUrl;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                shortLink = data.ResultUrl;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
@@ -477,78 +469,74 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        if (!await ValidateQuery(word).ConfigureAwait(false))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        using (var _http = _httpFactory.CreateClient())
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        string res;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            string res;
 | 
			
		||||
            try
 | 
			
		||||
            res = await _cache.GetOrCreateAsync($"define_{word}", e =>
 | 
			
		||||
            {
 | 
			
		||||
                res = await _cache.GetOrCreateAsync($"define_{word}", e =>
 | 
			
		||||
                {
 | 
			
		||||
                    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
 | 
			
		||||
                    return _http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word));
 | 
			
		||||
                }).ConfigureAwait(false);
 | 
			
		||||
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
 | 
			
		||||
                return _http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word));
 | 
			
		||||
            }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var data = JsonConvert.DeserializeObject<DefineModel>(res);
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<DefineModel>(res);
 | 
			
		||||
 | 
			
		||||
                var datas = data.Results
 | 
			
		||||
                    .Where(x => x.Senses is not null && x.Senses.Count > 0 && x.Senses[0].Definition is not null)
 | 
			
		||||
                    .Select(x => (Sense: x.Senses[0], x.PartOfSpeech));
 | 
			
		||||
            var datas = data.Results
 | 
			
		||||
                .Where(x => x.Senses is not null && x.Senses.Count > 0 && x.Senses[0].Definition is not null)
 | 
			
		||||
                .Select(x => (Sense: x.Senses[0], x.PartOfSpeech));
 | 
			
		||||
 | 
			
		||||
                if (!datas.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning("Definition not found: {Word}", word);
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.define_unknown).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var col = datas.Select(data => (
 | 
			
		||||
                    Definition: data.Sense.Definition is string
 | 
			
		||||
                        ? data.Sense.Definition.ToString()
 | 
			
		||||
                        : ((JArray)JToken.Parse(data.Sense.Definition.ToString())).First.ToString(),
 | 
			
		||||
                    Example: data.Sense.Examples is null || data.Sense.Examples.Count == 0
 | 
			
		||||
                        ? string.Empty
 | 
			
		||||
                        : data.Sense.Examples[0].Text,
 | 
			
		||||
                    Word: word,
 | 
			
		||||
                    WordType: string.IsNullOrWhiteSpace(data.PartOfSpeech) ? "-" : data.PartOfSpeech
 | 
			
		||||
                )).ToList();
 | 
			
		||||
 | 
			
		||||
                Log.Information($"Sending {col.Count} definition for: {word}");
 | 
			
		||||
 | 
			
		||||
                await ctx.SendPaginatedConfirmAsync(0, page =>
 | 
			
		||||
                {
 | 
			
		||||
                    var data = col.Skip(page).First();
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                        .WithDescription(ctx.User.Mention)
 | 
			
		||||
                        .AddField(GetText(strs.word), data.Word, true)
 | 
			
		||||
                        .AddField(GetText(strs._class), data.WordType, true)
 | 
			
		||||
                        .AddField(GetText(strs.definition), data.Definition)
 | 
			
		||||
                        .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(data.Example))
 | 
			
		||||
                        embed.AddField(GetText(strs.example), data.Example);
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                }, col.Count, 1);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            if (!datas.Any())
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Error retrieving definition data for: {Word}", word);
 | 
			
		||||
                Log.Warning("Definition not found: {Word}", word);
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.define_unknown).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var col = datas.Select(data => (
 | 
			
		||||
                Definition: data.Sense.Definition is string
 | 
			
		||||
                    ? data.Sense.Definition.ToString()
 | 
			
		||||
                    : ((JArray)JToken.Parse(data.Sense.Definition.ToString())).First.ToString(),
 | 
			
		||||
                Example: data.Sense.Examples is null || data.Sense.Examples.Count == 0
 | 
			
		||||
                    ? string.Empty
 | 
			
		||||
                    : data.Sense.Examples[0].Text,
 | 
			
		||||
                Word: word,
 | 
			
		||||
                WordType: string.IsNullOrWhiteSpace(data.PartOfSpeech) ? "-" : data.PartOfSpeech
 | 
			
		||||
            )).ToList();
 | 
			
		||||
 | 
			
		||||
            Log.Information($"Sending {col.Count} definition for: {word}");
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(0, page =>
 | 
			
		||||
            {
 | 
			
		||||
                var data = col.Skip(page).First();
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithDescription(ctx.User.Mention)
 | 
			
		||||
                    .AddField(GetText(strs.word), data.Word, true)
 | 
			
		||||
                    .AddField(GetText(strs._class), data.WordType, true)
 | 
			
		||||
                    .AddField(GetText(strs.definition), data.Definition)
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(data.Example))
 | 
			
		||||
                    embed.AddField(GetText(strs.example), data.Example);
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            }, col.Count, 1);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex, "Error retrieving definition data for: {Word}", word);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [NadekoCommand, Aliases]
 | 
			
		||||
    public async Task Catfact()
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var response = await http.GetStringAsync("https://catfact.ninja/fact").ConfigureAwait(false);
 | 
			
		||||
            if (response is null)
 | 
			
		||||
                return;
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var response = await http.GetStringAsync("https://catfact.ninja/fact").ConfigureAwait(false);
 | 
			
		||||
        if (response is null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
            var fact = JObject.Parse(response)["fact"].ToString();
 | 
			
		||||
            await SendConfirmAsync("🐈" + GetText(strs.catfact), fact).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        var fact = JObject.Parse(response)["fact"].ToString();
 | 
			
		||||
        await SendConfirmAsync("🐈" + GetText(strs.catfact), fact).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //done in 3.0
 | 
			
		||||
@@ -585,15 +573,13 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        if (!await ValidateQuery(query).ConfigureAwait(false))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)).ConfigureAwait(false);
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<WikipediaApiModel>(result);
 | 
			
		||||
            if (data.Query.Pages[0].Missing || string.IsNullOrWhiteSpace(data.Query.Pages[0].FullUrl))
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.wiki_page_not_found).ConfigureAwait(false);
 | 
			
		||||
            else
 | 
			
		||||
                await ctx.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)).ConfigureAwait(false);
 | 
			
		||||
        var data = JsonConvert.DeserializeObject<WikipediaApiModel>(result);
 | 
			
		||||
        if (data.Query.Pages[0].Missing || string.IsNullOrWhiteSpace(data.Query.Pages[0].FullUrl))
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.wiki_page_not_found).ConfigureAwait(false);
 | 
			
		||||
        else
 | 
			
		||||
            await ctx.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [NadekoCommand, Aliases]
 | 
			
		||||
@@ -605,24 +591,20 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        var colorObjects = colors.Take(10)
 | 
			
		||||
            .ToArray();
 | 
			
		||||
 | 
			
		||||
        using (var img = new Image<Rgba32>(colorObjects.Length * 50, 50))
 | 
			
		||||
        using var img = new Image<Rgba32>(colorObjects.Length * 50, 50);
 | 
			
		||||
        for (var i = 0; i < colorObjects.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            for (var i = 0; i < colorObjects.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var x = i * 50;
 | 
			
		||||
                img.Mutate(m => m.FillPolygon(colorObjects[i], new PointF[] {
 | 
			
		||||
                    new(x, 0),
 | 
			
		||||
                    new(x + 50, 0),
 | 
			
		||||
                    new(x + 50, 50),
 | 
			
		||||
                    new(x, 50)
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await using (var ms = img.ToStream())
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(ms, $"colors.png").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            var x = i * 50;
 | 
			
		||||
            img.Mutate(m => m.FillPolygon(colorObjects[i], new PointF[] {
 | 
			
		||||
                new(x, 0),
 | 
			
		||||
                new(x + 50, 0),
 | 
			
		||||
                new(x + 50, 50),
 | 
			
		||||
                new(x, 50)
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using var ms = img.ToStream();
 | 
			
		||||
        await ctx.Channel.SendFileAsync(ms, $"colors.png").ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [NadekoCommand, Aliases]
 | 
			
		||||
@@ -655,35 +637,33 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        http.DefaultRequestHeaders.Clear();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            http.DefaultRequestHeaders.Clear();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetStringAsync($"https://{Uri.EscapeDataString(target)}.fandom.com/api.php" +
 | 
			
		||||
                                                    $"?action=query" +
 | 
			
		||||
                                                    $"&format=json" +
 | 
			
		||||
                                                    $"&list=search" +
 | 
			
		||||
                                                    $"&srsearch={Uri.EscapeDataString(query)}" +
 | 
			
		||||
                                                    $"&srlimit=1").ConfigureAwait(false);
 | 
			
		||||
                var items = JObject.Parse(res);
 | 
			
		||||
                var title = items["query"]?["search"]?.FirstOrDefault()?["title"]?.ToString();
 | 
			
		||||
            var res = await http.GetStringAsync($"https://{Uri.EscapeDataString(target)}.fandom.com/api.php" +
 | 
			
		||||
                                                $"?action=query" +
 | 
			
		||||
                                                $"&format=json" +
 | 
			
		||||
                                                $"&list=search" +
 | 
			
		||||
                                                $"&srsearch={Uri.EscapeDataString(query)}" +
 | 
			
		||||
                                                $"&srlimit=1").ConfigureAwait(false);
 | 
			
		||||
            var items = JObject.Parse(res);
 | 
			
		||||
            var title = items["query"]?["search"]?.FirstOrDefault()?["title"]?.ToString();
 | 
			
		||||
                    
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(title))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.wikia_error).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var url = Uri.EscapeDataString($"https://{target}.fandom.com/wiki/{title}");
 | 
			
		||||
                var response = $@"`{GetText(strs.title)}` {title.SanitizeMentions()}
 | 
			
		||||
`{GetText(strs.url)}:` {url}";
 | 
			
		||||
                await ctx.Channel.SendMessageAsync(response).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(title))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.wikia_error).ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var url = Uri.EscapeDataString($"https://{target}.fandom.com/wiki/{title}");
 | 
			
		||||
            var response = $@"`{GetText(strs.title)}` {title.SanitizeMentions()}
 | 
			
		||||
`{GetText(strs.url)}:` {url}";
 | 
			
		||||
            await ctx.Channel.SendMessageAsync(response).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.wikia_error).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -694,13 +674,11 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        var obj = new BibleVerses();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http
 | 
			
		||||
                    .GetStringAsync("https://bible-api.com/" + book + " " + chapterAndVerse).ConfigureAwait(false);
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var res = await http
 | 
			
		||||
                .GetStringAsync("https://bible-api.com/" + book + " " + chapterAndVerse).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                obj = JsonConvert.DeserializeObject<BibleVerses>(res);
 | 
			
		||||
            }
 | 
			
		||||
            obj = JsonConvert.DeserializeObject<BibleVerses>(res);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -61,58 +61,56 @@ public class AnimeSearchService : INService
 | 
			
		||||
            if (!ok)
 | 
			
		||||
            {
 | 
			
		||||
                var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
                using (var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false))
 | 
			
		||||
                using var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false);
 | 
			
		||||
                var imageElem = document.QuerySelector("div.seriesimg > img");
 | 
			
		||||
                if (imageElem is null)
 | 
			
		||||
                    return null;
 | 
			
		||||
                var imageUrl = ((IHtmlImageElement)imageElem).Source;
 | 
			
		||||
 | 
			
		||||
                var descElem = document.QuerySelector("div#editdescription > p");
 | 
			
		||||
                var desc = descElem.InnerHtml;
 | 
			
		||||
 | 
			
		||||
                var genres = document.QuerySelector("div#seriesgenre").Children
 | 
			
		||||
                    .Select(x => x as IHtmlAnchorElement)
 | 
			
		||||
                    .Where(x => x != null)
 | 
			
		||||
                    .Select(x => $"[{x.InnerHtml}]({x.Href})")
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
 | 
			
		||||
                var authors = document
 | 
			
		||||
                    .QuerySelector("div#showauthors")
 | 
			
		||||
                    .Children
 | 
			
		||||
                    .Select(x => x as IHtmlAnchorElement)
 | 
			
		||||
                    .Where(x => x != null)
 | 
			
		||||
                    .Select(x => $"[{x.InnerHtml}]({x.Href})")
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
 | 
			
		||||
                var score = ((IHtmlSpanElement)document
 | 
			
		||||
                        .QuerySelector("h5.seriesother > span.uvotes"))
 | 
			
		||||
                    .InnerHtml;
 | 
			
		||||
 | 
			
		||||
                var status = document
 | 
			
		||||
                    .QuerySelector("div#editstatus")
 | 
			
		||||
                    .InnerHtml;
 | 
			
		||||
                var title = document
 | 
			
		||||
                    .QuerySelector("div.w-blog-content > div.seriestitlenu")
 | 
			
		||||
                    .InnerHtml;
 | 
			
		||||
 | 
			
		||||
                var obj = new NovelResult()
 | 
			
		||||
                {
 | 
			
		||||
                    var imageElem = document.QuerySelector("div.seriesimg > img");
 | 
			
		||||
                    if (imageElem is null)
 | 
			
		||||
                        return null;
 | 
			
		||||
                    var imageUrl = ((IHtmlImageElement)imageElem).Source;
 | 
			
		||||
                    Description = desc,
 | 
			
		||||
                    Authors = authors,
 | 
			
		||||
                    Genres = genres,
 | 
			
		||||
                    ImageUrl = imageUrl,
 | 
			
		||||
                    Link = link,
 | 
			
		||||
                    Score = score,
 | 
			
		||||
                    Status = status,
 | 
			
		||||
                    Title = title,
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                    var descElem = document.QuerySelector("div#editdescription > p");
 | 
			
		||||
                    var desc = descElem.InnerHtml;
 | 
			
		||||
                await _cache.SetNovelDataAsync(link,
 | 
			
		||||
                    JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    var genres = document.QuerySelector("div#seriesgenre").Children
 | 
			
		||||
                        .Select(x => x as IHtmlAnchorElement)
 | 
			
		||||
                        .Where(x => x != null)
 | 
			
		||||
                        .Select(x => $"[{x.InnerHtml}]({x.Href})")
 | 
			
		||||
                        .ToArray();
 | 
			
		||||
 | 
			
		||||
                    var authors = document
 | 
			
		||||
                        .QuerySelector("div#showauthors")
 | 
			
		||||
                        .Children
 | 
			
		||||
                        .Select(x => x as IHtmlAnchorElement)
 | 
			
		||||
                        .Where(x => x != null)
 | 
			
		||||
                        .Select(x => $"[{x.InnerHtml}]({x.Href})")
 | 
			
		||||
                        .ToArray();
 | 
			
		||||
 | 
			
		||||
                    var score = ((IHtmlSpanElement)document
 | 
			
		||||
                            .QuerySelector("h5.seriesother > span.uvotes"))
 | 
			
		||||
                        .InnerHtml;
 | 
			
		||||
 | 
			
		||||
                    var status = document
 | 
			
		||||
                        .QuerySelector("div#editstatus")
 | 
			
		||||
                        .InnerHtml;
 | 
			
		||||
                    var title = document
 | 
			
		||||
                        .QuerySelector("div.w-blog-content > div.seriestitlenu")
 | 
			
		||||
                        .InnerHtml;
 | 
			
		||||
 | 
			
		||||
                    var obj = new NovelResult()
 | 
			
		||||
                    {
 | 
			
		||||
                        Description = desc,
 | 
			
		||||
                        Authors = authors,
 | 
			
		||||
                        Genres = genres,
 | 
			
		||||
                        ImageUrl = imageUrl,
 | 
			
		||||
                        Link = link,
 | 
			
		||||
                        Score = score,
 | 
			
		||||
                        Status = status,
 | 
			
		||||
                        Title = title,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    await _cache.SetNovelDataAsync(link,
 | 
			
		||||
                        JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    return obj;
 | 
			
		||||
                }
 | 
			
		||||
                return obj;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return JsonConvert.DeserializeObject<NovelResult>(data);
 | 
			
		||||
 
 | 
			
		||||
@@ -156,15 +156,13 @@ public class FeedsService : INService
 | 
			
		||||
 | 
			
		||||
    public List<FeedSub> GetFeeds(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.GuildConfigsForId(guildId, 
 | 
			
		||||
                    set => set.Include(x => x.FeedSubs)
 | 
			
		||||
                        .ThenInclude(x => x.GuildConfig))
 | 
			
		||||
                .FeedSubs
 | 
			
		||||
                .OrderBy(x => x.Id)
 | 
			
		||||
                .ToList();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(guildId, 
 | 
			
		||||
                set => set.Include(x => x.FeedSubs)
 | 
			
		||||
                    .ThenInclude(x => x.GuildConfig))
 | 
			
		||||
            .FeedSubs
 | 
			
		||||
            .OrderBy(x => x.Id)
 | 
			
		||||
            .ToList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
 | 
			
		||||
@@ -177,32 +175,30 @@ public class FeedsService : INService
 | 
			
		||||
            Url = rssFeed.Trim(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId,
 | 
			
		||||
            set => set.Include(x => x.FeedSubs)
 | 
			
		||||
                .ThenInclude(x => x.GuildConfig));
 | 
			
		||||
 | 
			
		||||
        if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId,
 | 
			
		||||
                set => set.Include(x => x.FeedSubs)
 | 
			
		||||
                    .ThenInclude(x => x.GuildConfig));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        else if (gc.FeedSubs.Count >= 10)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
 | 
			
		||||
        gc.FeedSubs.Add(fs);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        //adding all, in case bot wasn't on this guild when it started
 | 
			
		||||
        foreach (var feed in gc.FeedSubs)
 | 
			
		||||
        {
 | 
			
		||||
            _subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            else if (gc.FeedSubs.Count >= 10)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            gc.FeedSubs.Add(fs);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            //adding all, in case bot wasn't on this guild when it started
 | 
			
		||||
            foreach (var feed in gc.FeedSubs)
 | 
			
		||||
            {
 | 
			
		||||
                _subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
 | 
			
		||||
                {
 | 
			
		||||
                    old.Add(feed);
 | 
			
		||||
                    return old;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
                old.Add(feed);
 | 
			
		||||
                return old;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
@@ -213,24 +209,22 @@ public class FeedsService : INService
 | 
			
		||||
        if (index < 0)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
 | 
			
		||||
                .FeedSubs
 | 
			
		||||
                .OrderBy(x => x.Id)
 | 
			
		||||
                .ToList();
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
 | 
			
		||||
            .FeedSubs
 | 
			
		||||
            .OrderBy(x => x.Id)
 | 
			
		||||
            .ToList();
 | 
			
		||||
 | 
			
		||||
            if (items.Count <= index)
 | 
			
		||||
                return false;
 | 
			
		||||
            var toRemove = items[index];
 | 
			
		||||
            _subs.AddOrUpdate(toRemove.Url.ToLower(), new HashSet<FeedSub>(), (key, old) =>
 | 
			
		||||
            {
 | 
			
		||||
                old.Remove(toRemove);
 | 
			
		||||
                return old;
 | 
			
		||||
            });
 | 
			
		||||
            uow.Remove(toRemove);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        if (items.Count <= index)
 | 
			
		||||
            return false;
 | 
			
		||||
        var toRemove = items[index];
 | 
			
		||||
        _subs.AddOrUpdate(toRemove.Url.ToLower(), new HashSet<FeedSub>(), (key, old) =>
 | 
			
		||||
        {
 | 
			
		||||
            old.Remove(toRemove);
 | 
			
		||||
            return old;
 | 
			
		||||
        });
 | 
			
		||||
        uow.Remove(toRemove);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -85,55 +85,49 @@ public class SearchesService : INService
 | 
			
		||||
    public async Task<byte[]> GetRipPictureFactory((string text, Uri avatarUrl) arg)
 | 
			
		||||
    {
 | 
			
		||||
        var (text, avatarUrl) = arg;
 | 
			
		||||
        using (var bg = Image.Load<Rgba32>(_imgs.Rip.ToArray()))
 | 
			
		||||
        using var bg = Image.Load<Rgba32>(_imgs.Rip.ToArray());
 | 
			
		||||
        var (succ, data) = (false, (byte[])null); //await _cache.TryGetImageDataAsync(avatarUrl);
 | 
			
		||||
        if (!succ)
 | 
			
		||||
        {
 | 
			
		||||
            var (succ, data) = (false, (byte[])null); //await _cache.TryGetImageDataAsync(avatarUrl);
 | 
			
		||||
            if (!succ)
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            data = await http.GetByteArrayAsync(avatarUrl);
 | 
			
		||||
            using (var avatarImg = Image.Load<Rgba32>(data))
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                {
 | 
			
		||||
                    data = await http.GetByteArrayAsync(avatarUrl);
 | 
			
		||||
                    using (var avatarImg = Image.Load<Rgba32>(data))
 | 
			
		||||
                    {
 | 
			
		||||
                        avatarImg.Mutate(x => x
 | 
			
		||||
                            .Resize(85, 85)
 | 
			
		||||
                            .ApplyRoundedCorners(42));
 | 
			
		||||
                        data = avatarImg.ToStream().ToArray();
 | 
			
		||||
                        DrawAvatar(bg, avatarImg);
 | 
			
		||||
                    }
 | 
			
		||||
                    await _cache.SetImageDataAsync(avatarUrl, data);
 | 
			
		||||
                }
 | 
			
		||||
                avatarImg.Mutate(x => x
 | 
			
		||||
                    .Resize(85, 85)
 | 
			
		||||
                    .ApplyRoundedCorners(42));
 | 
			
		||||
                data = avatarImg.ToStream().ToArray();
 | 
			
		||||
                DrawAvatar(bg, avatarImg);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                using (var avatarImg = Image.Load<Rgba32>(data))
 | 
			
		||||
                {
 | 
			
		||||
                    DrawAvatar(bg, avatarImg);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bg.Mutate(x => x.DrawText(
 | 
			
		||||
                new()
 | 
			
		||||
                {
 | 
			
		||||
                    TextOptions = new TextOptions
 | 
			
		||||
                    {
 | 
			
		||||
                        HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                        WrapTextWidth = 190,
 | 
			
		||||
                    }.WithFallbackFonts(_fonts.FallBackFonts)
 | 
			
		||||
                },
 | 
			
		||||
                text,
 | 
			
		||||
                _fonts.RipFont,
 | 
			
		||||
                SixLabors.ImageSharp.Color.Black,
 | 
			
		||||
                new(25, 225)));
 | 
			
		||||
 | 
			
		||||
            //flowa
 | 
			
		||||
            using (var flowers = Image.Load(_imgs.RipOverlay.ToArray()))
 | 
			
		||||
            {
 | 
			
		||||
                bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions()));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return bg.ToStream().ToArray();
 | 
			
		||||
            await _cache.SetImageDataAsync(avatarUrl, data);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            using var avatarImg = Image.Load<Rgba32>(data);
 | 
			
		||||
            DrawAvatar(bg, avatarImg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bg.Mutate(x => x.DrawText(
 | 
			
		||||
            new()
 | 
			
		||||
            {
 | 
			
		||||
                TextOptions = new TextOptions
 | 
			
		||||
                {
 | 
			
		||||
                    HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                    WrapTextWidth = 190,
 | 
			
		||||
                }.WithFallbackFonts(_fonts.FallBackFonts)
 | 
			
		||||
            },
 | 
			
		||||
            text,
 | 
			
		||||
            _fonts.RipFont,
 | 
			
		||||
            SixLabors.ImageSharp.Color.Black,
 | 
			
		||||
            new(25, 225)));
 | 
			
		||||
 | 
			
		||||
        //flowa
 | 
			
		||||
        using (var flowers = Image.Load(_imgs.RipOverlay.ToArray()))
 | 
			
		||||
        {
 | 
			
		||||
            bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return bg.ToStream().ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<WeatherData> GetWeatherDataAsync(string query)
 | 
			
		||||
@@ -148,25 +142,23 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task<WeatherData> GetWeatherDataFactory(string query)
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var data = await http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?" +
 | 
			
		||||
                                                     $"q={query}&" +
 | 
			
		||||
                                                     $"appid=42cd627dd60debf25a5739e50a217d74&" +
 | 
			
		||||
                                                     $"units=metric").ConfigureAwait(false);
 | 
			
		||||
            var data = await http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?" +
 | 
			
		||||
                                                 $"q={query}&" +
 | 
			
		||||
                                                 $"appid=42cd627dd60debf25a5739e50a217d74&" +
 | 
			
		||||
                                                 $"units=metric").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (data is null)
 | 
			
		||||
                    return null;
 | 
			
		||||
 | 
			
		||||
                return JsonConvert.DeserializeObject<WeatherData>(data);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex.Message);
 | 
			
		||||
            if (data is null)
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return JsonConvert.DeserializeObject<WeatherData>(data);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning(ex.Message);
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -195,49 +187,42 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using (var _http = _httpFactory.CreateClient())
 | 
			
		||||
            using var _http = _httpFactory.CreateClient();
 | 
			
		||||
            var res = await _cache.GetOrAddCachedDataAsync($"geo_{query}", _ =>
 | 
			
		||||
            {
 | 
			
		||||
                var res = await _cache.GetOrAddCachedDataAsync($"geo_{query}", _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    var url = "https://eu1.locationiq.com/v1/search.php?" +
 | 
			
		||||
                              (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) ? "key=" : $"key={_creds.LocationIqApiKey}&") +
 | 
			
		||||
                              $"q={Uri.EscapeDataString(query)}&" +
 | 
			
		||||
                              $"format=json";
 | 
			
		||||
                var url = "https://eu1.locationiq.com/v1/search.php?" +
 | 
			
		||||
                          (string.IsNullOrWhiteSpace(_creds.LocationIqApiKey) ? "key=" : $"key={_creds.LocationIqApiKey}&") +
 | 
			
		||||
                          $"q={Uri.EscapeDataString(query)}&" +
 | 
			
		||||
                          $"format=json";
 | 
			
		||||
 | 
			
		||||
                    var res = _http.GetStringAsync(url);
 | 
			
		||||
                    return res;
 | 
			
		||||
                }, "", TimeSpan.FromHours(1));
 | 
			
		||||
                var res = _http.GetStringAsync(url);
 | 
			
		||||
                return res;
 | 
			
		||||
            }, "", TimeSpan.FromHours(1));
 | 
			
		||||
 | 
			
		||||
                var responses = JsonConvert.DeserializeObject<LocationIqResponse[]>(res);
 | 
			
		||||
                if (responses is null || responses.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning("Geocode lookup failed for: {Query}", query);
 | 
			
		||||
                    return (default, TimeErrors.NotFound);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var geoData = responses[0];
 | 
			
		||||
 | 
			
		||||
                using (var req = new HttpRequestMessage(HttpMethod.Get, "http://api.timezonedb.com/v2.1/get-time-zone?" +
 | 
			
		||||
                                                                        $"key={_creds.TimezoneDbApiKey}&format=json&" +
 | 
			
		||||
                                                                        "by=position&" +
 | 
			
		||||
                                                                        $"lat={geoData.Lat}&lng={geoData.Lon}"))
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    using (var geoRes = await _http.SendAsync(req))
 | 
			
		||||
                    {
 | 
			
		||||
                        var resString = await geoRes.Content.ReadAsStringAsync();
 | 
			
		||||
                        var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(resString);
 | 
			
		||||
 | 
			
		||||
                        var time = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(timeObj.Timestamp);
 | 
			
		||||
 | 
			
		||||
                        return ((
 | 
			
		||||
                            Address: responses[0].DisplayName,
 | 
			
		||||
                            Time: time,
 | 
			
		||||
                            TimeZoneName: timeObj.TimezoneName
 | 
			
		||||
                        ), default);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            var responses = JsonConvert.DeserializeObject<LocationIqResponse[]>(res);
 | 
			
		||||
            if (responses is null || responses.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning("Geocode lookup failed for: {Query}", query);
 | 
			
		||||
                return (default, TimeErrors.NotFound);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var geoData = responses[0];
 | 
			
		||||
 | 
			
		||||
            using var req = new HttpRequestMessage(HttpMethod.Get, "http://api.timezonedb.com/v2.1/get-time-zone?" +
 | 
			
		||||
                                                                   $"key={_creds.TimezoneDbApiKey}&format=json&" +
 | 
			
		||||
                                                                   "by=position&" +
 | 
			
		||||
                                                                   $"lat={geoData.Lat}&lng={geoData.Lon}");
 | 
			
		||||
            using var geoRes = await _http.SendAsync(req);
 | 
			
		||||
            var resString = await geoRes.Content.ReadAsStringAsync();
 | 
			
		||||
            var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(resString);
 | 
			
		||||
 | 
			
		||||
            var time = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(timeObj.Timestamp);
 | 
			
		||||
 | 
			
		||||
            return ((
 | 
			
		||||
                Address: responses[0].DisplayName,
 | 
			
		||||
                Time: time,
 | 
			
		||||
                TimeZoneName: timeObj.TimezoneName
 | 
			
		||||
            ), default);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -310,21 +295,17 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
    public async Task<(string Setup, string Punchline)> GetRandomJoke()
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var res = await http.GetStringAsync("https://official-joke-api.appspot.com/random_joke");
 | 
			
		||||
            var resObj = JsonConvert.DeserializeAnonymousType(res, new {setup = "", punchline = ""});
 | 
			
		||||
            return (resObj.setup, resObj.punchline);
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var res = await http.GetStringAsync("https://official-joke-api.appspot.com/random_joke");
 | 
			
		||||
        var resObj = JsonConvert.DeserializeAnonymousType(res, new {setup = "", punchline = ""});
 | 
			
		||||
        return (resObj.setup, resObj.punchline);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> GetChuckNorrisJoke()
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var response = await http.GetStringAsync(new Uri("http://api.icndb.com/jokes/random/")).ConfigureAwait(false);
 | 
			
		||||
            return JObject.Parse(response)["value"]["joke"].ToString() + " 😆";
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var response = await http.GetStringAsync(new Uri("http://api.icndb.com/jokes/random/")).ConfigureAwait(false);
 | 
			
		||||
        return JObject.Parse(response)["value"]["joke"].ToString() + " 😆";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<MtgData> GetMtgCardAsync(string search)
 | 
			
		||||
@@ -367,30 +348,28 @@ public class SearchesService : INService
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        http.DefaultRequestHeaders.Clear();
 | 
			
		||||
        var response = await http.GetStringAsync($"https://api.magicthegathering.io/v1/cards?name={Uri.EscapeDataString(search)}")
 | 
			
		||||
            .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        var responseObject = JsonConvert.DeserializeObject<MtgResponse>(response);
 | 
			
		||||
        if (responseObject is null)
 | 
			
		||||
            return new MtgData[0];
 | 
			
		||||
 | 
			
		||||
        var cards = responseObject.Cards.Take(5).ToArray();
 | 
			
		||||
        if (cards.Length == 0)
 | 
			
		||||
            return new MtgData[0];
 | 
			
		||||
 | 
			
		||||
        var tasks = new List<Task<MtgData>>(cards.Length);
 | 
			
		||||
        for (var i = 0; i < cards.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            http.DefaultRequestHeaders.Clear();
 | 
			
		||||
            var response = await http.GetStringAsync($"https://api.magicthegathering.io/v1/cards?name={Uri.EscapeDataString(search)}")
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
            var card = cards[i];
 | 
			
		||||
 | 
			
		||||
            var responseObject = JsonConvert.DeserializeObject<MtgResponse>(response);
 | 
			
		||||
            if (responseObject is null)
 | 
			
		||||
                return new MtgData[0];
 | 
			
		||||
 | 
			
		||||
            var cards = responseObject.Cards.Take(5).ToArray();
 | 
			
		||||
            if (cards.Length == 0)
 | 
			
		||||
                return new MtgData[0];
 | 
			
		||||
 | 
			
		||||
            var tasks = new List<Task<MtgData>>(cards.Length);
 | 
			
		||||
            for (var i = 0; i < cards.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var card = cards[i];
 | 
			
		||||
 | 
			
		||||
                tasks.Add(GetMtgDataAsync(card));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await Task.WhenAll(tasks).ConfigureAwait(false);
 | 
			
		||||
            tasks.Add(GetMtgDataAsync(card));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return await Task.WhenAll(tasks).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<HearthstoneCardData> GetHearthstoneCardDataAsync(string name)
 | 
			
		||||
@@ -404,38 +383,36 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task<HearthstoneCardData> HearthstoneCardDataFactory(string name)
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        http.DefaultRequestHeaders.Clear();
 | 
			
		||||
        http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.RapidApiKey);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            http.DefaultRequestHeaders.Clear();
 | 
			
		||||
            http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.RapidApiKey);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.rapidapi.com/" +
 | 
			
		||||
                                                         $"cards/search/{Uri.EscapeDataString(name)}").ConfigureAwait(false);
 | 
			
		||||
                var objs = JsonConvert.DeserializeObject<HearthstoneCardData[]>(response);
 | 
			
		||||
                if (objs is null || objs.Length == 0)
 | 
			
		||||
                    return null;
 | 
			
		||||
                var data = objs.FirstOrDefault(x => x.Collectible)
 | 
			
		||||
                           ?? objs.FirstOrDefault(x => !string.IsNullOrEmpty(x.PlayerClass))
 | 
			
		||||
                           ?? objs.FirstOrDefault();
 | 
			
		||||
                if (data is null)
 | 
			
		||||
                    return null;
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(data.Img))
 | 
			
		||||
                {
 | 
			
		||||
                    data.Img = await _google.ShortenUrl(data.Img).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(data.Text))
 | 
			
		||||
                {
 | 
			
		||||
                    var converter = new Html2Markdown.Converter();
 | 
			
		||||
                    data.Text = converter.Convert(data.Text);
 | 
			
		||||
                }
 | 
			
		||||
                return data;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex.Message);
 | 
			
		||||
            var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.rapidapi.com/" +
 | 
			
		||||
                                                     $"cards/search/{Uri.EscapeDataString(name)}").ConfigureAwait(false);
 | 
			
		||||
            var objs = JsonConvert.DeserializeObject<HearthstoneCardData[]>(response);
 | 
			
		||||
            if (objs is null || objs.Length == 0)
 | 
			
		||||
                return null;
 | 
			
		||||
            var data = objs.FirstOrDefault(x => x.Collectible)
 | 
			
		||||
                       ?? objs.FirstOrDefault(x => !string.IsNullOrEmpty(x.PlayerClass))
 | 
			
		||||
                       ?? objs.FirstOrDefault();
 | 
			
		||||
            if (data is null)
 | 
			
		||||
                return null;
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(data.Img))
 | 
			
		||||
            {
 | 
			
		||||
                data.Img = await _google.ShortenUrl(data.Img).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(data.Text))
 | 
			
		||||
            {
 | 
			
		||||
                var converter = new Html2Markdown.Converter();
 | 
			
		||||
                data.Text = converter.Convert(data.Text);
 | 
			
		||||
            }
 | 
			
		||||
            return data;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex.Message);
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -450,16 +427,14 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task<OmdbMovie> GetMovieDataFactory(string name)
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var res = await http.GetStringAsync(string.Format("https://omdbapi.nadeko.bot/?t={0}&y=&plot=full&r=json",
 | 
			
		||||
                name.Trim().Replace(' ', '+'))).ConfigureAwait(false);
 | 
			
		||||
            var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
 | 
			
		||||
            if (movie?.Title is null)
 | 
			
		||||
                return null;
 | 
			
		||||
            movie.Poster = await _google.ShortenUrl(movie.Poster).ConfigureAwait(false);
 | 
			
		||||
            return movie;
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var res = await http.GetStringAsync(string.Format("https://omdbapi.nadeko.bot/?t={0}&y=&plot=full&r=json",
 | 
			
		||||
            name.Trim().Replace(' ', '+'))).ConfigureAwait(false);
 | 
			
		||||
        var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
 | 
			
		||||
        if (movie?.Title is null)
 | 
			
		||||
            return null;
 | 
			
		||||
        movie.Poster = await _google.ShortenUrl(movie.Poster).ConfigureAwait(false);
 | 
			
		||||
        return movie;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<int> GetSteamAppIdByName(string query)
 | 
			
		||||
@@ -486,20 +461,18 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
        var gamesMap = await _cache.GetOrAddCachedDataAsync(STEAM_GAME_IDS_KEY, async _ =>
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            {
 | 
			
		||||
                // https://api.steampowered.com/ISteamApps/GetAppList/v2/
 | 
			
		||||
                var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/").ConfigureAwait(false);
 | 
			
		||||
                var apps = JsonConvert.DeserializeAnonymousType(gamesStr, new { applist = new { apps = new List<SteamGameId>() } }).applist.apps;
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            // https://api.steampowered.com/ISteamApps/GetAppList/v2/
 | 
			
		||||
            var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/").ConfigureAwait(false);
 | 
			
		||||
            var apps = JsonConvert.DeserializeAnonymousType(gamesStr, new { applist = new { apps = new List<SteamGameId>() } }).applist.apps;
 | 
			
		||||
 | 
			
		||||
                return apps
 | 
			
		||||
                    .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
 | 
			
		||||
                    .GroupBy(x => x.Name)
 | 
			
		||||
                    .ToDictionary(x => x.Key, x => x.First().AppId);
 | 
			
		||||
                //await db.HashSetAsync("steam_game_ids", apps.Select(app => new HashEntry(app.Name.Trim().ToLowerInvariant(), app.AppId)).ToArray()).ConfigureAwait(false);
 | 
			
		||||
                //await db.StringSetAsync("steam_game_ids", gamesStr, TimeSpan.FromHours(24));
 | 
			
		||||
                //await db.KeyExpireAsync("steam_game_ids", TimeSpan.FromHours(24), CommandFlags.FireAndForget).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            return apps
 | 
			
		||||
                .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
 | 
			
		||||
                .GroupBy(x => x.Name)
 | 
			
		||||
                .ToDictionary(x => x.Key, x => x.First().AppId);
 | 
			
		||||
            //await db.HashSetAsync("steam_game_ids", apps.Select(app => new HashEntry(app.Name.Trim().ToLowerInvariant(), app.AppId)).ToArray()).ConfigureAwait(false);
 | 
			
		||||
            //await db.StringSetAsync("steam_game_ids", gamesStr, TimeSpan.FromHours(24));
 | 
			
		||||
            //await db.KeyExpireAsync("steam_game_ids", TimeSpan.FromHours(24), CommandFlags.FireAndForget).ConfigureAwait(false);
 | 
			
		||||
        }, default(string), TimeSpan.FromHours(24));
 | 
			
		||||
 | 
			
		||||
        if (gamesMap is null)
 | 
			
		||||
 
 | 
			
		||||
@@ -130,24 +130,22 @@ public sealed class StreamNotificationService : INService
 | 
			
		||||
                    var deleteGroups = failingStreams.GroupBy(x => x.Type)
 | 
			
		||||
                        .ToDictionary(x => x.Key, x => x.Select(x => x.Name).ToList());
 | 
			
		||||
 | 
			
		||||
                    using (var uow = _db.GetDbContext())
 | 
			
		||||
                    using var uow = _db.GetDbContext();
 | 
			
		||||
                    foreach (var kvp in deleteGroups)
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var kvp in deleteGroups)
 | 
			
		||||
                        {
 | 
			
		||||
                            Log.Information($"Deleting {kvp.Value.Count} {kvp.Key} streams because " +
 | 
			
		||||
                                            $"they've been erroring for more than {errorLimit}: {string.Join(", ", kvp.Value)}");
 | 
			
		||||
                        Log.Information($"Deleting {kvp.Value.Count} {kvp.Key} streams because " +
 | 
			
		||||
                                        $"they've been erroring for more than {errorLimit}: {string.Join(", ", kvp.Value)}");
 | 
			
		||||
 | 
			
		||||
                            var toDelete = uow.Set<FollowedStream>()
 | 
			
		||||
                                .AsQueryable()
 | 
			
		||||
                                .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username))
 | 
			
		||||
                                .ToList();
 | 
			
		||||
                        var toDelete = uow.Set<FollowedStream>()
 | 
			
		||||
                            .AsQueryable()
 | 
			
		||||
                            .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username))
 | 
			
		||||
                            .ToList();
 | 
			
		||||
 | 
			
		||||
                            uow.RemoveRange(toDelete);
 | 
			
		||||
                            uow.SaveChanges();
 | 
			
		||||
                        uow.RemoveRange(toDelete);
 | 
			
		||||
                        uow.SaveChanges();
 | 
			
		||||
                                
 | 
			
		||||
                            foreach(var loginToDelete in kvp.Value)
 | 
			
		||||
                                _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete));
 | 
			
		||||
                        }
 | 
			
		||||
                        foreach(var loginToDelete in kvp.Value)
 | 
			
		||||
                            _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
@@ -461,20 +459,18 @@ public sealed class StreamNotificationService : INService
 | 
			
		||||
    public bool ToggleStreamOffline(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        bool newValue;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            newValue = gc.NotifyStreamOffline = !gc.NotifyStreamOffline;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        newValue = gc.NotifyStreamOffline = !gc.NotifyStreamOffline;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
            if (newValue)
 | 
			
		||||
            {
 | 
			
		||||
                _offlineNotificationServers.Add(guildId);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _offlineNotificationServers.TryRemove(guildId);
 | 
			
		||||
            }
 | 
			
		||||
        if (newValue)
 | 
			
		||||
        {
 | 
			
		||||
            _offlineNotificationServers.Add(guildId);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            _offlineNotificationServers.TryRemove(guildId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return newValue;
 | 
			
		||||
@@ -510,35 +506,33 @@ public sealed class StreamNotificationService : INService
 | 
			
		||||
 | 
			
		||||
    public bool SetStreamMessage(ulong guildId, int index, string message, out FollowedStream fs)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var fss = uow.Set<FollowedStream>()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .OrderBy(x => x.Id)
 | 
			
		||||
            .ToList();
 | 
			
		||||
 | 
			
		||||
        if (fss.Count <= index)
 | 
			
		||||
        {
 | 
			
		||||
            var fss = uow.Set<FollowedStream>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                .OrderBy(x => x.Id)
 | 
			
		||||
                .ToList();
 | 
			
		||||
 | 
			
		||||
            if (fss.Count <= index)
 | 
			
		||||
            {
 | 
			
		||||
                fs = null;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fs = fss[index];
 | 
			
		||||
            fs.Message = message;
 | 
			
		||||
            lock (_shardLock)
 | 
			
		||||
            {
 | 
			
		||||
                var streams = GetLocalGuildStreams(fs.CreateKey(), guildId);
 | 
			
		||||
 | 
			
		||||
                // message doesn't participate in equality checking
 | 
			
		||||
                // removing and adding = update
 | 
			
		||||
                streams.Remove(fs);
 | 
			
		||||
                streams.Add(fs);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            fs = null;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fs = fss[index];
 | 
			
		||||
        fs.Message = message;
 | 
			
		||||
        lock (_shardLock)
 | 
			
		||||
        {
 | 
			
		||||
            var streams = GetLocalGuildStreams(fs.CreateKey(), guildId);
 | 
			
		||||
 | 
			
		||||
            // message doesn't participate in equality checking
 | 
			
		||||
            // removing and adding = update
 | 
			
		||||
            streams.Remove(fs);
 | 
			
		||||
            streams.Add(fs);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,22 +23,20 @@ public partial class Searches
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
 | 
			
		||||
                        var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                        var embed = _eb.Create().WithOkColor()
 | 
			
		||||
                            .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                            .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}")
 | 
			
		||||
                            .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                            .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
                        var sent = await ctx.Channel.EmbedAsync(embed)
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
                    using var http = _httpFactory.CreateClient();
 | 
			
		||||
                    var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
 | 
			
		||||
                    var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                    var embed = _eb.Create().WithOkColor()
 | 
			
		||||
                        .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                        .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}")
 | 
			
		||||
                        .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                        .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
                    var sent = await ctx.Channel.EmbedAsync(embed)
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        await Task.Delay(10000).ConfigureAwait(false);
 | 
			
		||||
                    await Task.Delay(10000).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (HttpRequestException)
 | 
			
		||||
                {
 | 
			
		||||
@@ -57,25 +55,23 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                {
 | 
			
		||||
                    var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                        .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}")
 | 
			
		||||
                        .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                        .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
                var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                    .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}")
 | 
			
		||||
                    .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                    .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
                        
 | 
			
		||||
                    var sent = await ctx.Channel.EmbedAsync(embed)
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
                var sent = await ctx.Channel.EmbedAsync(embed)
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await Task.Delay(10000).ConfigureAwait(false);
 | 
			
		||||
                await Task.Delay(10000).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (HttpRequestException)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,23 +18,21 @@ public class CommandMapService : IInputTransformer, INService
 | 
			
		||||
    {
 | 
			
		||||
        _eb = eb;
 | 
			
		||||
 | 
			
		||||
        using (var uow = db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var guildIds = client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
            var configs = uow.Set<GuildConfig>()
 | 
			
		||||
                .Include(gc => gc.CommandAliases)
 | 
			
		||||
                .Where(x => guildIds.Contains(x.GuildId))
 | 
			
		||||
                .ToList();
 | 
			
		||||
        using var uow = db.GetDbContext();
 | 
			
		||||
        var guildIds = client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
        var configs = uow.Set<GuildConfig>()
 | 
			
		||||
            .Include(gc => gc.CommandAliases)
 | 
			
		||||
            .Where(x => guildIds.Contains(x.GuildId))
 | 
			
		||||
            .ToList();
 | 
			
		||||
                
 | 
			
		||||
            AliasMaps = new(configs
 | 
			
		||||
                .ToDictionary(
 | 
			
		||||
                    x => x.GuildId,
 | 
			
		||||
                    x => new ConcurrentDictionary<string, string>(x.CommandAliases
 | 
			
		||||
                        .DistinctBy(ca => ca.Trigger)
 | 
			
		||||
                        .ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
 | 
			
		||||
        AliasMaps = new(configs
 | 
			
		||||
            .ToDictionary(
 | 
			
		||||
                x => x.GuildId,
 | 
			
		||||
                x => new ConcurrentDictionary<string, string>(x.CommandAliases
 | 
			
		||||
                    .DistinctBy(ca => ca.Trigger)
 | 
			
		||||
                    .ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
 | 
			
		||||
 | 
			
		||||
            _db = db;
 | 
			
		||||
        }
 | 
			
		||||
        _db = db;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int ClearAliases(ulong guildId)
 | 
			
		||||
@@ -42,13 +40,11 @@ public class CommandMapService : IInputTransformer, INService
 | 
			
		||||
        AliasMaps.TryRemove(guildId, out _);
 | 
			
		||||
 | 
			
		||||
        int count;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
            count = gc.CommandAliases.Count;
 | 
			
		||||
            gc.CommandAliases.Clear();
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
        count = gc.CommandAliases.Count;
 | 
			
		||||
        gc.CommandAliases.Clear();
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,11 +35,9 @@ public class ConverterService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task<Rates> GetCurrencyRates()
 | 
			
		||||
    {
 | 
			
		||||
        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
        {
 | 
			
		||||
            var res = await http.GetStringAsync("https://convertapi.nadeko.bot/latest").ConfigureAwait(false);
 | 
			
		||||
            return JsonConvert.DeserializeObject<Rates>(res);
 | 
			
		||||
        }
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var res = await http.GetStringAsync("https://convertapi.nadeko.bot/latest").ConfigureAwait(false);
 | 
			
		||||
        return JsonConvert.DeserializeObject<Rates>(res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task UpdateCurrency(bool shouldLoad)
 | 
			
		||||
 
 | 
			
		||||
@@ -224,64 +224,62 @@ public class PatreonRewardsService : INService
 | 
			
		||||
        {
 | 
			
		||||
            var eligibleFor = (int)(cents * settings.PatreonCurrencyPerCent);
 | 
			
		||||
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
            await using var uow = _db.GetDbContext();
 | 
			
		||||
            var users = uow.Set<RewardedUser>();
 | 
			
		||||
            var usr = await users.FirstOrDefaultAsyncEF(x => x.PatreonUserId == patreonUserId);
 | 
			
		||||
 | 
			
		||||
            if (usr is null)
 | 
			
		||||
            {
 | 
			
		||||
                var users = uow.Set<RewardedUser>();
 | 
			
		||||
                var usr = await users.FirstOrDefaultAsyncEF(x => x.PatreonUserId == patreonUserId);
 | 
			
		||||
 | 
			
		||||
                if (usr is null)
 | 
			
		||||
                users.Add(new()
 | 
			
		||||
                {
 | 
			
		||||
                    users.Add(new()
 | 
			
		||||
                    {
 | 
			
		||||
                        PatreonUserId = patreonUserId,
 | 
			
		||||
                        LastReward = now,
 | 
			
		||||
                        AmountRewardedThisMonth = eligibleFor,
 | 
			
		||||
                    });
 | 
			
		||||
                    PatreonUserId = patreonUserId,
 | 
			
		||||
                    LastReward = now,
 | 
			
		||||
                    AmountRewardedThisMonth = eligibleFor,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                    await uow.SaveChangesAsync();
 | 
			
		||||
                await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
                    await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true);
 | 
			
		||||
                await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true);
 | 
			
		||||
                        
 | 
			
		||||
                    Log.Information($"Sending new currency reward to {userId}");
 | 
			
		||||
                    await SendMessageToUser(userId, $"Thank you for your pledge! " +
 | 
			
		||||
                                                    $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !");
 | 
			
		||||
                    return eligibleFor;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (usr.LastReward.Month != now.Month)
 | 
			
		||||
                {
 | 
			
		||||
                    usr.LastReward = now;
 | 
			
		||||
                    usr.AmountRewardedThisMonth = eligibleFor;
 | 
			
		||||
 | 
			
		||||
                    await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
                    await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, gamble: true);
 | 
			
		||||
 | 
			
		||||
                    Log.Information($"Sending recurring currency reward to {userId}");
 | 
			
		||||
                    await SendMessageToUser(userId, $"Thank you for your continued support! " +
 | 
			
		||||
                                                    $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!");
 | 
			
		||||
 | 
			
		||||
                    return eligibleFor;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (usr.AmountRewardedThisMonth < eligibleFor)
 | 
			
		||||
                {
 | 
			
		||||
                    var toAward = eligibleFor - usr.AmountRewardedThisMonth;
 | 
			
		||||
 | 
			
		||||
                    usr.LastReward = now;
 | 
			
		||||
                    usr.AmountRewardedThisMonth = toAward;
 | 
			
		||||
                    await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
                    await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true);
 | 
			
		||||
                        
 | 
			
		||||
                    Log.Information($"Sending updated currency reward to {userId}");
 | 
			
		||||
                    await SendMessageToUser(userId, $"Thank you for increasing your pledge! " +
 | 
			
		||||
                                                    $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !");
 | 
			
		||||
                    return toAward;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return 0;
 | 
			
		||||
                Log.Information($"Sending new currency reward to {userId}");
 | 
			
		||||
                await SendMessageToUser(userId, $"Thank you for your pledge! " +
 | 
			
		||||
                                                $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !");
 | 
			
		||||
                return eligibleFor;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (usr.LastReward.Month != now.Month)
 | 
			
		||||
            {
 | 
			
		||||
                usr.LastReward = now;
 | 
			
		||||
                usr.AmountRewardedThisMonth = eligibleFor;
 | 
			
		||||
 | 
			
		||||
                await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
                await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, gamble: true);
 | 
			
		||||
 | 
			
		||||
                Log.Information($"Sending recurring currency reward to {userId}");
 | 
			
		||||
                await SendMessageToUser(userId, $"Thank you for your continued support! " +
 | 
			
		||||
                                                $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!");
 | 
			
		||||
 | 
			
		||||
                return eligibleFor;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (usr.AmountRewardedThisMonth < eligibleFor)
 | 
			
		||||
            {
 | 
			
		||||
                var toAward = eligibleFor - usr.AmountRewardedThisMonth;
 | 
			
		||||
 | 
			
		||||
                usr.LastReward = now;
 | 
			
		||||
                usr.AmountRewardedThisMonth = toAward;
 | 
			
		||||
                await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
                await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true);
 | 
			
		||||
                        
 | 
			
		||||
                Log.Information($"Sending updated currency reward to {userId}");
 | 
			
		||||
                await SendMessageToUser(userId, $"Thank you for increasing your pledge! " +
 | 
			
		||||
                                                $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !");
 | 
			
		||||
                return toAward;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,23 +56,19 @@ public class RemindService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task RemoveReminders(List<Reminder> reminders)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            uow.Set<Reminder>()
 | 
			
		||||
                .RemoveRange(reminders);
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        uow.Set<Reminder>()
 | 
			
		||||
            .RemoveRange(reminders);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task<List<Reminder>> GetRemindersBeforeAsync(DateTime now)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.Reminders
 | 
			
		||||
                .FromSqlInterpolated($"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
 | 
			
		||||
                .ToListAsync();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.Reminders
 | 
			
		||||
            .FromSqlInterpolated($"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public struct RemindObject
 | 
			
		||||
 
 | 
			
		||||
@@ -431,10 +431,8 @@ public partial class Utility : NadekoModule
 | 
			
		||||
                    return msg;
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
        await using (var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false))
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.User.SendFileAsync(stream, title, title, false).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        await using var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false);
 | 
			
		||||
        await ctx.User.SendFileAsync(stream, title, title, false).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
    private static SemaphoreSlim sem = new(1, 1);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,42 +52,38 @@ public class ClubService : INService
 | 
			
		||||
    public ClubInfo TransferClub(IUser from, IUser newOwner)
 | 
			
		||||
    {
 | 
			
		||||
        ClubInfo club;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByOwner(from.Id);
 | 
			
		||||
            var newOwnerUser = uow.GetOrCreateUser(newOwner);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByOwner(@from.Id);
 | 
			
		||||
        var newOwnerUser = uow.GetOrCreateUser(newOwner);
 | 
			
		||||
 | 
			
		||||
            if (club is null ||
 | 
			
		||||
                club.Owner.UserId != from.Id ||
 | 
			
		||||
                !club.Users.Contains(newOwnerUser))
 | 
			
		||||
                return null;
 | 
			
		||||
        if (club is null ||
 | 
			
		||||
            club.Owner.UserId != @from.Id ||
 | 
			
		||||
            !club.Users.Contains(newOwnerUser))
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
            club.Owner.IsClubAdmin = true; // old owner will stay as admin
 | 
			
		||||
            newOwnerUser.IsClubAdmin = true;
 | 
			
		||||
            club.Owner = newOwnerUser;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        club.Owner.IsClubAdmin = true; // old owner will stay as admin
 | 
			
		||||
        newOwnerUser.IsClubAdmin = true;
 | 
			
		||||
        club.Owner = newOwnerUser;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return club;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ToggleAdmin(IUser owner, IUser toAdmin)
 | 
			
		||||
    {
 | 
			
		||||
        bool newState;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var club = uow.Clubs.GetByOwner(owner.Id);
 | 
			
		||||
            var adminUser = uow.GetOrCreateUser(toAdmin);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var club = uow.Clubs.GetByOwner(owner.Id);
 | 
			
		||||
        var adminUser = uow.GetOrCreateUser(toAdmin);
 | 
			
		||||
 | 
			
		||||
            if (club is null || club.Owner.UserId != owner.Id ||
 | 
			
		||||
                !club.Users.Contains(adminUser))
 | 
			
		||||
                throw new InvalidOperationException();
 | 
			
		||||
        if (club is null || club.Owner.UserId != owner.Id ||
 | 
			
		||||
            !club.Users.Contains(adminUser))
 | 
			
		||||
            throw new InvalidOperationException();
 | 
			
		||||
 | 
			
		||||
            if (club.OwnerId == adminUser.Id)
 | 
			
		||||
                return true;
 | 
			
		||||
        if (club.OwnerId == adminUser.Id)
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
            newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return newState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -102,25 +98,21 @@ public class ClubService : INService
 | 
			
		||||
    {
 | 
			
		||||
        if (url != null)
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
            using (var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
 | 
			
		||||
            {
 | 
			
		||||
                if (!temp.IsImage() || temp.GetImageSize() > 11)
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var club = uow.Clubs.GetByOwner(ownerUserId);
 | 
			
		||||
 | 
			
		||||
            if (club is null)
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
 | 
			
		||||
            if (!temp.IsImage() || temp.GetImageSize() > 11)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            club.ImageUrl = url.ToString();
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var club = uow.Clubs.GetByOwner(ownerUserId);
 | 
			
		||||
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        club.ImageUrl = url.ToString();
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -137,95 +129,85 @@ public class ClubService : INService
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByName(name, discrim);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
            else
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByName(name, discrim);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
        else
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ApplyToClub(IUser user, ClubInfo club)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var du = uow.GetOrCreateUser(user);
 | 
			
		||||
        uow.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))
 | 
			
		||||
        {
 | 
			
		||||
            var du = uow.GetOrCreateUser(user);
 | 
			
		||||
            uow.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.Set<ClubApplicants>().Add(app);
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            //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.Set<ClubApplicants>().Add(app);
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
 | 
			
		||||
    {
 | 
			
		||||
        discordUser = null;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
            if (applicant is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
        if (applicant is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            applicant.User.Club = club;
 | 
			
		||||
            applicant.User.IsClubAdmin = false;
 | 
			
		||||
            club.Applicants.Remove(applicant);
 | 
			
		||||
        applicant.User.Club = club;
 | 
			
		||||
        applicant.User.IsClubAdmin = false;
 | 
			
		||||
        club.Applicants.Remove(applicant);
 | 
			
		||||
 | 
			
		||||
            //remove that user's all other applications
 | 
			
		||||
            uow.Set<ClubApplicants>()
 | 
			
		||||
                .RemoveRange(uow.Set<ClubApplicants>()
 | 
			
		||||
                    .AsQueryable()
 | 
			
		||||
                    .Where(x => x.UserId == applicant.User.Id));
 | 
			
		||||
        //remove that user's all other applications
 | 
			
		||||
        uow.Set<ClubApplicants>()
 | 
			
		||||
            .RemoveRange(uow.Set<ClubApplicants>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => x.UserId == applicant.User.Id));
 | 
			
		||||
 | 
			
		||||
            discordUser = applicant.User;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        discordUser = applicant.User;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool LeaveClub(IUser user)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var du = uow.GetOrCreateUser(user);
 | 
			
		||||
            if (du.Club is null || du.Club.OwnerId == du.Id)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var du = uow.GetOrCreateUser(user);
 | 
			
		||||
        if (du.Club is null || du.Club.OwnerId == du.Id)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            du.Club = null;
 | 
			
		||||
            du.IsClubAdmin = false;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        du.Club = null;
 | 
			
		||||
        du.IsClubAdmin = false;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -234,121 +216,109 @@ public class ClubService : INService
 | 
			
		||||
        if (level < 5)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            club.MinimumLevelReq = level;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        club.MinimumLevelReq = level;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ChangeClubDescription(ulong userId, string desc)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            club.Description = desc?.TrimTo(150, true);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        club.Description = desc?.TrimTo(150, true);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Disband(ulong userId, out ClubInfo club)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByOwner(userId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            uow.Clubs.Remove(club);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.Clubs.Remove(club);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Ban(ulong bannerId, string userName, out ClubInfo club)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByOwnerOrAdmin(bannerId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
 | 
			
		||||
                  ?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())?.User;
 | 
			
		||||
        if (usr is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        club.Bans.Add(new()
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByOwnerOrAdmin(bannerId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
            Club = club,
 | 
			
		||||
            User = usr,
 | 
			
		||||
        });
 | 
			
		||||
        club.Users.Remove(usr);
 | 
			
		||||
 | 
			
		||||
            var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
 | 
			
		||||
                      ?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())?.User;
 | 
			
		||||
            if (usr is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
 | 
			
		||||
        if (app != null)
 | 
			
		||||
            club.Applicants.Remove(app);
 | 
			
		||||
 | 
			
		||||
            if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            club.Bans.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                Club = club,
 | 
			
		||||
                User = usr,
 | 
			
		||||
            });
 | 
			
		||||
            club.Users.Remove(usr);
 | 
			
		||||
 | 
			
		||||
            var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
 | 
			
		||||
            if (app != null)
 | 
			
		||||
                club.Applicants.Remove(app);
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
            if (ban is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
        if (ban is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            club.Bans.Remove(ban);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        club.Bans.Remove(ban);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Kick(ulong kickerId, string userName, out ClubInfo club)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            club = uow.Clubs.GetByOwnerOrAdmin(kickerId);
 | 
			
		||||
            if (club is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        club = uow.Clubs.GetByOwnerOrAdmin(kickerId);
 | 
			
		||||
        if (club is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
            if (usr is null)
 | 
			
		||||
                return false;
 | 
			
		||||
        var usr = club.Users.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
 | 
			
		||||
        if (usr is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
 | 
			
		||||
                return false;
 | 
			
		||||
        if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
            club.Users.Remove(usr);
 | 
			
		||||
            var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
 | 
			
		||||
            if (app != null)
 | 
			
		||||
                club.Applicants.Remove(app);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        club.Users.Remove(usr);
 | 
			
		||||
        var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
 | 
			
		||||
        if (app != null)
 | 
			
		||||
            club.Applicants.Remove(app);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ public class XpService : INService
 | 
			
		||||
    private readonly FontProvider _fonts;
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly Task updateXpTask;
 | 
			
		||||
    private readonly Task _updateXpTask;
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
    private readonly XpConfigService _xpConfig;
 | 
			
		||||
    private readonly IPubSub _pubSub;
 | 
			
		||||
@@ -124,19 +124,19 @@ public class XpService : INService
 | 
			
		||||
            allGuildConfigs.Where(x => x.XpSettings.ServerExcluded)
 | 
			
		||||
                .Select(x => x.GuildId));
 | 
			
		||||
 | 
			
		||||
        _cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
 | 
			
		||||
        _cmd.OnMessageNoTrigger += Cmd_OnMessageNoTrigger;
 | 
			
		||||
            
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
        _client.UserVoiceStateUpdated += _client_OnUserVoiceStateUpdated;
 | 
			
		||||
        _client.UserVoiceStateUpdated += Client_OnUserVoiceStateUpdated;
 | 
			
		||||
            
 | 
			
		||||
        // Scan guilds on startup.
 | 
			
		||||
        _client.GuildAvailable += _client_OnGuildAvailable;
 | 
			
		||||
        _client.GuildAvailable += Client_OnGuildAvailable;
 | 
			
		||||
        foreach (var guild in _client.Guilds)
 | 
			
		||||
        {
 | 
			
		||||
            _client_OnGuildAvailable(guild);
 | 
			
		||||
            Client_OnGuildAvailable(guild);
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
        updateXpTask = Task.Run(UpdateLoop);
 | 
			
		||||
        _updateXpTask = Task.Run(UpdateLoop);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task UpdateLoop()
 | 
			
		||||
@@ -318,45 +318,41 @@ public class XpService : INService
 | 
			
		||||
 | 
			
		||||
    public void SetCurrencyReward(ulong guildId, int level, int amount)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var settings = uow.XpSettingsFor(guildId);
 | 
			
		||||
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
        {
 | 
			
		||||
            var settings = uow.XpSettingsFor(guildId);
 | 
			
		||||
 | 
			
		||||
            if (amount <= 0)
 | 
			
		||||
            var toRemove = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
 | 
			
		||||
            if (toRemove != null)
 | 
			
		||||
            {
 | 
			
		||||
                var toRemove = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
 | 
			
		||||
                if (toRemove != null)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Remove(toRemove);
 | 
			
		||||
                    settings.CurrencyRewards.Remove(toRemove);
 | 
			
		||||
                }
 | 
			
		||||
                uow.Remove(toRemove);
 | 
			
		||||
                settings.CurrencyRewards.Remove(toRemove);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var rew = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
 | 
			
		||||
 | 
			
		||||
                if (rew != null)
 | 
			
		||||
                    rew.Amount = amount;
 | 
			
		||||
                else
 | 
			
		||||
                    settings.CurrencyRewards.Add(new()
 | 
			
		||||
                    {
 | 
			
		||||
                        Level = level,
 | 
			
		||||
                        Amount = amount,
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var rew = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
 | 
			
		||||
 | 
			
		||||
            if (rew != null)
 | 
			
		||||
                rew.Amount = amount;
 | 
			
		||||
            else
 | 
			
		||||
                settings.CurrencyRewards.Add(new()
 | 
			
		||||
                {
 | 
			
		||||
                    Level = level,
 | 
			
		||||
                    Amount = amount,
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<XpCurrencyReward> GetCurrencyRewards(ulong id)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.XpSettingsFor(id)
 | 
			
		||||
                .CurrencyRewards
 | 
			
		||||
                .ToArray();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.XpSettingsFor(id)
 | 
			
		||||
            .CurrencyRewards
 | 
			
		||||
            .ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
 | 
			
		||||
@@ -410,66 +406,52 @@ public class XpService : INService
 | 
			
		||||
 | 
			
		||||
    public List<UserXpStats> GetUserXps(ulong guildId, int page)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.UserXpStats.GetUsersFor(guildId, page);
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.UserXpStats.GetUsersFor(guildId, page);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<UserXpStats> GetTopUserXps(ulong guildId, int count)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.UserXpStats.GetTopUserXps(guildId, count);
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.UserXpStats.GetTopUserXps(guildId, count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DiscordUser[] GetUserXps(int page)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.DiscordUser.GetUsersXpLeaderboardFor(page);
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.DiscordUser.GetUsersXpLeaderboardFor(page);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var user = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
            user.NotifyOnLevelUp = type;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var user = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
        user.NotifyOnLevelUp = type;
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public XpNotificationLocation GetNotificationType(ulong userId, ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var user = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
            return user.NotifyOnLevelUp;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var user = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
        return user.NotifyOnLevelUp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public XpNotificationLocation GetNotificationType(IUser user)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.GetOrCreateUser(user).NotifyOnLevelUp;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GetOrCreateUser(user).NotifyOnLevelUp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ChangeNotificationType(IUser user, XpNotificationLocation type)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var du = uow.GetOrCreateUser(user);
 | 
			
		||||
            du.NotifyOnLevelUp = type;
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var du = uow.GetOrCreateUser(user);
 | 
			
		||||
        du.NotifyOnLevelUp = type;
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task _client_OnGuildAvailable(SocketGuild guild)
 | 
			
		||||
    private Task Client_OnGuildAvailable(SocketGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
@@ -482,7 +464,7 @@ public class XpService : INService
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    private Task _client_OnUserVoiceStateUpdated(SocketUser socketUser, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
    private Task Client_OnUserVoiceStateUpdated(SocketUser socketUser, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
    {
 | 
			
		||||
        if (socketUser is not SocketGuildUser user || user.IsBot)
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
@@ -605,7 +587,7 @@ public class XpService : INService
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    private Task _cmd_OnMessageNoTrigger(IUserMessage arg)
 | 
			
		||||
    private Task Cmd_OnMessageNoTrigger(IUserMessage arg)
 | 
			
		||||
    {
 | 
			
		||||
        if (arg.Author is not SocketGuildUser user || user.IsBot)
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
@@ -659,14 +641,12 @@ public class XpService : INService
 | 
			
		||||
        
 | 
			
		||||
    public void AddXp(ulong userId, ulong guildId, int amount)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var usr = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var usr = uow.GetOrCreateUserXpStats(guildId, userId);
 | 
			
		||||
 | 
			
		||||
            usr.AwardedXp += amount;
 | 
			
		||||
        usr.AwardedXp += amount;
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool IsServerExcluded(ulong id)
 | 
			
		||||
@@ -728,92 +708,86 @@ public class XpService : INService
 | 
			
		||||
 | 
			
		||||
    public bool ToggleExcludeServer(ulong id)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var xpSetting = uow.XpSettingsFor(id);
 | 
			
		||||
        if (_excludedServers.Add(id))
 | 
			
		||||
        {
 | 
			
		||||
            var xpSetting = uow.XpSettingsFor(id);
 | 
			
		||||
            if (_excludedServers.Add(id))
 | 
			
		||||
            {
 | 
			
		||||
                xpSetting.ServerExcluded = true;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _excludedServers.TryRemove(id);
 | 
			
		||||
            xpSetting.ServerExcluded = false;
 | 
			
		||||
            xpSetting.ServerExcluded = true;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
            return false;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _excludedServers.TryRemove(id);
 | 
			
		||||
        xpSetting.ServerExcluded = false;
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ToggleExcludeRole(ulong guildId, ulong rId)
 | 
			
		||||
    {
 | 
			
		||||
        var roles = _excludedRoles.GetOrAdd(guildId, _ => new());
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var xpSetting = uow.XpSettingsFor(guildId);
 | 
			
		||||
        var excludeObj = new ExcludedItem
 | 
			
		||||
        {
 | 
			
		||||
            var xpSetting = uow.XpSettingsFor(guildId);
 | 
			
		||||
            var excludeObj = new ExcludedItem
 | 
			
		||||
            {
 | 
			
		||||
                ItemId = rId,
 | 
			
		||||
                ItemType = ExcludedItemType.Role,
 | 
			
		||||
            };
 | 
			
		||||
            ItemId = rId,
 | 
			
		||||
            ItemType = ExcludedItemType.Role,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            if (roles.Add(rId))
 | 
			
		||||
        if (roles.Add(rId))
 | 
			
		||||
        {
 | 
			
		||||
            if (xpSetting.ExclusionList.Add(excludeObj))
 | 
			
		||||
            {
 | 
			
		||||
                if (xpSetting.ExclusionList.Add(excludeObj))
 | 
			
		||||
                {
 | 
			
		||||
                    uow.SaveChanges();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            roles.TryRemove(rId);
 | 
			
		||||
 | 
			
		||||
            var toDelete = xpSetting.ExclusionList.FirstOrDefault(x => x.Equals(excludeObj));
 | 
			
		||||
            if (toDelete != null)
 | 
			
		||||
            {
 | 
			
		||||
                roles.TryRemove(rId);
 | 
			
		||||
 | 
			
		||||
                var toDelete = xpSetting.ExclusionList.FirstOrDefault(x => x.Equals(excludeObj));
 | 
			
		||||
                if (toDelete != null)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Remove(toDelete);
 | 
			
		||||
                    uow.SaveChanges();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
                uow.Remove(toDelete);
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool ToggleExcludeChannel(ulong guildId, ulong chId)
 | 
			
		||||
    {
 | 
			
		||||
        var channels = _excludedChannels.GetOrAdd(guildId, _ => new());
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var xpSetting = uow.XpSettingsFor(guildId);
 | 
			
		||||
        var excludeObj = new ExcludedItem
 | 
			
		||||
        {
 | 
			
		||||
            var xpSetting = uow.XpSettingsFor(guildId);
 | 
			
		||||
            var excludeObj = new ExcludedItem
 | 
			
		||||
            {
 | 
			
		||||
                ItemId = chId,
 | 
			
		||||
                ItemType = ExcludedItemType.Channel,
 | 
			
		||||
            };
 | 
			
		||||
            ItemId = chId,
 | 
			
		||||
            ItemType = ExcludedItemType.Channel,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            if (channels.Add(chId))
 | 
			
		||||
        if (channels.Add(chId))
 | 
			
		||||
        {
 | 
			
		||||
            if (xpSetting.ExclusionList.Add(excludeObj))
 | 
			
		||||
            {
 | 
			
		||||
                if (xpSetting.ExclusionList.Add(excludeObj))
 | 
			
		||||
                {
 | 
			
		||||
                    uow.SaveChanges();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            channels.TryRemove(chId);
 | 
			
		||||
 | 
			
		||||
            if (xpSetting.ExclusionList.Remove(excludeObj))
 | 
			
		||||
            {
 | 
			
		||||
                channels.TryRemove(chId);
 | 
			
		||||
 | 
			
		||||
                if (xpSetting.ExclusionList.Remove(excludeObj))
 | 
			
		||||
                {
 | 
			
		||||
                    uow.SaveChanges();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -844,230 +818,226 @@ public class XpService : INService
 | 
			
		||||
                    VerticalAlignment = VerticalAlignment.Top,
 | 
			
		||||
                }
 | 
			
		||||
            }.WithFallbackFonts(_fonts.FallBackFonts);
 | 
			
		||||
                
 | 
			
		||||
            using (var img = Image.Load<Rgba32>(_images.XpBackground, out var imageFormat))
 | 
			
		||||
 | 
			
		||||
            using var img = Image.Load<Rgba32>(_images.XpBackground, out var imageFormat);
 | 
			
		||||
            if (_template.User.Name.Show)
 | 
			
		||||
            {
 | 
			
		||||
                if (_template.User.Name.Show)
 | 
			
		||||
                var fontSize = (int)(_template.User.Name.FontSize * 0.9);
 | 
			
		||||
                var username = stats.User.ToString();
 | 
			
		||||
                var usernameFont = _fonts.NotoSans
 | 
			
		||||
                    .CreateFont(fontSize, FontStyle.Bold);
 | 
			
		||||
 | 
			
		||||
                var size = TextMeasurer.Measure($"@{username}", new(usernameFont));
 | 
			
		||||
                var scale = 400f / size.Width;
 | 
			
		||||
                if (scale < 1)
 | 
			
		||||
                {
 | 
			
		||||
                    var fontSize = (int)(_template.User.Name.FontSize * 0.9);
 | 
			
		||||
                    var username = stats.User.ToString();
 | 
			
		||||
                    var usernameFont = _fonts.NotoSans
 | 
			
		||||
                        .CreateFont(fontSize, FontStyle.Bold);
 | 
			
		||||
 | 
			
		||||
                    var size = TextMeasurer.Measure($"@{username}", new(usernameFont));
 | 
			
		||||
                    var scale = 400f / size.Width;
 | 
			
		||||
                    if (scale < 1)
 | 
			
		||||
                    {
 | 
			
		||||
                        usernameFont = _fonts.NotoSans
 | 
			
		||||
                            .CreateFont(_template.User.Name.FontSize * scale, FontStyle.Bold);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    img.Mutate(x =>
 | 
			
		||||
                    {
 | 
			
		||||
                        x.DrawText(usernameTextOptions,
 | 
			
		||||
                            "@" + username,
 | 
			
		||||
                            usernameFont,
 | 
			
		||||
                            _template.User.Name.Color,
 | 
			
		||||
                            new(_template.User.Name.Pos.X, _template.User.Name.Pos.Y + 8));
 | 
			
		||||
                    });
 | 
			
		||||
                    usernameFont = _fonts.NotoSans
 | 
			
		||||
                        .CreateFont(_template.User.Name.FontSize * scale, FontStyle.Bold);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //club name
 | 
			
		||||
 | 
			
		||||
                if (_template.Club.Name.Show)
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    var clubName = stats.User.Club?.ToString() ?? "-";
 | 
			
		||||
                    x.DrawText(usernameTextOptions,
 | 
			
		||||
                        "@" + username,
 | 
			
		||||
                        usernameFont,
 | 
			
		||||
                        _template.User.Name.Color,
 | 
			
		||||
                        new(_template.User.Name.Pos.X, _template.User.Name.Pos.Y + 8));
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                    var clubFont = _fonts.NotoSans
 | 
			
		||||
                        .CreateFont(_template.Club.Name.FontSize, FontStyle.Regular);
 | 
			
		||||
            //club name
 | 
			
		||||
 | 
			
		||||
            if (_template.Club.Name.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var clubName = stats.User.Club?.ToString() ?? "-";
 | 
			
		||||
 | 
			
		||||
                var clubFont = _fonts.NotoSans
 | 
			
		||||
                    .CreateFont(_template.Club.Name.FontSize, FontStyle.Regular);
 | 
			
		||||
                        
 | 
			
		||||
                    img.Mutate(x => x.DrawText(clubTextOptions,
 | 
			
		||||
                        clubName,
 | 
			
		||||
                        clubFont,
 | 
			
		||||
                        _template.Club.Name.Color,
 | 
			
		||||
                        new(_template.Club.Name.Pos.X + 50, _template.Club.Name.Pos.Y - 8))
 | 
			
		||||
                img.Mutate(x => x.DrawText(clubTextOptions,
 | 
			
		||||
                    clubName,
 | 
			
		||||
                    clubFont,
 | 
			
		||||
                    _template.Club.Name.Color,
 | 
			
		||||
                    new(_template.Club.Name.Pos.X + 50, _template.Club.Name.Pos.Y - 8))
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GlobalLevel.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.DrawText(
 | 
			
		||||
                        stats.Global.Level.ToString(),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.GlobalLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GlobalLevel.Color,
 | 
			
		||||
                        new(_template.User.GlobalLevel.Pos.X, _template.User.GlobalLevel.Pos.Y)
 | 
			
		||||
                    ); //level
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GuildLevel.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.DrawText(
 | 
			
		||||
                        stats.Guild.Level.ToString(),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.GuildLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GuildLevel.Color,
 | 
			
		||||
                        new(_template.User.GuildLevel.Pos.X, _template.User.GuildLevel.Pos.Y)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.GlobalLevel.Show)
 | 
			
		||||
 | 
			
		||||
            var pen = new Pen(SixLabors.ImageSharp.Color.Black, 1);
 | 
			
		||||
 | 
			
		||||
            var global = stats.Global;
 | 
			
		||||
            var guild = stats.Guild;
 | 
			
		||||
 | 
			
		||||
            //xp bar
 | 
			
		||||
            if (_template.User.Xp.Bar.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var xpPercent = global.LevelXp / (float) global.RequiredXp;
 | 
			
		||||
                DrawXpBar(xpPercent, _template.User.Xp.Bar.Global, img);
 | 
			
		||||
                xpPercent = guild.LevelXp / (float) guild.RequiredXp;
 | 
			
		||||
                DrawXpBar(xpPercent, _template.User.Xp.Bar.Guild, img);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.Xp.Global.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x => x.DrawText($"{global.LevelXp}/{global.RequiredXp}",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Global.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(_template.User.Xp.Global.Pos.X, _template.User.Xp.Global.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.Xp.Guild.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x => x.DrawText($"{guild.LevelXp}/{guild.RequiredXp}",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Guild.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(_template.User.Xp.Guild.Pos.X, _template.User.Xp.Guild.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (stats.FullGuildStats.AwardedXp != 0 && _template.User.Xp.Awarded.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var sign = stats.FullGuildStats.AwardedXp > 0
 | 
			
		||||
                    ? "+ "
 | 
			
		||||
                    : "";
 | 
			
		||||
                var awX = _template.User.Xp.Awarded.Pos.X -
 | 
			
		||||
                          (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5);
 | 
			
		||||
                var awY = _template.User.Xp.Awarded.Pos.Y;
 | 
			
		||||
                img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Awarded.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Awarded.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(awX, awY)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //ranking
 | 
			
		||||
            if (_template.User.GlobalRank.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x => x.DrawText(stats.GlobalRanking.ToString(),
 | 
			
		||||
                    _fonts.UniSans.CreateFont(_template.User.GlobalRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    _template.User.GlobalRank.Color,
 | 
			
		||||
                    new(_template.User.GlobalRank.Pos.X, _template.User.GlobalRank.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GuildRank.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x => x.DrawText(stats.GuildRanking.ToString(),
 | 
			
		||||
                    _fonts.UniSans.CreateFont(_template.User.GuildRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    _template.User.GuildRank.Color,
 | 
			
		||||
                    new(_template.User.GuildRank.Pos.X, _template.User.GuildRank.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //time on this level
 | 
			
		||||
 | 
			
		||||
            string GetTimeSpent(DateTime time, string format)
 | 
			
		||||
            {
 | 
			
		||||
                var offset = DateTime.UtcNow - time;
 | 
			
		||||
                return string.Format(format, offset.Days, offset.Hours, offset.Minutes);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.TimeOnLevel.Global.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                    x.DrawText(GetTimeSpent(stats.User.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.TimeOnLevel.Global.Color,
 | 
			
		||||
                        new(_template.User.TimeOnLevel.Global.Pos.X,
 | 
			
		||||
                            _template.User.TimeOnLevel.Global.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.TimeOnLevel.Guild.Show)
 | 
			
		||||
            {
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                    x.DrawText(
 | 
			
		||||
                        GetTimeSpent(stats.FullGuildStats.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.TimeOnLevel.Guild.Color,
 | 
			
		||||
                        new(_template.User.TimeOnLevel.Guild.Pos.X,
 | 
			
		||||
                            _template.User.TimeOnLevel.Guild.Pos.Y)));
 | 
			
		||||
            }
 | 
			
		||||
            //avatar
 | 
			
		||||
 | 
			
		||||
            if (stats.User.AvatarId != null && _template.User.Icon.Show)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x =>
 | 
			
		||||
                    var avatarUrl = stats.User.RealAvatarUrl();
 | 
			
		||||
 | 
			
		||||
                    var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl);
 | 
			
		||||
                    if (!succ)
 | 
			
		||||
                    {
 | 
			
		||||
                        x.DrawText(
 | 
			
		||||
                            stats.Global.Level.ToString(),
 | 
			
		||||
                            _fonts.NotoSans.CreateFont(_template.User.GlobalLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                            _template.User.GlobalLevel.Color,
 | 
			
		||||
                            new(_template.User.GlobalLevel.Pos.X, _template.User.GlobalLevel.Pos.Y)
 | 
			
		||||
                        ); //level
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.GuildLevel.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x =>
 | 
			
		||||
                    {
 | 
			
		||||
                        x.DrawText(
 | 
			
		||||
                            stats.Guild.Level.ToString(),
 | 
			
		||||
                            _fonts.NotoSans.CreateFont(_template.User.GuildLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                            _template.User.GuildLevel.Color,
 | 
			
		||||
                            new(_template.User.GuildLevel.Pos.X, _template.User.GuildLevel.Pos.Y)
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var pen = new Pen(SixLabors.ImageSharp.Color.Black, 1);
 | 
			
		||||
 | 
			
		||||
                var global = stats.Global;
 | 
			
		||||
                var guild = stats.Guild;
 | 
			
		||||
 | 
			
		||||
                //xp bar
 | 
			
		||||
                if (_template.User.Xp.Bar.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    var xpPercent = global.LevelXp / (float) global.RequiredXp;
 | 
			
		||||
                    DrawXpBar(xpPercent, _template.User.Xp.Bar.Global, img);
 | 
			
		||||
                    xpPercent = guild.LevelXp / (float) guild.RequiredXp;
 | 
			
		||||
                    DrawXpBar(xpPercent, _template.User.Xp.Bar.Guild, img);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.Xp.Global.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x => x.DrawText($"{global.LevelXp}/{global.RequiredXp}",
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.Xp.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                        Brushes.Solid(_template.User.Xp.Global.Color),
 | 
			
		||||
                        pen,
 | 
			
		||||
                        new(_template.User.Xp.Global.Pos.X, _template.User.Xp.Global.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.Xp.Guild.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x => x.DrawText($"{guild.LevelXp}/{guild.RequiredXp}",
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.Xp.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                        Brushes.Solid(_template.User.Xp.Guild.Color),
 | 
			
		||||
                        pen,
 | 
			
		||||
                        new(_template.User.Xp.Guild.Pos.X, _template.User.Xp.Guild.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (stats.FullGuildStats.AwardedXp != 0 && _template.User.Xp.Awarded.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    var sign = stats.FullGuildStats.AwardedXp > 0
 | 
			
		||||
                        ? "+ "
 | 
			
		||||
                        : "";
 | 
			
		||||
                    var awX = _template.User.Xp.Awarded.Pos.X -
 | 
			
		||||
                              (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5);
 | 
			
		||||
                    var awY = _template.User.Xp.Awarded.Pos.Y;
 | 
			
		||||
                    img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})",
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.Xp.Awarded.FontSize, FontStyle.Bold),
 | 
			
		||||
                        Brushes.Solid(_template.User.Xp.Awarded.Color),
 | 
			
		||||
                        pen,
 | 
			
		||||
                        new(awX, awY)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //ranking
 | 
			
		||||
                if (_template.User.GlobalRank.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x => x.DrawText(stats.GlobalRanking.ToString(),
 | 
			
		||||
                        _fonts.UniSans.CreateFont(_template.User.GlobalRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GlobalRank.Color,
 | 
			
		||||
                        new(_template.User.GlobalRank.Pos.X, _template.User.GlobalRank.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.GuildRank.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x => x.DrawText(stats.GuildRanking.ToString(),
 | 
			
		||||
                        _fonts.UniSans.CreateFont(_template.User.GuildRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GuildRank.Color,
 | 
			
		||||
                        new(_template.User.GuildRank.Pos.X, _template.User.GuildRank.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //time on this level
 | 
			
		||||
 | 
			
		||||
                string GetTimeSpent(DateTime time, string format)
 | 
			
		||||
                {
 | 
			
		||||
                    var offset = DateTime.UtcNow - time;
 | 
			
		||||
                    return string.Format(format, offset.Days, offset.Hours, offset.Minutes);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.TimeOnLevel.Global.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x =>
 | 
			
		||||
                        x.DrawText(GetTimeSpent(stats.User.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                            _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                            _template.User.TimeOnLevel.Global.Color,
 | 
			
		||||
                            new(_template.User.TimeOnLevel.Global.Pos.X,
 | 
			
		||||
                                _template.User.TimeOnLevel.Global.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (_template.User.TimeOnLevel.Guild.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    img.Mutate(x =>
 | 
			
		||||
                        x.DrawText(
 | 
			
		||||
                            GetTimeSpent(stats.FullGuildStats.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                            _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                            _template.User.TimeOnLevel.Guild.Color,
 | 
			
		||||
                            new(_template.User.TimeOnLevel.Guild.Pos.X,
 | 
			
		||||
                                _template.User.TimeOnLevel.Guild.Pos.Y)));
 | 
			
		||||
                }
 | 
			
		||||
                //avatar
 | 
			
		||||
 | 
			
		||||
                if (stats.User.AvatarId != null && _template.User.Icon.Show)
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var avatarUrl = stats.User.RealAvatarUrl();
 | 
			
		||||
 | 
			
		||||
                        var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl);
 | 
			
		||||
                        if (!succ)
 | 
			
		||||
                        using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                        {
 | 
			
		||||
                            using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                            var avatarData = await http.GetByteArrayAsync(avatarUrl);
 | 
			
		||||
                            using (var tempDraw = Image.Load(avatarData))
 | 
			
		||||
                            {
 | 
			
		||||
                                var avatarData = await http.GetByteArrayAsync(avatarUrl);
 | 
			
		||||
                                using (var tempDraw = Image.Load(avatarData))
 | 
			
		||||
                                tempDraw.Mutate(x => x
 | 
			
		||||
                                    .Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
 | 
			
		||||
                                    .ApplyRoundedCorners(
 | 
			
		||||
                                        Math.Max(_template.User.Icon.Size.X, _template.User.Icon.Size.Y) / 2));
 | 
			
		||||
                                await using (var stream = tempDraw.ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    tempDraw.Mutate(x => x
 | 
			
		||||
                                        .Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
 | 
			
		||||
                                        .ApplyRoundedCorners(
 | 
			
		||||
                                            Math.Max(_template.User.Icon.Size.X, _template.User.Icon.Size.Y) / 2));
 | 
			
		||||
                                    await using (var stream = tempDraw.ToStream())
 | 
			
		||||
                                    {
 | 
			
		||||
                                        data = stream.ToArray();
 | 
			
		||||
                                    }
 | 
			
		||||
                                    data = stream.ToArray();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            await _cache.SetImageDataAsync(avatarUrl, data);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        using (var toDraw = Image.Load(data))
 | 
			
		||||
                        {
 | 
			
		||||
                            if (toDraw.Size() != new Size(_template.User.Icon.Size.X, _template.User.Icon.Size.Y))
 | 
			
		||||
                            {
 | 
			
		||||
                                toDraw.Mutate(x =>
 | 
			
		||||
                                    x.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y));
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            img.Mutate(x => x.DrawImage(toDraw,
 | 
			
		||||
                                new Point(_template.User.Icon.Pos.X, _template.User.Icon.Pos.Y), 1));
 | 
			
		||||
                        }
 | 
			
		||||
                        await _cache.SetImageDataAsync(avatarUrl, data);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
 | 
			
		||||
                    using var toDraw = Image.Load(data);
 | 
			
		||||
                    if (toDraw.Size() != new Size(_template.User.Icon.Size.X, _template.User.Icon.Size.Y))
 | 
			
		||||
                    {
 | 
			
		||||
                        Log.Warning(ex, "Error drawing avatar image");
 | 
			
		||||
                        toDraw.Mutate(x =>
 | 
			
		||||
                            x.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //club image
 | 
			
		||||
                if (_template.Club.Icon.Show)
 | 
			
		||||
                    img.Mutate(x => x.DrawImage(toDraw,
 | 
			
		||||
                        new Point(_template.User.Icon.Pos.X, _template.User.Icon.Pos.Y), 1));
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    await DrawClubImage(img, stats);
 | 
			
		||||
                    Log.Warning(ex, "Error drawing avatar image");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                img.Mutate(x => x.Resize(_template.OutputSize.X, _template.OutputSize.Y));
 | 
			
		||||
                return ((Stream) img.ToStream(imageFormat), imageFormat);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //club image
 | 
			
		||||
            if (_template.Club.Icon.Show)
 | 
			
		||||
            {
 | 
			
		||||
                await DrawClubImage(img, stats);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            img.Mutate(x => x.Resize(_template.OutputSize.X, _template.OutputSize.Y));
 | 
			
		||||
            return ((Stream) img.ToStream(imageFormat), imageFormat);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    void DrawXpBar(float percent, XpBar info, Image<Rgba32> img)
 | 
			
		||||
    private void DrawXpBar(float percent, XpBar info, Image<Rgba32> img)
 | 
			
		||||
    {
 | 
			
		||||
        var x1 = info.PointA.X;
 | 
			
		||||
        var y1 = info.PointA.Y;
 | 
			
		||||
@@ -1151,18 +1121,16 @@ public class XpService : INService
 | 
			
		||||
                    await _cache.SetImageDataAsync(imgUrl, data);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                using (var toDraw = Image.Load(data))
 | 
			
		||||
                using var toDraw = Image.Load(data);
 | 
			
		||||
                if (toDraw.Size() != new Size(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y))
 | 
			
		||||
                {
 | 
			
		||||
                    if (toDraw.Size() != new Size(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y))
 | 
			
		||||
                    {
 | 
			
		||||
                        toDraw.Mutate(x => x.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    img.Mutate(x => x.DrawImage(
 | 
			
		||||
                        toDraw,
 | 
			
		||||
                        new Point(_template.Club.Icon.Pos.X, _template.Club.Icon.Pos.Y),
 | 
			
		||||
                        1));
 | 
			
		||||
                    toDraw.Mutate(x => x.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                img.Mutate(x => x.DrawImage(
 | 
			
		||||
                    toDraw,
 | 
			
		||||
                    new Point(_template.Club.Icon.Pos.X, _template.Club.Icon.Pos.Y),
 | 
			
		||||
                    1));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
@@ -1173,20 +1141,16 @@ public class XpService : INService
 | 
			
		||||
 | 
			
		||||
    public void XpReset(ulong guildId, ulong userId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            uow.UserXpStats.ResetGuildUserXp(userId, guildId);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        uow.UserXpStats.ResetGuildUserXp(userId, guildId);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void XpReset(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            uow.UserXpStats.ResetGuildXp(guildId);
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        uow.UserXpStats.ResetGuildXp(guildId);
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ResetXpRewards(ulong guildId)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
    <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
 | 
			
		||||
    <OutputType>exe</OutputType>
 | 
			
		||||
    <ApplicationIcon>nadeko_icon.ico</ApplicationIcon>
 | 
			
		||||
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,18 +28,16 @@ public class DbService
 | 
			
		||||
 | 
			
		||||
    public void Setup()
 | 
			
		||||
    {
 | 
			
		||||
        using (var context = new NadekoContext(options))
 | 
			
		||||
        using var context = new NadekoContext(options);
 | 
			
		||||
        if (context.Database.GetPendingMigrations().Any())
 | 
			
		||||
        {
 | 
			
		||||
            if (context.Database.GetPendingMigrations().Any())
 | 
			
		||||
            {
 | 
			
		||||
                var mContext = new NadekoContext(migrateOptions);
 | 
			
		||||
                mContext.Database.Migrate();
 | 
			
		||||
                mContext.SaveChanges();
 | 
			
		||||
                mContext.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");
 | 
			
		||||
            context.SaveChanges();
 | 
			
		||||
            var mContext = new NadekoContext(migrateOptions);
 | 
			
		||||
            mContext.Database.Migrate();
 | 
			
		||||
            mContext.SaveChanges();
 | 
			
		||||
            mContext.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
        context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");
 | 
			
		||||
        context.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NadekoContext GetDbContextInternal()
 | 
			
		||||
@@ -48,11 +46,9 @@ public class DbService
 | 
			
		||||
        context.Database.SetCommandTimeout(60);
 | 
			
		||||
        var conn = context.Database.GetDbConnection();
 | 
			
		||||
        conn.Open();
 | 
			
		||||
        using (var com = conn.CreateCommand())
 | 
			
		||||
        {
 | 
			
		||||
            com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF";
 | 
			
		||||
            com.ExecuteNonQuery();
 | 
			
		||||
        }
 | 
			
		||||
        using var com = conn.CreateCommand();
 | 
			
		||||
        com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF";
 | 
			
		||||
        com.ExecuteNonQuery();
 | 
			
		||||
        return context;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -148,18 +148,14 @@ public class GreetSettingsService : INService
 | 
			
		||||
 | 
			
		||||
    public string GetDmGreetMsg(ulong id)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.GuildConfigsForId(id, set => set)?.DmGreetMessageText;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(id, set => set)?.DmGreetMessageText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string GetGreetMsg(ulong gid)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public string GetBoostMessage(ulong gid)
 | 
			
		||||
@@ -308,10 +304,8 @@ public class GreetSettingsService : INService
 | 
			
		||||
 | 
			
		||||
    public string GetByeMessage(ulong gid)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
 | 
			
		||||
@@ -340,30 +334,28 @@ public class GreetSettingsService : INService
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            conf.DmGreetMessageText = settings.DmGreetMessageText?.SanitizeMentions();
 | 
			
		||||
            conf.ChannelGreetMessageText = settings.ChannelGreetMessageText?.SanitizeMentions();
 | 
			
		||||
            conf.ChannelByeMessageText = settings.ChannelByeMessageText?.SanitizeMentions();
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        conf.DmGreetMessageText = settings.DmGreetMessageText?.SanitizeMentions();
 | 
			
		||||
        conf.ChannelGreetMessageText = settings.ChannelGreetMessageText?.SanitizeMentions();
 | 
			
		||||
        conf.ChannelByeMessageText = settings.ChannelByeMessageText?.SanitizeMentions();
 | 
			
		||||
 | 
			
		||||
            conf.AutoDeleteGreetMessagesTimer = settings.AutoDeleteGreetMessagesTimer;
 | 
			
		||||
            conf.AutoDeleteGreetMessages = settings.AutoDeleteGreetMessagesTimer > 0;
 | 
			
		||||
        conf.AutoDeleteGreetMessagesTimer = settings.AutoDeleteGreetMessagesTimer;
 | 
			
		||||
        conf.AutoDeleteGreetMessages = settings.AutoDeleteGreetMessagesTimer > 0;
 | 
			
		||||
 | 
			
		||||
            conf.AutoDeleteByeMessagesTimer = settings.AutoDeleteByeMessagesTimer;
 | 
			
		||||
            conf.AutoDeleteByeMessages = settings.AutoDeleteByeMessagesTimer > 0;
 | 
			
		||||
        conf.AutoDeleteByeMessagesTimer = settings.AutoDeleteByeMessagesTimer;
 | 
			
		||||
        conf.AutoDeleteByeMessages = settings.AutoDeleteByeMessagesTimer > 0;
 | 
			
		||||
 | 
			
		||||
            conf.GreetMessageChannelId = settings.GreetMessageChannelId;
 | 
			
		||||
            conf.ByeMessageChannelId = settings.ByeMessageChannelId;
 | 
			
		||||
        conf.GreetMessageChannelId = settings.GreetMessageChannelId;
 | 
			
		||||
        conf.ByeMessageChannelId = settings.ByeMessageChannelId;
 | 
			
		||||
 | 
			
		||||
            conf.SendChannelGreetMessage = settings.SendChannelGreetMessage;
 | 
			
		||||
            conf.SendChannelByeMessage = settings.SendChannelByeMessage;
 | 
			
		||||
        conf.SendChannelGreetMessage = settings.SendChannelGreetMessage;
 | 
			
		||||
        conf.SendChannelByeMessage = settings.SendChannelByeMessage;
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        }
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -371,17 +363,15 @@ public class GreetSettingsService : INService
 | 
			
		||||
    public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
 | 
			
		||||
    {
 | 
			
		||||
        bool enabled;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
 | 
			
		||||
            conf.GreetMessageChannelId = channelId;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
 | 
			
		||||
        conf.GreetMessageChannelId = channelId;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -393,62 +383,52 @@ public class GreetSettingsService : INService
 | 
			
		||||
            throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
        bool greetMsgEnabled;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            conf.ChannelGreetMessageText = message;
 | 
			
		||||
            greetMsgEnabled = conf.SendChannelGreetMessage;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        conf.ChannelGreetMessageText = message;
 | 
			
		||||
        greetMsgEnabled = conf.SendChannelGreetMessage;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return greetMsgEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
 | 
			
		||||
    {
 | 
			
		||||
        bool enabled;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #region Get Enabled Status
 | 
			
		||||
    public bool GetGreetDmEnabled(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            return conf.SendDmGreetMessage;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        return conf.SendDmGreetMessage;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public bool GetGreetEnabled(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            return conf.SendChannelGreetMessage;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        return conf.SendChannelGreetMessage;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public bool GetByeEnabled(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            return conf.SendChannelByeMessage;
 | 
			
		||||
        }
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        return conf.SendChannelByeMessage;
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
        
 | 
			
		||||
@@ -481,34 +461,30 @@ public class GreetSettingsService : INService
 | 
			
		||||
            throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
        bool greetMsgEnabled;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            conf.DmGreetMessageText = message;
 | 
			
		||||
            greetMsgEnabled = conf.SendDmGreetMessage;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        conf.DmGreetMessageText = message;
 | 
			
		||||
        greetMsgEnabled = conf.SendDmGreetMessage;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return greetMsgEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
 | 
			
		||||
    {
 | 
			
		||||
        bool enabled;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
 | 
			
		||||
            conf.ByeMessageChannelId = channelId;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
 | 
			
		||||
        conf.ByeMessageChannelId = channelId;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -520,17 +496,15 @@ public class GreetSettingsService : INService
 | 
			
		||||
            throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
        bool byeMsgEnabled;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            conf.ChannelByeMessageText = message;
 | 
			
		||||
            byeMsgEnabled = conf.SendChannelByeMessage;
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        conf.ChannelByeMessageText = message;
 | 
			
		||||
        byeMsgEnabled = conf.SendChannelByeMessage;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return byeMsgEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -539,16 +513,14 @@ public class GreetSettingsService : INService
 | 
			
		||||
        if (timer < 0 || timer > 600)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            conf.AutoDeleteByeMessagesTimer = timer;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
        conf.AutoDeleteByeMessagesTimer = timer;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task SetGreetDel(ulong id, int timer)
 | 
			
		||||
@@ -556,16 +528,14 @@ public class GreetSettingsService : INService
 | 
			
		||||
        if (timer < 0 || timer > 600)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var conf = uow.GuildConfigsForId(id, set => set);
 | 
			
		||||
            conf.AutoDeleteGreetMessagesTimer = timer;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var conf = uow.GuildConfigsForId(id, set => set);
 | 
			
		||||
        conf.AutoDeleteGreetMessagesTimer = timer;
 | 
			
		||||
 | 
			
		||||
            var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
            GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
 | 
			
		||||
        var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
        GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool SetBoostMessage(ulong guildId, ref string message)
 | 
			
		||||
 
 | 
			
		||||
@@ -54,11 +54,9 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
            throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", nameof(amount));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task AddAsync(ulong userId, string reason, long amount, bool gamble = false)
 | 
			
		||||
@@ -98,16 +96,14 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
            throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
 | 
			
		||||
 | 
			
		||||
        var userIdHashSet = new HashSet<ulong>(idArray.Length);
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        for (var i = 0; i < idArray.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            for (var i = 0; i < idArray.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                // i have to prevent same user changing more than once as it will cause db error
 | 
			
		||||
                if (userIdHashSet.Add(idArray[i]))
 | 
			
		||||
                    InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow);
 | 
			
		||||
            }
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            // i have to prevent same user changing more than once as it will cause db error
 | 
			
		||||
            if (userIdHashSet.Add(idArray[i]))
 | 
			
		||||
                InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow);
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public async Task RemoveBulkAsync(IEnumerable<ulong> userIds, IEnumerable<string> reasons, IEnumerable<long> amounts, bool gamble = false)
 | 
			
		||||
@@ -120,16 +116,14 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
            throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
 | 
			
		||||
 | 
			
		||||
        var userIdHashSet = new HashSet<ulong>(idArray.Length);
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        for (var i = 0; i < idArray.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            for (var i = 0; i < idArray.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                // i have to prevent same user changing more than once as it will cause db error
 | 
			
		||||
                if (userIdHashSet.Add(idArray[i]))
 | 
			
		||||
                    InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow);
 | 
			
		||||
            }
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
            // i have to prevent same user changing more than once as it will cause db error
 | 
			
		||||
            if (userIdHashSet.Add(idArray[i]))
 | 
			
		||||
                InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow);
 | 
			
		||||
        }
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<bool> InternalRemoveAsync(ulong userId, string userName, string userDiscrim, string avatar, string reason, long amount, bool gamble = false)
 | 
			
		||||
@@ -140,11 +134,9 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool result;
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
 | 
			
		||||
        await uow.SaveChangesAsync();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,12 +66,10 @@ public class Localization : ILocalization, INService
 | 
			
		||||
 | 
			
		||||
        if (GuildCultureInfos.TryRemove(guildId, out var _))
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
                gc.Locale = null;
 | 
			
		||||
                uow.SaveChanges();
 | 
			
		||||
            }
 | 
			
		||||
            using var uow = _db.GetDbContext();
 | 
			
		||||
            var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
            gc.Locale = null;
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -120,22 +120,18 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
                return;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = _httpFactory.CreateClient())
 | 
			
		||||
                {
 | 
			
		||||
                    using (var content = new FormUrlEncodedContent(
 | 
			
		||||
                               new Dictionary<string, string> {
 | 
			
		||||
                                   { "shard_count",  _creds.TotalShards.ToString()},
 | 
			
		||||
                                   { "shard_id", client.ShardId.ToString() },
 | 
			
		||||
                                   { "server_count", client.Guilds.Count().ToString() }
 | 
			
		||||
                               }))
 | 
			
		||||
                    {
 | 
			
		||||
                        content.Headers.Clear();
 | 
			
		||||
                        content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
 | 
			
		||||
                        http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                using var content = new FormUrlEncodedContent(
 | 
			
		||||
                    new Dictionary<string, string> {
 | 
			
		||||
                        { "shard_count",  _creds.TotalShards.ToString()},
 | 
			
		||||
                        { "shard_id", client.ShardId.ToString() },
 | 
			
		||||
                        { "server_count", client.Guilds.Count().ToString() }
 | 
			
		||||
                    });
 | 
			
		||||
                content.Headers.Clear();
 | 
			
		||||
                content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
 | 
			
		||||
                http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);
 | 
			
		||||
 | 
			
		||||
                        using (await http.PostAsync(new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), content).ConfigureAwait(false)) { }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                using (await http.PostAsync(new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), content).ConfigureAwait(false)) { }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
@@ -55,14 +56,12 @@ public static class IUserExtensions
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var file = File.Open(filePath, FileMode.Open))
 | 
			
		||||
        {
 | 
			
		||||
            return await user.SendFileAsync(file, caption ?? "x", text, isTTS).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        await using var file = File.Open(filePath, FileMode.Open);
 | 
			
		||||
        return await UserExtensions.SendFileAsync(user, file, caption ?? "x", text, isTTS).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) =>
 | 
			
		||||
        await user.SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false);
 | 
			
		||||
        await UserExtensions.SendFileAsync(user, fileStream, fileName, caption, isTTS).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
    // This method is used by everything that fetches the avatar from a user
 | 
			
		||||
    public static Uri RealAvatarUrl(this IUser usr, ushort size = 128)
 | 
			
		||||
 
 | 
			
		||||
@@ -49,22 +49,20 @@ public static class ProcessExtensions
 | 
			
		||||
 | 
			
		||||
        if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
 | 
			
		||||
        {
 | 
			
		||||
            using (var reader = new StringReader(stdout))
 | 
			
		||||
            using var reader = new StringReader(stdout);
 | 
			
		||||
            while (true)
 | 
			
		||||
            {
 | 
			
		||||
                while (true)
 | 
			
		||||
                var text = reader.ReadLine();
 | 
			
		||||
                if (text is null)
 | 
			
		||||
                {
 | 
			
		||||
                    var text = reader.ReadLine();
 | 
			
		||||
                    if (text is null)
 | 
			
		||||
                    {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                    if (int.TryParse(text, out var id))
 | 
			
		||||
                    {
 | 
			
		||||
                        children.Add(id);
 | 
			
		||||
                        // Recursively get the children
 | 
			
		||||
                        GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
                    }
 | 
			
		||||
                if (int.TryParse(text, out var id))
 | 
			
		||||
                {
 | 
			
		||||
                    children.Add(id);
 | 
			
		||||
                    // Recursively get the children
 | 
			
		||||
                    GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user