mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Global usings and file scoped namespaces
This commit is contained in:
		@@ -6,11 +6,8 @@ using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@@ -19,83 +16,81 @@ using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using NadekoBot.Common.Configs;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
using NadekoBot.Modules.Searches;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public sealed class Bot
 | 
			
		||||
{
 | 
			
		||||
    public sealed class Bot
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
    private readonly CommandService _commandService;
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly IBotCredsProvider _credsProvider;
 | 
			
		||||
        
 | 
			
		||||
    public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
 | 
			
		||||
        
 | 
			
		||||
    public DiscordSocketClient Client { get; }
 | 
			
		||||
    public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
 | 
			
		||||
 | 
			
		||||
    private IServiceProvider Services { get; set; }
 | 
			
		||||
        
 | 
			
		||||
    public string Mention { get; private set; }
 | 
			
		||||
    public bool IsReady { get; private set; }
 | 
			
		||||
 | 
			
		||||
    public Bot(int shardId, int? totalShards)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly CommandService _commandService;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private readonly IBotCredsProvider _credsProvider;
 | 
			
		||||
        
 | 
			
		||||
        public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
 | 
			
		||||
        
 | 
			
		||||
        public DiscordSocketClient Client { get; }
 | 
			
		||||
        public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
 | 
			
		||||
        if (shardId < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(shardId));
 | 
			
		||||
 | 
			
		||||
        private IServiceProvider Services { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        public string Mention { get; private set; }
 | 
			
		||||
        public bool IsReady { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public Bot(int shardId, int? totalShards)
 | 
			
		||||
        {
 | 
			
		||||
            if (shardId < 0)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(shardId));
 | 
			
		||||
 | 
			
		||||
            _credsProvider = new BotCredsProvider(totalShards);
 | 
			
		||||
            _creds = _credsProvider.GetCreds();
 | 
			
		||||
        _credsProvider = new BotCredsProvider(totalShards);
 | 
			
		||||
        _creds = _credsProvider.GetCreds();
 | 
			
		||||
            
 | 
			
		||||
            _db = new DbService(_creds);
 | 
			
		||||
        _db = new DbService(_creds);
 | 
			
		||||
 | 
			
		||||
            if (shardId == 0)
 | 
			
		||||
            {
 | 
			
		||||
                _db.Setup();
 | 
			
		||||
            }
 | 
			
		||||
        if (shardId == 0)
 | 
			
		||||
        {
 | 
			
		||||
            _db.Setup();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            Client = new DiscordSocketClient(new DiscordSocketConfig
 | 
			
		||||
            {
 | 
			
		||||
                MessageCacheSize = 50,
 | 
			
		||||
                LogLevel = LogSeverity.Warning,
 | 
			
		||||
                ConnectionTimeout = int.MaxValue,
 | 
			
		||||
                TotalShards = _creds.TotalShards,
 | 
			
		||||
                ShardId = shardId,
 | 
			
		||||
                AlwaysDownloadUsers = false,
 | 
			
		||||
                ExclusiveBulkDelete = true,
 | 
			
		||||
            });
 | 
			
		||||
        Client = new DiscordSocketClient(new DiscordSocketConfig
 | 
			
		||||
        {
 | 
			
		||||
            MessageCacheSize = 50,
 | 
			
		||||
            LogLevel = LogSeverity.Warning,
 | 
			
		||||
            ConnectionTimeout = int.MaxValue,
 | 
			
		||||
            TotalShards = _creds.TotalShards,
 | 
			
		||||
            ShardId = shardId,
 | 
			
		||||
            AlwaysDownloadUsers = false,
 | 
			
		||||
            ExclusiveBulkDelete = true,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            _commandService = new CommandService(new CommandServiceConfig()
 | 
			
		||||
            {
 | 
			
		||||
                CaseSensitiveCommands = false,
 | 
			
		||||
                DefaultRunMode = RunMode.Sync,
 | 
			
		||||
            });
 | 
			
		||||
        _commandService = new CommandService(new CommandServiceConfig()
 | 
			
		||||
        {
 | 
			
		||||
            CaseSensitiveCommands = false,
 | 
			
		||||
            DefaultRunMode = RunMode.Sync,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
#if GLOBAL_NADEKO || DEBUG
 | 
			
		||||
            Client.Log += Client_Log;
 | 
			
		||||
        Client.Log += Client_Log;
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public List<ulong> GetCurrentGuildIds()
 | 
			
		||||
    public List<ulong> GetCurrentGuildIds()
 | 
			
		||||
    {
 | 
			
		||||
        return Client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void AddServices()
 | 
			
		||||
    {
 | 
			
		||||
        var startingGuildIdList = GetCurrentGuildIds();
 | 
			
		||||
        var sw = Stopwatch.StartNew();
 | 
			
		||||
        var _bot = Client.CurrentUser;
 | 
			
		||||
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            return Client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
            uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
 | 
			
		||||
            AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddServices()
 | 
			
		||||
        {
 | 
			
		||||
            var startingGuildIdList = GetCurrentGuildIds();
 | 
			
		||||
            var sw = Stopwatch.StartNew();
 | 
			
		||||
            var _bot = Client.CurrentUser;
 | 
			
		||||
 | 
			
		||||
            using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
 | 
			
		||||
                AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            var svcs = new ServiceCollection()
 | 
			
		||||
        var svcs = new ServiceCollection()
 | 
			
		||||
                .AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
 | 
			
		||||
                .AddSingleton<IBotCredsProvider>(_credsProvider)
 | 
			
		||||
                .AddSingleton(_db) // database
 | 
			
		||||
@@ -118,248 +113,247 @@ namespace NadekoBot
 | 
			
		||||
#else
 | 
			
		||||
                .AddSingleton<ILogCommandService, LogCommandService>()
 | 
			
		||||
#endif
 | 
			
		||||
                ;
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
            svcs.AddHttpClient();
 | 
			
		||||
            svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
 | 
			
		||||
            {
 | 
			
		||||
                AllowAutoRedirect = false
 | 
			
		||||
            });
 | 
			
		||||
        svcs.AddHttpClient();
 | 
			
		||||
        svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
 | 
			
		||||
        {
 | 
			
		||||
            AllowAutoRedirect = false
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
 | 
			
		||||
            {
 | 
			
		||||
                svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                svcs.AddSingleton<RemoteGrpcCoordinator>()
 | 
			
		||||
                    .AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
 | 
			
		||||
                    .AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
 | 
			
		||||
            }
 | 
			
		||||
        if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
 | 
			
		||||
        {
 | 
			
		||||
            svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            svcs.AddSingleton<RemoteGrpcCoordinator>()
 | 
			
		||||
                .AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
 | 
			
		||||
                .AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            svcs.AddSingleton<RedisLocalDataCache>()
 | 
			
		||||
                .AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
 | 
			
		||||
                .AddSingleton<RedisImagesCache>()
 | 
			
		||||
                .AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
 | 
			
		||||
                .AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
 | 
			
		||||
                .AddSingleton<IDataCache, RedisCache>();
 | 
			
		||||
        svcs.AddSingleton<RedisLocalDataCache>()
 | 
			
		||||
            .AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
 | 
			
		||||
            .AddSingleton<RedisImagesCache>()
 | 
			
		||||
            .AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
 | 
			
		||||
            .AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
 | 
			
		||||
            .AddSingleton<IDataCache, RedisCache>();
 | 
			
		||||
            
 | 
			
		||||
            svcs.Scan(scan => scan
 | 
			
		||||
                .FromAssemblyOf<IReadyExecutor>()
 | 
			
		||||
                .AddClasses(classes => classes
 | 
			
		||||
                        .AssignableToAny(
 | 
			
		||||
                    // services
 | 
			
		||||
                    typeof(INService),
 | 
			
		||||
        svcs.Scan(scan => scan
 | 
			
		||||
            .FromAssemblyOf<IReadyExecutor>()
 | 
			
		||||
            .AddClasses(classes => classes
 | 
			
		||||
                    .AssignableToAny(
 | 
			
		||||
                        // services
 | 
			
		||||
                        typeof(INService),
 | 
			
		||||
                    
 | 
			
		||||
                    // behaviours
 | 
			
		||||
                    typeof(IEarlyBehavior),
 | 
			
		||||
                    typeof(ILateBlocker),
 | 
			
		||||
                    typeof(IInputTransformer),
 | 
			
		||||
                    typeof(ILateExecutor))
 | 
			
		||||
                        // behaviours
 | 
			
		||||
                        typeof(IEarlyBehavior),
 | 
			
		||||
                        typeof(ILateBlocker),
 | 
			
		||||
                        typeof(IInputTransformer),
 | 
			
		||||
                        typeof(ILateExecutor))
 | 
			
		||||
#if GLOBAL_NADEKO
 | 
			
		||||
                    .WithoutAttribute<NoPublicBotAttribute>()
 | 
			
		||||
#endif
 | 
			
		||||
                )
 | 
			
		||||
                .AsSelfWithInterfaces()
 | 
			
		||||
                .WithSingletonLifetime()
 | 
			
		||||
            );
 | 
			
		||||
            )
 | 
			
		||||
            .AsSelfWithInterfaces()
 | 
			
		||||
            .WithSingletonLifetime()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
            //initialize Services
 | 
			
		||||
            Services = svcs.BuildServiceProvider();
 | 
			
		||||
            var exec = Services.GetRequiredService<IBehaviourExecutor>();
 | 
			
		||||
            exec.Initialize();
 | 
			
		||||
        //initialize Services
 | 
			
		||||
        Services = svcs.BuildServiceProvider();
 | 
			
		||||
        var exec = Services.GetRequiredService<IBehaviourExecutor>();
 | 
			
		||||
        exec.Initialize();
 | 
			
		||||
 | 
			
		||||
            if (Client.ShardId == 0)
 | 
			
		||||
            {
 | 
			
		||||
                ApplyConfigMigrations();
 | 
			
		||||
            }
 | 
			
		||||
        if (Client.ShardId == 0)
 | 
			
		||||
        {
 | 
			
		||||
            ApplyConfigMigrations();
 | 
			
		||||
        }
 | 
			
		||||
            
 | 
			
		||||
            _ = LoadTypeReaders(typeof(Bot).Assembly);
 | 
			
		||||
        _ = LoadTypeReaders(typeof(Bot).Assembly);
 | 
			
		||||
 | 
			
		||||
            sw.Stop();
 | 
			
		||||
            Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
        }
 | 
			
		||||
        sw.Stop();
 | 
			
		||||
        Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        private void ApplyConfigMigrations()
 | 
			
		||||
    private void ApplyConfigMigrations()
 | 
			
		||||
    {
 | 
			
		||||
        // execute all migrators
 | 
			
		||||
        var migrators = Services.GetServices<IConfigMigrator>();
 | 
			
		||||
        foreach (var migrator in migrators)
 | 
			
		||||
        {
 | 
			
		||||
            // execute all migrators
 | 
			
		||||
            var migrators = Services.GetServices<IConfigMigrator>();
 | 
			
		||||
            foreach (var migrator in migrators)
 | 
			
		||||
            {
 | 
			
		||||
                migrator.EnsureMigrated();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<object> LoadTypeReaders(Assembly assembly)
 | 
			
		||||
        {
 | 
			
		||||
            Type[] allTypes;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                allTypes = assembly.GetTypes();
 | 
			
		||||
            }
 | 
			
		||||
            catch (ReflectionTypeLoadException ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex.LoaderExceptions[0], "Error getting types");
 | 
			
		||||
                return Enumerable.Empty<object>();
 | 
			
		||||
            }
 | 
			
		||||
            var filteredTypes = allTypes
 | 
			
		||||
                .Where(x => x.IsSubclassOf(typeof(TypeReader))
 | 
			
		||||
                    && x.BaseType.GetGenericArguments().Length > 0
 | 
			
		||||
                    && !x.IsAbstract);
 | 
			
		||||
 | 
			
		||||
            var toReturn = new List<object>();
 | 
			
		||||
            foreach (var ft in filteredTypes)
 | 
			
		||||
            {
 | 
			
		||||
                var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
 | 
			
		||||
                var baseType = ft.BaseType;
 | 
			
		||||
                var typeArgs = baseType.GetGenericArguments();
 | 
			
		||||
                _commandService.AddTypeReader(typeArgs[0], x);
 | 
			
		||||
                toReturn.Add(x);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task LoginAsync(string token)
 | 
			
		||||
        {
 | 
			
		||||
            var clientReady = new TaskCompletionSource<bool>();
 | 
			
		||||
 | 
			
		||||
            Task SetClientReady()
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    clientReady.TrySetResult(true);
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
 | 
			
		||||
                        {
 | 
			
		||||
                            await chan.CloseAsync().ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //connect
 | 
			
		||||
            Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
 | 
			
		||||
                await Client.StartAsync().ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (HttpException ex)
 | 
			
		||||
            {
 | 
			
		||||
                LoginErrorHandler.Handle(ex);
 | 
			
		||||
                Helpers.ReadErrorAndExit(3);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                LoginErrorHandler.Handle(ex);
 | 
			
		||||
                Helpers.ReadErrorAndExit(4);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Client.Ready += SetClientReady;
 | 
			
		||||
            await clientReady.Task.ConfigureAwait(false);
 | 
			
		||||
            Client.Ready -= SetClientReady;
 | 
			
		||||
            
 | 
			
		||||
            Client.JoinedGuild += Client_JoinedGuild;
 | 
			
		||||
            Client.LeftGuild += Client_LeftGuild;
 | 
			
		||||
            
 | 
			
		||||
            Log.Information("Shard {0} logged in.", Client.ShardId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Client_LeftGuild(SocketGuild arg)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Client_JoinedGuild(SocketGuild arg)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                GuildConfig gc;
 | 
			
		||||
                using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    gc = uow.GuildConfigsForId(arg.Id);
 | 
			
		||||
                }
 | 
			
		||||
                await JoinedGuild.Invoke(gc).ConfigureAwait(false);
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task RunAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sw = Stopwatch.StartNew();
 | 
			
		||||
 | 
			
		||||
            await LoginAsync(_creds.Token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            Mention = Client.CurrentUser.Mention;
 | 
			
		||||
            Log.Information("Shard {ShardId} loading services...", Client.ShardId);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                AddServices();
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Error adding services");
 | 
			
		||||
                Helpers.ReadErrorAndExit(9);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sw.Stop();
 | 
			
		||||
            Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
 | 
			
		||||
            var commandHandler = Services.GetRequiredService<CommandHandler>();
 | 
			
		||||
 | 
			
		||||
            // start handling messages received in commandhandler
 | 
			
		||||
            await commandHandler.StartHandling().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
 | 
			
		||||
            
 | 
			
		||||
            IsReady = true;
 | 
			
		||||
            _ = Task.Run(ExecuteReadySubscriptions);
 | 
			
		||||
            Log.Information("Shard {ShardId} ready", Client.ShardId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task ExecuteReadySubscriptions()
 | 
			
		||||
        {
 | 
			
		||||
            var readyExecutors = Services.GetServices<IReadyExecutor>();
 | 
			
		||||
            var tasks = readyExecutors.Select(async toExec => 
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await toExec.OnReadyAsync();
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Error(ex,
 | 
			
		||||
                        "Failed running OnReadyAsync method on {Type} type: {Message}",
 | 
			
		||||
                        toExec.GetType().Name,
 | 
			
		||||
                        ex.Message);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return Task.WhenAll(tasks);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Client_Log(LogMessage arg)
 | 
			
		||||
        {
 | 
			
		||||
            if (arg.Exception != null)
 | 
			
		||||
                Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
 | 
			
		||||
            else
 | 
			
		||||
                Log.Warning(arg.Source + " | " + arg.Message);
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task RunAndBlockAsync()
 | 
			
		||||
        {
 | 
			
		||||
            await RunAsync().ConfigureAwait(false);
 | 
			
		||||
            await Task.Delay(-1).ConfigureAwait(false);
 | 
			
		||||
            migrator.EnsureMigrated();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private IEnumerable<object> LoadTypeReaders(Assembly assembly)
 | 
			
		||||
    {
 | 
			
		||||
        Type[] allTypes;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            allTypes = assembly.GetTypes();
 | 
			
		||||
        }
 | 
			
		||||
        catch (ReflectionTypeLoadException ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning(ex.LoaderExceptions[0], "Error getting types");
 | 
			
		||||
            return Enumerable.Empty<object>();
 | 
			
		||||
        }
 | 
			
		||||
        var filteredTypes = allTypes
 | 
			
		||||
            .Where(x => x.IsSubclassOf(typeof(TypeReader))
 | 
			
		||||
                        && x.BaseType.GetGenericArguments().Length > 0
 | 
			
		||||
                        && !x.IsAbstract);
 | 
			
		||||
 | 
			
		||||
        var toReturn = new List<object>();
 | 
			
		||||
        foreach (var ft in filteredTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
 | 
			
		||||
            var baseType = ft.BaseType;
 | 
			
		||||
            var typeArgs = baseType.GetGenericArguments();
 | 
			
		||||
            _commandService.AddTypeReader(typeArgs[0], x);
 | 
			
		||||
            toReturn.Add(x);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task LoginAsync(string token)
 | 
			
		||||
    {
 | 
			
		||||
        var clientReady = new TaskCompletionSource<bool>();
 | 
			
		||||
 | 
			
		||||
        Task SetClientReady()
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                clientReady.TrySetResult(true);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
 | 
			
		||||
                    {
 | 
			
		||||
                        await chan.CloseAsync().ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //connect
 | 
			
		||||
        Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
 | 
			
		||||
            await Client.StartAsync().ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch (HttpException ex)
 | 
			
		||||
        {
 | 
			
		||||
            LoginErrorHandler.Handle(ex);
 | 
			
		||||
            Helpers.ReadErrorAndExit(3);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            LoginErrorHandler.Handle(ex);
 | 
			
		||||
            Helpers.ReadErrorAndExit(4);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Client.Ready += SetClientReady;
 | 
			
		||||
        await clientReady.Task.ConfigureAwait(false);
 | 
			
		||||
        Client.Ready -= SetClientReady;
 | 
			
		||||
            
 | 
			
		||||
        Client.JoinedGuild += Client_JoinedGuild;
 | 
			
		||||
        Client.LeftGuild += Client_LeftGuild;
 | 
			
		||||
            
 | 
			
		||||
        Log.Information("Shard {0} logged in.", Client.ShardId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task Client_LeftGuild(SocketGuild arg)
 | 
			
		||||
    {
 | 
			
		||||
        Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task Client_JoinedGuild(SocketGuild arg)
 | 
			
		||||
    {
 | 
			
		||||
        Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            GuildConfig gc;
 | 
			
		||||
            using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                gc = uow.GuildConfigsForId(arg.Id);
 | 
			
		||||
            }
 | 
			
		||||
            await JoinedGuild.Invoke(gc).ConfigureAwait(false);
 | 
			
		||||
        });
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task RunAsync()
 | 
			
		||||
    {
 | 
			
		||||
        var sw = Stopwatch.StartNew();
 | 
			
		||||
 | 
			
		||||
        await LoginAsync(_creds.Token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        Mention = Client.CurrentUser.Mention;
 | 
			
		||||
        Log.Information("Shard {ShardId} loading services...", Client.ShardId);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            AddServices();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex, "Error adding services");
 | 
			
		||||
            Helpers.ReadErrorAndExit(9);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sw.Stop();
 | 
			
		||||
        Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
 | 
			
		||||
        var commandHandler = Services.GetRequiredService<CommandHandler>();
 | 
			
		||||
 | 
			
		||||
        // start handling messages received in commandhandler
 | 
			
		||||
        await commandHandler.StartHandling().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
 | 
			
		||||
            
 | 
			
		||||
        IsReady = true;
 | 
			
		||||
        _ = Task.Run(ExecuteReadySubscriptions);
 | 
			
		||||
        Log.Information("Shard {ShardId} ready", Client.ShardId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task ExecuteReadySubscriptions()
 | 
			
		||||
    {
 | 
			
		||||
        var readyExecutors = Services.GetServices<IReadyExecutor>();
 | 
			
		||||
        var tasks = readyExecutors.Select(async toExec => 
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await toExec.OnReadyAsync();
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex,
 | 
			
		||||
                    "Failed running OnReadyAsync method on {Type} type: {Message}",
 | 
			
		||||
                    toExec.GetType().Name,
 | 
			
		||||
                    ex.Message);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Task.WhenAll(tasks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task Client_Log(LogMessage arg)
 | 
			
		||||
    {
 | 
			
		||||
        if (arg.Exception != null)
 | 
			
		||||
            Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
 | 
			
		||||
        else
 | 
			
		||||
            Log.Warning(arg.Source + " | " + arg.Message);
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task RunAndBlockAsync()
 | 
			
		||||
    {
 | 
			
		||||
        await RunAsync().ConfigureAwait(false);
 | 
			
		||||
        await Task.Delay(-1).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +1,17 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class AsyncLazy<T> : Lazy<Task<T>>
 | 
			
		||||
{
 | 
			
		||||
    public class AsyncLazy<T> : Lazy<Task<T>>
 | 
			
		||||
    {
 | 
			
		||||
        public AsyncLazy(Func<T> valueFactory) :
 | 
			
		||||
            base(() => Task.Run(valueFactory))
 | 
			
		||||
        { }
 | 
			
		||||
    public AsyncLazy(Func<T> valueFactory) :
 | 
			
		||||
        base(() => Task.Run(valueFactory))
 | 
			
		||||
    { }
 | 
			
		||||
 | 
			
		||||
        public AsyncLazy(Func<Task<T>> taskFactory) :
 | 
			
		||||
            base(() => Task.Run(taskFactory))
 | 
			
		||||
        { }
 | 
			
		||||
    public AsyncLazy(Func<Task<T>> taskFactory) :
 | 
			
		||||
        base(() => Task.Run(taskFactory))
 | 
			
		||||
    { }
 | 
			
		||||
 | 
			
		||||
        public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
    public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class AliasesAttribute : AliasAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class AliasesAttribute : AliasAttribute
 | 
			
		||||
    public AliasesAttribute([CallerMemberName] string memberName = "")
 | 
			
		||||
        : base(CommandNameLoadHelper.GetAliasesFor(memberName))
 | 
			
		||||
    {
 | 
			
		||||
        public AliasesAttribute([CallerMemberName] string memberName = "")
 | 
			
		||||
            : base(CommandNameLoadHelper.GetAliasesFor(memberName))
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace Discord
 | 
			
		||||
{
 | 
			
		||||
    public class BotPermAttribute : RequireBotPermissionAttribute
 | 
			
		||||
    {
 | 
			
		||||
        public BotPermAttribute(GuildPerm permission) : base((GuildPermission)permission)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
namespace Discord;
 | 
			
		||||
 | 
			
		||||
        public BotPermAttribute(ChannelPerm permission) : base((ChannelPermission)permission)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
public class BotPermAttribute : RequireBotPermissionAttribute
 | 
			
		||||
{
 | 
			
		||||
    public BotPermAttribute(GuildPerm permission) : base((GuildPermission)permission)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public BotPermAttribute(ChannelPerm permission) : base((ChannelPermission)permission)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +1,32 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.IO;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
public static class CommandNameLoadHelper
 | 
			
		||||
{
 | 
			
		||||
    public static class CommandNameLoadHelper
 | 
			
		||||
        
 | 
			
		||||
    private static YamlDotNet.Serialization.IDeserializer _deserializer
 | 
			
		||||
        = new YamlDotNet.Serialization.Deserializer();
 | 
			
		||||
        
 | 
			
		||||
    public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
 | 
			
		||||
        = new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames()); 
 | 
			
		||||
    public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
        private static YamlDotNet.Serialization.IDeserializer _deserializer
 | 
			
		||||
            = new YamlDotNet.Serialization.Deserializer();
 | 
			
		||||
        
 | 
			
		||||
        public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
 | 
			
		||||
            = new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames()); 
 | 
			
		||||
        public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
 | 
			
		||||
        {
 | 
			
		||||
            var text = File.ReadAllText(aliasesFilePath);
 | 
			
		||||
            return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
 | 
			
		||||
        }
 | 
			
		||||
        var text = File.ReadAllText(aliasesFilePath);
 | 
			
		||||
        return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static string[] GetAliasesFor(string methodName)
 | 
			
		||||
            => LazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
 | 
			
		||||
                ? aliases.Skip(1).ToArray()
 | 
			
		||||
                : Array.Empty<string>();
 | 
			
		||||
    public static string[] GetAliasesFor(string methodName)
 | 
			
		||||
        => LazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
 | 
			
		||||
            ? aliases.Skip(1).ToArray()
 | 
			
		||||
            : Array.Empty<string>();
 | 
			
		||||
 | 
			
		||||
        public static string GetCommandNameFor(string methodName)
 | 
			
		||||
        {
 | 
			
		||||
            methodName = methodName.ToLowerInvariant();
 | 
			
		||||
            var toReturn = LazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
 | 
			
		||||
                ? aliases[0]
 | 
			
		||||
                : methodName;
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
    public static string GetCommandNameFor(string methodName)
 | 
			
		||||
    {
 | 
			
		||||
        methodName = methodName.ToLowerInvariant();
 | 
			
		||||
        var toReturn = LazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
 | 
			
		||||
            ? aliases[0]
 | 
			
		||||
            : methodName;
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,12 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class DescriptionAttribute : SummaryAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class DescriptionAttribute : SummaryAttribute
 | 
			
		||||
    // Localization.LoadCommand(memberName.ToLowerInvariant()).Desc
 | 
			
		||||
    public DescriptionAttribute(string text = "") : base(text)
 | 
			
		||||
    {
 | 
			
		||||
        // Localization.LoadCommand(memberName.ToLowerInvariant()).Desc
 | 
			
		||||
        public DescriptionAttribute(string text = "") : base(text)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace Discord.Commands
 | 
			
		||||
namespace Discord.Commands;
 | 
			
		||||
 | 
			
		||||
public class LeftoverAttribute : RemainderAttribute
 | 
			
		||||
{
 | 
			
		||||
    public class LeftoverAttribute : RemainderAttribute
 | 
			
		||||
    public LeftoverAttribute()
 | 
			
		||||
    {
 | 
			
		||||
        public LeftoverAttribute()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,16 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class NadekoCommandAttribute : CommandAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class NadekoCommandAttribute : CommandAttribute
 | 
			
		||||
    public NadekoCommandAttribute([CallerMemberName] string memberName="") 
 | 
			
		||||
        : base(CommandNameLoadHelper.GetCommandNameFor(memberName))
 | 
			
		||||
    {
 | 
			
		||||
        public NadekoCommandAttribute([CallerMemberName] string memberName="") 
 | 
			
		||||
            : base(CommandNameLoadHelper.GetCommandNameFor(memberName))
 | 
			
		||||
        {
 | 
			
		||||
            this.MethodName = memberName.ToLowerInvariant();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string MethodName { get; }
 | 
			
		||||
        this.MethodName = memberName.ToLowerInvariant();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public string MethodName { get; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,11 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Class)]
 | 
			
		||||
sealed class NadekoModuleAttribute : GroupAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Class)]
 | 
			
		||||
    sealed class NadekoModuleAttribute : GroupAttribute
 | 
			
		||||
    public NadekoModuleAttribute(string moduleName) : base(moduleName)
 | 
			
		||||
    {
 | 
			
		||||
        public NadekoModuleAttribute(string moduleName) : base(moduleName)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class NadekoOptionsAttribute : Attribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class NadekoOptionsAttribute : Attribute
 | 
			
		||||
    {
 | 
			
		||||
        public Type OptionType { get; set; }
 | 
			
		||||
    public Type OptionType { get; set; }
 | 
			
		||||
 | 
			
		||||
        public NadekoOptionsAttribute(Type t)
 | 
			
		||||
        {
 | 
			
		||||
            this.OptionType = t;
 | 
			
		||||
        }
 | 
			
		||||
    public NadekoOptionsAttribute(Type t)
 | 
			
		||||
    {
 | 
			
		||||
        this.OptionType = t;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +1,18 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
 | 
			
		||||
    public sealed class OwnerOnlyAttribute : PreconditionAttribute
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
 | 
			
		||||
        }
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
 | 
			
		||||
public sealed class OwnerOnlyAttribute : PreconditionAttribute
 | 
			
		||||
{
 | 
			
		||||
    public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
 | 
			
		||||
    {
 | 
			
		||||
        var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
 | 
			
		||||
 | 
			
		||||
        return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +1,36 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class RatelimitAttribute : PreconditionAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class RatelimitAttribute : PreconditionAttribute
 | 
			
		||||
    public int Seconds { get; }
 | 
			
		||||
 | 
			
		||||
    public RatelimitAttribute(int seconds)
 | 
			
		||||
    {
 | 
			
		||||
        public int Seconds { get; }
 | 
			
		||||
        if (seconds <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(seconds));
 | 
			
		||||
 | 
			
		||||
        public RatelimitAttribute(int seconds)
 | 
			
		||||
        {
 | 
			
		||||
            if (seconds <= 0)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(seconds));
 | 
			
		||||
 | 
			
		||||
            Seconds = seconds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            if (Seconds == 0)
 | 
			
		||||
                return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
 | 
			
		||||
            var cache = services.GetRequiredService<IDataCache>();
 | 
			
		||||
            var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
 | 
			
		||||
 | 
			
		||||
            if(rem is null)
 | 
			
		||||
                return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
 | 
			
		||||
            var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromError(msgContent));
 | 
			
		||||
        }
 | 
			
		||||
        Seconds = seconds;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
    {
 | 
			
		||||
        if (Seconds == 0)
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
 | 
			
		||||
        var cache = services.GetRequiredService<IDataCache>();
 | 
			
		||||
        var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
 | 
			
		||||
 | 
			
		||||
        if(rem is null)
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
 | 
			
		||||
        var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
 | 
			
		||||
 | 
			
		||||
        return Task.FromResult(PreconditionResult.FromError(msgContent));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,16 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Attributes
 | 
			
		||||
namespace NadekoBot.Common.Attributes;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
public sealed class UsageAttribute : RemarksAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
    public sealed class UsageAttribute : RemarksAttribute
 | 
			
		||||
    // public static string GetUsage(string memberName)
 | 
			
		||||
    // {
 | 
			
		||||
    //     var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
 | 
			
		||||
    //     return JsonConvert.SerializeObject(usage);
 | 
			
		||||
    // }
 | 
			
		||||
    public UsageAttribute(string text = "") : base(text)
 | 
			
		||||
    {
 | 
			
		||||
        // public static string GetUsage(string memberName)
 | 
			
		||||
        // {
 | 
			
		||||
        //     var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
 | 
			
		||||
        //     return JsonConvert.SerializeObject(usage);
 | 
			
		||||
        // }
 | 
			
		||||
        public UsageAttribute(string text = "") : base(text)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,33 +1,31 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
 | 
			
		||||
namespace Discord
 | 
			
		||||
namespace Discord;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
 | 
			
		||||
public class UserPermAttribute : PreconditionAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
 | 
			
		||||
    public class UserPermAttribute : PreconditionAttribute
 | 
			
		||||
    public RequireUserPermissionAttribute UserPermissionAttribute { get; }
 | 
			
		||||
 | 
			
		||||
    public UserPermAttribute(GuildPerm permission)
 | 
			
		||||
    {
 | 
			
		||||
        public RequireUserPermissionAttribute UserPermissionAttribute { get; }
 | 
			
		||||
 | 
			
		||||
        public UserPermAttribute(GuildPerm permission)
 | 
			
		||||
        {
 | 
			
		||||
            UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public UserPermAttribute(ChannelPerm permission)
 | 
			
		||||
        {
 | 
			
		||||
            UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            var permService = services.GetRequiredService<DiscordPermOverrideService>();
 | 
			
		||||
            if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
 | 
			
		||||
                return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
            
 | 
			
		||||
            return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
 | 
			
		||||
        }
 | 
			
		||||
        UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public UserPermAttribute(ChannelPerm permission)
 | 
			
		||||
    {
 | 
			
		||||
        UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
    {
 | 
			
		||||
        var permService = services.GetRequiredService<DiscordPermOverrideService>();
 | 
			
		||||
        if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
            
 | 
			
		||||
        return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +1,19 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public class CmdStrings
 | 
			
		||||
    {
 | 
			
		||||
        public string[] Usages { get; }
 | 
			
		||||
        public string Description { get; }
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
        [JsonConstructor]
 | 
			
		||||
        public CmdStrings(
 | 
			
		||||
            [JsonProperty("args")]string[] usages,
 | 
			
		||||
            [JsonProperty("desc")]string description
 | 
			
		||||
        )
 | 
			
		||||
        {
 | 
			
		||||
            Usages = usages;
 | 
			
		||||
            Description = description;
 | 
			
		||||
        }
 | 
			
		||||
public class CmdStrings
 | 
			
		||||
{
 | 
			
		||||
    public string[] Usages { get; }
 | 
			
		||||
    public string Description { get; }
 | 
			
		||||
 | 
			
		||||
    [JsonConstructor]
 | 
			
		||||
    public CmdStrings(
 | 
			
		||||
        [JsonProperty("args")]string[] usages,
 | 
			
		||||
        [JsonProperty("desc")]string description
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        Usages = usages;
 | 
			
		||||
        Description = description;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,77 +1,74 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Collections
 | 
			
		||||
namespace NadekoBot.Common.Collections;
 | 
			
		||||
 | 
			
		||||
public static class DisposableReadOnlyListExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class DisposableReadOnlyListExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
 | 
			
		||||
            => new DisposableReadOnlyList<T>(arr);
 | 
			
		||||
    public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
 | 
			
		||||
        => new DisposableReadOnlyList<T>(arr);
 | 
			
		||||
 | 
			
		||||
        public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
 | 
			
		||||
            => new DisposableReadOnlyList<TKey, TValue>(arr);
 | 
			
		||||
    public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
 | 
			
		||||
        => new DisposableReadOnlyList<TKey, TValue>(arr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
 | 
			
		||||
    where T : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private readonly IReadOnlyList<T> _arr;
 | 
			
		||||
 | 
			
		||||
    public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
    public T this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
    public DisposableReadOnlyList(IReadOnlyList<T> arr)
 | 
			
		||||
    {
 | 
			
		||||
        this._arr = arr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
 | 
			
		||||
    public IEnumerator<T> GetEnumerator()
 | 
			
		||||
        => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    IEnumerator IEnumerable.GetEnumerator()
 | 
			
		||||
        => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public sealed class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
 | 
			
		||||
        where T : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IReadOnlyList<T> _arr;
 | 
			
		||||
 | 
			
		||||
        public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
        public T this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
        public DisposableReadOnlyList(IReadOnlyList<T> arr)
 | 
			
		||||
        foreach (var item in _arr)
 | 
			
		||||
        {
 | 
			
		||||
            this._arr = arr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<T> GetEnumerator()
 | 
			
		||||
            => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator()
 | 
			
		||||
            => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in _arr)
 | 
			
		||||
            {
 | 
			
		||||
                item.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public sealed class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
 | 
			
		||||
        where U : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
 | 
			
		||||
 | 
			
		||||
        public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
        KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
        public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
 | 
			
		||||
        {
 | 
			
		||||
            this._arr = arr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
 | 
			
		||||
            _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
            _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in _arr)
 | 
			
		||||
            {
 | 
			
		||||
                item.Value.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            item.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
 | 
			
		||||
    where U : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
 | 
			
		||||
 | 
			
		||||
    public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
    KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
    public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
 | 
			
		||||
    {
 | 
			
		||||
        this._arr = arr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
 | 
			
		||||
        _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
        _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var item in _arr)
 | 
			
		||||
        {
 | 
			
		||||
            item.Value.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,141 +1,138 @@
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Collections
 | 
			
		||||
namespace NadekoBot.Common.Collections;
 | 
			
		||||
 | 
			
		||||
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
 | 
			
		||||
{
 | 
			
		||||
    public class IndexedCollection<T> : IList<T> where T : class, IIndexed
 | 
			
		||||
    public List<T> Source { get; }
 | 
			
		||||
    private readonly object _locker = new object();
 | 
			
		||||
 | 
			
		||||
    public int Count => Source.Count;
 | 
			
		||||
    public bool IsReadOnly => false;
 | 
			
		||||
    public int IndexOf(T item) => item.Index;
 | 
			
		||||
 | 
			
		||||
    public IndexedCollection()
 | 
			
		||||
    {
 | 
			
		||||
        public List<T> Source { get; }
 | 
			
		||||
        private readonly object _locker = new object();
 | 
			
		||||
 | 
			
		||||
        public int Count => Source.Count;
 | 
			
		||||
        public bool IsReadOnly => false;
 | 
			
		||||
        public int IndexOf(T item) => item.Index;
 | 
			
		||||
 | 
			
		||||
        public IndexedCollection()
 | 
			
		||||
        {
 | 
			
		||||
            Source = new List<T>();
 | 
			
		||||
        }
 | 
			
		||||
        Source = new List<T>();
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        public IndexedCollection(IEnumerable<T> source)
 | 
			
		||||
    public IndexedCollection(IEnumerable<T> source)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            Source = source.OrderBy(x => x.Index).ToList();
 | 
			
		||||
            UpdateIndexes();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void UpdateIndexes()
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            for (var i = 0; i < Source.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                Source = source.OrderBy(x => x.Index).ToList();
 | 
			
		||||
                UpdateIndexes();
 | 
			
		||||
                if (Source[i].Index != i)
 | 
			
		||||
                    Source[i].Index = i;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public void UpdateIndexes()
 | 
			
		||||
    public static implicit operator List<T>(IndexedCollection<T> x) =>
 | 
			
		||||
        x.Source;
 | 
			
		||||
 | 
			
		||||
    public List<T> ToList() => Source.ToList();
 | 
			
		||||
 | 
			
		||||
    public IEnumerator<T> GetEnumerator() =>
 | 
			
		||||
        Source.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
        Source.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
    public void Add(T item)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            item.Index = Source.Count;
 | 
			
		||||
            Source.Add(item);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public virtual void Clear()
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            Source.Clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Contains(T item)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            return Source.Contains(item);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void CopyTo(T[] array, int arrayIndex)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            Source.CopyTo(array, arrayIndex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public virtual bool Remove(T item)
 | 
			
		||||
    {
 | 
			
		||||
        bool removed;
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (removed = Source.Remove(item))
 | 
			
		||||
            {
 | 
			
		||||
                for (var i = 0; i < Source.Count; i++)
 | 
			
		||||
                for (int i = 0; i < Source.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (Source[i].Index != i)
 | 
			
		||||
                        Source[i].Index = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return removed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static implicit operator List<T>(IndexedCollection<T> x) =>
 | 
			
		||||
            x.Source;
 | 
			
		||||
 | 
			
		||||
        public List<T> ToList() => Source.ToList();
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<T> GetEnumerator() =>
 | 
			
		||||
            Source.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
            Source.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        public void Add(T item)
 | 
			
		||||
    public virtual void Insert(int index, T item)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            Source.Insert(index, item);
 | 
			
		||||
            for (int i = index; i < Source.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                item.Index = Source.Count;
 | 
			
		||||
                Source.Add(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void Clear()
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                Source.Clear();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Contains(T item)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                return Source.Contains(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void CopyTo(T[] array, int arrayIndex)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                Source.CopyTo(array, arrayIndex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual bool Remove(T item)
 | 
			
		||||
        {
 | 
			
		||||
            bool removed;
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                if (removed = Source.Remove(item))
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Source.Count; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (Source[i].Index != i)
 | 
			
		||||
                            Source[i].Index = i;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return removed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void Insert(int index, T item)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                Source.Insert(index, item);
 | 
			
		||||
                for (int i = index; i < Source.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    Source[i].Index = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void RemoveAt(int index)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                Source.RemoveAt(index);
 | 
			
		||||
                for (int i = index; i < Source.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    Source[i].Index = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual T this[int index]
 | 
			
		||||
        {
 | 
			
		||||
            get { return Source[index]; }
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                lock (_locker)
 | 
			
		||||
                {
 | 
			
		||||
                    value.Index = index;
 | 
			
		||||
                    Source[index] = value;
 | 
			
		||||
                }
 | 
			
		||||
                Source[i].Index = i;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public virtual void RemoveAt(int index)
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            Source.RemoveAt(index);
 | 
			
		||||
            for (int i = index; i < Source.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                Source[i].Index = i;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public virtual T this[int index]
 | 
			
		||||
    {
 | 
			
		||||
        get { return Source[index]; }
 | 
			
		||||
        set
 | 
			
		||||
        {
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                value.Index = index;
 | 
			
		||||
                Source[index] = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class CommandData
 | 
			
		||||
{
 | 
			
		||||
    public class CommandData
 | 
			
		||||
    {
 | 
			
		||||
        public string Cmd { get; set; }
 | 
			
		||||
        public string Desc { get; set; }
 | 
			
		||||
        public string[] Usage { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    public string Cmd { get; set; }
 | 
			
		||||
    public string Desc { get; set; }
 | 
			
		||||
    public string[] Usage { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,69 +1,68 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using Cloneable;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Configs
 | 
			
		||||
{
 | 
			
		||||
    [Cloneable]
 | 
			
		||||
    public sealed partial class BotConfig : ICloneable<BotConfig>
 | 
			
		||||
    {
 | 
			
		||||
        [Comment(@"DO NOT CHANGE")]
 | 
			
		||||
        public int Version { get; set; } = 2;
 | 
			
		||||
namespace NadekoBot.Common.Configs;
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Most commands, when executed, have a small colored line
 | 
			
		||||
[Cloneable]
 | 
			
		||||
public sealed partial class BotConfig : ICloneable<BotConfig>
 | 
			
		||||
{
 | 
			
		||||
    [Comment(@"DO NOT CHANGE")]
 | 
			
		||||
    public int Version { get; set; } = 2;
 | 
			
		||||
 | 
			
		||||
    [Comment(@"Most commands, when executed, have a small colored line
 | 
			
		||||
next to the response. The color depends whether the command
 | 
			
		||||
is completed, errored or in progress (pending)
 | 
			
		||||
Color settings below are for the color of those lines.
 | 
			
		||||
To get color's hex, you can go here https://htmlcolorcodes.com/
 | 
			
		||||
and copy the hex code fo your selected color (marked as #)")]
 | 
			
		||||
        public ColorConfig Color { get; set; }
 | 
			
		||||
    public ColorConfig Color { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
 | 
			
		||||
        public CultureInfo DefaultLocale { get; set; }
 | 
			
		||||
    [Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
 | 
			
		||||
    public CultureInfo DefaultLocale { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Style in which executed commands will show up in the console.
 | 
			
		||||
    [Comment(@"Style in which executed commands will show up in the console.
 | 
			
		||||
Allowed values: Simple, Normal, None")]
 | 
			
		||||
        public ConsoleOutputType ConsoleOutputType { get; set; }
 | 
			
		||||
    public ConsoleOutputType ConsoleOutputType { get; set; }
 | 
			
		||||
 | 
			
		||||
//         [Comment(@"For what kind of updates will the bot check.
 | 
			
		||||
// Allowed values: Release, Commit, None")]
 | 
			
		||||
//         public UpdateCheckType CheckForUpdates { get; set; }
 | 
			
		||||
 | 
			
		||||
        // [Comment(@"How often will the bot check for updates, in hours")]
 | 
			
		||||
        // public int CheckUpdateInterval { get; set; }
 | 
			
		||||
    // [Comment(@"How often will the bot check for updates, in hours")]
 | 
			
		||||
    // public int CheckUpdateInterval { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
 | 
			
		||||
        public bool ForwardMessages { get; set; }
 | 
			
		||||
    [Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
 | 
			
		||||
    public bool ForwardMessages { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
 | 
			
		||||
    [Comment(@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
 | 
			
		||||
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
 | 
			
		||||
        public bool ForwardToAllOwners { get; set; }
 | 
			
		||||
    public bool ForwardToAllOwners { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"When a user DMs the bot with a message which is not a command
 | 
			
		||||
    [Comment(@"When a user DMs the bot with a message which is not a command
 | 
			
		||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
 | 
			
		||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
 | 
			
		||||
        [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
			
		||||
        public string DmHelpText { get; set; }
 | 
			
		||||
    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
			
		||||
    public string DmHelpText { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
 | 
			
		||||
    [Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
 | 
			
		||||
Case insensitive.
 | 
			
		||||
Leave empty to reply with DmHelpText to every DM.")]
 | 
			
		||||
        public List<string> DmHelpTextKeywords { get; set; }
 | 
			
		||||
    public List<string> DmHelpTextKeywords { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"This is the response for the .h command")]
 | 
			
		||||
        [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
			
		||||
        public string HelpText { get; set; }
 | 
			
		||||
        [Comment(@"List of modules and commands completely blocked on the bot")]
 | 
			
		||||
        public BlockedConfig Blocked { get; set; }
 | 
			
		||||
    [Comment(@"This is the response for the .h command")]
 | 
			
		||||
    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
			
		||||
    public string HelpText { get; set; }
 | 
			
		||||
    [Comment(@"List of modules and commands completely blocked on the bot")]
 | 
			
		||||
    public BlockedConfig Blocked { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Which string will be used to recognize the commands")]
 | 
			
		||||
        public string Prefix { get; set; }
 | 
			
		||||
    [Comment(@"Which string will be used to recognize the commands")]
 | 
			
		||||
    public string Prefix { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
 | 
			
		||||
    [Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
 | 
			
		||||
1st user who joins will get greeted immediately
 | 
			
		||||
If more users join within the next 5 seconds, they will be greeted in groups of 5.
 | 
			
		||||
This will cause %user.mention% and other placeholders to be replaced with multiple users. 
 | 
			
		||||
@@ -72,12 +71,12 @@ it will become invalid, as it will resolve to a list of avatars of grouped users
 | 
			
		||||
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
 | 
			
		||||
      servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
 | 
			
		||||
      and (slightly) reduce the greet spam in those servers.")]
 | 
			
		||||
        public bool GroupGreets { get; set; }
 | 
			
		||||
    public bool GroupGreets { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Whether the bot will rotate through all specified statuses.
 | 
			
		||||
    [Comment(@"Whether the bot will rotate through all specified statuses.
 | 
			
		||||
This setting can be changed via .rots command.
 | 
			
		||||
See RotatingStatuses submodule in Administration.")]
 | 
			
		||||
        public bool RotateStatuses { get; set; }
 | 
			
		||||
    public bool RotateStatuses { get; set; }
 | 
			
		||||
 | 
			
		||||
//         [Comment(@"Whether the prefix will be a suffix, or prefix.
 | 
			
		||||
// For example, if your prefix is ! you will run a command called 'cash' by typing either
 | 
			
		||||
@@ -85,23 +84,23 @@ See RotatingStatuses submodule in Administration.")]
 | 
			
		||||
// 'cash @Someone!' if your prefixIsSuffix: true")]
 | 
			
		||||
//         public bool PrefixIsSuffix { get; set; }
 | 
			
		||||
 | 
			
		||||
        // public string Prefixed(string text) => PrefixIsSuffix
 | 
			
		||||
        //     ? text + Prefix
 | 
			
		||||
        //     : Prefix + text;
 | 
			
		||||
    // public string Prefixed(string text) => PrefixIsSuffix
 | 
			
		||||
    //     ? text + Prefix
 | 
			
		||||
    //     : Prefix + text;
 | 
			
		||||
 | 
			
		||||
        public string Prefixed(string text)
 | 
			
		||||
            => Prefix + text;
 | 
			
		||||
    public string Prefixed(string text)
 | 
			
		||||
        => Prefix + text;
 | 
			
		||||
 | 
			
		||||
        public BotConfig()
 | 
			
		||||
        {
 | 
			
		||||
            var color = new ColorConfig();
 | 
			
		||||
            Color = color;
 | 
			
		||||
            DefaultLocale = new CultureInfo("en-US");
 | 
			
		||||
            ConsoleOutputType = ConsoleOutputType.Normal;
 | 
			
		||||
            ForwardMessages = false;
 | 
			
		||||
            ForwardToAllOwners = false;
 | 
			
		||||
            DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
 | 
			
		||||
            HelpText = @"{
 | 
			
		||||
    public BotConfig()
 | 
			
		||||
    {
 | 
			
		||||
        var color = new ColorConfig();
 | 
			
		||||
        Color = color;
 | 
			
		||||
        DefaultLocale = new CultureInfo("en-US");
 | 
			
		||||
        ConsoleOutputType = ConsoleOutputType.Normal;
 | 
			
		||||
        ForwardMessages = false;
 | 
			
		||||
        ForwardToAllOwners = false;
 | 
			
		||||
        DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
 | 
			
		||||
        HelpText = @"{
 | 
			
		||||
  ""title"": ""To invite me to your server, use this link"",
 | 
			
		||||
  ""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
 | 
			
		||||
  ""color"": 53380,
 | 
			
		||||
@@ -126,59 +125,58 @@ See RotatingStatuses submodule in Administration.")]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}";
 | 
			
		||||
            var blocked = new BlockedConfig();
 | 
			
		||||
            Blocked = blocked;
 | 
			
		||||
            Prefix = ".";
 | 
			
		||||
            RotateStatuses = false;
 | 
			
		||||
            GroupGreets = false;
 | 
			
		||||
            DmHelpTextKeywords = new List<string>()
 | 
			
		||||
            {
 | 
			
		||||
                "help",
 | 
			
		||||
                "commands",
 | 
			
		||||
                "cmds",
 | 
			
		||||
                "module",
 | 
			
		||||
                "can you do"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cloneable]
 | 
			
		||||
    public sealed partial class BlockedConfig
 | 
			
		||||
    {
 | 
			
		||||
        public HashSet<string> Commands { get; set; }
 | 
			
		||||
        public HashSet<string> Modules { get; set; }
 | 
			
		||||
 | 
			
		||||
        public BlockedConfig()
 | 
			
		||||
        var blocked = new BlockedConfig();
 | 
			
		||||
        Blocked = blocked;
 | 
			
		||||
        Prefix = ".";
 | 
			
		||||
        RotateStatuses = false;
 | 
			
		||||
        GroupGreets = false;
 | 
			
		||||
        DmHelpTextKeywords = new List<string>()
 | 
			
		||||
        {
 | 
			
		||||
            Modules = new HashSet<string>();
 | 
			
		||||
            Commands = new HashSet<string>();
 | 
			
		||||
        }
 | 
			
		||||
            "help",
 | 
			
		||||
            "commands",
 | 
			
		||||
            "cmds",
 | 
			
		||||
            "module",
 | 
			
		||||
            "can you do"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    [Cloneable]
 | 
			
		||||
    public partial class ColorConfig
 | 
			
		||||
[Cloneable]
 | 
			
		||||
public sealed partial class BlockedConfig
 | 
			
		||||
{
 | 
			
		||||
    public HashSet<string> Commands { get; set; }
 | 
			
		||||
    public HashSet<string> Modules { get; set; }
 | 
			
		||||
 | 
			
		||||
    public BlockedConfig()
 | 
			
		||||
    {
 | 
			
		||||
        [Comment(@"Color used for embed responses when command successfully executes")]
 | 
			
		||||
        public Rgba32 Ok { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Color used for embed responses when command has an error")]
 | 
			
		||||
        public Rgba32 Error { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Color used for embed responses while command is doing work or is in progress")]
 | 
			
		||||
        public Rgba32 Pending { get; set; }
 | 
			
		||||
 | 
			
		||||
        public ColorConfig()
 | 
			
		||||
        {
 | 
			
		||||
            Ok = Rgba32.ParseHex("00e584");
 | 
			
		||||
            Error = Rgba32.ParseHex("ee281f");
 | 
			
		||||
            Pending = Rgba32.ParseHex("faa61a");
 | 
			
		||||
        }
 | 
			
		||||
        Modules = new HashSet<string>();
 | 
			
		||||
        Commands = new HashSet<string>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Cloneable]
 | 
			
		||||
public partial class ColorConfig
 | 
			
		||||
{
 | 
			
		||||
    [Comment(@"Color used for embed responses when command successfully executes")]
 | 
			
		||||
    public Rgba32 Ok { get; set; }
 | 
			
		||||
        
 | 
			
		||||
    [Comment(@"Color used for embed responses when command has an error")]
 | 
			
		||||
    public Rgba32 Error { get; set; }
 | 
			
		||||
        
 | 
			
		||||
    [Comment(@"Color used for embed responses while command is doing work or is in progress")]
 | 
			
		||||
    public Rgba32 Pending { get; set; }
 | 
			
		||||
 | 
			
		||||
    public ColorConfig()
 | 
			
		||||
    {
 | 
			
		||||
        Ok = Rgba32.ParseHex("00e584");
 | 
			
		||||
        Error = Rgba32.ParseHex("ee281f");
 | 
			
		||||
        Pending = Rgba32.ParseHex("faa61a");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
    public enum ConsoleOutputType
 | 
			
		||||
    {
 | 
			
		||||
        Normal = 0,
 | 
			
		||||
        Simple = 1,
 | 
			
		||||
        None = 2,
 | 
			
		||||
    }
 | 
			
		||||
public enum ConsoleOutputType
 | 
			
		||||
{
 | 
			
		||||
    Normal = 0,
 | 
			
		||||
    Simple = 1,
 | 
			
		||||
    None = 2,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
namespace NadekoBot.Common.Configs
 | 
			
		||||
namespace NadekoBot.Common.Configs;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Base interface for available config serializers
 | 
			
		||||
/// </summary>
 | 
			
		||||
public interface IConfigSeria
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Base interface for available config serializers
 | 
			
		||||
    /// Serialize the object to string 
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface IConfigSeria
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Serialize the object to string 
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Serialize<T>(T obj);
 | 
			
		||||
    public string Serialize<T>(T obj);
 | 
			
		||||
        
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Deserialize string data into an object of the specified type
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public T Deserialize<T>(string data);
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Deserialize string data into an object of the specified type
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public T Deserialize<T>(string data);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,97 +1,95 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public sealed class Creds : IBotCredentials
 | 
			
		||||
{
 | 
			
		||||
    public sealed class Creds : IBotCredentials
 | 
			
		||||
    public Creds()
 | 
			
		||||
    {
 | 
			
		||||
        public Creds()
 | 
			
		||||
        Version = 1;
 | 
			
		||||
        Token = string.Empty;
 | 
			
		||||
        OwnerIds = new List<ulong>();
 | 
			
		||||
        TotalShards = 1;
 | 
			
		||||
        GoogleApiKey = string.Empty;
 | 
			
		||||
        Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
 | 
			
		||||
        Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
 | 
			
		||||
        BotListToken = string.Empty;
 | 
			
		||||
        CleverbotApiKey = string.Empty;
 | 
			
		||||
        RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
 | 
			
		||||
        Db = new()
 | 
			
		||||
        {
 | 
			
		||||
            Version = 1;
 | 
			
		||||
            Token = string.Empty;
 | 
			
		||||
            OwnerIds = new List<ulong>();
 | 
			
		||||
            TotalShards = 1;
 | 
			
		||||
            GoogleApiKey = string.Empty;
 | 
			
		||||
            Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
 | 
			
		||||
            Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
 | 
			
		||||
            BotListToken = string.Empty;
 | 
			
		||||
            CleverbotApiKey = string.Empty;
 | 
			
		||||
            RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
 | 
			
		||||
            Db = new()
 | 
			
		||||
            {
 | 
			
		||||
                Type = "sqlite",
 | 
			
		||||
                ConnectionString = "Data Source=data/NadekoBot.db"
 | 
			
		||||
            };
 | 
			
		||||
            Type = "sqlite",
 | 
			
		||||
            ConnectionString = "Data Source=data/NadekoBot.db"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            CoordinatorUrl = "http://localhost:3442";
 | 
			
		||||
        CoordinatorUrl = "http://localhost:3442";
 | 
			
		||||
 | 
			
		||||
            RestartCommand = new()
 | 
			
		||||
            {
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        RestartCommand = new()
 | 
			
		||||
        {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"DO NOT CHANGE")]
 | 
			
		||||
        public int Version { get; set; }
 | 
			
		||||
    [Comment(@"DO NOT CHANGE")]
 | 
			
		||||
    public int Version { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
 | 
			
		||||
        public string Token { get; set; }
 | 
			
		||||
    [Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
 | 
			
		||||
    public string Token { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"List of Ids of the users who have bot owner permissions
 | 
			
		||||
    [Comment(@"List of Ids of the users who have bot owner permissions
 | 
			
		||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
 | 
			
		||||
        public ICollection<ulong> OwnerIds { get; set; }
 | 
			
		||||
    public ICollection<ulong> OwnerIds { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"The number of shards that the bot will running on.
 | 
			
		||||
    [Comment(@"The number of shards that the bot will running on.
 | 
			
		||||
Leave at 1 if you don't know what you're doing.")]
 | 
			
		||||
        public int TotalShards { get; set; }
 | 
			
		||||
    public int TotalShards { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
 | 
			
		||||
    [Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
 | 
			
		||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
 | 
			
		||||
Used only for Youtube Data Api (at the moment).")]
 | 
			
		||||
        public string GoogleApiKey { get; set; }
 | 
			
		||||
    public string GoogleApiKey { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
 | 
			
		||||
        public VotesSettings Votes { get; set; }
 | 
			
		||||
    [Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
 | 
			
		||||
    public VotesSettings Votes { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Patreon auto reward system settings.
 | 
			
		||||
    [Comment(@"Patreon auto reward system settings.
 | 
			
		||||
go to https://www.patreon.com/portal -> my clients -> create client")]
 | 
			
		||||
        public PatreonSettings Patreon { get; set; }
 | 
			
		||||
    public PatreonSettings Patreon { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Api key for sending stats to DiscordBotList.")]
 | 
			
		||||
        public string BotListToken { get; set; }
 | 
			
		||||
    [Comment(@"Api key for sending stats to DiscordBotList.")]
 | 
			
		||||
    public string BotListToken { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Official cleverbot api key.")]
 | 
			
		||||
        public string CleverbotApiKey { get; set; }
 | 
			
		||||
    [Comment(@"Official cleverbot api key.")]
 | 
			
		||||
    public string CleverbotApiKey { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
 | 
			
		||||
        public string RedisOptions { get; set; }
 | 
			
		||||
    [Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
 | 
			
		||||
    public string RedisOptions { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
 | 
			
		||||
        public DbOptions Db { get; set; }
 | 
			
		||||
    [Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
 | 
			
		||||
    public DbOptions Db { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
 | 
			
		||||
    [Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
 | 
			
		||||
Change only if you've changed the coordinator address or port.")]
 | 
			
		||||
        public string CoordinatorUrl { get; set; }
 | 
			
		||||
    public string CoordinatorUrl { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
 | 
			
		||||
        public string RapidApiKey { get; set; }
 | 
			
		||||
    [Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
 | 
			
		||||
    public string RapidApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
 | 
			
		||||
    [Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
 | 
			
		||||
Used only for .time command.")]
 | 
			
		||||
        public string LocationIqApiKey { get; set; }
 | 
			
		||||
    public string LocationIqApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
 | 
			
		||||
    [Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
 | 
			
		||||
Used only for .time command")]
 | 
			
		||||
        public string TimezoneDbApiKey { get; set; }
 | 
			
		||||
    public string TimezoneDbApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
 | 
			
		||||
    [Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
 | 
			
		||||
Used for cryptocurrency related commands.")]
 | 
			
		||||
        public string CoinmarketcapApiKey { get; set; }
 | 
			
		||||
    public string CoinmarketcapApiKey { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        [Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
 | 
			
		||||
        public string OsuApiKey { get; set; }
 | 
			
		||||
    [Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
 | 
			
		||||
    public string OsuApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Command and args which will be used to restart the bot.
 | 
			
		||||
    [Comment(@"Command and args which will be used to restart the bot.
 | 
			
		||||
Only used if bot is executed directly (NOT through the coordinator)
 | 
			
		||||
placeholders: 
 | 
			
		||||
    {0} -> shard id 
 | 
			
		||||
@@ -102,118 +100,117 @@ Linux default
 | 
			
		||||
Windows default
 | 
			
		||||
    cmd: NadekoBot.exe
 | 
			
		||||
    args: {0}")]
 | 
			
		||||
        public RestartConfig RestartCommand { get; set; }
 | 
			
		||||
    public RestartConfig RestartCommand { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public class DbOptions
 | 
			
		||||
    public class DbOptions
 | 
			
		||||
    {
 | 
			
		||||
        [Comment(@"Database type. Only sqlite supported atm")]
 | 
			
		||||
        public string Type { get; set; }
 | 
			
		||||
        [Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
 | 
			
		||||
        public string ConnectionString { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // todo fixup patreon
 | 
			
		||||
    public sealed record PatreonSettings
 | 
			
		||||
    {
 | 
			
		||||
        public string ClientId { get; set; }
 | 
			
		||||
        public string AccessToken { get; set; }
 | 
			
		||||
        public string RefreshToken { get; set; }
 | 
			
		||||
        public string ClientSecret { get; set; }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
 | 
			
		||||
        public string CampaignId { get; set; }
 | 
			
		||||
 | 
			
		||||
        public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
 | 
			
		||||
        {
 | 
			
		||||
            [Comment(@"Database type. Only sqlite supported atm")]
 | 
			
		||||
            public string Type { get; set; }
 | 
			
		||||
            [Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
 | 
			
		||||
            public string ConnectionString { get; set; }
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
        // todo fixup patreon
 | 
			
		||||
        public sealed record PatreonSettings
 | 
			
		||||
        {
 | 
			
		||||
            public string ClientId { get; set; }
 | 
			
		||||
            public string AccessToken { get; set; }
 | 
			
		||||
            public string RefreshToken { get; set; }
 | 
			
		||||
            public string ClientSecret { get; set; }
 | 
			
		||||
 | 
			
		||||
            [Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
 | 
			
		||||
            public string CampaignId { get; set; }
 | 
			
		||||
 | 
			
		||||
            public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
 | 
			
		||||
            {
 | 
			
		||||
                AccessToken = accessToken;
 | 
			
		||||
                RefreshToken = refreshToken;
 | 
			
		||||
                ClientSecret = clientSecret;
 | 
			
		||||
                CampaignId = campaignId;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public PatreonSettings()
 | 
			
		||||
            {
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
            AccessToken = accessToken;
 | 
			
		||||
            RefreshToken = refreshToken;
 | 
			
		||||
            ClientSecret = clientSecret;
 | 
			
		||||
            CampaignId = campaignId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public sealed record VotesSettings
 | 
			
		||||
        public PatreonSettings()
 | 
			
		||||
        {
 | 
			
		||||
            [Comment(@"top.gg votes service url
 | 
			
		||||
This is the url of your instance of the NadekoBot.Votes api
 | 
			
		||||
Example: https://votes.my.cool.bot.com")]
 | 
			
		||||
            public string TopggServiceUrl { get; set; }
 | 
			
		||||
            
 | 
			
		||||
            [Comment(@"Authorization header value sent to the TopGG service url with each request
 | 
			
		||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
 | 
			
		||||
            public string TopggKey { get; set; }
 | 
			
		||||
            
 | 
			
		||||
            [Comment(@"discords.com votes service url
 | 
			
		||||
This is the url of your instance of the NadekoBot.Votes api
 | 
			
		||||
Example: https://votes.my.cool.bot.com")]
 | 
			
		||||
            public string DiscordsServiceUrl { get; set; }
 | 
			
		||||
            
 | 
			
		||||
            [Comment(@"Authorization header value sent to the Discords service url with each request
 | 
			
		||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
 | 
			
		||||
            public string DiscordsKey { get; set; }
 | 
			
		||||
 | 
			
		||||
            public VotesSettings()
 | 
			
		||||
            {
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
 | 
			
		||||
            {
 | 
			
		||||
                TopggServiceUrl = topggServiceUrl;
 | 
			
		||||
                TopggKey = topggKey;
 | 
			
		||||
                DiscordsServiceUrl = discordsServiceUrl;
 | 
			
		||||
                DiscordsKey = discordsKey;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class Old
 | 
			
		||||
        {
 | 
			
		||||
            public string Token { get; set; } = string.Empty;
 | 
			
		||||
            public ulong[] OwnerIds { get; set; } = new ulong[1];
 | 
			
		||||
            public string LoLApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string GoogleApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string MashapeKey { get; set; } = string.Empty;
 | 
			
		||||
            public string OsuApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string SoundCloudClientId { get; set; } = string.Empty;
 | 
			
		||||
            public string CleverbotApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string CarbonKey { get; set; } = string.Empty;
 | 
			
		||||
            public int TotalShards { get; set; } = 1;
 | 
			
		||||
            public string PatreonAccessToken { get; set; } = string.Empty;
 | 
			
		||||
            public string PatreonCampaignId { get; set; } = "334038";
 | 
			
		||||
            public RestartConfig RestartCommand { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
            public string ShardRunCommand { get; set; } = string.Empty;
 | 
			
		||||
            public string ShardRunArguments { get; set; } = string.Empty;
 | 
			
		||||
            public int? ShardRunPort { get; set; } = null;
 | 
			
		||||
            public string MiningProxyUrl { get; set; } = string.Empty;
 | 
			
		||||
            public string MiningProxyCreds { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
            public string BotListToken { get; set; } = string.Empty;
 | 
			
		||||
            public string TwitchClientId { get; set; } = string.Empty;
 | 
			
		||||
            public string VotesToken { get; set; } = string.Empty;
 | 
			
		||||
            public string VotesUrl { get; set; } = string.Empty;
 | 
			
		||||
            public string RedisOptions { get; set; } = string.Empty;
 | 
			
		||||
            public string LocationIqApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string TimezoneDbApiKey { get; set; } = string.Empty;
 | 
			
		||||
            public string CoinmarketcapApiKey { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
            public class RestartConfig
 | 
			
		||||
            {
 | 
			
		||||
                public RestartConfig(string cmd, string args)
 | 
			
		||||
                {
 | 
			
		||||
                    this.Cmd = cmd;
 | 
			
		||||
                    this.Args = args;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public string Cmd { get; set; }
 | 
			
		||||
                public string Args { get; set; }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public sealed record VotesSettings
 | 
			
		||||
    {
 | 
			
		||||
        [Comment(@"top.gg votes service url
 | 
			
		||||
This is the url of your instance of the NadekoBot.Votes api
 | 
			
		||||
Example: https://votes.my.cool.bot.com")]
 | 
			
		||||
        public string TopggServiceUrl { get; set; }
 | 
			
		||||
            
 | 
			
		||||
        [Comment(@"Authorization header value sent to the TopGG service url with each request
 | 
			
		||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
 | 
			
		||||
        public string TopggKey { get; set; }
 | 
			
		||||
            
 | 
			
		||||
        [Comment(@"discords.com votes service url
 | 
			
		||||
This is the url of your instance of the NadekoBot.Votes api
 | 
			
		||||
Example: https://votes.my.cool.bot.com")]
 | 
			
		||||
        public string DiscordsServiceUrl { get; set; }
 | 
			
		||||
            
 | 
			
		||||
        [Comment(@"Authorization header value sent to the Discords service url with each request
 | 
			
		||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
 | 
			
		||||
        public string DiscordsKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        public VotesSettings()
 | 
			
		||||
        {
 | 
			
		||||
                
 | 
			
		||||
        }
 | 
			
		||||
            
 | 
			
		||||
        public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
 | 
			
		||||
        {
 | 
			
		||||
            TopggServiceUrl = topggServiceUrl;
 | 
			
		||||
            TopggKey = topggKey;
 | 
			
		||||
            DiscordsServiceUrl = discordsServiceUrl;
 | 
			
		||||
            DiscordsKey = discordsKey;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class Old
 | 
			
		||||
    {
 | 
			
		||||
        public string Token { get; set; } = string.Empty;
 | 
			
		||||
        public ulong[] OwnerIds { get; set; } = new ulong[1];
 | 
			
		||||
        public string LoLApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string GoogleApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string MashapeKey { get; set; } = string.Empty;
 | 
			
		||||
        public string OsuApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string SoundCloudClientId { get; set; } = string.Empty;
 | 
			
		||||
        public string CleverbotApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string CarbonKey { get; set; } = string.Empty;
 | 
			
		||||
        public int TotalShards { get; set; } = 1;
 | 
			
		||||
        public string PatreonAccessToken { get; set; } = string.Empty;
 | 
			
		||||
        public string PatreonCampaignId { get; set; } = "334038";
 | 
			
		||||
        public RestartConfig RestartCommand { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
        public string ShardRunCommand { get; set; } = string.Empty;
 | 
			
		||||
        public string ShardRunArguments { get; set; } = string.Empty;
 | 
			
		||||
        public int? ShardRunPort { get; set; } = null;
 | 
			
		||||
        public string MiningProxyUrl { get; set; } = string.Empty;
 | 
			
		||||
        public string MiningProxyCreds { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
        public string BotListToken { get; set; } = string.Empty;
 | 
			
		||||
        public string TwitchClientId { get; set; } = string.Empty;
 | 
			
		||||
        public string VotesToken { get; set; } = string.Empty;
 | 
			
		||||
        public string VotesUrl { get; set; } = string.Empty;
 | 
			
		||||
        public string RedisOptions { get; set; } = string.Empty;
 | 
			
		||||
        public string LocationIqApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string TimezoneDbApiKey { get; set; } = string.Empty;
 | 
			
		||||
        public string CoinmarketcapApiKey { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
        public class RestartConfig
 | 
			
		||||
        {
 | 
			
		||||
            public RestartConfig(string cmd, string args)
 | 
			
		||||
            {
 | 
			
		||||
                this.Cmd = cmd;
 | 
			
		||||
                this.Args = args;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public string Cmd { get; set; }
 | 
			
		||||
            public string Args { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,43 +1,41 @@
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class DownloadTracker : INService
 | 
			
		||||
{
 | 
			
		||||
    public class DownloadTracker : INService
 | 
			
		||||
    private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
 | 
			
		||||
    private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Ensures all users on the specified guild were downloaded within the last hour. 
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="guild">Guild to check and potentially download users from</param>
 | 
			
		||||
    /// <returns>Task representing download state</returns>
 | 
			
		||||
    public async Task EnsureUsersDownloadedAsync(IGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
 | 
			
		||||
        private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Ensures all users on the specified guild were downloaded within the last hour. 
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="guild">Guild to check and potentially download users from</param>
 | 
			
		||||
        /// <returns>Task representing download state</returns>
 | 
			
		||||
        public async Task EnsureUsersDownloadedAsync(IGuild guild)
 | 
			
		||||
        await downloadUsersSemaphore.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await downloadUsersSemaphore.WaitAsync();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var now = DateTime.UtcNow;
 | 
			
		||||
            var now = DateTime.UtcNow;
 | 
			
		||||
 | 
			
		||||
                // download once per hour at most
 | 
			
		||||
                var added = LastDownloads.AddOrUpdate(
 | 
			
		||||
                    guild.Id,
 | 
			
		||||
                    now,
 | 
			
		||||
                    (key, old) => (now - old) > TimeSpan.FromHours(1) ? now : old);
 | 
			
		||||
            // download once per hour at most
 | 
			
		||||
            var added = LastDownloads.AddOrUpdate(
 | 
			
		||||
                guild.Id,
 | 
			
		||||
                now,
 | 
			
		||||
                (key, old) => (now - old) > TimeSpan.FromHours(1) ? now : old);
 | 
			
		||||
 | 
			
		||||
                // means that this entry was just added - download the users
 | 
			
		||||
                if (added == now)
 | 
			
		||||
                    await guild.DownloadUsersAsync();
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                downloadUsersSemaphore.Release();
 | 
			
		||||
            }
 | 
			
		||||
            // means that this entry was just added - download the users
 | 
			
		||||
            if (added == now)
 | 
			
		||||
                await guild.DownloadUsersAsync();
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            downloadUsersSemaphore.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class BotCredentialsExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class BotCredentialsExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static bool IsOwner(this IBotCredentials creds, IUser user)
 | 
			
		||||
            => creds.OwnerIds.Contains(user.Id);
 | 
			
		||||
    }
 | 
			
		||||
    public static bool IsOwner(this IBotCredentials creds, IUser user)
 | 
			
		||||
        => creds.OwnerIds.Contains(user.Id);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,81 +1,77 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Modules.Music;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
using NadekoBot.Modules.Music.Resolvers;
 | 
			
		||||
using NadekoBot.Modules.Music.Services;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class ServiceCollectionExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class ServiceCollectionExtensions
 | 
			
		||||
    public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
 | 
			
		||||
        => totalShards <= 1
 | 
			
		||||
            ? services
 | 
			
		||||
                .AddSingleton<IStringsSource, LocalFileStringsSource>()
 | 
			
		||||
                .AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
 | 
			
		||||
                .AddSingleton<IBotStrings, BotStrings>()
 | 
			
		||||
            : services.AddSingleton<IStringsSource, LocalFileStringsSource>()
 | 
			
		||||
                .AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
 | 
			
		||||
                .AddSingleton<IBotStrings, BotStrings>();
 | 
			
		||||
 | 
			
		||||
    public static IServiceCollection AddConfigServices(this IServiceCollection services)
 | 
			
		||||
    {
 | 
			
		||||
        public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
 | 
			
		||||
            => totalShards <= 1
 | 
			
		||||
                ? services
 | 
			
		||||
                    .AddSingleton<IStringsSource, LocalFileStringsSource>()
 | 
			
		||||
                    .AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
 | 
			
		||||
                    .AddSingleton<IBotStrings, BotStrings>()
 | 
			
		||||
                : services.AddSingleton<IStringsSource, LocalFileStringsSource>()
 | 
			
		||||
                    .AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
 | 
			
		||||
                    .AddSingleton<IBotStrings, BotStrings>();
 | 
			
		||||
        var baseType = typeof(ConfigServiceBase<>);
 | 
			
		||||
 | 
			
		||||
        public static IServiceCollection AddConfigServices(this IServiceCollection services)
 | 
			
		||||
        foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
 | 
			
		||||
        {
 | 
			
		||||
            var baseType = typeof(ConfigServiceBase<>);
 | 
			
		||||
 | 
			
		||||
            foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
 | 
			
		||||
            if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
 | 
			
		||||
            {
 | 
			
		||||
                if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
 | 
			
		||||
                {
 | 
			
		||||
                    services.AddSingleton(type);
 | 
			
		||||
                    services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
 | 
			
		||||
                }
 | 
			
		||||
                services.AddSingleton(type);
 | 
			
		||||
                services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return services;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
 | 
			
		||||
            => services.AddSealedSubclassesOf(typeof(IConfigMigrator));
 | 
			
		||||
        return services;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static IServiceCollection AddMusic(this IServiceCollection services)
 | 
			
		||||
            => services
 | 
			
		||||
                .AddSingleton<IMusicService, MusicService>()
 | 
			
		||||
                .AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
 | 
			
		||||
                .AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
 | 
			
		||||
                .AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
 | 
			
		||||
                .AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
 | 
			
		||||
                .AddSingleton<IRadioResolver, RadioResolver>()
 | 
			
		||||
                .AddSingleton<ITrackCacher, RedisTrackCacher>()
 | 
			
		||||
                .AddSingleton<YtLoader>()
 | 
			
		||||
                .AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
 | 
			
		||||
    public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
 | 
			
		||||
        => services.AddSealedSubclassesOf(typeof(IConfigMigrator));
 | 
			
		||||
 | 
			
		||||
    public static IServiceCollection AddMusic(this IServiceCollection services)
 | 
			
		||||
        => services
 | 
			
		||||
            .AddSingleton<IMusicService, MusicService>()
 | 
			
		||||
            .AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
 | 
			
		||||
            .AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
 | 
			
		||||
            .AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
 | 
			
		||||
            .AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
 | 
			
		||||
            .AddSingleton<IRadioResolver, RadioResolver>()
 | 
			
		||||
            .AddSingleton<ITrackCacher, RedisTrackCacher>()
 | 
			
		||||
            .AddSingleton<YtLoader>()
 | 
			
		||||
            .AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
 | 
			
		||||
        
 | 
			
		||||
        // consider using scrutor, because slightly different versions
 | 
			
		||||
        // of this might be needed in several different places
 | 
			
		||||
        public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
 | 
			
		||||
    // consider using scrutor, because slightly different versions
 | 
			
		||||
    // of this might be needed in several different places
 | 
			
		||||
    public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
 | 
			
		||||
    {
 | 
			
		||||
        var subTypes = Assembly.GetCallingAssembly()
 | 
			
		||||
            .ExportedTypes
 | 
			
		||||
            .Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
 | 
			
		||||
 | 
			
		||||
        foreach (var subType in subTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var subTypes = Assembly.GetCallingAssembly()
 | 
			
		||||
                .ExportedTypes
 | 
			
		||||
                .Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
 | 
			
		||||
 | 
			
		||||
            foreach (var subType in subTypes)
 | 
			
		||||
            {
 | 
			
		||||
                services.AddSingleton(baseType, subType);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return services;
 | 
			
		||||
            services.AddSingleton(baseType, subType);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var conf = ConfigurationOptions.Parse(redisOptions);
 | 
			
		||||
            services.AddSingleton(ConnectionMultiplexer.Connect(conf));
 | 
			
		||||
            return services;
 | 
			
		||||
        }
 | 
			
		||||
        return services;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var conf = ConfigurationOptions.Parse(redisOptions);
 | 
			
		||||
        services.AddSingleton(ConnectionMultiplexer.Connect(conf));
 | 
			
		||||
        return services;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,255 +1,250 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace Discord;
 | 
			
		||||
// just a copy paste from discord.net in order to rename it, for compatibility iwth v3 which is gonna use custom lib
 | 
			
		||||
 | 
			
		||||
namespace Discord
 | 
			
		||||
// Summary:
 | 
			
		||||
//     Defines the available permissions for a channel.
 | 
			
		||||
[Flags]
 | 
			
		||||
public enum GuildPerm : ulong
 | 
			
		||||
{
 | 
			
		||||
    // just a copy paste from discord.net in order to rename it, for compatibility iwth v3 which is gonna use custom lib
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Defines the available permissions for a channel.
 | 
			
		||||
    [Flags]
 | 
			
		||||
    public enum GuildPerm : ulong
 | 
			
		||||
    {
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows creation of instant invites.
 | 
			
		||||
        CreateInstantInvite = 1,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows kicking members.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        KickMembers = 2,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows banning members.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        BanMembers = 4,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows all permissions and bypasses channel permission overwrites.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        Administrator = 8,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of channels.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageChannels = 16,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of the guild.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageGuild = 32,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for the addition of reactions to messages.
 | 
			
		||||
        AddReactions = 64,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for viewing of audit logs.
 | 
			
		||||
        ViewAuditLog = 128,
 | 
			
		||||
        PrioritySpeaker = 256,
 | 
			
		||||
        ReadMessages = 1024,
 | 
			
		||||
        ViewChannel = 1024,
 | 
			
		||||
        SendMessages = 2048,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for sending of text-to-speech messages.
 | 
			
		||||
        SendTTSMessages = 4096,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for deletion of other users messages.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageMessages = 8192,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows links sent by users with this permission will be auto-embedded.
 | 
			
		||||
        EmbedLinks = 16384,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for uploading images and files.
 | 
			
		||||
        AttachFiles = 32768,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for reading of message history.
 | 
			
		||||
        ReadMessageHistory = 65536,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for using the @everyone tag to notify all users in a channel, and the
 | 
			
		||||
        //     @here tag to notify all online users in a channel.
 | 
			
		||||
        MentionEveryone = 131072,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows the usage of custom emojis from other servers.
 | 
			
		||||
        UseExternalEmojis = 262144,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for joining of a voice channel.
 | 
			
		||||
        Connect = 1048576,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for speaking in a voice channel.
 | 
			
		||||
        Speak = 2097152,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for muting members in a voice channel.
 | 
			
		||||
        MuteMembers = 4194304,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for deafening of members in a voice channel.
 | 
			
		||||
        DeafenMembers = 8388608,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for moving of members between voice channels.
 | 
			
		||||
        MoveMembers = 16777216,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for using voice-activity-detection in a voice channel.
 | 
			
		||||
        UseVAD = 33554432,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for modification of own nickname.
 | 
			
		||||
        ChangeNickname = 67108864,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for modification of other users nicknames.
 | 
			
		||||
        ManageNicknames = 134217728,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of roles.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageRoles = 268435456,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of webhooks.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageWebhooks = 536870912,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of emojis.
 | 
			
		||||
        //
 | 
			
		||||
        // Remarks:
 | 
			
		||||
        //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
        //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
        ManageEmojis = 1073741824
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Defines the available permissions for a channel.
 | 
			
		||||
    [Flags]
 | 
			
		||||
    public enum ChannelPerm : ulong
 | 
			
		||||
    {
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows creation of instant invites.
 | 
			
		||||
        CreateInstantInvite = 1,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of channels.
 | 
			
		||||
        ManageChannel = 16,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for the addition of reactions to messages.
 | 
			
		||||
        AddReactions = 64,
 | 
			
		||||
        PrioritySpeaker = 256,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for reading of messages. This flag is obsolete, use Discord.ChannelPermission.ViewChannel
 | 
			
		||||
        //     instead.
 | 
			
		||||
        ReadMessages = 1024,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows guild members to view a channel, which includes reading messages in text
 | 
			
		||||
        //     channels.
 | 
			
		||||
        ViewChannel = 1024,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for sending messages in a channel.
 | 
			
		||||
        SendMessages = 2048,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for sending of text-to-speech messages.
 | 
			
		||||
        SendTTSMessages = 4096,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for deletion of other users messages.
 | 
			
		||||
        ManageMessages = 8192,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows links sent by users with this permission will be auto-embedded.
 | 
			
		||||
        EmbedLinks = 16384,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for uploading images and files.
 | 
			
		||||
        AttachFiles = 32768,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for reading of message history.
 | 
			
		||||
        ReadMessageHistory = 65536,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for using the @everyone tag to notify all users in a channel, and the
 | 
			
		||||
        //     @here tag to notify all online users in a channel.
 | 
			
		||||
        MentionEveryone = 131072,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows the usage of custom emojis from other servers.
 | 
			
		||||
        UseExternalEmojis = 262144,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for joining of a voice channel.
 | 
			
		||||
        Connect = 1048576,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for speaking in a voice channel.
 | 
			
		||||
        Speak = 2097152,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for muting members in a voice channel.
 | 
			
		||||
        MuteMembers = 4194304,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for deafening of members in a voice channel.
 | 
			
		||||
        DeafenMembers = 8388608,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for moving of members between voice channels.
 | 
			
		||||
        MoveMembers = 16777216,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows for using voice-activity-detection in a voice channel.
 | 
			
		||||
        UseVAD = 33554432,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of roles.
 | 
			
		||||
        ManageRoles = 268435456,
 | 
			
		||||
        //
 | 
			
		||||
        // Summary:
 | 
			
		||||
        //     Allows management and editing of webhooks.
 | 
			
		||||
        ManageWebhooks = 536870912
 | 
			
		||||
    }
 | 
			
		||||
    //     Allows creation of instant invites.
 | 
			
		||||
    CreateInstantInvite = 1,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows kicking members.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    KickMembers = 2,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows banning members.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    BanMembers = 4,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows all permissions and bypasses channel permission overwrites.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    Administrator = 8,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of channels.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageChannels = 16,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of the guild.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageGuild = 32,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for the addition of reactions to messages.
 | 
			
		||||
    AddReactions = 64,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for viewing of audit logs.
 | 
			
		||||
    ViewAuditLog = 128,
 | 
			
		||||
    PrioritySpeaker = 256,
 | 
			
		||||
    ReadMessages = 1024,
 | 
			
		||||
    ViewChannel = 1024,
 | 
			
		||||
    SendMessages = 2048,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for sending of text-to-speech messages.
 | 
			
		||||
    SendTTSMessages = 4096,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for deletion of other users messages.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageMessages = 8192,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows links sent by users with this permission will be auto-embedded.
 | 
			
		||||
    EmbedLinks = 16384,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for uploading images and files.
 | 
			
		||||
    AttachFiles = 32768,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for reading of message history.
 | 
			
		||||
    ReadMessageHistory = 65536,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for using the @everyone tag to notify all users in a channel, and the
 | 
			
		||||
    //     @here tag to notify all online users in a channel.
 | 
			
		||||
    MentionEveryone = 131072,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows the usage of custom emojis from other servers.
 | 
			
		||||
    UseExternalEmojis = 262144,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for joining of a voice channel.
 | 
			
		||||
    Connect = 1048576,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for speaking in a voice channel.
 | 
			
		||||
    Speak = 2097152,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for muting members in a voice channel.
 | 
			
		||||
    MuteMembers = 4194304,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for deafening of members in a voice channel.
 | 
			
		||||
    DeafenMembers = 8388608,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for moving of members between voice channels.
 | 
			
		||||
    MoveMembers = 16777216,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for using voice-activity-detection in a voice channel.
 | 
			
		||||
    UseVAD = 33554432,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for modification of own nickname.
 | 
			
		||||
    ChangeNickname = 67108864,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for modification of other users nicknames.
 | 
			
		||||
    ManageNicknames = 134217728,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of roles.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageRoles = 268435456,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of webhooks.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageWebhooks = 536870912,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of emojis.
 | 
			
		||||
    //
 | 
			
		||||
    // Remarks:
 | 
			
		||||
    //     This permission requires the owner account to use two-factor authentication when
 | 
			
		||||
    //     used on a guild that has server-wide 2FA enabled.
 | 
			
		||||
    ManageEmojis = 1073741824
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Summary:
 | 
			
		||||
//     Defines the available permissions for a channel.
 | 
			
		||||
[Flags]
 | 
			
		||||
public enum ChannelPerm : ulong
 | 
			
		||||
{
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows creation of instant invites.
 | 
			
		||||
    CreateInstantInvite = 1,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of channels.
 | 
			
		||||
    ManageChannel = 16,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for the addition of reactions to messages.
 | 
			
		||||
    AddReactions = 64,
 | 
			
		||||
    PrioritySpeaker = 256,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for reading of messages. This flag is obsolete, use Discord.ChannelPermission.ViewChannel
 | 
			
		||||
    //     instead.
 | 
			
		||||
    ReadMessages = 1024,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows guild members to view a channel, which includes reading messages in text
 | 
			
		||||
    //     channels.
 | 
			
		||||
    ViewChannel = 1024,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for sending messages in a channel.
 | 
			
		||||
    SendMessages = 2048,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for sending of text-to-speech messages.
 | 
			
		||||
    SendTTSMessages = 4096,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for deletion of other users messages.
 | 
			
		||||
    ManageMessages = 8192,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows links sent by users with this permission will be auto-embedded.
 | 
			
		||||
    EmbedLinks = 16384,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for uploading images and files.
 | 
			
		||||
    AttachFiles = 32768,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for reading of message history.
 | 
			
		||||
    ReadMessageHistory = 65536,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for using the @everyone tag to notify all users in a channel, and the
 | 
			
		||||
    //     @here tag to notify all online users in a channel.
 | 
			
		||||
    MentionEveryone = 131072,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows the usage of custom emojis from other servers.
 | 
			
		||||
    UseExternalEmojis = 262144,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for joining of a voice channel.
 | 
			
		||||
    Connect = 1048576,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for speaking in a voice channel.
 | 
			
		||||
    Speak = 2097152,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for muting members in a voice channel.
 | 
			
		||||
    MuteMembers = 4194304,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for deafening of members in a voice channel.
 | 
			
		||||
    DeafenMembers = 8388608,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for moving of members between voice channels.
 | 
			
		||||
    MoveMembers = 16777216,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows for using voice-activity-detection in a voice channel.
 | 
			
		||||
    UseVAD = 33554432,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of roles.
 | 
			
		||||
    ManageRoles = 268435456,
 | 
			
		||||
    //
 | 
			
		||||
    // Summary:
 | 
			
		||||
    //     Allows management and editing of webhooks.
 | 
			
		||||
    ManageWebhooks = 536870912
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
public static class Helpers
 | 
			
		||||
{
 | 
			
		||||
    public static class Helpers
 | 
			
		||||
    public static void ReadErrorAndExit(int exitCode)
 | 
			
		||||
    {
 | 
			
		||||
        public static void ReadErrorAndExit(int exitCode)
 | 
			
		||||
        {
 | 
			
		||||
            if (!Console.IsInputRedirected)
 | 
			
		||||
                Console.ReadKey();
 | 
			
		||||
        if (!Console.IsInputRedirected)
 | 
			
		||||
            Console.ReadKey();
 | 
			
		||||
            
 | 
			
		||||
            Environment.Exit(exitCode);
 | 
			
		||||
        }
 | 
			
		||||
        Environment.Exit(exitCode);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +1,31 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Discord;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public interface IBotCredentials
 | 
			
		||||
{
 | 
			
		||||
    public interface IBotCredentials
 | 
			
		||||
    {
 | 
			
		||||
        string Token { get; }
 | 
			
		||||
        string GoogleApiKey { get; }
 | 
			
		||||
        ICollection<ulong> OwnerIds { get; }
 | 
			
		||||
        string RapidApiKey { get; }
 | 
			
		||||
    string Token { get; }
 | 
			
		||||
    string GoogleApiKey { get; }
 | 
			
		||||
    ICollection<ulong> OwnerIds { get; }
 | 
			
		||||
    string RapidApiKey { get; }
 | 
			
		||||
 | 
			
		||||
        Creds.DbOptions Db { get; }
 | 
			
		||||
        string OsuApiKey { get; }
 | 
			
		||||
        int TotalShards { get; }
 | 
			
		||||
        Creds.PatreonSettings Patreon { get; }
 | 
			
		||||
        string CleverbotApiKey { get; }
 | 
			
		||||
        RestartConfig RestartCommand { get; }
 | 
			
		||||
        Creds.VotesSettings Votes { get; }
 | 
			
		||||
        string BotListToken { get; }
 | 
			
		||||
        string RedisOptions { get; }
 | 
			
		||||
        string LocationIqApiKey { get; }
 | 
			
		||||
        string TimezoneDbApiKey { get; }
 | 
			
		||||
        string CoinmarketcapApiKey { get; }
 | 
			
		||||
        string CoordinatorUrl { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public class RestartConfig
 | 
			
		||||
    {
 | 
			
		||||
        public string Cmd { get; set; }
 | 
			
		||||
        public string Args { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    Creds.DbOptions Db { get; }
 | 
			
		||||
    string OsuApiKey { get; }
 | 
			
		||||
    int TotalShards { get; }
 | 
			
		||||
    Creds.PatreonSettings Patreon { get; }
 | 
			
		||||
    string CleverbotApiKey { get; }
 | 
			
		||||
    RestartConfig RestartCommand { get; }
 | 
			
		||||
    Creds.VotesSettings Votes { get; }
 | 
			
		||||
    string BotListToken { get; }
 | 
			
		||||
    string RedisOptions { get; }
 | 
			
		||||
    string LocationIqApiKey { get; }
 | 
			
		||||
    string TimezoneDbApiKey { get; }
 | 
			
		||||
    string CoinmarketcapApiKey { get; }
 | 
			
		||||
    string CoordinatorUrl { get; set; }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
public class RestartConfig
 | 
			
		||||
{
 | 
			
		||||
    public string Cmd { get; set; }
 | 
			
		||||
    public string Args { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public interface ICloneable<T> where T : new()
 | 
			
		||||
{
 | 
			
		||||
    public interface ICloneable<T> where T : new()
 | 
			
		||||
    {
 | 
			
		||||
        public T Clone();
 | 
			
		||||
    }
 | 
			
		||||
    public T Clone();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
{
 | 
			
		||||
    public interface IEmbedBuilder
 | 
			
		||||
    {
 | 
			
		||||
        IEmbedBuilder WithDescription(string desc);
 | 
			
		||||
        IEmbedBuilder WithTitle(string title);
 | 
			
		||||
        IEmbedBuilder AddField(string title, object value, bool isInline = false);
 | 
			
		||||
        IEmbedBuilder WithFooter(string text, string iconUrl = null);
 | 
			
		||||
        IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
 | 
			
		||||
        IEmbedBuilder WithColor(EmbedColor color);
 | 
			
		||||
        Embed Build();
 | 
			
		||||
        IEmbedBuilder WithUrl(string url);
 | 
			
		||||
        IEmbedBuilder WithImageUrl(string url);
 | 
			
		||||
        IEmbedBuilder WithThumbnailUrl(string url);
 | 
			
		||||
    }
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
    public enum EmbedColor
 | 
			
		||||
    {
 | 
			
		||||
        Ok,
 | 
			
		||||
        Pending,
 | 
			
		||||
        Error,
 | 
			
		||||
    }
 | 
			
		||||
public interface IEmbedBuilder
 | 
			
		||||
{
 | 
			
		||||
    IEmbedBuilder WithDescription(string desc);
 | 
			
		||||
    IEmbedBuilder WithTitle(string title);
 | 
			
		||||
    IEmbedBuilder AddField(string title, object value, bool isInline = false);
 | 
			
		||||
    IEmbedBuilder WithFooter(string text, string iconUrl = null);
 | 
			
		||||
    IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
 | 
			
		||||
    IEmbedBuilder WithColor(EmbedColor color);
 | 
			
		||||
    Embed Build();
 | 
			
		||||
    IEmbedBuilder WithUrl(string url);
 | 
			
		||||
    IEmbedBuilder WithImageUrl(string url);
 | 
			
		||||
    IEmbedBuilder WithThumbnailUrl(string url);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public enum EmbedColor
 | 
			
		||||
{
 | 
			
		||||
    Ok,
 | 
			
		||||
    Pending,
 | 
			
		||||
    Error,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public interface INadekoCommandOptions
 | 
			
		||||
{
 | 
			
		||||
    public interface INadekoCommandOptions
 | 
			
		||||
    {
 | 
			
		||||
        void NormalizeOptions();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    void NormalizeOptions();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,6 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
public interface IPlaceholderProvider
 | 
			
		||||
{
 | 
			
		||||
    public interface IPlaceholderProvider
 | 
			
		||||
    {
 | 
			
		||||
        public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
 | 
			
		||||
    }
 | 
			
		||||
    public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,50 +1,48 @@
 | 
			
		||||
using System;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class ImageUrls
 | 
			
		||||
{
 | 
			
		||||
    public class ImageUrls
 | 
			
		||||
    [Comment("DO NOT CHANGE")]
 | 
			
		||||
    public int Version { get; set; } = 3;
 | 
			
		||||
 | 
			
		||||
    public CoinData Coins { get; set; }
 | 
			
		||||
    public Uri[] Currency { get; set; }
 | 
			
		||||
    public Uri[] Dice { get; set; }
 | 
			
		||||
    public RategirlData Rategirl { get; set; }
 | 
			
		||||
    public XpData Xp { get; set; }
 | 
			
		||||
 | 
			
		||||
    //new
 | 
			
		||||
    public RipData Rip { get; set; }
 | 
			
		||||
    public SlotData Slots { get; set; }
 | 
			
		||||
 | 
			
		||||
    public class RipData
 | 
			
		||||
    {
 | 
			
		||||
        [Comment("DO NOT CHANGE")]
 | 
			
		||||
        public int Version { get; set; } = 3;
 | 
			
		||||
 | 
			
		||||
        public CoinData Coins { get; set; }
 | 
			
		||||
        public Uri[] Currency { get; set; }
 | 
			
		||||
        public Uri[] Dice { get; set; }
 | 
			
		||||
        public RategirlData Rategirl { get; set; }
 | 
			
		||||
        public XpData Xp { get; set; }
 | 
			
		||||
 | 
			
		||||
        //new
 | 
			
		||||
        public RipData Rip { get; set; }
 | 
			
		||||
        public SlotData Slots { get; set; }
 | 
			
		||||
 | 
			
		||||
        public class RipData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
            public Uri Overlay { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class SlotData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Emojis { get; set; }
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class CoinData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Heads { get; set; }
 | 
			
		||||
            public Uri[] Tails { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class RategirlData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Matrix { get; set; }
 | 
			
		||||
            public Uri Dot { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class XpData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
        public Uri Overlay { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public class SlotData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri[] Emojis { get; set; }
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class CoinData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri[] Heads { get; set; }
 | 
			
		||||
        public Uri[] Tails { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class RategirlData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri Matrix { get; set; }
 | 
			
		||||
        public Uri Dot { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class XpData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +1,32 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.JsonConverters
 | 
			
		||||
namespace NadekoBot.Common.JsonConverters;
 | 
			
		||||
 | 
			
		||||
public class Rgba32Converter : JsonConverter<Rgba32>
 | 
			
		||||
{
 | 
			
		||||
    public class Rgba32Converter : JsonConverter<Rgba32>
 | 
			
		||||
    public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            return Rgba32.ParseHex(reader.GetString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            writer.WriteStringValue(value.ToHex());
 | 
			
		||||
        }
 | 
			
		||||
        return Rgba32.ParseHex(reader.GetString());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public class CultureInfoConverter : JsonConverter<CultureInfo>
 | 
			
		||||
    {
 | 
			
		||||
        public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            return new CultureInfo(reader.GetString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            writer.WriteStringValue(value.Name);
 | 
			
		||||
        }
 | 
			
		||||
    public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        writer.WriteStringValue(value.ToHex());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
public class CultureInfoConverter : JsonConverter<CultureInfo>
 | 
			
		||||
{
 | 
			
		||||
    public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        return new CultureInfo(reader.GetString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
 | 
			
		||||
    {
 | 
			
		||||
        writer.WriteStringValue(value.Name);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,98 +1,96 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
// needs proper invalid input check (character array input out of range)
 | 
			
		||||
// needs negative number support
 | 
			
		||||
public readonly struct kwum : IEquatable<kwum>
 | 
			
		||||
{
 | 
			
		||||
    // needs proper invalid input check (character array input out of range)
 | 
			
		||||
    // needs negative number support
 | 
			
		||||
    public readonly struct kwum : IEquatable<kwum>
 | 
			
		||||
    private readonly int _value;
 | 
			
		||||
    private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
 | 
			
		||||
 | 
			
		||||
    public kwum(int num)
 | 
			
		||||
        => _value = num;
 | 
			
		||||
        
 | 
			
		||||
    public kwum(in char c)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly int _value;
 | 
			
		||||
        private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
 | 
			
		||||
        if (!IsValidChar(c))
 | 
			
		||||
            throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
 | 
			
		||||
 | 
			
		||||
        public kwum(int num)
 | 
			
		||||
            => _value = num;
 | 
			
		||||
        
 | 
			
		||||
        public kwum(in char c)
 | 
			
		||||
        _value = InternalCharToValue(c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
    private static int InternalCharToValue(in char c) 
 | 
			
		||||
        => ValidCharacters.IndexOf(c);
 | 
			
		||||
 | 
			
		||||
    public kwum(in ReadOnlySpan<char> input)
 | 
			
		||||
    {;
 | 
			
		||||
        _value = 0;
 | 
			
		||||
        for (var index = 0; index < input.Length; index++)
 | 
			
		||||
        {
 | 
			
		||||
            var c = input[index];
 | 
			
		||||
            if (!IsValidChar(c))
 | 
			
		||||
                throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
 | 
			
		||||
                throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
 | 
			
		||||
 | 
			
		||||
            _value = InternalCharToValue(c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        private static int InternalCharToValue(in char c) 
 | 
			
		||||
            => ValidCharacters.IndexOf(c);
 | 
			
		||||
 | 
			
		||||
        public kwum(in ReadOnlySpan<char> input)
 | 
			
		||||
        {;
 | 
			
		||||
            _value = 0;
 | 
			
		||||
            for (var index = 0; index < input.Length; index++)
 | 
			
		||||
            {
 | 
			
		||||
                var c = input[index];
 | 
			
		||||
                if (!IsValidChar(c))
 | 
			
		||||
                    throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
 | 
			
		||||
 | 
			
		||||
                _value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
 | 
			
		||||
        {
 | 
			
		||||
            value = default;
 | 
			
		||||
            foreach(var c in input)
 | 
			
		||||
                if (!IsValidChar(c))
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
            value = new kwum(input);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static kwum operator +(kwum left, kwum right)
 | 
			
		||||
            => new kwum(left._value + right._value);
 | 
			
		||||
 | 
			
		||||
        public static bool operator ==(kwum left, kwum right)
 | 
			
		||||
            => left._value == right._value;
 | 
			
		||||
 | 
			
		||||
        public static bool operator !=(kwum left, kwum right)
 | 
			
		||||
            => !(left == right);
 | 
			
		||||
 | 
			
		||||
        public static implicit operator long(kwum kwum)
 | 
			
		||||
            => kwum._value;
 | 
			
		||||
        
 | 
			
		||||
        public static implicit operator int(kwum kwum)
 | 
			
		||||
            => kwum._value;
 | 
			
		||||
        public static implicit operator kwum(int num)
 | 
			
		||||
            => new kwum(num);
 | 
			
		||||
 | 
			
		||||
        public static bool IsValidChar(char c)
 | 
			
		||||
            => ValidCharacters.Contains(c);
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            var count = ValidCharacters.Length;
 | 
			
		||||
            var localValue = _value;
 | 
			
		||||
            var arrSize = (int)Math.Log(localValue, count) + 1;
 | 
			
		||||
            Span<char> chars = new char[arrSize];
 | 
			
		||||
            while (localValue > 0)
 | 
			
		||||
            {
 | 
			
		||||
                localValue = Math.DivRem(localValue, count, out var rem);
 | 
			
		||||
                chars[--arrSize] = ValidCharacters[(int)rem];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new string(chars);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
            => obj is kwum kw && kw == this;
 | 
			
		||||
 | 
			
		||||
        public bool Equals(kwum other)
 | 
			
		||||
            => other == this;
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode()
 | 
			
		||||
        {
 | 
			
		||||
            return _value.GetHashCode();
 | 
			
		||||
            _value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
 | 
			
		||||
    {
 | 
			
		||||
        value = default;
 | 
			
		||||
        foreach(var c in input)
 | 
			
		||||
            if (!IsValidChar(c))
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
        value = new kwum(input);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static kwum operator +(kwum left, kwum right)
 | 
			
		||||
        => new kwum(left._value + right._value);
 | 
			
		||||
 | 
			
		||||
    public static bool operator ==(kwum left, kwum right)
 | 
			
		||||
        => left._value == right._value;
 | 
			
		||||
 | 
			
		||||
    public static bool operator !=(kwum left, kwum right)
 | 
			
		||||
        => !(left == right);
 | 
			
		||||
 | 
			
		||||
    public static implicit operator long(kwum kwum)
 | 
			
		||||
        => kwum._value;
 | 
			
		||||
        
 | 
			
		||||
    public static implicit operator int(kwum kwum)
 | 
			
		||||
        => kwum._value;
 | 
			
		||||
    public static implicit operator kwum(int num)
 | 
			
		||||
        => new kwum(num);
 | 
			
		||||
 | 
			
		||||
    public static bool IsValidChar(char c)
 | 
			
		||||
        => ValidCharacters.Contains(c);
 | 
			
		||||
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
        var count = ValidCharacters.Length;
 | 
			
		||||
        var localValue = _value;
 | 
			
		||||
        var arrSize = (int)Math.Log(localValue, count) + 1;
 | 
			
		||||
        Span<char> chars = new char[arrSize];
 | 
			
		||||
        while (localValue > 0)
 | 
			
		||||
        {
 | 
			
		||||
            localValue = Math.DivRem(localValue, count, out var rem);
 | 
			
		||||
            chars[--arrSize] = ValidCharacters[(int)rem];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new string(chars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override bool Equals(object obj)
 | 
			
		||||
        => obj is kwum kw && kw == this;
 | 
			
		||||
 | 
			
		||||
    public bool Equals(kwum other)
 | 
			
		||||
        => other == this;
 | 
			
		||||
 | 
			
		||||
    public override int GetHashCode()
 | 
			
		||||
    {
 | 
			
		||||
        return _value.GetHashCode();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
using CommandLine;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public class LbOpts : INadekoCommandOptions
 | 
			
		||||
    {
 | 
			
		||||
        [Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
 | 
			
		||||
        public bool Clean { get; set; }
 | 
			
		||||
        public void NormalizeOptions()
 | 
			
		||||
        {
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class LbOpts : INadekoCommandOptions
 | 
			
		||||
{
 | 
			
		||||
    [Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
 | 
			
		||||
    public bool Clean { get; set; }
 | 
			
		||||
    public void NormalizeOptions()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,57 +1,54 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using Discord.Net;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class LoginErrorHandler
 | 
			
		||||
{
 | 
			
		||||
    public class LoginErrorHandler
 | 
			
		||||
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
    public static void Handle(Exception ex)
 | 
			
		||||
    {
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void Handle(Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
 | 
			
		||||
        }
 | 
			
		||||
        Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void Handle(HttpException ex)
 | 
			
		||||
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
    public static void Handle(HttpException ex)
 | 
			
		||||
    {
 | 
			
		||||
        switch (ex.HttpCode)
 | 
			
		||||
        {
 | 
			
		||||
            switch (ex.HttpCode)
 | 
			
		||||
            {
 | 
			
		||||
                case HttpStatusCode.Unauthorized:
 | 
			
		||||
                    Log.Error("Your bot token is wrong.\n" +
 | 
			
		||||
                        "You can find the bot token under the Bot tab in the developer page.\n" +
 | 
			
		||||
                        "Fix your token in the credentials file and restart the bot");
 | 
			
		||||
                    break;
 | 
			
		||||
            case HttpStatusCode.Unauthorized:
 | 
			
		||||
                Log.Error("Your bot token is wrong.\n" +
 | 
			
		||||
                          "You can find the bot token under the Bot tab in the developer page.\n" +
 | 
			
		||||
                          "Fix your token in the credentials file and restart the bot");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case HttpStatusCode.BadRequest:
 | 
			
		||||
                    Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
 | 
			
		||||
                        "Use the JSON Guide as reference to fix it and restart the bot.");
 | 
			
		||||
                    Log.Error("If you are on Linux, make sure Redis is installed and running");
 | 
			
		||||
                    break;
 | 
			
		||||
            case HttpStatusCode.BadRequest:
 | 
			
		||||
                Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
 | 
			
		||||
                          "Use the JSON Guide as reference to fix it and restart the bot.");
 | 
			
		||||
                Log.Error("If you are on Linux, make sure Redis is installed and running");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case HttpStatusCode.RequestTimeout:
 | 
			
		||||
                    Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
 | 
			
		||||
                        "from connecting to the internet");
 | 
			
		||||
                    break;
 | 
			
		||||
            case HttpStatusCode.RequestTimeout:
 | 
			
		||||
                Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
 | 
			
		||||
                          "from connecting to the internet");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case HttpStatusCode.ServiceUnavailable:
 | 
			
		||||
                case HttpStatusCode.InternalServerError:
 | 
			
		||||
                    Log.Error("Discord is having internal issues. Please, try again later");
 | 
			
		||||
                    break;
 | 
			
		||||
            case HttpStatusCode.ServiceUnavailable:
 | 
			
		||||
            case HttpStatusCode.InternalServerError:
 | 
			
		||||
                Log.Error("Discord is having internal issues. Please, try again later");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case HttpStatusCode.TooManyRequests:
 | 
			
		||||
                    Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
 | 
			
		||||
                        "Global ratelimits usually last for an hour");
 | 
			
		||||
                    break;
 | 
			
		||||
            case HttpStatusCode.TooManyRequests:
 | 
			
		||||
                Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
 | 
			
		||||
                          "Global ratelimits usually last for an hour");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    Log.Warning("An error occurred while attempting to connect to Discord");
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Log.Fatal(ex.ToString());
 | 
			
		||||
            default:
 | 
			
		||||
                Log.Warning("An error occurred while attempting to connect to Discord");
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log.Fatal(ex.ToString());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Implemented by modules which block execution before anything is executed
 | 
			
		||||
/// </summary>
 | 
			
		||||
public interface IEarlyBehavior
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Implemented by modules which block execution before anything is executed
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface IEarlyBehavior
 | 
			
		||||
    {
 | 
			
		||||
        int Priority { get; }
 | 
			
		||||
        Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    int Priority { get; }
 | 
			
		||||
    Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
public interface IInputTransformer
 | 
			
		||||
{
 | 
			
		||||
    public interface IInputTransformer
 | 
			
		||||
    {
 | 
			
		||||
        Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,11 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
public interface ILateBlocker
 | 
			
		||||
{
 | 
			
		||||
    public interface ILateBlocker
 | 
			
		||||
    {
 | 
			
		||||
        public int Priority { get; }
 | 
			
		||||
    public int Priority { get; }
 | 
			
		||||
        
 | 
			
		||||
        Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Last thing to be executed, won't stop further executions
 | 
			
		||||
/// </summary>
 | 
			
		||||
public interface ILateExecutor
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Last thing to be executed, won't stop further executions
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface ILateExecutor
 | 
			
		||||
    {
 | 
			
		||||
        Task LateExecute(IGuild guild, IUserMessage msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    Task LateExecute(IGuild guild, IUserMessage msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -2,33 +2,32 @@
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
public struct ModuleBehaviorResult
 | 
			
		||||
{
 | 
			
		||||
    public struct ModuleBehaviorResult
 | 
			
		||||
    public bool Blocked { get; set; }
 | 
			
		||||
    public string NewInput { get; set; }
 | 
			
		||||
 | 
			
		||||
    public static ModuleBehaviorResult None() => new ModuleBehaviorResult
 | 
			
		||||
    {
 | 
			
		||||
        public bool Blocked { get; set; }
 | 
			
		||||
        public string NewInput { get; set; }
 | 
			
		||||
        Blocked = false,
 | 
			
		||||
        NewInput = null,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
        public static ModuleBehaviorResult None() => new ModuleBehaviorResult
 | 
			
		||||
        {
 | 
			
		||||
            Blocked = false,
 | 
			
		||||
            NewInput = null,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        public static ModuleBehaviorResult FromBlocked(bool blocked) => new ModuleBehaviorResult
 | 
			
		||||
        {
 | 
			
		||||
            Blocked = blocked,
 | 
			
		||||
            NewInput = null,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface IModuleBehavior
 | 
			
		||||
    public static ModuleBehaviorResult FromBlocked(bool blocked) => new ModuleBehaviorResult
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Negative priority means it will try to apply as early as possible
 | 
			
		||||
        /// Positive priority menas it will try to apply as late as possible
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        int Priority { get; }
 | 
			
		||||
        Task<ModuleBehaviorResult> ApplyBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg);
 | 
			
		||||
    }
 | 
			
		||||
        Blocked = blocked,
 | 
			
		||||
        NewInput = null,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public interface IModuleBehavior
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Negative priority means it will try to apply as early as possible
 | 
			
		||||
    /// Positive priority menas it will try to apply as late as possible
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    int Priority { get; }
 | 
			
		||||
    Task<ModuleBehaviorResult> ApplyBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,15 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors
 | 
			
		||||
namespace NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// All services which need to execute something after
 | 
			
		||||
/// the bot is ready should implement this interface 
 | 
			
		||||
/// </summary>
 | 
			
		||||
public interface IReadyExecutor
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// All services which need to execute something after
 | 
			
		||||
    /// the bot is ready should implement this interface 
 | 
			
		||||
    /// Executed when bot is ready
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface IReadyExecutor
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Executed when bot is ready
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Task OnReadyAsync();
 | 
			
		||||
    }
 | 
			
		||||
    public Task OnReadyAsync();
 | 
			
		||||
}
 | 
			
		||||
@@ -6,152 +6,151 @@ using NadekoBot.Extensions;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules
 | 
			
		||||
namespace NadekoBot.Modules;
 | 
			
		||||
 | 
			
		||||
public abstract class NadekoModule : ModuleBase
 | 
			
		||||
{
 | 
			
		||||
    public abstract class NadekoModule : ModuleBase
 | 
			
		||||
    protected CultureInfo _cultureInfo { get; set; }
 | 
			
		||||
    public IBotStrings Strings { get; set; }
 | 
			
		||||
    public CommandHandler CmdHandler { get; set; }
 | 
			
		||||
    public ILocalization Localization { get; set; }
 | 
			
		||||
    public IEmbedBuilderService _eb { get; set; }
 | 
			
		||||
 | 
			
		||||
    public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
 | 
			
		||||
 | 
			
		||||
    protected ICommandContext ctx => Context;
 | 
			
		||||
 | 
			
		||||
    protected NadekoModule()
 | 
			
		||||
    {
 | 
			
		||||
        protected CultureInfo _cultureInfo { get; set; }
 | 
			
		||||
        public IBotStrings Strings { get; set; }
 | 
			
		||||
        public CommandHandler CmdHandler { get; set; }
 | 
			
		||||
        public ILocalization Localization { get; set; }
 | 
			
		||||
        public IEmbedBuilderService _eb { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
 | 
			
		||||
    protected override void BeforeExecute(CommandInfo cmd)
 | 
			
		||||
    {
 | 
			
		||||
        _cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    protected string GetText(in LocStr data) =>
 | 
			
		||||
        Strings.GetText(data, _cultureInfo);
 | 
			
		||||
 | 
			
		||||
        protected ICommandContext ctx => Context;
 | 
			
		||||
    public Task<IUserMessage> SendErrorAsync(string error)
 | 
			
		||||
        => ctx.Channel.SendErrorAsync(_eb, error);
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
 | 
			
		||||
        => ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> SendConfirmAsync(string text)
 | 
			
		||||
        => ctx.Channel.SendConfirmAsync(_eb, text);
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
 | 
			
		||||
        => ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> SendPendingAsync(string text)
 | 
			
		||||
        => ctx.Channel.SendPendingAsync(_eb, text);
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> ErrorLocalizedAsync(LocStr str) 
 | 
			
		||||
        => SendErrorAsync(GetText(str));
 | 
			
		||||
 | 
			
		||||
        protected NadekoModule()
 | 
			
		||||
    public Task<IUserMessage> PendingLocalizedAsync(LocStr str) 
 | 
			
		||||
        => SendPendingAsync(GetText(str));
 | 
			
		||||
        
 | 
			
		||||
    public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str) 
 | 
			
		||||
        => SendConfirmAsync(GetText(str));
 | 
			
		||||
 | 
			
		||||
    public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str) 
 | 
			
		||||
        => SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
    public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str) 
 | 
			
		||||
        => SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
    public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
 | 
			
		||||
        => SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
 | 
			
		||||
    {
 | 
			
		||||
        embed
 | 
			
		||||
            .WithPendingColor()
 | 
			
		||||
            .WithFooter("yes/no");
 | 
			
		||||
 | 
			
		||||
        var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
            input = input?.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
            if (input != "YES" && input != "Y")
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() => msg.DeleteAsync());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
 | 
			
		||||
    public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
 | 
			
		||||
    {
 | 
			
		||||
        var userInputTask = new TaskCompletionSource<string>();
 | 
			
		||||
        var dsc = (DiscordSocketClient)ctx.Client;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            dsc.MessageReceived += MessageReceived;
 | 
			
		||||
 | 
			
		||||
            if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false)) != userInputTask.Task)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await userInputTask.Task.ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            dsc.MessageReceived -= MessageReceived;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void BeforeExecute(CommandInfo cmd)
 | 
			
		||||
        Task MessageReceived(SocketMessage arg)
 | 
			
		||||
        {
 | 
			
		||||
            _cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        protected string GetText(in LocStr data) =>
 | 
			
		||||
            Strings.GetText(data, _cultureInfo);
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> SendErrorAsync(string error)
 | 
			
		||||
            => ctx.Channel.SendErrorAsync(_eb, error);
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
 | 
			
		||||
            => ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> SendConfirmAsync(string text)
 | 
			
		||||
            => ctx.Channel.SendConfirmAsync(_eb, text);
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
 | 
			
		||||
            => ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> SendPendingAsync(string text)
 | 
			
		||||
            => ctx.Channel.SendPendingAsync(_eb, text);
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> ErrorLocalizedAsync(LocStr str) 
 | 
			
		||||
            => SendErrorAsync(GetText(str));
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> PendingLocalizedAsync(LocStr str) 
 | 
			
		||||
            => SendPendingAsync(GetText(str));
 | 
			
		||||
        
 | 
			
		||||
        public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str) 
 | 
			
		||||
            => SendConfirmAsync(GetText(str));
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str) 
 | 
			
		||||
            => SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str) 
 | 
			
		||||
            => SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
 | 
			
		||||
            => SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
 | 
			
		||||
        {
 | 
			
		||||
            embed
 | 
			
		||||
                .WithPendingColor()
 | 
			
		||||
                .WithFooter("yes/no");
 | 
			
		||||
 | 
			
		||||
            var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            try
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
                input = input?.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
                if (input != "YES" && input != "Y")
 | 
			
		||||
                if (!(arg is SocketUserMessage userMsg) ||
 | 
			
		||||
                    !(userMsg.Channel is ITextChannel chan) ||
 | 
			
		||||
                    userMsg.Author.Id != userId ||
 | 
			
		||||
                    userMsg.Channel.Id != channelId)
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(() => msg.DeleteAsync());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
 | 
			
		||||
        public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
 | 
			
		||||
        {
 | 
			
		||||
            var userInputTask = new TaskCompletionSource<string>();
 | 
			
		||||
            var dsc = (DiscordSocketClient)ctx.Client;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                dsc.MessageReceived += MessageReceived;
 | 
			
		||||
 | 
			
		||||
                if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false)) != userInputTask.Task)
 | 
			
		||||
                {
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return await userInputTask.Task.ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                dsc.MessageReceived -= MessageReceived;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task MessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(() =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (!(arg is SocketUserMessage userMsg) ||
 | 
			
		||||
                        !(userMsg.Channel is ITextChannel chan) ||
 | 
			
		||||
                        userMsg.Author.Id != userId ||
 | 
			
		||||
                        userMsg.Channel.Id != channelId)
 | 
			
		||||
                    {
 | 
			
		||||
                        return Task.CompletedTask;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (userInputTask.TrySetResult(arg.Content))
 | 
			
		||||
                    {
 | 
			
		||||
                        userMsg.DeleteAfter(1);
 | 
			
		||||
                    }
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
                });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (userInputTask.TrySetResult(arg.Content))
 | 
			
		||||
                {
 | 
			
		||||
                    userMsg.DeleteAfter(1);
 | 
			
		||||
                }
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public abstract class NadekoModule<TService> : NadekoModule
 | 
			
		||||
public abstract class NadekoModule<TService> : NadekoModule
 | 
			
		||||
{
 | 
			
		||||
    public TService _service { get; set; }
 | 
			
		||||
 | 
			
		||||
    protected NadekoModule() : base()
 | 
			
		||||
    {
 | 
			
		||||
        public TService _service { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected NadekoModule() : base()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public abstract class NadekoSubmodule : NadekoModule
 | 
			
		||||
    {
 | 
			
		||||
        protected NadekoSubmodule() : base() { }
 | 
			
		||||
    }
 | 
			
		||||
public abstract class NadekoSubmodule : NadekoModule
 | 
			
		||||
{
 | 
			
		||||
    protected NadekoSubmodule() : base() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
 | 
			
		||||
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
 | 
			
		||||
{
 | 
			
		||||
    protected NadekoSubmodule() : base()
 | 
			
		||||
    {
 | 
			
		||||
        protected NadekoSubmodule() : base()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Modules
 | 
			
		||||
namespace NadekoBot.Modules;
 | 
			
		||||
 | 
			
		||||
public static class NadekoModuleExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class NadekoModuleExtensions
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,74 +1,72 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class NadekoRandom : Random
 | 
			
		||||
{
 | 
			
		||||
    public class NadekoRandom : Random
 | 
			
		||||
    readonly RandomNumberGenerator _rng;
 | 
			
		||||
 | 
			
		||||
    public NadekoRandom() : base()
 | 
			
		||||
    {
 | 
			
		||||
        readonly RandomNumberGenerator _rng;
 | 
			
		||||
 | 
			
		||||
        public NadekoRandom() : base()
 | 
			
		||||
        {
 | 
			
		||||
            _rng = RandomNumberGenerator.Create();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int Next()
 | 
			
		||||
        {
 | 
			
		||||
            var bytes = new byte[sizeof(int)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            return Math.Abs(BitConverter.ToInt32(bytes, 0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int Next(int maxValue)
 | 
			
		||||
        {
 | 
			
		||||
            if (maxValue <= 0)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
            var bytes = new byte[sizeof(int)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int Next(int minValue, int maxValue)
 | 
			
		||||
        {
 | 
			
		||||
            if (minValue > maxValue)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
            if (minValue == maxValue)
 | 
			
		||||
                return minValue;
 | 
			
		||||
            var bytes = new byte[sizeof(int)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
 | 
			
		||||
            return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public long NextLong(long minValue, long maxValue)
 | 
			
		||||
        {
 | 
			
		||||
            if (minValue > maxValue)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
            if (minValue == maxValue)
 | 
			
		||||
                return minValue;
 | 
			
		||||
            var bytes = new byte[sizeof(long)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
 | 
			
		||||
            return (sign * BitConverter.ToInt64(bytes, 0)) % (maxValue - minValue) + minValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void NextBytes(byte[] buffer)
 | 
			
		||||
        {
 | 
			
		||||
            _rng.GetBytes(buffer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override double Sample()
 | 
			
		||||
        {
 | 
			
		||||
            var bytes = new byte[sizeof(double)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override double NextDouble()
 | 
			
		||||
        {
 | 
			
		||||
            var bytes = new byte[sizeof(double)];
 | 
			
		||||
            _rng.GetBytes(bytes);
 | 
			
		||||
            return BitConverter.ToDouble(bytes, 0);
 | 
			
		||||
        }
 | 
			
		||||
        _rng = RandomNumberGenerator.Create();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public override int Next()
 | 
			
		||||
    {
 | 
			
		||||
        var bytes = new byte[sizeof(int)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        return Math.Abs(BitConverter.ToInt32(bytes, 0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override int Next(int maxValue)
 | 
			
		||||
    {
 | 
			
		||||
        if (maxValue <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
        var bytes = new byte[sizeof(int)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override int Next(int minValue, int maxValue)
 | 
			
		||||
    {
 | 
			
		||||
        if (minValue > maxValue)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
        if (minValue == maxValue)
 | 
			
		||||
            return minValue;
 | 
			
		||||
        var bytes = new byte[sizeof(int)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
 | 
			
		||||
        return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long NextLong(long minValue, long maxValue)
 | 
			
		||||
    {
 | 
			
		||||
        if (minValue > maxValue)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(maxValue));
 | 
			
		||||
        if (minValue == maxValue)
 | 
			
		||||
            return minValue;
 | 
			
		||||
        var bytes = new byte[sizeof(long)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
 | 
			
		||||
        return (sign * BitConverter.ToInt64(bytes, 0)) % (maxValue - minValue) + minValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override void NextBytes(byte[] buffer)
 | 
			
		||||
    {
 | 
			
		||||
        _rng.GetBytes(buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override double Sample()
 | 
			
		||||
    {
 | 
			
		||||
        var bytes = new byte[sizeof(double)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override double NextDouble()
 | 
			
		||||
    {
 | 
			
		||||
        var bytes = new byte[sizeof(double)];
 | 
			
		||||
        _rng.GetBytes(bytes);
 | 
			
		||||
        return BitConverter.ToDouble(bytes, 0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,17 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
 | 
			
		||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
 | 
			
		||||
{
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
 | 
			
		||||
    public sealed class NoPublicBotAttribute : PreconditionAttribute
 | 
			
		||||
    public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
#if GLOBAL_NADEKO
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
 | 
			
		||||
#else
 | 
			
		||||
            return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
        return Task.FromResult(PreconditionResult.FromSuccess());
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,49 +1,46 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
public class OldImageUrls
 | 
			
		||||
{
 | 
			
		||||
    public class OldImageUrls
 | 
			
		||||
    public int Version { get; set; } = 2;
 | 
			
		||||
 | 
			
		||||
    public CoinData Coins { get; set; }
 | 
			
		||||
    public Uri[] Currency { get; set; }
 | 
			
		||||
    public Uri[] Dice { get; set; }
 | 
			
		||||
    public RategirlData Rategirl { get; set; }
 | 
			
		||||
    public XpData Xp { get; set; }
 | 
			
		||||
 | 
			
		||||
    //new
 | 
			
		||||
    public RipData Rip { get; set; }
 | 
			
		||||
    public SlotData Slots { get; set; }
 | 
			
		||||
 | 
			
		||||
    public class RipData
 | 
			
		||||
    {
 | 
			
		||||
        public int Version { get; set; } = 2;
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
        public Uri Overlay { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public CoinData Coins { get; set; }
 | 
			
		||||
        public Uri[] Currency { get; set; }
 | 
			
		||||
        public Uri[] Dice { get; set; }
 | 
			
		||||
        public RategirlData Rategirl { get; set; }
 | 
			
		||||
        public XpData Xp { get; set; }
 | 
			
		||||
    public class SlotData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri[] Emojis { get; set; }
 | 
			
		||||
        public Uri[] Numbers { get; set; }
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        //new
 | 
			
		||||
        public RipData Rip { get; set; }
 | 
			
		||||
        public SlotData Slots { get; set; }
 | 
			
		||||
    public class CoinData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri[] Heads { get; set; }
 | 
			
		||||
        public Uri[] Tails { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public class RipData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
            public Uri Overlay { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    public class RategirlData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri Matrix { get; set; }
 | 
			
		||||
        public Uri Dot { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public class SlotData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Emojis { get; set; }
 | 
			
		||||
            public Uri[] Numbers { get; set; }
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class CoinData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Heads { get; set; }
 | 
			
		||||
            public Uri[] Tails { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class RategirlData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Matrix { get; set; }
 | 
			
		||||
            public Uri Dot { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class XpData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    public class XpData
 | 
			
		||||
    {
 | 
			
		||||
        public Uri Bg { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +1,23 @@
 | 
			
		||||
using CommandLine;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public static class OptionsParser
 | 
			
		||||
    {
 | 
			
		||||
        public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
 | 
			
		||||
            => ParseFrom(new T(), args).Item1;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
        public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
 | 
			
		||||
public static class OptionsParser
 | 
			
		||||
{
 | 
			
		||||
    public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
 | 
			
		||||
        => ParseFrom(new T(), args).Item1;
 | 
			
		||||
 | 
			
		||||
    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 =>
 | 
			
		||||
             {
 | 
			
		||||
                 x.HelpWriter = null;
 | 
			
		||||
             }))
 | 
			
		||||
            {
 | 
			
		||||
                var res = p.ParseArguments<T>(args);
 | 
			
		||||
                options = res.MapResult(x => x, x => options);
 | 
			
		||||
                options.NormalizeOptions();
 | 
			
		||||
                return (options, res.Tag == ParserResultType.Parsed);
 | 
			
		||||
            }
 | 
			
		||||
            var res = p.ParseArguments<T>(args);
 | 
			
		||||
            options = res.MapResult(x => x, x => options);
 | 
			
		||||
            options.NormalizeOptions();
 | 
			
		||||
            return (options, res.Tag == ParserResultType.Parsed);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class OsuMapData
 | 
			
		||||
{
 | 
			
		||||
    public class OsuMapData
 | 
			
		||||
    {
 | 
			
		||||
        public string Title { get; set; }
 | 
			
		||||
        public string Artist { get; set; }
 | 
			
		||||
        public string Version { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    public string Title { get; set; }
 | 
			
		||||
    public string Artist { get; set; }
 | 
			
		||||
    public string Version { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,41 +1,40 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class OsuUserBests
 | 
			
		||||
{
 | 
			
		||||
    public class OsuUserBests
 | 
			
		||||
    {
 | 
			
		||||
        [JsonProperty("beatmap_id")] public string BeatmapId { get; set; }
 | 
			
		||||
    [JsonProperty("beatmap_id")] public string BeatmapId { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("score_id")] public string ScoreId { get; set; }
 | 
			
		||||
    [JsonProperty("score_id")] public string ScoreId { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("score")] public string Score { get; set; }
 | 
			
		||||
    [JsonProperty("score")] public string Score { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("maxcombo")] public string Maxcombo { get; set; }
 | 
			
		||||
    [JsonProperty("maxcombo")] public string Maxcombo { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("count50")] public double Count50 { get; set; }
 | 
			
		||||
    [JsonProperty("count50")] public double Count50 { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("count100")] public double Count100 { get; set; }
 | 
			
		||||
    [JsonProperty("count100")] public double Count100 { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("count300")] public double Count300 { get; set; }
 | 
			
		||||
    [JsonProperty("count300")] public double Count300 { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("countmiss")] public int Countmiss { get; set; }
 | 
			
		||||
    [JsonProperty("countmiss")] public int Countmiss { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("countkatu")] public double Countkatu { get; set; }
 | 
			
		||||
    [JsonProperty("countkatu")] public double Countkatu { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("countgeki")] public double Countgeki { get; set; }
 | 
			
		||||
    [JsonProperty("countgeki")] public double Countgeki { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("perfect")] public string Perfect { get; set; }
 | 
			
		||||
    [JsonProperty("perfect")] public string Perfect { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("enabled_mods")] public int EnabledMods { get; set; }
 | 
			
		||||
    [JsonProperty("enabled_mods")] public int EnabledMods { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("user_id")] public string UserId { get; set; }
 | 
			
		||||
    [JsonProperty("user_id")] public string UserId { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("date")] public string Date { get; set; }
 | 
			
		||||
    [JsonProperty("date")] public string Date { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("rank")] public string Rank { get; set; }
 | 
			
		||||
    [JsonProperty("rank")] public string Rank { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("pp")] public double Pp { get; set; }
 | 
			
		||||
    [JsonProperty("pp")] public double Pp { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("replay_available")] public string ReplayAvailable { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    [JsonProperty("replay_available")] public string ReplayAvailable { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +1,22 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
public static class PlatformHelper
 | 
			
		||||
{
 | 
			
		||||
    public static class PlatformHelper
 | 
			
		||||
    {
 | 
			
		||||
        private const int ProcessorCountRefreshIntervalMs = 30000;
 | 
			
		||||
    private const int ProcessorCountRefreshIntervalMs = 30000;
 | 
			
		||||
 | 
			
		||||
        private static volatile int _processorCount;
 | 
			
		||||
        private static volatile int _lastProcessorCountRefreshTicks;
 | 
			
		||||
    private static volatile int _processorCount;
 | 
			
		||||
    private static volatile int _lastProcessorCountRefreshTicks;
 | 
			
		||||
 | 
			
		||||
        public static int ProcessorCount {
 | 
			
		||||
            get {
 | 
			
		||||
                var now = Environment.TickCount;
 | 
			
		||||
                if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
 | 
			
		||||
                {
 | 
			
		||||
                    _processorCount = Environment.ProcessorCount;
 | 
			
		||||
                    _lastProcessorCountRefreshTicks = now;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return _processorCount;
 | 
			
		||||
    public static int ProcessorCount {
 | 
			
		||||
        get {
 | 
			
		||||
            var now = Environment.TickCount;
 | 
			
		||||
            if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
 | 
			
		||||
            {
 | 
			
		||||
                _processorCount = Environment.ProcessorCount;
 | 
			
		||||
                _lastProcessorCountRefreshTicks = now;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return _processorCount;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
namespace NadekoBot.Common.Pokemon
 | 
			
		||||
namespace NadekoBot.Common.Pokemon;
 | 
			
		||||
 | 
			
		||||
public class PokemonNameId
 | 
			
		||||
{
 | 
			
		||||
    public class PokemonNameId
 | 
			
		||||
    {
 | 
			
		||||
        public int Id { get; set; }
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    public int Id { get; set; }
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +1,38 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Pokemon
 | 
			
		||||
namespace NadekoBot.Common.Pokemon;
 | 
			
		||||
 | 
			
		||||
public class SearchPokemon
 | 
			
		||||
{
 | 
			
		||||
    public class SearchPokemon
 | 
			
		||||
    public class GenderRatioClass
 | 
			
		||||
    {
 | 
			
		||||
        public class GenderRatioClass
 | 
			
		||||
        {
 | 
			
		||||
            public float M { get; set; }
 | 
			
		||||
            public float F { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class BaseStatsClass
 | 
			
		||||
        {
 | 
			
		||||
            public int HP { get; set; }
 | 
			
		||||
            public int ATK { get; set; }
 | 
			
		||||
            public int DEF { get; set; }
 | 
			
		||||
            public int SPA { get; set; }
 | 
			
		||||
            public int SPD { get; set; }
 | 
			
		||||
            public int SPE { get; set; }
 | 
			
		||||
 | 
			
		||||
            public override string ToString() => $@"💚**HP:**  {HP,-4} ⚔**ATK:** {ATK,-4} 🛡**DEF:** {DEF,-4}
 | 
			
		||||
✨**SPA:** {SPA,-4} 🎇**SPD:** {SPD,-4} 💨**SPE:** {SPE,-4}";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("num")]
 | 
			
		||||
        public int Id { get; set; }
 | 
			
		||||
        public string Species { get; set; }
 | 
			
		||||
        public string[] Types { get; set; }
 | 
			
		||||
        public GenderRatioClass GenderRatio { get; set; }
 | 
			
		||||
        public BaseStatsClass BaseStats { get; set; }
 | 
			
		||||
        public Dictionary<string, string> Abilities { get; set; }
 | 
			
		||||
        public float HeightM { get; set; }
 | 
			
		||||
        public float WeightKg { get; set; }
 | 
			
		||||
        public string Color { get; set; }
 | 
			
		||||
        public string[] Evos { get; set; }
 | 
			
		||||
        public string[] EggGroups { get; set; }
 | 
			
		||||
        public float M { get; set; }
 | 
			
		||||
        public float F { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public class BaseStatsClass
 | 
			
		||||
    {
 | 
			
		||||
        public int HP { get; set; }
 | 
			
		||||
        public int ATK { get; set; }
 | 
			
		||||
        public int DEF { get; set; }
 | 
			
		||||
        public int SPA { get; set; }
 | 
			
		||||
        public int SPD { get; set; }
 | 
			
		||||
        public int SPE { get; set; }
 | 
			
		||||
 | 
			
		||||
        public override string ToString() => $@"💚**HP:**  {HP,-4} ⚔**ATK:** {ATK,-4} 🛡**DEF:** {DEF,-4}
 | 
			
		||||
✨**SPA:** {SPA,-4} 🎇**SPD:** {SPD,-4} 💨**SPE:** {SPE,-4}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [JsonProperty("num")]
 | 
			
		||||
    public int Id { get; set; }
 | 
			
		||||
    public string Species { get; set; }
 | 
			
		||||
    public string[] Types { get; set; }
 | 
			
		||||
    public GenderRatioClass GenderRatio { get; set; }
 | 
			
		||||
    public BaseStatsClass BaseStats { get; set; }
 | 
			
		||||
    public Dictionary<string, string> Abilities { get; set; }
 | 
			
		||||
    public float HeightM { get; set; }
 | 
			
		||||
    public float WeightKg { get; set; }
 | 
			
		||||
    public string Color { get; set; }
 | 
			
		||||
    public string[] Evos { get; set; }
 | 
			
		||||
    public string[] EggGroups { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
namespace NadekoBot.Common.Pokemon
 | 
			
		||||
namespace NadekoBot.Common.Pokemon;
 | 
			
		||||
 | 
			
		||||
public class SearchPokemonAbility
 | 
			
		||||
{
 | 
			
		||||
    public class SearchPokemonAbility
 | 
			
		||||
    {
 | 
			
		||||
        public string Desc { get; set; }
 | 
			
		||||
        public string ShortDesc { get; set; }
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public float Rating { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    public string Desc { get; set; }
 | 
			
		||||
    public string ShortDesc { get; set; }
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public float Rating { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,95 +1,90 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class EventPubSub : IPubSub
 | 
			
		||||
{
 | 
			
		||||
    public class EventPubSub : IPubSub
 | 
			
		||||
    private readonly Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>> _actions
 | 
			
		||||
        = new Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>>();
 | 
			
		||||
    private readonly object locker = new object();
 | 
			
		||||
        
 | 
			
		||||
    public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>> _actions
 | 
			
		||||
            = new Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>>();
 | 
			
		||||
        private readonly object locker = new object();
 | 
			
		||||
        
 | 
			
		||||
        public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
        Func<object, ValueTask> localAction = obj => action((TData) obj);
 | 
			
		||||
        lock(locker)
 | 
			
		||||
        {
 | 
			
		||||
            Func<object, ValueTask> localAction = obj => action((TData) obj);
 | 
			
		||||
            lock(locker)
 | 
			
		||||
            Dictionary<Delegate, List<Func<object, ValueTask>>> keyActions;
 | 
			
		||||
            if (!_actions.TryGetValue(key.Key, out keyActions))
 | 
			
		||||
            {
 | 
			
		||||
                Dictionary<Delegate, List<Func<object, ValueTask>>> keyActions;
 | 
			
		||||
                if (!_actions.TryGetValue(key.Key, out keyActions))
 | 
			
		||||
                {
 | 
			
		||||
                    keyActions = new Dictionary<Delegate, List<Func<object, ValueTask>>>();
 | 
			
		||||
                    _actions[key.Key] = keyActions;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                List<Func<object, ValueTask>> sameActions;
 | 
			
		||||
                if (!keyActions.TryGetValue(action, out sameActions))
 | 
			
		||||
                {
 | 
			
		||||
                    sameActions = new List<Func<object, ValueTask>>();
 | 
			
		||||
                    keyActions[action] = sameActions;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sameActions.Add(localAction);
 | 
			
		||||
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
                keyActions = new Dictionary<Delegate, List<Func<object, ValueTask>>>();
 | 
			
		||||
                _actions[key.Key] = keyActions;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Task Pub<TData>(in TypedKey<TData> key, TData data)
 | 
			
		||||
        {
 | 
			
		||||
            lock (locker)
 | 
			
		||||
            {
 | 
			
		||||
                if(_actions.TryGetValue(key.Key, out var actions))
 | 
			
		||||
                {
 | 
			
		||||
                    // if this class ever gets used, this needs to be properly implemented
 | 
			
		||||
                    // 1. ignore all valuetasks which are completed
 | 
			
		||||
                    // 2. return task.whenall all other tasks
 | 
			
		||||
                    return Task.WhenAll(actions
 | 
			
		||||
                        .SelectMany(kvp => kvp.Value)
 | 
			
		||||
                        .Select(action => action(data).AsTask()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            List<Func<object, ValueTask>> sameActions;
 | 
			
		||||
            if (!keyActions.TryGetValue(action, out sameActions))
 | 
			
		||||
            {
 | 
			
		||||
                sameActions = new List<Func<object, ValueTask>>();
 | 
			
		||||
                keyActions[action] = sameActions;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task Unsub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
            sameActions.Add(localAction);
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public Task Pub<TData>(in TypedKey<TData> key, TData data)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        {
 | 
			
		||||
            lock (locker)
 | 
			
		||||
            if(_actions.TryGetValue(key.Key, out var actions))
 | 
			
		||||
            {
 | 
			
		||||
                // get subscriptions for this action
 | 
			
		||||
                if (_actions.TryGetValue(key.Key, out var actions))
 | 
			
		||||
                // if this class ever gets used, this needs to be properly implemented
 | 
			
		||||
                // 1. ignore all valuetasks which are completed
 | 
			
		||||
                // 2. return task.whenall all other tasks
 | 
			
		||||
                return Task.WhenAll(actions
 | 
			
		||||
                    .SelectMany(kvp => kvp.Value)
 | 
			
		||||
                    .Select(action => action(data).AsTask()));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task Unsub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        {
 | 
			
		||||
            // get subscriptions for this action
 | 
			
		||||
            if (_actions.TryGetValue(key.Key, out var actions))
 | 
			
		||||
            {
 | 
			
		||||
                var hashCode = action.GetHashCode();
 | 
			
		||||
                // get subscriptions which have the same action hash code
 | 
			
		||||
                // note: having this as a list allows for multiple subscriptions of
 | 
			
		||||
                //       the same insance's/static method
 | 
			
		||||
                if (actions.TryGetValue(action, out var sameActions))
 | 
			
		||||
                {
 | 
			
		||||
                    var hashCode = action.GetHashCode();
 | 
			
		||||
                    // get subscriptions which have the same action hash code
 | 
			
		||||
                    // note: having this as a list allows for multiple subscriptions of
 | 
			
		||||
                    //       the same insance's/static method
 | 
			
		||||
                    if (actions.TryGetValue(action, out var sameActions))
 | 
			
		||||
                    {
 | 
			
		||||
                        // remove last subscription
 | 
			
		||||
                        sameActions.RemoveAt(sameActions.Count - 1);
 | 
			
		||||
                    // remove last subscription
 | 
			
		||||
                    sameActions.RemoveAt(sameActions.Count - 1);
 | 
			
		||||
                        
 | 
			
		||||
                        // if the last subscription was the only subscription
 | 
			
		||||
                        // we can safely remove this action's dictionary entry
 | 
			
		||||
                        if (sameActions.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            actions.Remove(action);
 | 
			
		||||
                    // if the last subscription was the only subscription
 | 
			
		||||
                    // we can safely remove this action's dictionary entry
 | 
			
		||||
                    if (sameActions.Count == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        actions.Remove(action);
 | 
			
		||||
                            
 | 
			
		||||
                            // if our dictionary has no more elements after 
 | 
			
		||||
                            // removing the entry
 | 
			
		||||
                            // it's safe to remove it from the key's subscriptions
 | 
			
		||||
                            if (actions.Count == 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                _actions.Remove(key.Key);
 | 
			
		||||
                            }
 | 
			
		||||
                        // if our dictionary has no more elements after 
 | 
			
		||||
                        // removing the entry
 | 
			
		||||
                        // it's safe to remove it from the key's subscriptions
 | 
			
		||||
                        if (actions.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            _actions.Remove(key.Key);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public interface IPubSub
 | 
			
		||||
{
 | 
			
		||||
    public interface IPubSub
 | 
			
		||||
    {
 | 
			
		||||
        public Task Pub<TData>(in TypedKey<TData> key, TData data);
 | 
			
		||||
        public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action);
 | 
			
		||||
    }
 | 
			
		||||
    public Task Pub<TData>(in TypedKey<TData> key, TData data);
 | 
			
		||||
    public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public interface ISeria
 | 
			
		||||
{
 | 
			
		||||
    public interface ISeria
 | 
			
		||||
    {
 | 
			
		||||
        byte[] Serialize<T>(T data);
 | 
			
		||||
        T Deserialize<T>(byte[] data);
 | 
			
		||||
    }
 | 
			
		||||
    byte[] Serialize<T>(T data);
 | 
			
		||||
    T Deserialize<T>(byte[] data);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +1,27 @@
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using NadekoBot.Common.JsonConverters;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public class JsonSeria : ISeria
 | 
			
		||||
    {
 | 
			
		||||
        private JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
 | 
			
		||||
        {
 | 
			
		||||
            Converters =
 | 
			
		||||
            {
 | 
			
		||||
                new Rgba32Converter(),
 | 
			
		||||
                new CultureInfoConverter(),
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        public byte[] Serialize<T>(T data) 
 | 
			
		||||
            => JsonSerializer.SerializeToUtf8Bytes(data, serializerOptions);
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
        public T Deserialize<T>(byte[] data)
 | 
			
		||||
public class JsonSeria : ISeria
 | 
			
		||||
{
 | 
			
		||||
    private JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
 | 
			
		||||
    {
 | 
			
		||||
        Converters =
 | 
			
		||||
        {
 | 
			
		||||
            if (data is null)
 | 
			
		||||
                return default;
 | 
			
		||||
            new Rgba32Converter(),
 | 
			
		||||
            new CultureInfoConverter(),
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    public byte[] Serialize<T>(T data) 
 | 
			
		||||
        => JsonSerializer.SerializeToUtf8Bytes(data, serializerOptions);
 | 
			
		||||
 | 
			
		||||
    public T Deserialize<T>(byte[] data)
 | 
			
		||||
    {
 | 
			
		||||
        if (data is null)
 | 
			
		||||
            return default;
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            return JsonSerializer.Deserialize<T>(data, serializerOptions);
 | 
			
		||||
        }
 | 
			
		||||
        return JsonSerializer.Deserialize<T>(data, serializerOptions);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,46 +1,42 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Serilog;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public sealed class RedisPubSub : IPubSub
 | 
			
		||||
{
 | 
			
		||||
    public sealed class RedisPubSub : IPubSub
 | 
			
		||||
    private readonly ConnectionMultiplexer _multi;
 | 
			
		||||
    private readonly ISeria _serializer;
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
    public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ConnectionMultiplexer _multi;
 | 
			
		||||
        private readonly ISeria _serializer;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        _multi = multi;
 | 
			
		||||
        _serializer = serializer;
 | 
			
		||||
        _creds = creds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _multi = multi;
 | 
			
		||||
            _serializer = serializer;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
        }
 | 
			
		||||
    public Task Pub<TData>(in TypedKey<TData> key, TData data)
 | 
			
		||||
    {
 | 
			
		||||
        var serialized = _serializer.Serialize(data);
 | 
			
		||||
        return _multi.GetSubscriber().PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public Task Pub<TData>(in TypedKey<TData> key, TData data)
 | 
			
		||||
    public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
    {
 | 
			
		||||
        var eventName = key.Key;
 | 
			
		||||
        return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", async (ch, data) =>
 | 
			
		||||
        {
 | 
			
		||||
            var serialized = _serializer.Serialize(data);
 | 
			
		||||
            return _multi.GetSubscriber().PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
 | 
			
		||||
        {
 | 
			
		||||
            var eventName = key.Key;
 | 
			
		||||
            return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", async (ch, data) =>
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var dataObj = _serializer.Deserialize<TData>(data);
 | 
			
		||||
                    await action(dataObj);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Error($"Error handling the event {eventName}: {ex.Message}");
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
                var dataObj = _serializer.Deserialize<TData>(data);
 | 
			
		||||
                await action(dataObj);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error($"Error handling the event {eventName}: {ex.Message}");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +1,28 @@
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public readonly struct TypedKey<TData>
 | 
			
		||||
{
 | 
			
		||||
    public readonly struct TypedKey<TData>
 | 
			
		||||
    public readonly string Key;
 | 
			
		||||
 | 
			
		||||
    public TypedKey(in string key)
 | 
			
		||||
    {
 | 
			
		||||
        public readonly string Key;
 | 
			
		||||
 | 
			
		||||
        public TypedKey(in string key)
 | 
			
		||||
        {
 | 
			
		||||
            Key = key;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static implicit operator TypedKey<TData>(in string input)
 | 
			
		||||
            => new TypedKey<TData>(input);
 | 
			
		||||
        public static implicit operator string(in TypedKey<TData> input)
 | 
			
		||||
            => input.Key;
 | 
			
		||||
 | 
			
		||||
        public static bool operator ==(in TypedKey<TData> left, in TypedKey<TData> right)
 | 
			
		||||
            => left.Key == right.Key;
 | 
			
		||||
        public static bool operator !=(in TypedKey<TData> left, in TypedKey<TData> right)
 | 
			
		||||
            => !(left == right);
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
            => obj is TypedKey<TData> o && o == this;
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode() => Key?.GetHashCode() ?? 0;
 | 
			
		||||
 | 
			
		||||
        public override string ToString() => Key;
 | 
			
		||||
        Key = key;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static implicit operator TypedKey<TData>(in string input)
 | 
			
		||||
        => new TypedKey<TData>(input);
 | 
			
		||||
    public static implicit operator string(in TypedKey<TData> input)
 | 
			
		||||
        => input.Key;
 | 
			
		||||
 | 
			
		||||
    public static bool operator ==(in TypedKey<TData> left, in TypedKey<TData> right)
 | 
			
		||||
        => left.Key == right.Key;
 | 
			
		||||
    public static bool operator !=(in TypedKey<TData> left, in TypedKey<TData> right)
 | 
			
		||||
        => !(left == right);
 | 
			
		||||
 | 
			
		||||
    public override bool Equals(object obj)
 | 
			
		||||
        => obj is TypedKey<TData> o && o == this;
 | 
			
		||||
 | 
			
		||||
    public override int GetHashCode() => Key?.GetHashCode() ?? 0;
 | 
			
		||||
 | 
			
		||||
    public override string ToString() => Key;
 | 
			
		||||
}
 | 
			
		||||
@@ -3,36 +3,35 @@ using NadekoBot.Common.Yml;
 | 
			
		||||
using NadekoBot.Common.Configs;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class YamlSeria : IConfigSeria
 | 
			
		||||
{
 | 
			
		||||
    public class YamlSeria : IConfigSeria
 | 
			
		||||
    private readonly ISerializer _serializer;
 | 
			
		||||
    private readonly IDeserializer _deserializer;
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex CodePointRegex
 | 
			
		||||
        = new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
 | 
			
		||||
            RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    public YamlSeria()
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ISerializer _serializer;
 | 
			
		||||
        private readonly IDeserializer _deserializer;
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex CodePointRegex
 | 
			
		||||
            = new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
 | 
			
		||||
                RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        public YamlSeria()
 | 
			
		||||
        {
 | 
			
		||||
            _serializer = Yaml.Serializer;
 | 
			
		||||
            _deserializer = Yaml.Deserializer;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public string Serialize<T>(T obj)
 | 
			
		||||
        {
 | 
			
		||||
            var escapedOutput = _serializer.Serialize(obj);
 | 
			
		||||
            var output = CodePointRegex.Replace(escapedOutput, me =>
 | 
			
		||||
            {
 | 
			
		||||
                var str = me.Groups["code"].Value;
 | 
			
		||||
                var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
 | 
			
		||||
                return newString;
 | 
			
		||||
            });
 | 
			
		||||
            return output;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public T Deserialize<T>(string data) 
 | 
			
		||||
            => _deserializer.Deserialize<T>(data);
 | 
			
		||||
        _serializer = Yaml.Serializer;
 | 
			
		||||
        _deserializer = Yaml.Deserializer;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public string Serialize<T>(T obj)
 | 
			
		||||
    {
 | 
			
		||||
        var escapedOutput = _serializer.Serialize(obj);
 | 
			
		||||
        var output = CodePointRegex.Replace(escapedOutput, me =>
 | 
			
		||||
        {
 | 
			
		||||
            var str = me.Groups["code"].Value;
 | 
			
		||||
            var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
 | 
			
		||||
            return newString;
 | 
			
		||||
        });
 | 
			
		||||
        return output;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public T Deserialize<T>(string data) 
 | 
			
		||||
        => _deserializer.Deserialize<T>(data);
 | 
			
		||||
}
 | 
			
		||||
@@ -3,235 +3,229 @@ using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
using NadekoBot.Modules.Music.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Replacements
 | 
			
		||||
namespace NadekoBot.Common.Replacements;
 | 
			
		||||
 | 
			
		||||
public class ReplacementBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ReplacementBuilder
 | 
			
		||||
    private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
 | 
			
		||||
    private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
 | 
			
		||||
    private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder()
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
 | 
			
		||||
        private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
 | 
			
		||||
        private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
 | 
			
		||||
        WithRngRegex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder()
 | 
			
		||||
    public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, SocketGuild g, DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        return this.WithUser(usr)
 | 
			
		||||
            .WithChannel(ch)
 | 
			
		||||
            .WithServer(client, g)
 | 
			
		||||
            .WithClient(client);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithDefault(ICommandContext ctx) =>
 | 
			
		||||
        WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithMention(DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%bot.mention%", () => client.CurrentUser.Mention);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithClient(DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        WithMention(client);
 | 
			
		||||
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%shardid%", () => client.ShardId.ToString());
 | 
			
		||||
        _reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
 | 
			
		||||
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%bot.status%", () => client.Status.ToString());
 | 
			
		||||
        _reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
 | 
			
		||||
        _reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
 | 
			
		||||
        _reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
 | 
			
		||||
        _reps.TryAdd("%bot.time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
 | 
			
		||||
        _reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
 | 
			
		||||
        _reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
 | 
			
		||||
        _reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
 | 
			
		||||
 | 
			
		||||
        WithStats(client);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithServer(DiscordSocketClient client, SocketGuild g)
 | 
			
		||||
    {
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%sid%", () => g is null ? "DM" : g.Id.ToString());
 | 
			
		||||
        _reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
 | 
			
		||||
        _reps.TryAdd("%members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
 | 
			
		||||
        _reps.TryAdd("%server_time%", () =>
 | 
			
		||||
        {
 | 
			
		||||
            WithRngRegex();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, SocketGuild g, DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            return this.WithUser(usr)
 | 
			
		||||
                .WithChannel(ch)
 | 
			
		||||
                .WithServer(client, g)
 | 
			
		||||
                .WithClient(client);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithDefault(ICommandContext ctx) =>
 | 
			
		||||
            WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithMention(DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%bot.mention%", () => client.CurrentUser.Mention);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithClient(DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            WithMention(client);
 | 
			
		||||
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%shardid%", () => client.ShardId.ToString());
 | 
			
		||||
            _reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
 | 
			
		||||
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%bot.status%", () => client.Status.ToString());
 | 
			
		||||
            _reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
 | 
			
		||||
            _reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
 | 
			
		||||
            _reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
 | 
			
		||||
            _reps.TryAdd("%bot.time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
 | 
			
		||||
            _reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
 | 
			
		||||
            _reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
 | 
			
		||||
 | 
			
		||||
            WithStats(client);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithServer(DiscordSocketClient client, SocketGuild g)
 | 
			
		||||
        {
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%sid%", () => g is null ? "DM" : g.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
 | 
			
		||||
            _reps.TryAdd("%members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
 | 
			
		||||
            _reps.TryAdd("%server_time%", () =>
 | 
			
		||||
            TimeZoneInfo to = TimeZoneInfo.Local;
 | 
			
		||||
            if (g != null)
 | 
			
		||||
            {
 | 
			
		||||
                TimeZoneInfo to = TimeZoneInfo.Local;
 | 
			
		||||
                if (g != null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
 | 
			
		||||
                        to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
 | 
			
		||||
                    TimeZoneInfo.Utc,
 | 
			
		||||
                    to).ToString("HH:mm ") + to.StandardName.GetInitials();
 | 
			
		||||
            });
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
 | 
			
		||||
            _reps.TryAdd("%server.members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
 | 
			
		||||
            _reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
 | 
			
		||||
            _reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
 | 
			
		||||
            _reps.TryAdd("%server.time%", () =>
 | 
			
		||||
            {
 | 
			
		||||
                TimeZoneInfo to = TimeZoneInfo.Local;
 | 
			
		||||
                if (g != null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
 | 
			
		||||
                        to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
 | 
			
		||||
                    TimeZoneInfo.Utc,
 | 
			
		||||
                    to).ToString("HH:mm ") + to.StandardName.GetInitials();
 | 
			
		||||
            });
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithChannel(IMessageChannel ch)
 | 
			
		||||
        {
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
 | 
			
		||||
            _reps.TryAdd("%chname%", () => ch.Name);
 | 
			
		||||
            _reps.TryAdd("%cid%", () => ch?.Id.ToString());
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%channel.mention%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
 | 
			
		||||
            _reps.TryAdd("%channel.name%", () => ch.Name);
 | 
			
		||||
            _reps.TryAdd("%channel.id%", () => ch.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%channel.created%", () => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
 | 
			
		||||
            _reps.TryAdd("%channel.nsfw%", () => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
 | 
			
		||||
            _reps.TryAdd("%channel.topic%", () => (ch as ITextChannel)?.Topic ?? "-");
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithUser(IUser user)
 | 
			
		||||
        {
 | 
			
		||||
            // /*OBSOLETE*/
 | 
			
		||||
            // _reps.TryAdd("%user%", () => user.Mention);
 | 
			
		||||
            // _reps.TryAdd("%userfull%", () => user.ToString());
 | 
			
		||||
            // _reps.TryAdd("%username%", () => user.Username);
 | 
			
		||||
            // _reps.TryAdd("%userdiscrim%", () => user.Discriminator);
 | 
			
		||||
            // _reps.TryAdd("%useravatar%", () => user.RealAvatarUrl()?.ToString());
 | 
			
		||||
            // _reps.TryAdd("%id%", () => user.Id.ToString());
 | 
			
		||||
            // _reps.TryAdd("%uid%", () => user.Id.ToString());
 | 
			
		||||
            // /*NEW*/
 | 
			
		||||
            // _reps.TryAdd("%user.mention%", () => user.Mention);
 | 
			
		||||
            // _reps.TryAdd("%user.fullname%", () => user.ToString());
 | 
			
		||||
            // _reps.TryAdd("%user.name%", () => user.Username);
 | 
			
		||||
            // _reps.TryAdd("%user.discrim%", () => user.Discriminator);
 | 
			
		||||
            // _reps.TryAdd("%user.avatar%", () => user.RealAvatarUrl()?.ToString());
 | 
			
		||||
            // _reps.TryAdd("%user.id%", () => user.Id.ToString());
 | 
			
		||||
            // _reps.TryAdd("%user.created_time%", () => user.CreatedAt.ToString("HH:mm"));
 | 
			
		||||
            // _reps.TryAdd("%user.created_date%", () => user.CreatedAt.ToString("dd.MM.yyyy"));
 | 
			
		||||
            // _reps.TryAdd("%user.joined_time%", () => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-");
 | 
			
		||||
            // _reps.TryAdd("%user.joined_date%", () => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-");
 | 
			
		||||
            WithManyUsers(new[] {user});
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithManyUsers(IEnumerable<IUser> users)
 | 
			
		||||
        {
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%user%", () => string.Join(" ", users.Select(user => user.Mention)));
 | 
			
		||||
            _reps.TryAdd("%userfull%", () => string.Join(" ", users.Select(user => user.ToString())));
 | 
			
		||||
            _reps.TryAdd("%username%", () => string.Join(" ", users.Select(user => user.Username)));
 | 
			
		||||
            _reps.TryAdd("%userdiscrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
 | 
			
		||||
            _reps.TryAdd("%useravatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
 | 
			
		||||
            _reps.TryAdd("%id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
            _reps.TryAdd("%uid%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%user.mention%", () => string.Join(" ", users.Select(user => user.Mention)));
 | 
			
		||||
            _reps.TryAdd("%user.fullname%", () => string.Join(" ", users.Select(user => user.ToString())));
 | 
			
		||||
            _reps.TryAdd("%user.name%", () => string.Join(" ", users.Select(user => user.Username)));
 | 
			
		||||
            _reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
 | 
			
		||||
            _reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
 | 
			
		||||
            _reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
            _reps.TryAdd("%user.created_time%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
 | 
			
		||||
            _reps.TryAdd("%user.created_date%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
 | 
			
		||||
            _reps.TryAdd("%user.joined_time%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
 | 
			
		||||
            _reps.TryAdd("%user.joined_date%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private ReplacementBuilder WithStats(DiscordSocketClient c)
 | 
			
		||||
        {
 | 
			
		||||
            /*OBSOLETE*/
 | 
			
		||||
            _reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
            _reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
            /*NEW*/
 | 
			
		||||
            _reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
            _reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
 | 
			
		||||
#endif
 | 
			
		||||
            _reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithRngRegex()
 | 
			
		||||
        {
 | 
			
		||||
            var rng = new NadekoRandom();
 | 
			
		||||
            _regex.TryAdd(rngRegex, (match) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (!int.TryParse(match.Groups["from"].ToString(), out var from))
 | 
			
		||||
                    from = 0;
 | 
			
		||||
                if (!int.TryParse(match.Groups["to"].ToString(), out var to))
 | 
			
		||||
                    to = 0;
 | 
			
		||||
 | 
			
		||||
                if (from == 0 && to == 0)
 | 
			
		||||
                    return rng.Next(0, 11).ToString();
 | 
			
		||||
 | 
			
		||||
                if (from >= to)
 | 
			
		||||
                    return string.Empty;
 | 
			
		||||
 | 
			
		||||
                return rng.Next(from, to + 1).ToString();
 | 
			
		||||
            });
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithOverride(string key, Func<string> output)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.AddOrUpdate(key, output, delegate { return output; });
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Replacer Build()
 | 
			
		||||
        {
 | 
			
		||||
            return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var provider in phProviders)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var ovr in provider.GetPlaceholders())
 | 
			
		||||
                {
 | 
			
		||||
                    _reps.TryAdd(ovr.Name, ovr.Func);
 | 
			
		||||
                }
 | 
			
		||||
                if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
 | 
			
		||||
                    to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
            return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
 | 
			
		||||
                TimeZoneInfo.Utc,
 | 
			
		||||
                to).ToString("HH:mm ") + to.StandardName.GetInitials();
 | 
			
		||||
        });
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
 | 
			
		||||
        _reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
 | 
			
		||||
        _reps.TryAdd("%server.members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
 | 
			
		||||
        _reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
 | 
			
		||||
        _reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
 | 
			
		||||
        _reps.TryAdd("%server.time%", () =>
 | 
			
		||||
        {
 | 
			
		||||
            TimeZoneInfo to = TimeZoneInfo.Local;
 | 
			
		||||
            if (g != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
 | 
			
		||||
                    to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
 | 
			
		||||
                TimeZoneInfo.Utc,
 | 
			
		||||
                to).ToString("HH:mm ") + to.StandardName.GetInitials();
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithChannel(IMessageChannel ch)
 | 
			
		||||
    {
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
 | 
			
		||||
        _reps.TryAdd("%chname%", () => ch.Name);
 | 
			
		||||
        _reps.TryAdd("%cid%", () => ch?.Id.ToString());
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%channel.mention%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
 | 
			
		||||
        _reps.TryAdd("%channel.name%", () => ch.Name);
 | 
			
		||||
        _reps.TryAdd("%channel.id%", () => ch.Id.ToString());
 | 
			
		||||
        _reps.TryAdd("%channel.created%", () => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
 | 
			
		||||
        _reps.TryAdd("%channel.nsfw%", () => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
 | 
			
		||||
        _reps.TryAdd("%channel.topic%", () => (ch as ITextChannel)?.Topic ?? "-");
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithUser(IUser user)
 | 
			
		||||
    {
 | 
			
		||||
        // /*OBSOLETE*/
 | 
			
		||||
        // _reps.TryAdd("%user%", () => user.Mention);
 | 
			
		||||
        // _reps.TryAdd("%userfull%", () => user.ToString());
 | 
			
		||||
        // _reps.TryAdd("%username%", () => user.Username);
 | 
			
		||||
        // _reps.TryAdd("%userdiscrim%", () => user.Discriminator);
 | 
			
		||||
        // _reps.TryAdd("%useravatar%", () => user.RealAvatarUrl()?.ToString());
 | 
			
		||||
        // _reps.TryAdd("%id%", () => user.Id.ToString());
 | 
			
		||||
        // _reps.TryAdd("%uid%", () => user.Id.ToString());
 | 
			
		||||
        // /*NEW*/
 | 
			
		||||
        // _reps.TryAdd("%user.mention%", () => user.Mention);
 | 
			
		||||
        // _reps.TryAdd("%user.fullname%", () => user.ToString());
 | 
			
		||||
        // _reps.TryAdd("%user.name%", () => user.Username);
 | 
			
		||||
        // _reps.TryAdd("%user.discrim%", () => user.Discriminator);
 | 
			
		||||
        // _reps.TryAdd("%user.avatar%", () => user.RealAvatarUrl()?.ToString());
 | 
			
		||||
        // _reps.TryAdd("%user.id%", () => user.Id.ToString());
 | 
			
		||||
        // _reps.TryAdd("%user.created_time%", () => user.CreatedAt.ToString("HH:mm"));
 | 
			
		||||
        // _reps.TryAdd("%user.created_date%", () => user.CreatedAt.ToString("dd.MM.yyyy"));
 | 
			
		||||
        // _reps.TryAdd("%user.joined_time%", () => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-");
 | 
			
		||||
        // _reps.TryAdd("%user.joined_date%", () => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-");
 | 
			
		||||
        WithManyUsers(new[] {user});
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithManyUsers(IEnumerable<IUser> users)
 | 
			
		||||
    {
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%user%", () => string.Join(" ", users.Select(user => user.Mention)));
 | 
			
		||||
        _reps.TryAdd("%userfull%", () => string.Join(" ", users.Select(user => user.ToString())));
 | 
			
		||||
        _reps.TryAdd("%username%", () => string.Join(" ", users.Select(user => user.Username)));
 | 
			
		||||
        _reps.TryAdd("%userdiscrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
 | 
			
		||||
        _reps.TryAdd("%useravatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
 | 
			
		||||
        _reps.TryAdd("%id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
        _reps.TryAdd("%uid%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%user.mention%", () => string.Join(" ", users.Select(user => user.Mention)));
 | 
			
		||||
        _reps.TryAdd("%user.fullname%", () => string.Join(" ", users.Select(user => user.ToString())));
 | 
			
		||||
        _reps.TryAdd("%user.name%", () => string.Join(" ", users.Select(user => user.Username)));
 | 
			
		||||
        _reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
 | 
			
		||||
        _reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
 | 
			
		||||
        _reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
 | 
			
		||||
        _reps.TryAdd("%user.created_time%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
 | 
			
		||||
        _reps.TryAdd("%user.created_date%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
 | 
			
		||||
        _reps.TryAdd("%user.joined_time%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
 | 
			
		||||
        _reps.TryAdd("%user.joined_date%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ReplacementBuilder WithStats(DiscordSocketClient c)
 | 
			
		||||
    {
 | 
			
		||||
        /*OBSOLETE*/
 | 
			
		||||
        _reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
        _reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        /*NEW*/
 | 
			
		||||
        _reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
        _reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
 | 
			
		||||
#endif
 | 
			
		||||
        _reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithRngRegex()
 | 
			
		||||
    {
 | 
			
		||||
        var rng = new NadekoRandom();
 | 
			
		||||
        _regex.TryAdd(rngRegex, (match) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (!int.TryParse(match.Groups["from"].ToString(), out var from))
 | 
			
		||||
                from = 0;
 | 
			
		||||
            if (!int.TryParse(match.Groups["to"].ToString(), out var to))
 | 
			
		||||
                to = 0;
 | 
			
		||||
 | 
			
		||||
            if (from == 0 && to == 0)
 | 
			
		||||
                return rng.Next(0, 11).ToString();
 | 
			
		||||
 | 
			
		||||
            if (from >= to)
 | 
			
		||||
                return string.Empty;
 | 
			
		||||
 | 
			
		||||
            return rng.Next(from, to + 1).ToString();
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithOverride(string key, Func<string> output)
 | 
			
		||||
    {
 | 
			
		||||
        _reps.AddOrUpdate(key, output, delegate { return output; });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Replacer Build()
 | 
			
		||||
    {
 | 
			
		||||
        return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var provider in phProviders)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var ovr in provider.GetPlaceholders())
 | 
			
		||||
            {
 | 
			
		||||
                _reps.TryAdd(ovr.Name, ovr.Func);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,91 +1,88 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Replacements
 | 
			
		||||
namespace NadekoBot.Common.Replacements;
 | 
			
		||||
 | 
			
		||||
public class Replacer
 | 
			
		||||
{
 | 
			
		||||
    public class Replacer
 | 
			
		||||
    private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
 | 
			
		||||
    private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
 | 
			
		||||
 | 
			
		||||
    public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
 | 
			
		||||
        private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
 | 
			
		||||
 | 
			
		||||
        public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
 | 
			
		||||
        {
 | 
			
		||||
            _replacements = replacements;
 | 
			
		||||
            _regex = regex;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Replace(string input)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
                return input;
 | 
			
		||||
 | 
			
		||||
            foreach (var (Key, Text) in _replacements)
 | 
			
		||||
            {
 | 
			
		||||
                if (input.Contains(Key))
 | 
			
		||||
                    input = input.Replace(Key, Text(), StringComparison.InvariantCulture);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var item in _regex)
 | 
			
		||||
            {
 | 
			
		||||
                input = item.Regex.Replace(input, (m) => item.Replacement(m));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return input;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public SmartText Replace(SmartText data)
 | 
			
		||||
            => data switch
 | 
			
		||||
            {
 | 
			
		||||
                SmartEmbedText embedData => Replace(embedData),
 | 
			
		||||
                SmartPlainText plain => Replace(plain),
 | 
			
		||||
                _ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public SmartPlainText Replace(SmartPlainText plainText)
 | 
			
		||||
            => Replace(plainText.Text);
 | 
			
		||||
 | 
			
		||||
        public SmartEmbedText Replace(SmartEmbedText embedData)
 | 
			
		||||
        {
 | 
			
		||||
            var newEmbedData = new SmartEmbedText();
 | 
			
		||||
            newEmbedData.PlainText = Replace(embedData.PlainText);
 | 
			
		||||
            newEmbedData.Description = Replace(embedData.Description);
 | 
			
		||||
            newEmbedData.Title = Replace(embedData.Title);
 | 
			
		||||
            newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
 | 
			
		||||
            newEmbedData.Image = Replace(embedData.Image);
 | 
			
		||||
            newEmbedData.Url = Replace(embedData.Url);
 | 
			
		||||
            if (embedData.Author != null)
 | 
			
		||||
            {
 | 
			
		||||
                newEmbedData.Author = new SmartTextEmbedAuthor();
 | 
			
		||||
                newEmbedData.Author.Name = Replace(embedData.Author.Name);
 | 
			
		||||
                newEmbedData.Author.IconUrl = Replace(embedData.Author.IconUrl);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (embedData.Fields != null)
 | 
			
		||||
            {
 | 
			
		||||
                var fields = new List<SmartTextEmbedField>();
 | 
			
		||||
                foreach (var f in embedData.Fields)
 | 
			
		||||
                {
 | 
			
		||||
                    var newF = new SmartTextEmbedField();
 | 
			
		||||
                    newF.Name = Replace(f.Name);
 | 
			
		||||
                    newF.Value = Replace(f.Value);
 | 
			
		||||
                    newF.Inline = f.Inline;
 | 
			
		||||
                    fields.Add(newF);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                newEmbedData.Fields = fields.ToArray();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (embedData.Footer != null)
 | 
			
		||||
            {
 | 
			
		||||
                newEmbedData.Footer = new SmartTextEmbedFooter();
 | 
			
		||||
                newEmbedData.Footer.Text = Replace(embedData.Footer.Text);
 | 
			
		||||
                newEmbedData.Footer.IconUrl = Replace(embedData.Footer.IconUrl);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newEmbedData.Color = embedData.Color;
 | 
			
		||||
 | 
			
		||||
            return newEmbedData;
 | 
			
		||||
        }
 | 
			
		||||
        _replacements = replacements;
 | 
			
		||||
        _regex = regex;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public string Replace(string input)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
            return input;
 | 
			
		||||
 | 
			
		||||
        foreach (var (Key, Text) in _replacements)
 | 
			
		||||
        {
 | 
			
		||||
            if (input.Contains(Key))
 | 
			
		||||
                input = input.Replace(Key, Text(), StringComparison.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach (var item in _regex)
 | 
			
		||||
        {
 | 
			
		||||
            input = item.Regex.Replace(input, (m) => item.Replacement(m));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return input;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SmartText Replace(SmartText data)
 | 
			
		||||
        => data switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText embedData => Replace(embedData),
 | 
			
		||||
            SmartPlainText plain => Replace(plain),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public SmartPlainText Replace(SmartPlainText plainText)
 | 
			
		||||
        => Replace(plainText.Text);
 | 
			
		||||
 | 
			
		||||
    public SmartEmbedText Replace(SmartEmbedText embedData)
 | 
			
		||||
    {
 | 
			
		||||
        var newEmbedData = new SmartEmbedText();
 | 
			
		||||
        newEmbedData.PlainText = Replace(embedData.PlainText);
 | 
			
		||||
        newEmbedData.Description = Replace(embedData.Description);
 | 
			
		||||
        newEmbedData.Title = Replace(embedData.Title);
 | 
			
		||||
        newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
 | 
			
		||||
        newEmbedData.Image = Replace(embedData.Image);
 | 
			
		||||
        newEmbedData.Url = Replace(embedData.Url);
 | 
			
		||||
        if (embedData.Author != null)
 | 
			
		||||
        {
 | 
			
		||||
            newEmbedData.Author = new SmartTextEmbedAuthor();
 | 
			
		||||
            newEmbedData.Author.Name = Replace(embedData.Author.Name);
 | 
			
		||||
            newEmbedData.Author.IconUrl = Replace(embedData.Author.IconUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (embedData.Fields != null)
 | 
			
		||||
        {
 | 
			
		||||
            var fields = new List<SmartTextEmbedField>();
 | 
			
		||||
            foreach (var f in embedData.Fields)
 | 
			
		||||
            {
 | 
			
		||||
                var newF = new SmartTextEmbedField();
 | 
			
		||||
                newF.Name = Replace(f.Name);
 | 
			
		||||
                newF.Value = Replace(f.Value);
 | 
			
		||||
                newF.Inline = f.Inline;
 | 
			
		||||
                fields.Add(newF);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newEmbedData.Fields = fields.ToArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (embedData.Footer != null)
 | 
			
		||||
        {
 | 
			
		||||
            newEmbedData.Footer = new SmartTextEmbedFooter();
 | 
			
		||||
            newEmbedData.Footer.Text = Replace(embedData.Footer.Text);
 | 
			
		||||
            newEmbedData.Footer.IconUrl = Replace(embedData.Footer.IconUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newEmbedData.Color = embedData.Color;
 | 
			
		||||
 | 
			
		||||
        return newEmbedData;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,14 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json.Serialization;
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public class RequireObjectPropertiesContractResolver : DefaultContractResolver
 | 
			
		||||
{
 | 
			
		||||
    public class RequireObjectPropertiesContractResolver : DefaultContractResolver
 | 
			
		||||
    protected override JsonObjectContract CreateObjectContract(Type objectType)
 | 
			
		||||
    {
 | 
			
		||||
        protected override JsonObjectContract CreateObjectContract(Type objectType)
 | 
			
		||||
        {
 | 
			
		||||
            var contract = base.CreateObjectContract(objectType);
 | 
			
		||||
            contract.ItemRequired = Required.DisallowNull;
 | 
			
		||||
            return contract;
 | 
			
		||||
        }
 | 
			
		||||
        var contract = base.CreateObjectContract(objectType);
 | 
			
		||||
        contract.ItemRequired = Required.DisallowNull;
 | 
			
		||||
        return contract;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,63 +1,60 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
public struct ShmartNumber : IEquatable<ShmartNumber>
 | 
			
		||||
{
 | 
			
		||||
    public struct ShmartNumber : IEquatable<ShmartNumber>
 | 
			
		||||
    public long Value { get; }
 | 
			
		||||
    public string Input { get; }
 | 
			
		||||
 | 
			
		||||
    public ShmartNumber(long val, string input = null)
 | 
			
		||||
    {
 | 
			
		||||
        public long Value { get; }
 | 
			
		||||
        public string Input { get; }
 | 
			
		||||
 | 
			
		||||
        public ShmartNumber(long val, string input = null)
 | 
			
		||||
        {
 | 
			
		||||
            Value = val;
 | 
			
		||||
            Input = input;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static implicit operator ShmartNumber(long num)
 | 
			
		||||
        {
 | 
			
		||||
            return new ShmartNumber(num);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static implicit operator long(ShmartNumber num)
 | 
			
		||||
        {
 | 
			
		||||
            return num.Value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static implicit operator ShmartNumber(int num)
 | 
			
		||||
        {
 | 
			
		||||
            return new ShmartNumber(num);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            return Value.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
        {
 | 
			
		||||
            return obj is ShmartNumber sn
 | 
			
		||||
                ? Equals(sn)
 | 
			
		||||
                : false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Equals(ShmartNumber other)
 | 
			
		||||
        {
 | 
			
		||||
            return other.Value == Value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode()
 | 
			
		||||
        {
 | 
			
		||||
            return Value.GetHashCode() ^ Input.GetHashCode(StringComparison.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator ==(ShmartNumber left, ShmartNumber right)
 | 
			
		||||
        {
 | 
			
		||||
            return left.Equals(right);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool operator !=(ShmartNumber left, ShmartNumber right)
 | 
			
		||||
        {
 | 
			
		||||
            return !(left == right);
 | 
			
		||||
        }
 | 
			
		||||
        Value = val;
 | 
			
		||||
        Input = input;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public static implicit operator ShmartNumber(long num)
 | 
			
		||||
    {
 | 
			
		||||
        return new ShmartNumber(num);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static implicit operator long(ShmartNumber num)
 | 
			
		||||
    {
 | 
			
		||||
        return num.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static implicit operator ShmartNumber(int num)
 | 
			
		||||
    {
 | 
			
		||||
        return new ShmartNumber(num);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
        return Value.ToString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override bool Equals(object obj)
 | 
			
		||||
    {
 | 
			
		||||
        return obj is ShmartNumber sn
 | 
			
		||||
            ? Equals(sn)
 | 
			
		||||
            : false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Equals(ShmartNumber other)
 | 
			
		||||
    {
 | 
			
		||||
        return other.Value == Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override int GetHashCode()
 | 
			
		||||
    {
 | 
			
		||||
        return Value.GetHashCode() ^ Input.GetHashCode(StringComparison.InvariantCulture);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool operator ==(ShmartNumber left, ShmartNumber right)
 | 
			
		||||
    {
 | 
			
		||||
        return left.Equals(right);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool operator !=(ShmartNumber left, ShmartNumber right)
 | 
			
		||||
    {
 | 
			
		||||
        return !(left == right);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,137 +1,133 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public sealed record SmartEmbedText : SmartText
 | 
			
		||||
{
 | 
			
		||||
    public sealed record SmartEmbedText : SmartText
 | 
			
		||||
    public string PlainText { get; set; }
 | 
			
		||||
    public string Title { get; set; }
 | 
			
		||||
    public string Description { get; set; }
 | 
			
		||||
    public string Url { get; set; }
 | 
			
		||||
    public string Thumbnail { get; set; }
 | 
			
		||||
    public string Image { get; set; }
 | 
			
		||||
 | 
			
		||||
    public SmartTextEmbedAuthor Author { get; set; }
 | 
			
		||||
    public SmartTextEmbedFooter Footer { get; set; }
 | 
			
		||||
    public SmartTextEmbedField[] Fields { get; set; }
 | 
			
		||||
 | 
			
		||||
    public uint Color { get; set; } = 7458112;
 | 
			
		||||
 | 
			
		||||
    public bool IsValid =>
 | 
			
		||||
        !string.IsNullOrWhiteSpace(Title) ||
 | 
			
		||||
        !string.IsNullOrWhiteSpace(Description) ||
 | 
			
		||||
        !string.IsNullOrWhiteSpace(Url) ||
 | 
			
		||||
        !string.IsNullOrWhiteSpace(Thumbnail) ||
 | 
			
		||||
        !string.IsNullOrWhiteSpace(Image) ||
 | 
			
		||||
        (Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
 | 
			
		||||
        (Fields != null && Fields.Length > 0);
 | 
			
		||||
 | 
			
		||||
    public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
 | 
			
		||||
    {
 | 
			
		||||
        public string PlainText { get; set; }
 | 
			
		||||
        public string Title { get; set; }
 | 
			
		||||
        public string Description { get; set; }
 | 
			
		||||
        public string Url { get; set; }
 | 
			
		||||
        public string Thumbnail { get; set; }
 | 
			
		||||
        public string Image { get; set; }
 | 
			
		||||
 | 
			
		||||
        public SmartTextEmbedAuthor Author { get; set; }
 | 
			
		||||
        public SmartTextEmbedFooter Footer { get; set; }
 | 
			
		||||
        public SmartTextEmbedField[] Fields { get; set; }
 | 
			
		||||
 | 
			
		||||
        public uint Color { get; set; } = 7458112;
 | 
			
		||||
 | 
			
		||||
        public bool IsValid =>
 | 
			
		||||
            !string.IsNullOrWhiteSpace(Title) ||
 | 
			
		||||
            !string.IsNullOrWhiteSpace(Description) ||
 | 
			
		||||
            !string.IsNullOrWhiteSpace(Url) ||
 | 
			
		||||
            !string.IsNullOrWhiteSpace(Thumbnail) ||
 | 
			
		||||
            !string.IsNullOrWhiteSpace(Image) ||
 | 
			
		||||
            (Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
 | 
			
		||||
            (Fields != null && Fields.Length > 0);
 | 
			
		||||
 | 
			
		||||
        public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
 | 
			
		||||
        {
 | 
			
		||||
            var set = new SmartEmbedText();
 | 
			
		||||
        var set = new SmartEmbedText();
 | 
			
		||||
            
 | 
			
		||||
            set.PlainText = plainText;
 | 
			
		||||
            set.Title = eb.Title;
 | 
			
		||||
            set.Description = eb.Description;
 | 
			
		||||
            set.Url = eb.Url;
 | 
			
		||||
            set.Thumbnail = eb.Thumbnail?.Url;
 | 
			
		||||
            set.Image = eb.Image?.Url;
 | 
			
		||||
            set.Author = eb.Author is EmbedAuthor ea
 | 
			
		||||
                ? new()
 | 
			
		||||
                {
 | 
			
		||||
                    Name = ea.Name,
 | 
			
		||||
                    Url = ea.Url,
 | 
			
		||||
                    IconUrl = ea.IconUrl
 | 
			
		||||
                }
 | 
			
		||||
                : null;
 | 
			
		||||
            set.Footer = eb.Footer is EmbedFooter ef
 | 
			
		||||
                ? new()
 | 
			
		||||
                {
 | 
			
		||||
                    Text = ef.Text,
 | 
			
		||||
                    IconUrl = ef.IconUrl
 | 
			
		||||
                }
 | 
			
		||||
                : null;
 | 
			
		||||
        set.PlainText = plainText;
 | 
			
		||||
        set.Title = eb.Title;
 | 
			
		||||
        set.Description = eb.Description;
 | 
			
		||||
        set.Url = eb.Url;
 | 
			
		||||
        set.Thumbnail = eb.Thumbnail?.Url;
 | 
			
		||||
        set.Image = eb.Image?.Url;
 | 
			
		||||
        set.Author = eb.Author is EmbedAuthor ea
 | 
			
		||||
            ? new()
 | 
			
		||||
            {
 | 
			
		||||
                Name = ea.Name,
 | 
			
		||||
                Url = ea.Url,
 | 
			
		||||
                IconUrl = ea.IconUrl
 | 
			
		||||
            }
 | 
			
		||||
            : null;
 | 
			
		||||
        set.Footer = eb.Footer is EmbedFooter ef
 | 
			
		||||
            ? new()
 | 
			
		||||
            {
 | 
			
		||||
                Text = ef.Text,
 | 
			
		||||
                IconUrl = ef.IconUrl
 | 
			
		||||
            }
 | 
			
		||||
            : null;
 | 
			
		||||
 | 
			
		||||
            if (eb.Fields.Length > 0)
 | 
			
		||||
                set.Fields = eb
 | 
			
		||||
                    .Fields
 | 
			
		||||
                    .Select(field => new SmartTextEmbedField()
 | 
			
		||||
                    {
 | 
			
		||||
                        Inline = field.Inline,
 | 
			
		||||
                        Name = field.Name,
 | 
			
		||||
                        Value = field.Value,
 | 
			
		||||
                    })
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
        if (eb.Fields.Length > 0)
 | 
			
		||||
            set.Fields = eb
 | 
			
		||||
                .Fields
 | 
			
		||||
                .Select(field => new SmartTextEmbedField()
 | 
			
		||||
                {
 | 
			
		||||
                    Inline = field.Inline,
 | 
			
		||||
                    Name = field.Name,
 | 
			
		||||
                    Value = field.Value,
 | 
			
		||||
                })
 | 
			
		||||
                .ToArray();
 | 
			
		||||
 | 
			
		||||
            set.Color = eb.Color?.RawValue ?? 0;
 | 
			
		||||
            return set;
 | 
			
		||||
        }
 | 
			
		||||
        set.Color = eb.Color?.RawValue ?? 0;
 | 
			
		||||
        return set;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        public EmbedBuilder GetEmbed()
 | 
			
		||||
    public EmbedBuilder GetEmbed()
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
            .WithColor(Color);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(Title))
 | 
			
		||||
            embed.WithTitle(Title);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(Description))
 | 
			
		||||
            embed.WithDescription(Description);
 | 
			
		||||
 | 
			
		||||
        if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(Url);
 | 
			
		||||
 | 
			
		||||
        if (Footer != null)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                .WithColor(Color);
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(Title))
 | 
			
		||||
                embed.WithTitle(Title);
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(Description))
 | 
			
		||||
                embed.WithDescription(Description);
 | 
			
		||||
 | 
			
		||||
            if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute))
 | 
			
		||||
                embed.WithUrl(Url);
 | 
			
		||||
 | 
			
		||||
            if (Footer != null)
 | 
			
		||||
            embed.WithFooter(efb =>
 | 
			
		||||
            {
 | 
			
		||||
                embed.WithFooter(efb =>
 | 
			
		||||
                {
 | 
			
		||||
                    efb.WithText(Footer.Text);
 | 
			
		||||
                    if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
 | 
			
		||||
                        efb.WithIconUrl(Footer.IconUrl);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
 | 
			
		||||
                embed.WithThumbnailUrl(Thumbnail);
 | 
			
		||||
 | 
			
		||||
            if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
 | 
			
		||||
                embed.WithImageUrl(Image);
 | 
			
		||||
 | 
			
		||||
            if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
 | 
			
		||||
            {
 | 
			
		||||
                if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
 | 
			
		||||
                    Author.IconUrl = null;
 | 
			
		||||
                if (!Uri.IsWellFormedUriString(Author.Url, UriKind.Absolute))
 | 
			
		||||
                    Author.Url = null;
 | 
			
		||||
 | 
			
		||||
                embed.WithAuthor(Author.Name, Author.IconUrl, Author.Url);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Fields != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var f in Fields)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
 | 
			
		||||
                        embed.AddField(f.Name, f.Value, f.Inline);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return embed;
 | 
			
		||||
                efb.WithText(Footer.Text);
 | 
			
		||||
                if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
 | 
			
		||||
                    efb.WithIconUrl(Footer.IconUrl);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void NormalizeFields()
 | 
			
		||||
        if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
 | 
			
		||||
            embed.WithThumbnailUrl(Thumbnail);
 | 
			
		||||
 | 
			
		||||
        if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
 | 
			
		||||
            embed.WithImageUrl(Image);
 | 
			
		||||
 | 
			
		||||
        if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
 | 
			
		||||
        {
 | 
			
		||||
            if (Fields != null && Fields.Length > 0)
 | 
			
		||||
            if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
 | 
			
		||||
                Author.IconUrl = null;
 | 
			
		||||
            if (!Uri.IsWellFormedUriString(Author.Url, UriKind.Absolute))
 | 
			
		||||
                Author.Url = null;
 | 
			
		||||
 | 
			
		||||
            embed.WithAuthor(Author.Name, Author.IconUrl, Author.Url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Fields != null)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var f in Fields)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var f in Fields)
 | 
			
		||||
                {
 | 
			
		||||
                    f.Name = f.Name.TrimTo(256);
 | 
			
		||||
                    f.Value = f.Value.TrimTo(1024);
 | 
			
		||||
                }
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
 | 
			
		||||
                    embed.AddField(f.Name, f.Value, f.Inline);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return embed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void NormalizeFields()
 | 
			
		||||
    {
 | 
			
		||||
        if (Fields != null && Fields.Length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var f in Fields)
 | 
			
		||||
            {
 | 
			
		||||
                f.Name = f.Name.TrimTo(256);
 | 
			
		||||
                f.Value = f.Value.TrimTo(1024);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,22 @@
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public sealed record SmartPlainText : SmartText
 | 
			
		||||
{
 | 
			
		||||
    public sealed record SmartPlainText : SmartText
 | 
			
		||||
    public string Text { get; init; }
 | 
			
		||||
 | 
			
		||||
    public SmartPlainText(string text)
 | 
			
		||||
    {
 | 
			
		||||
        public string Text { get; init; }
 | 
			
		||||
        Text = text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public SmartPlainText(string text)
 | 
			
		||||
        {
 | 
			
		||||
            Text = text;
 | 
			
		||||
        }
 | 
			
		||||
    public static implicit operator SmartPlainText(string input)
 | 
			
		||||
        => new SmartPlainText(input);
 | 
			
		||||
 | 
			
		||||
        public static implicit operator SmartPlainText(string input)
 | 
			
		||||
            => new SmartPlainText(input);
 | 
			
		||||
    public static implicit operator string(SmartPlainText input)
 | 
			
		||||
        => input.Text;
 | 
			
		||||
 | 
			
		||||
        public static implicit operator string(SmartPlainText input)
 | 
			
		||||
            => input.Text;
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            return Text;
 | 
			
		||||
        }
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
        return Text;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,53 +1,51 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public abstract record SmartText
 | 
			
		||||
{
 | 
			
		||||
    public abstract record SmartText
 | 
			
		||||
    {
 | 
			
		||||
        public bool IsEmbed => this is SmartEmbedText;
 | 
			
		||||
        public bool IsPlainText => this is SmartPlainText;
 | 
			
		||||
    public bool IsEmbed => this is SmartEmbedText;
 | 
			
		||||
    public bool IsPlainText => this is SmartPlainText;
 | 
			
		||||
 | 
			
		||||
        public static SmartText operator +(SmartText text, string input)
 | 
			
		||||
            => text switch
 | 
			
		||||
            {
 | 
			
		||||
                SmartEmbedText set => set with { PlainText = set.PlainText + input },
 | 
			
		||||
                SmartPlainText spt => new SmartPlainText(spt.Text + input),
 | 
			
		||||
                _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
            };
 | 
			
		||||
        
 | 
			
		||||
        public static SmartText operator +(string input, SmartText text)
 | 
			
		||||
            => text switch
 | 
			
		||||
            {
 | 
			
		||||
                SmartEmbedText set => set with { PlainText = input + set.PlainText },
 | 
			
		||||
                SmartPlainText spt => new SmartPlainText(input + spt.Text),
 | 
			
		||||
                _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
            };
 | 
			
		||||
        
 | 
			
		||||
        public static SmartText CreateFrom(string input)
 | 
			
		||||
    public static SmartText operator +(SmartText text, string input)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
 | 
			
		||||
            SmartEmbedText set => set with { PlainText = set.PlainText + input },
 | 
			
		||||
            SmartPlainText spt => new SmartPlainText(spt.Text + input),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
    public static SmartText operator +(string input, SmartText text)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText set => set with { PlainText = input + set.PlainText },
 | 
			
		||||
            SmartPlainText spt => new SmartPlainText(input + spt.Text),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
    public static SmartText CreateFrom(string input)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
 | 
			
		||||
        {
 | 
			
		||||
            return new SmartPlainText(input);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
 | 
			
		||||
 | 
			
		||||
            smartEmbedText.NormalizeFields();
 | 
			
		||||
 | 
			
		||||
            if (!smartEmbedText.IsValid)
 | 
			
		||||
            {
 | 
			
		||||
                return new SmartPlainText(input);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
 | 
			
		||||
 | 
			
		||||
                smartEmbedText.NormalizeFields();
 | 
			
		||||
 | 
			
		||||
                if (!smartEmbedText.IsValid)
 | 
			
		||||
                {
 | 
			
		||||
                    return new SmartPlainText(input);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return smartEmbedText;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return new SmartPlainText(input);
 | 
			
		||||
            }
 | 
			
		||||
            return smartEmbedText;
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            return new SmartPlainText(input);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public class SmartTextEmbedAuthor
 | 
			
		||||
{
 | 
			
		||||
    public class SmartTextEmbedAuthor
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public string IconUrl { get; set; }
 | 
			
		||||
        [JsonProperty("icon_url")]
 | 
			
		||||
        private string Icon_Url { set => IconUrl = value; }
 | 
			
		||||
        public string Url { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public string IconUrl { get; set; }
 | 
			
		||||
    [JsonProperty("icon_url")]
 | 
			
		||||
    private string Icon_Url { set => IconUrl = value; }
 | 
			
		||||
    public string Url { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public class SmartTextEmbedField
 | 
			
		||||
{
 | 
			
		||||
    public class SmartTextEmbedField
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public string Value { get; set; }
 | 
			
		||||
        public bool Inline { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public string Value { get; set; }
 | 
			
		||||
    public bool Inline { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public class SmartTextEmbedFooter
 | 
			
		||||
{
 | 
			
		||||
    public class SmartTextEmbedFooter
 | 
			
		||||
    {
 | 
			
		||||
        public string Text { get; set; }
 | 
			
		||||
        public string IconUrl { get; set; }
 | 
			
		||||
        [JsonProperty("icon_url")]
 | 
			
		||||
        private string Icon_Url { set => IconUrl = value; }
 | 
			
		||||
    }
 | 
			
		||||
    public string Text { get; set; }
 | 
			
		||||
    public string IconUrl { get; set; }
 | 
			
		||||
    [JsonProperty("icon_url")]
 | 
			
		||||
    private string Icon_Url { set => IconUrl = value; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,91 +1,89 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
namespace NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
public sealed class ReactionEventWrapper : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    public sealed class ReactionEventWrapper : IDisposable
 | 
			
		||||
    public IUserMessage Message { get; }
 | 
			
		||||
    public event Action<SocketReaction> OnReactionAdded = delegate { };
 | 
			
		||||
    public event Action<SocketReaction> OnReactionRemoved = delegate { };
 | 
			
		||||
    public event Action OnReactionsCleared = delegate { };
 | 
			
		||||
 | 
			
		||||
    public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
 | 
			
		||||
    {
 | 
			
		||||
        public IUserMessage Message { get; }
 | 
			
		||||
        public event Action<SocketReaction> OnReactionAdded = delegate { };
 | 
			
		||||
        public event Action<SocketReaction> OnReactionRemoved = delegate { };
 | 
			
		||||
        public event Action OnReactionsCleared = delegate { };
 | 
			
		||||
        Message = msg ?? throw new ArgumentNullException(nameof(msg));
 | 
			
		||||
        _client = client;
 | 
			
		||||
 | 
			
		||||
        public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
 | 
			
		||||
        {
 | 
			
		||||
            Message = msg ?? throw new ArgumentNullException(nameof(msg));
 | 
			
		||||
            _client = client;
 | 
			
		||||
 | 
			
		||||
            _client.ReactionAdded += Discord_ReactionAdded;
 | 
			
		||||
            _client.ReactionRemoved += Discord_ReactionRemoved;
 | 
			
		||||
            _client.ReactionsCleared += Discord_ReactionsCleared;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
 | 
			
		||||
        {
 | 
			
		||||
            Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (msg.Id == Message.Id)
 | 
			
		||||
                        OnReactionsCleared?.Invoke();
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Discord_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
 | 
			
		||||
        {
 | 
			
		||||
            Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (msg.Id == Message.Id)
 | 
			
		||||
                        OnReactionRemoved?.Invoke(reaction);
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Discord_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
 | 
			
		||||
        {
 | 
			
		||||
            Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (msg.Id == Message.Id)
 | 
			
		||||
                        OnReactionAdded?.Invoke(reaction);
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void UnsubAll()
 | 
			
		||||
        {
 | 
			
		||||
            _client.ReactionAdded -= Discord_ReactionAdded;
 | 
			
		||||
            _client.ReactionRemoved -= Discord_ReactionRemoved;
 | 
			
		||||
            _client.ReactionsCleared -= Discord_ReactionsCleared;
 | 
			
		||||
            OnReactionAdded = null;
 | 
			
		||||
            OnReactionRemoved = null;
 | 
			
		||||
            OnReactionsCleared = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool disposing = false;
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (disposing)
 | 
			
		||||
                return;
 | 
			
		||||
            disposing = true;
 | 
			
		||||
            UnsubAll();
 | 
			
		||||
        }
 | 
			
		||||
        _client.ReactionAdded += Discord_ReactionAdded;
 | 
			
		||||
        _client.ReactionRemoved += Discord_ReactionRemoved;
 | 
			
		||||
        _client.ReactionsCleared += Discord_ReactionsCleared;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
 | 
			
		||||
    {
 | 
			
		||||
        Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (msg.Id == Message.Id)
 | 
			
		||||
                    OnReactionsCleared?.Invoke();
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task Discord_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
 | 
			
		||||
    {
 | 
			
		||||
        Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (msg.Id == Message.Id)
 | 
			
		||||
                    OnReactionRemoved?.Invoke(reaction);
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task Discord_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
 | 
			
		||||
    {
 | 
			
		||||
        Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (msg.Id == Message.Id)
 | 
			
		||||
                    OnReactionAdded?.Invoke(reaction);
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void UnsubAll()
 | 
			
		||||
    {
 | 
			
		||||
        _client.ReactionAdded -= Discord_ReactionAdded;
 | 
			
		||||
        _client.ReactionRemoved -= Discord_ReactionRemoved;
 | 
			
		||||
        _client.ReactionsCleared -= Discord_ReactionsCleared;
 | 
			
		||||
        OnReactionAdded = null;
 | 
			
		||||
        OnReactionRemoved = null;
 | 
			
		||||
        OnReactionsCleared = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private bool disposing = false;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        if (disposing)
 | 
			
		||||
            return;
 | 
			
		||||
        disposing = true;
 | 
			
		||||
        UnsubAll();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public enum AddRemove
 | 
			
		||||
{
 | 
			
		||||
    public enum AddRemove
 | 
			
		||||
    {
 | 
			
		||||
        Add = int.MinValue,
 | 
			
		||||
        Rem = int.MinValue + 1,
 | 
			
		||||
        Rm = int.MinValue + 1,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    Add = int.MinValue,
 | 
			
		||||
    Rem = int.MinValue + 1,
 | 
			
		||||
    Rm = int.MinValue + 1,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,90 +1,87 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Modules.CustomReactions.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
 | 
			
		||||
    private readonly CommandHandler _handler;
 | 
			
		||||
    private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
    public CommandTypeReader(CommandHandler handler, CommandService cmds)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandHandler _handler;
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
        public CommandTypeReader(CommandHandler handler, CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _handler = handler;
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var prefix = _handler.GetPrefix(context.Guild);
 | 
			
		||||
            if (!input.StartsWith(prefix.ToUpperInvariant(), StringComparison.InvariantCulture))
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
            input = input.Substring(prefix.Length);
 | 
			
		||||
 | 
			
		||||
            var cmd = _cmds.Commands.FirstOrDefault(c => c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
 | 
			
		||||
            if (cmd is null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
 | 
			
		||||
        }
 | 
			
		||||
        _handler = handler;
 | 
			
		||||
        _cmds = cmds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
        private readonly CustomReactionsService _crs;
 | 
			
		||||
        private readonly CommandHandler _commandHandler;
 | 
			
		||||
        input = input.ToUpperInvariant();
 | 
			
		||||
        var prefix = _handler.GetPrefix(context.Guild);
 | 
			
		||||
        if (!input.StartsWith(prefix.ToUpperInvariant(), StringComparison.InvariantCulture))
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
        public CommandOrCrTypeReader(
 | 
			
		||||
            CommandService cmds,
 | 
			
		||||
            CustomReactionsService crs,
 | 
			
		||||
            CommandHandler commandHandler)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
            _crs = crs;
 | 
			
		||||
            _commandHandler = commandHandler;
 | 
			
		||||
        }
 | 
			
		||||
        input = input.Substring(prefix.Length);
 | 
			
		||||
 | 
			
		||||
        public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
        var cmd = _cmds.Commands.FirstOrDefault(c => c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
 | 
			
		||||
        if (cmd is null)
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
            if (_crs.ReactionExists(context.Guild?.Id, input))
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input).ConfigureAwait(false);
 | 
			
		||||
            if (cmd.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, CommandOrCrInfo.Type.Normal));
 | 
			
		||||
            }
 | 
			
		||||
            return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class CommandOrCrInfo
 | 
			
		||||
    {
 | 
			
		||||
        public enum Type
 | 
			
		||||
        {
 | 
			
		||||
            Normal,
 | 
			
		||||
            Custom,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public Type CmdType { get; set; }
 | 
			
		||||
        public bool IsCustom => CmdType == Type.Custom;
 | 
			
		||||
 | 
			
		||||
        public CommandOrCrInfo(string input, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            this.Name = input;
 | 
			
		||||
            this.CmdType = type;
 | 
			
		||||
        }
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
 | 
			
		||||
{
 | 
			
		||||
    private readonly CommandService _cmds;
 | 
			
		||||
    private readonly CustomReactionsService _crs;
 | 
			
		||||
    private readonly CommandHandler _commandHandler;
 | 
			
		||||
 | 
			
		||||
    public CommandOrCrTypeReader(
 | 
			
		||||
        CommandService cmds,
 | 
			
		||||
        CustomReactionsService crs,
 | 
			
		||||
        CommandHandler commandHandler)
 | 
			
		||||
    {
 | 
			
		||||
        _cmds = cmds;
 | 
			
		||||
        _crs = crs;
 | 
			
		||||
        _commandHandler = commandHandler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        input = input.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
        if (_crs.ReactionExists(context.Guild?.Id, input))
 | 
			
		||||
        {
 | 
			
		||||
            return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input).ConfigureAwait(false);
 | 
			
		||||
        if (cmd.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, CommandOrCrInfo.Type.Normal));
 | 
			
		||||
        }
 | 
			
		||||
        return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class CommandOrCrInfo
 | 
			
		||||
{
 | 
			
		||||
    public enum Type
 | 
			
		||||
    {
 | 
			
		||||
        Normal,
 | 
			
		||||
        Custom,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public Type CmdType { get; set; }
 | 
			
		||||
    public bool IsCustom => CmdType == Type.Custom;
 | 
			
		||||
 | 
			
		||||
    public CommandOrCrInfo(string input, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        this.Name = input;
 | 
			
		||||
        this.CmdType = type;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,16 +2,15 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
 | 
			
		||||
        {
 | 
			
		||||
            if (!Emote.TryParse(input, out var emote))
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid emote"));
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(emote));
 | 
			
		||||
        }
 | 
			
		||||
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
 | 
			
		||||
{
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
 | 
			
		||||
    {
 | 
			
		||||
        if (!Emote.TryParse(input, out var emote))
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid emote"));
 | 
			
		||||
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(emote));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,53 +1,51 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
 | 
			
		||||
    private readonly GuildTimezoneService _gts;
 | 
			
		||||
 | 
			
		||||
    public GuildDateTimeTypeReader(GuildTimezoneService gts)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly GuildTimezoneService _gts;
 | 
			
		||||
 | 
			
		||||
        public GuildDateTimeTypeReader(GuildTimezoneService gts)
 | 
			
		||||
        {
 | 
			
		||||
            _gts = gts;
 | 
			
		||||
        }
 | 
			
		||||
        _gts = gts;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            var gdt = Parse(context.Guild.Id, input);
 | 
			
		||||
            if(gdt is null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        var gdt = Parse(context.Guild.Id, input);
 | 
			
		||||
        if(gdt is null)
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private GuildDateTime Parse(ulong guildId, string input)
 | 
			
		||||
        {
 | 
			
		||||
            if (!DateTime.TryParse(input, out var dt))
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var tz = _gts.GetTimeZoneOrUtc(guildId);
 | 
			
		||||
 | 
			
		||||
            return new(tz, dt);
 | 
			
		||||
        }
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class GuildDateTime
 | 
			
		||||
    private GuildDateTime Parse(ulong guildId, string input)
 | 
			
		||||
    {
 | 
			
		||||
        public TimeZoneInfo Timezone { get; }
 | 
			
		||||
        public DateTime CurrentGuildTime { get; }
 | 
			
		||||
        public DateTime InputTime { get; }
 | 
			
		||||
        public DateTime InputTimeUtc { get; }
 | 
			
		||||
        if (!DateTime.TryParse(input, out var dt))
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
 | 
			
		||||
        {
 | 
			
		||||
            var now = DateTime.UtcNow;
 | 
			
		||||
            Timezone = guildTimezone;
 | 
			
		||||
            CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
 | 
			
		||||
            InputTime = inputTime;
 | 
			
		||||
            InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
 | 
			
		||||
        }
 | 
			
		||||
        var tz = _gts.GetTimeZoneOrUtc(guildId);
 | 
			
		||||
 | 
			
		||||
        return new(tz, dt);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class GuildDateTime
 | 
			
		||||
{
 | 
			
		||||
    public TimeZoneInfo Timezone { get; }
 | 
			
		||||
    public DateTime CurrentGuildTime { get; }
 | 
			
		||||
    public DateTime InputTime { get; }
 | 
			
		||||
    public DateTime InputTimeUtc { get; }
 | 
			
		||||
 | 
			
		||||
    public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
 | 
			
		||||
    {
 | 
			
		||||
        var now = DateTime.UtcNow;
 | 
			
		||||
        Timezone = guildTimezone;
 | 
			
		||||
        CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
 | 
			
		||||
        InputTime = inputTime;
 | 
			
		||||
        InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,31 +1,29 @@
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
    public GuildTypeReader(DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
        public GuildTypeReader(DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.Trim().ToUpperInvariant();
 | 
			
		||||
            var guilds = _client.Guilds;
 | 
			
		||||
            var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input) ?? //by id
 | 
			
		||||
                        guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == input); //by name
 | 
			
		||||
 | 
			
		||||
            if (guild != null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromSuccess(guild));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found"));
 | 
			
		||||
        }
 | 
			
		||||
        _client = client;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        input = input.Trim().ToUpperInvariant();
 | 
			
		||||
        var guilds = _client.Guilds;
 | 
			
		||||
        var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input) ?? //by id
 | 
			
		||||
                    guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == input); //by name
 | 
			
		||||
 | 
			
		||||
        if (guild != null)
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(guild));
 | 
			
		||||
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +1,23 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class KwumTypeReader : NadekoTypeReader<kwum>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class KwumTypeReader : NadekoTypeReader<kwum>
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            if (kwum.TryParse(input, out var val))
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromSuccess(val));
 | 
			
		||||
        if (kwum.TryParse(input, out var val))
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(val));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid kwum"));
 | 
			
		||||
        }
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid kwum"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public sealed class SmartTextTypeReader : NadekoTypeReader<SmartText>
 | 
			
		||||
public sealed class SmartTextTypeReader : NadekoTypeReader<SmartText>
 | 
			
		||||
{
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
 | 
			
		||||
        {
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
 | 
			
		||||
        }
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +1,26 @@
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders.Models
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
 | 
			
		||||
public class PermissionAction
 | 
			
		||||
{
 | 
			
		||||
    public class PermissionAction
 | 
			
		||||
    public static PermissionAction Enable => new PermissionAction(true);
 | 
			
		||||
    public static PermissionAction Disable => new PermissionAction(false);
 | 
			
		||||
 | 
			
		||||
    public bool Value { get; }
 | 
			
		||||
 | 
			
		||||
    public PermissionAction(bool value)
 | 
			
		||||
    {
 | 
			
		||||
        public static PermissionAction Enable => new PermissionAction(true);
 | 
			
		||||
        public static PermissionAction Disable => new PermissionAction(false);
 | 
			
		||||
 | 
			
		||||
        public bool Value { get; }
 | 
			
		||||
 | 
			
		||||
        public PermissionAction(bool value)
 | 
			
		||||
        {
 | 
			
		||||
            this.Value = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool Equals(object obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj is null || GetType() != obj.GetType())
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return this.Value == ((PermissionAction)obj).Value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int GetHashCode() => Value.GetHashCode();
 | 
			
		||||
        this.Value = value;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public override bool Equals(object obj)
 | 
			
		||||
    {
 | 
			
		||||
        if (obj is null || GetType() != obj.GetType())
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.Value == ((PermissionAction)obj).Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override int GetHashCode() => Value.GetHashCode();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,65 +1,62 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders.Models
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
 | 
			
		||||
public class StoopidTime
 | 
			
		||||
{
 | 
			
		||||
    public class StoopidTime
 | 
			
		||||
    public string Input { get; set; }
 | 
			
		||||
    public TimeSpan Time { get; set; }
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex _regex = new Regex(
 | 
			
		||||
        @"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
 | 
			
		||||
        RegexOptions.Compiled | RegexOptions.Multiline);
 | 
			
		||||
 | 
			
		||||
    private StoopidTime() { }
 | 
			
		||||
 | 
			
		||||
    public static StoopidTime FromInput(string input)
 | 
			
		||||
    {
 | 
			
		||||
        public string Input { get; set; }
 | 
			
		||||
        public TimeSpan Time { get; set; }
 | 
			
		||||
        var m = _regex.Match(input);
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex _regex = new Regex(
 | 
			
		||||
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
 | 
			
		||||
                                RegexOptions.Compiled | RegexOptions.Multiline);
 | 
			
		||||
 | 
			
		||||
        private StoopidTime() { }
 | 
			
		||||
 | 
			
		||||
        public static StoopidTime FromInput(string input)
 | 
			
		||||
        if (m.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            var m = _regex.Match(input);
 | 
			
		||||
 | 
			
		||||
            if (m.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Invalid string input format.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string output = "";
 | 
			
		||||
            var namesAndValues = new Dictionary<string, int>();
 | 
			
		||||
 | 
			
		||||
            foreach (var groupName in _regex.GetGroupNames())
 | 
			
		||||
            {
 | 
			
		||||
                if (groupName == "0") continue;
 | 
			
		||||
                if (!int.TryParse(m.Groups[groupName].Value, out var value))
 | 
			
		||||
                {
 | 
			
		||||
                    namesAndValues[groupName] = 0;
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (value < 1)
 | 
			
		||||
                {
 | 
			
		||||
                    throw new ArgumentException($"Invalid {groupName} value.");
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                namesAndValues[groupName] = value;
 | 
			
		||||
                output += m.Groups[groupName].Value + " " + groupName + " ";
 | 
			
		||||
            }
 | 
			
		||||
            var ts = new TimeSpan(30 * namesAndValues["months"] +
 | 
			
		||||
                                                    7 * namesAndValues["weeks"] +
 | 
			
		||||
                                                    namesAndValues["days"],
 | 
			
		||||
                                                    namesAndValues["hours"],
 | 
			
		||||
                                                    namesAndValues["minutes"],
 | 
			
		||||
                                                    namesAndValues["seconds"]);
 | 
			
		||||
            if (ts > TimeSpan.FromDays(90))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Time is too long.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new StoopidTime()
 | 
			
		||||
            {
 | 
			
		||||
                Input = input,
 | 
			
		||||
                Time = ts,
 | 
			
		||||
            };
 | 
			
		||||
            throw new ArgumentException("Invalid string input format.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        string output = "";
 | 
			
		||||
        var namesAndValues = new Dictionary<string, int>();
 | 
			
		||||
 | 
			
		||||
        foreach (var groupName in _regex.GetGroupNames())
 | 
			
		||||
        {
 | 
			
		||||
            if (groupName == "0") continue;
 | 
			
		||||
            if (!int.TryParse(m.Groups[groupName].Value, out var value))
 | 
			
		||||
            {
 | 
			
		||||
                namesAndValues[groupName] = 0;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (value < 1)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException($"Invalid {groupName} value.");
 | 
			
		||||
            }
 | 
			
		||||
                
 | 
			
		||||
            namesAndValues[groupName] = value;
 | 
			
		||||
            output += m.Groups[groupName].Value + " " + groupName + " ";
 | 
			
		||||
        }
 | 
			
		||||
        var ts = new TimeSpan(30 * namesAndValues["months"] +
 | 
			
		||||
                              7 * namesAndValues["weeks"] +
 | 
			
		||||
                              namesAndValues["days"],
 | 
			
		||||
            namesAndValues["hours"],
 | 
			
		||||
            namesAndValues["minutes"],
 | 
			
		||||
            namesAndValues["seconds"]);
 | 
			
		||||
        if (ts > TimeSpan.FromDays(90))
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("Time is too long.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new StoopidTime()
 | 
			
		||||
        {
 | 
			
		||||
            Input = input,
 | 
			
		||||
            Time = ts,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,55 +1,53 @@
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
 | 
			
		||||
    private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
    public ModuleTypeReader(CommandService cmds)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
        public ModuleTypeReader(CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
            if (module is null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(module));
 | 
			
		||||
        }
 | 
			
		||||
        _cmds = cmds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
        input = input.ToUpperInvariant();
 | 
			
		||||
        var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
        if (module is null)
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
        public ModuleOrCrTypeReader(CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
            if (module is null && input != "ACTUALCUSTOMREACTIONS")
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
 | 
			
		||||
            {
 | 
			
		||||
                Name = input,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public sealed class ModuleOrCrInfo
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(module));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
 | 
			
		||||
{
 | 
			
		||||
    private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
    public ModuleOrCrTypeReader(CommandService cmds)
 | 
			
		||||
    {
 | 
			
		||||
        _cmds = cmds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        input = input.ToUpperInvariant();
 | 
			
		||||
        var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
        if (module is null && input != "ACTUALCUSTOMREACTIONS")
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
        return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
 | 
			
		||||
        {
 | 
			
		||||
            Name = input,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class ModuleOrCrInfo
 | 
			
		||||
{
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,12 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public abstract class NadekoTypeReader<T> : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input, IServiceProvider services)
 | 
			
		||||
            => ReadAsync(ctx, input);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
public abstract class NadekoTypeReader<T> : TypeReader
 | 
			
		||||
{
 | 
			
		||||
    public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
 | 
			
		||||
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input, IServiceProvider services)
 | 
			
		||||
        => ReadAsync(ctx, input);
 | 
			
		||||
}
 | 
			
		||||
@@ -2,39 +2,38 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Used instead of bool for more flexible keywords for true/false only in the permission module
 | 
			
		||||
/// </summary>
 | 
			
		||||
public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Used instead of bool for more flexible keywords for true/false only in the permission module
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        input = input.ToUpperInvariant();
 | 
			
		||||
        switch (input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            switch (input)
 | 
			
		||||
            {
 | 
			
		||||
                case "1":
 | 
			
		||||
                case "T":
 | 
			
		||||
                case "TRUE":
 | 
			
		||||
                case "ENABLE":
 | 
			
		||||
                case "ENABLED":
 | 
			
		||||
                case "ALLOW":
 | 
			
		||||
                case "PERMIT":
 | 
			
		||||
                case "UNBAN":
 | 
			
		||||
                    return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable));
 | 
			
		||||
                case "0":
 | 
			
		||||
                case "F":
 | 
			
		||||
                case "FALSE":
 | 
			
		||||
                case "DENY":
 | 
			
		||||
                case "DISABLE":
 | 
			
		||||
                case "DISABLED":
 | 
			
		||||
                case "DISALLOW":
 | 
			
		||||
                case "BAN":
 | 
			
		||||
                    return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
 | 
			
		||||
                default:
 | 
			
		||||
                    return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
 | 
			
		||||
            }
 | 
			
		||||
            case "1":
 | 
			
		||||
            case "T":
 | 
			
		||||
            case "TRUE":
 | 
			
		||||
            case "ENABLE":
 | 
			
		||||
            case "ENABLED":
 | 
			
		||||
            case "ALLOW":
 | 
			
		||||
            case "PERMIT":
 | 
			
		||||
            case "UNBAN":
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable));
 | 
			
		||||
            case "0":
 | 
			
		||||
            case "F":
 | 
			
		||||
            case "FALSE":
 | 
			
		||||
            case "DENY":
 | 
			
		||||
            case "DISABLE":
 | 
			
		||||
            case "DISABLED":
 | 
			
		||||
            case "DISALLOW":
 | 
			
		||||
            case "BAN":
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
 | 
			
		||||
            default:
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +1,23 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
 | 
			
		||||
    {
 | 
			
		||||
        public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Yield();
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
            input = input.Replace("#", "", StringComparison.InvariantCulture);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(Color.ParseHex(input));
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
 | 
			
		||||
            }
 | 
			
		||||
public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
 | 
			
		||||
{
 | 
			
		||||
    public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        await Task.Yield();
 | 
			
		||||
 | 
			
		||||
        input = input.Replace("#", "", StringComparison.InvariantCulture);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            return TypeReaderResult.FromSuccess(Color.ParseHex(input));
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,107 +1,105 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly GamblingConfigService _gambling;
 | 
			
		||||
 | 
			
		||||
    public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private readonly GamblingConfigService _gambling;
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _gambling = gambling;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
 | 
			
		||||
    public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        await Task.Yield();
 | 
			
		||||
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
            return TypeReaderResult.FromError(CommandError.ParseFailed, "Input is empty.");
 | 
			
		||||
 | 
			
		||||
        var i = input.Trim().ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
        i = i.Replace("K", "000");
 | 
			
		||||
 | 
			
		||||
        //can't add m because it will conflict with max atm
 | 
			
		||||
 | 
			
		||||
        if (TryHandlePercentage(context, i, out var num))
 | 
			
		||||
            return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _gambling = gambling;
 | 
			
		||||
            var expr = new NCalc.Expression(i, NCalc.EvaluateOptions.IgnoreCase);
 | 
			
		||||
            expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
 | 
			
		||||
            var lon = (long)(decimal.Parse(expr.Evaluate().ToString()));
 | 
			
		||||
            return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        catch (Exception)
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Yield();
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
                return TypeReaderResult.FromError(CommandError.ParseFailed, "Input is empty.");
 | 
			
		||||
 | 
			
		||||
            var i = input.Trim().ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
            i = i.Replace("K", "000");
 | 
			
		||||
 | 
			
		||||
            //can't add m because it will conflict with max atm
 | 
			
		||||
 | 
			
		||||
            if (TryHandlePercentage(context, i, out var num))
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var expr = new NCalc.Expression(i, NCalc.EvaluateOptions.IgnoreCase);
 | 
			
		||||
                expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
 | 
			
		||||
                var lon = (long)(decimal.Parse(expr.Evaluate().ToString()));
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception)
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromError(CommandError.ParseFailed, $"Invalid input: {input}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx)
 | 
			
		||||
        {
 | 
			
		||||
            switch (name.ToUpperInvariant())
 | 
			
		||||
            {
 | 
			
		||||
                case "PI":
 | 
			
		||||
                    args.Result = Math.PI;
 | 
			
		||||
                    break;
 | 
			
		||||
                case "E":
 | 
			
		||||
                    args.Result = Math.E;
 | 
			
		||||
                    break;
 | 
			
		||||
                case "ALL":
 | 
			
		||||
                case "ALLIN":
 | 
			
		||||
                    args.Result = Cur(ctx);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "HALF":
 | 
			
		||||
                    args.Result = Cur(ctx) / 2;
 | 
			
		||||
                    break;
 | 
			
		||||
                case "MAX":
 | 
			
		||||
                    args.Result = Max(ctx);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex percentRegex = new Regex(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        private long Cur(ICommandContext ctx)
 | 
			
		||||
        {
 | 
			
		||||
            using var uow = _db.GetDbContext();
 | 
			
		||||
            return uow.DiscordUser.GetUserCurrency(ctx.User.Id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long Max(ICommandContext ctx)
 | 
			
		||||
        {
 | 
			
		||||
            var settings = _gambling.Data;
 | 
			
		||||
            var max = settings.MaxBet;
 | 
			
		||||
            return max == 0
 | 
			
		||||
                ? Cur(ctx)
 | 
			
		||||
                : max;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
 | 
			
		||||
        {
 | 
			
		||||
            num = 0;
 | 
			
		||||
            var m = percentRegex.Match(input);
 | 
			
		||||
            if (m.Captures.Count != 0)
 | 
			
		||||
            {
 | 
			
		||||
                if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
                num = (long)(Cur(ctx) * (percent / 100.0f));
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
            return TypeReaderResult.FromError(CommandError.ParseFailed, $"Invalid input: {input}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx)
 | 
			
		||||
    {
 | 
			
		||||
        switch (name.ToUpperInvariant())
 | 
			
		||||
        {
 | 
			
		||||
            case "PI":
 | 
			
		||||
                args.Result = Math.PI;
 | 
			
		||||
                break;
 | 
			
		||||
            case "E":
 | 
			
		||||
                args.Result = Math.E;
 | 
			
		||||
                break;
 | 
			
		||||
            case "ALL":
 | 
			
		||||
            case "ALLIN":
 | 
			
		||||
                args.Result = Cur(ctx);
 | 
			
		||||
                break;
 | 
			
		||||
            case "HALF":
 | 
			
		||||
                args.Result = Cur(ctx) / 2;
 | 
			
		||||
                break;
 | 
			
		||||
            case "MAX":
 | 
			
		||||
                args.Result = Max(ctx);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex percentRegex = new Regex(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    private long Cur(ICommandContext ctx)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.DiscordUser.GetUserCurrency(ctx.User.Id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long Max(ICommandContext ctx)
 | 
			
		||||
    {
 | 
			
		||||
        var settings = _gambling.Data;
 | 
			
		||||
        var max = settings.MaxBet;
 | 
			
		||||
        return max == 0
 | 
			
		||||
            ? Cur(ctx)
 | 
			
		||||
            : max;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
 | 
			
		||||
    {
 | 
			
		||||
        num = 0;
 | 
			
		||||
        var m = percentRegex.Match(input);
 | 
			
		||||
        if (m.Captures.Count != 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            num = (long)(Cur(ctx) * (percent / 100.0f));
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +1,23 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Common.TypeReaders.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders
 | 
			
		||||
namespace NadekoBot.Common.TypeReaders;
 | 
			
		||||
 | 
			
		||||
public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
 | 
			
		||||
{
 | 
			
		||||
    public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
 | 
			
		||||
    public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.Unsuccessful, "Input is empty."));
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.Unsuccessful, "Input is empty."));
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var time = StoopidTime.FromInput(input);
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromSuccess(time));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
 | 
			
		||||
            }
 | 
			
		||||
            var time = StoopidTime.FromInput(input);
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(time));
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,11 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
public class CommentAttribute : Attribute
 | 
			
		||||
{
 | 
			
		||||
    public class CommentAttribute : Attribute
 | 
			
		||||
    {
 | 
			
		||||
        public string Comment { get; }
 | 
			
		||||
    public string Comment { get; }
 | 
			
		||||
 | 
			
		||||
        public CommentAttribute(string comment)
 | 
			
		||||
        {
 | 
			
		||||
            Comment = comment;
 | 
			
		||||
        }
 | 
			
		||||
    public CommentAttribute(string comment)
 | 
			
		||||
    {
 | 
			
		||||
        Comment = comment;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +1,69 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
using YamlDotNet.Serialization.TypeInspectors;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class CommentGatheringTypeInspector : TypeInspectorSkeleton
 | 
			
		||||
{
 | 
			
		||||
    public class CommentGatheringTypeInspector : TypeInspectorSkeleton
 | 
			
		||||
    private readonly ITypeInspector innerTypeDescriptor;
 | 
			
		||||
 | 
			
		||||
    public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ITypeInspector innerTypeDescriptor;
 | 
			
		||||
        this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
 | 
			
		||||
    public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
 | 
			
		||||
    {
 | 
			
		||||
        return innerTypeDescriptor
 | 
			
		||||
            .GetProperties(type, container)
 | 
			
		||||
            .Select(d => new CommentsPropertyDescriptor(d));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IPropertyDescriptor baseDescriptor;
 | 
			
		||||
 | 
			
		||||
        public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
 | 
			
		||||
        {
 | 
			
		||||
            this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
            this.baseDescriptor = baseDescriptor;
 | 
			
		||||
            Name = baseDescriptor.Name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
 | 
			
		||||
        {
 | 
			
		||||
            return innerTypeDescriptor
 | 
			
		||||
                .GetProperties(type, container)
 | 
			
		||||
                .Select(d => new CommentsPropertyDescriptor(d));
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        public Type Type { get { return baseDescriptor.Type; } }
 | 
			
		||||
 | 
			
		||||
        public Type TypeOverride {
 | 
			
		||||
            get { return baseDescriptor.TypeOverride; }
 | 
			
		||||
            set { baseDescriptor.TypeOverride = value; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
 | 
			
		||||
        public int Order { get; set; }
 | 
			
		||||
 | 
			
		||||
        public ScalarStyle ScalarStyle {
 | 
			
		||||
            get { return baseDescriptor.ScalarStyle; }
 | 
			
		||||
            set { baseDescriptor.ScalarStyle = value; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool CanWrite { get { return baseDescriptor.CanWrite; } }
 | 
			
		||||
 | 
			
		||||
        public void Write(object target, object value)
 | 
			
		||||
        {
 | 
			
		||||
            private readonly IPropertyDescriptor baseDescriptor;
 | 
			
		||||
            baseDescriptor.Write(target, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
 | 
			
		||||
            {
 | 
			
		||||
                this.baseDescriptor = baseDescriptor;
 | 
			
		||||
                Name = baseDescriptor.Name;
 | 
			
		||||
            }
 | 
			
		||||
        public T GetCustomAttribute<T>() where T : Attribute
 | 
			
		||||
        {
 | 
			
		||||
            return baseDescriptor.GetCustomAttribute<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            public string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
            public Type Type { get { return baseDescriptor.Type; } }
 | 
			
		||||
 | 
			
		||||
            public Type TypeOverride {
 | 
			
		||||
                get { return baseDescriptor.TypeOverride; }
 | 
			
		||||
                set { baseDescriptor.TypeOverride = value; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public int Order { get; set; }
 | 
			
		||||
 | 
			
		||||
            public ScalarStyle ScalarStyle {
 | 
			
		||||
                get { return baseDescriptor.ScalarStyle; }
 | 
			
		||||
                set { baseDescriptor.ScalarStyle = value; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public bool CanWrite { get { return baseDescriptor.CanWrite; } }
 | 
			
		||||
 | 
			
		||||
            public void Write(object target, object value)
 | 
			
		||||
            {
 | 
			
		||||
                baseDescriptor.Write(target, value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public T GetCustomAttribute<T>() where T : Attribute
 | 
			
		||||
            {
 | 
			
		||||
                return baseDescriptor.GetCustomAttribute<T>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public IObjectDescriptor Read(object target)
 | 
			
		||||
            {
 | 
			
		||||
                var comment = baseDescriptor.GetCustomAttribute<CommentAttribute>();
 | 
			
		||||
                return comment != null
 | 
			
		||||
                    ? new CommentsObjectDescriptor(baseDescriptor.Read(target), comment.Comment)
 | 
			
		||||
                    : baseDescriptor.Read(target);
 | 
			
		||||
            }
 | 
			
		||||
        public IObjectDescriptor Read(object target)
 | 
			
		||||
        {
 | 
			
		||||
            var comment = baseDescriptor.GetCustomAttribute<CommentAttribute>();
 | 
			
		||||
            return comment != null
 | 
			
		||||
                ? new CommentsObjectDescriptor(baseDescriptor.Read(target), comment.Comment)
 | 
			
		||||
                : baseDescriptor.Read(target);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +1,22 @@
 | 
			
		||||
using System;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public sealed class CommentsObjectDescriptor : IObjectDescriptor
 | 
			
		||||
{
 | 
			
		||||
    public sealed class CommentsObjectDescriptor : IObjectDescriptor
 | 
			
		||||
    private readonly IObjectDescriptor innerDescriptor;
 | 
			
		||||
 | 
			
		||||
    public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IObjectDescriptor innerDescriptor;
 | 
			
		||||
 | 
			
		||||
        public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
 | 
			
		||||
        {
 | 
			
		||||
            this.innerDescriptor = innerDescriptor;
 | 
			
		||||
            this.Comment = comment;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Comment { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public object Value { get { return innerDescriptor.Value; } }
 | 
			
		||||
        public Type Type { get { return innerDescriptor.Type; } }
 | 
			
		||||
        public Type StaticType { get { return innerDescriptor.StaticType; } }
 | 
			
		||||
        public ScalarStyle ScalarStyle { get { return innerDescriptor.ScalarStyle; } }
 | 
			
		||||
        this.innerDescriptor = innerDescriptor;
 | 
			
		||||
        this.Comment = comment;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public string Comment { get; private set; }
 | 
			
		||||
 | 
			
		||||
    public object Value { get { return innerDescriptor.Value; } }
 | 
			
		||||
    public Type Type { get { return innerDescriptor.Type; } }
 | 
			
		||||
    public Type StaticType { get { return innerDescriptor.StaticType; } }
 | 
			
		||||
    public ScalarStyle ScalarStyle { get { return innerDescriptor.ScalarStyle; } }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,24 +3,23 @@ using YamlDotNet.Core.Events;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
using YamlDotNet.Serialization.ObjectGraphVisitors;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
 | 
			
		||||
{
 | 
			
		||||
    public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
 | 
			
		||||
    public CommentsObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
 | 
			
		||||
        : base(nextVisitor)
 | 
			
		||||
    {
 | 
			
		||||
        public CommentsObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
 | 
			
		||||
            : base(nextVisitor)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
 | 
			
		||||
        {
 | 
			
		||||
            var commentsDescriptor = value as CommentsObjectDescriptor;
 | 
			
		||||
            if (commentsDescriptor != null && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
 | 
			
		||||
            {
 | 
			
		||||
                context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.EnterMapping(key, value, context);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
 | 
			
		||||
    {
 | 
			
		||||
        var commentsDescriptor = value as CommentsObjectDescriptor;
 | 
			
		||||
        if (commentsDescriptor != null && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
 | 
			
		||||
        {
 | 
			
		||||
            context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return base.EnterMapping(key, value, context);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,31 +2,30 @@
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
using YamlDotNet.Serialization.EventEmitters;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
 | 
			
		||||
{
 | 
			
		||||
    public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
 | 
			
		||||
    public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)
 | 
			
		||||
        : base(nextEmitter) { }
 | 
			
		||||
 | 
			
		||||
    public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
 | 
			
		||||
    {
 | 
			
		||||
        public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)
 | 
			
		||||
            : base(nextEmitter) { }
 | 
			
		||||
 | 
			
		||||
        public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
 | 
			
		||||
        if (typeof(string).IsAssignableFrom(eventInfo.Source.Type))
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (typeof(string).IsAssignableFrom(eventInfo.Source.Type))
 | 
			
		||||
            string value = eventInfo.Source.Value as string;
 | 
			
		||||
            if (!string.IsNullOrEmpty(value))
 | 
			
		||||
            {
 | 
			
		||||
                string value = eventInfo.Source.Value as string;
 | 
			
		||||
                if (!string.IsNullOrEmpty(value))
 | 
			
		||||
                {
 | 
			
		||||
                    bool isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
 | 
			
		||||
                    if (isMultiLine)
 | 
			
		||||
                        eventInfo = new ScalarEventInfo(eventInfo.Source)
 | 
			
		||||
                        {
 | 
			
		||||
                            Style = ScalarStyle.Literal,
 | 
			
		||||
                        };
 | 
			
		||||
                }
 | 
			
		||||
                bool isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
 | 
			
		||||
                if (isMultiLine)
 | 
			
		||||
                    eventInfo = new ScalarEventInfo(eventInfo.Source)
 | 
			
		||||
                    {
 | 
			
		||||
                        Style = ScalarStyle.Literal,
 | 
			
		||||
                    };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            nextEmitter.Emit(eventInfo, emitter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        nextEmitter.Emit(eventInfo, emitter);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,52 +1,50 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Core.Events;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class Rgba32Converter : IYamlTypeConverter
 | 
			
		||||
{
 | 
			
		||||
    public class Rgba32Converter : IYamlTypeConverter
 | 
			
		||||
    public bool Accepts(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        public bool Accepts(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            return type == typeof(Rgba32);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var scalar = parser.Consume<Scalar>();
 | 
			
		||||
            var result = Rgba32.ParseHex(scalar.Value);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var color = (Rgba32)value;
 | 
			
		||||
            var val = (uint) (color.B << 0 | color.G << 8 | color.R << 16);
 | 
			
		||||
            emitter.Emit(new Scalar(val.ToString("X6").ToLower()));
 | 
			
		||||
        }
 | 
			
		||||
        return type == typeof(Rgba32);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public class CultureInfoConverter : IYamlTypeConverter
 | 
			
		||||
 | 
			
		||||
    public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        public bool Accepts(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            return type == typeof(CultureInfo);
 | 
			
		||||
        }
 | 
			
		||||
        var scalar = parser.Consume<Scalar>();
 | 
			
		||||
        var result = Rgba32.ParseHex(scalar.Value);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var scalar = parser.Consume<Scalar>();
 | 
			
		||||
            var result = new CultureInfo(scalar.Value);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
    public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var color = (Rgba32)value;
 | 
			
		||||
        var val = (uint) (color.B << 0 | color.G << 8 | color.R << 16);
 | 
			
		||||
        emitter.Emit(new Scalar(val.ToString("X6").ToLower()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
public class CultureInfoConverter : IYamlTypeConverter
 | 
			
		||||
{
 | 
			
		||||
    public bool Accepts(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        return type == typeof(CultureInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var ci = (CultureInfo)value;
 | 
			
		||||
            emitter.Emit(new Scalar(ci.Name));
 | 
			
		||||
        }
 | 
			
		||||
    public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var scalar = parser.Consume<Scalar>();
 | 
			
		||||
        var result = new CultureInfo(scalar.Value);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var ci = (CultureInfo)value;
 | 
			
		||||
        emitter.Emit(new Scalar(ci.Name));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +1,26 @@
 | 
			
		||||
using System;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Core;
 | 
			
		||||
using YamlDotNet.Core.Events;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class UriConverter : IYamlTypeConverter
 | 
			
		||||
{
 | 
			
		||||
    public class UriConverter : IYamlTypeConverter
 | 
			
		||||
    public bool Accepts(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        public bool Accepts(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            return type == typeof(Uri);
 | 
			
		||||
        }
 | 
			
		||||
        return type == typeof(Uri);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var scalar = parser.Consume<Scalar>();
 | 
			
		||||
            var result = new Uri(scalar.Value);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
    public object ReadYaml(IParser parser, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var scalar = parser.Consume<Scalar>();
 | 
			
		||||
        var result = new Uri(scalar.Value);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
        {
 | 
			
		||||
            var uri = (Uri)value;
 | 
			
		||||
            emitter.Emit(new Scalar(uri.ToString()));
 | 
			
		||||
        }
 | 
			
		||||
    public void WriteYaml(IEmitter emitter, object value, Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var uri = (Uri)value;
 | 
			
		||||
        emitter.Emit(new Scalar(uri.ToString()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +1,25 @@
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
{
 | 
			
		||||
    public class Yaml
 | 
			
		||||
    {
 | 
			
		||||
        public static ISerializer Serializer => new SerializerBuilder()
 | 
			
		||||
            .WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
 | 
			
		||||
            .WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
 | 
			
		||||
            .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
 | 
			
		||||
            .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
 | 
			
		||||
            .WithIndentedSequences()
 | 
			
		||||
            .WithTypeConverter(new Rgba32Converter())
 | 
			
		||||
            .WithTypeConverter(new CultureInfoConverter())
 | 
			
		||||
            .WithTypeConverter(new UriConverter())
 | 
			
		||||
            .Build();
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
        public static IDeserializer Deserializer => new DeserializerBuilder()
 | 
			
		||||
            .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
 | 
			
		||||
            .WithTypeConverter(new Rgba32Converter())
 | 
			
		||||
            .WithTypeConverter(new CultureInfoConverter())
 | 
			
		||||
            .WithTypeConverter(new UriConverter())
 | 
			
		||||
            .IgnoreUnmatchedProperties()
 | 
			
		||||
            .Build();
 | 
			
		||||
    }
 | 
			
		||||
public class Yaml
 | 
			
		||||
{
 | 
			
		||||
    public static ISerializer Serializer => new SerializerBuilder()
 | 
			
		||||
        .WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
 | 
			
		||||
        .WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
 | 
			
		||||
        .WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
 | 
			
		||||
        .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
 | 
			
		||||
        .WithIndentedSequences()
 | 
			
		||||
        .WithTypeConverter(new Rgba32Converter())
 | 
			
		||||
        .WithTypeConverter(new CultureInfoConverter())
 | 
			
		||||
        .WithTypeConverter(new UriConverter())
 | 
			
		||||
        .Build();
 | 
			
		||||
 | 
			
		||||
    public static IDeserializer Deserializer => new DeserializerBuilder()
 | 
			
		||||
        .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
 | 
			
		||||
        .WithTypeConverter(new Rgba32Converter())
 | 
			
		||||
        .WithTypeConverter(new CultureInfoConverter())
 | 
			
		||||
        .WithTypeConverter(new UriConverter())
 | 
			
		||||
        .IgnoreUnmatchedProperties()
 | 
			
		||||
        .Build();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,58 +1,57 @@
 | 
			
		||||
namespace NadekoBot.Common.Yml
 | 
			
		||||
namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class YamlHelper
 | 
			
		||||
{
 | 
			
		||||
    public class YamlHelper
 | 
			
		||||
    // https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// This is modified code from yamldotnet's repo which handles parsing unicode code points
 | 
			
		||||
    /// it is needed as yamldotnet doesn't support unescaped unicode characters 
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="point">Unicode code point</param>
 | 
			
		||||
    /// <returns>Actual character</returns>
 | 
			
		||||
    public static string UnescapeUnicodeCodePoint(string point)
 | 
			
		||||
    {
 | 
			
		||||
        // https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// This is modified code from yamldotnet's repo which handles parsing unicode code points
 | 
			
		||||
        /// it is needed as yamldotnet doesn't support unescaped unicode characters 
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="point">Unicode code point</param>
 | 
			
		||||
        /// <returns>Actual character</returns>
 | 
			
		||||
        public static string UnescapeUnicodeCodePoint(string point)
 | 
			
		||||
        var character = 0;
 | 
			
		||||
 | 
			
		||||
        // Scan the character value.
 | 
			
		||||
 | 
			
		||||
        foreach(var c in point)
 | 
			
		||||
        {
 | 
			
		||||
            var character = 0;
 | 
			
		||||
 | 
			
		||||
            // Scan the character value.
 | 
			
		||||
 | 
			
		||||
            foreach(var c in point)
 | 
			
		||||
            {
 | 
			
		||||
                if (!IsHex(c))
 | 
			
		||||
                {
 | 
			
		||||
                    return point;
 | 
			
		||||
                }
 | 
			
		||||
                character = (character << 4) + AsHex(c);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check the value and write the character.
 | 
			
		||||
 | 
			
		||||
            if (character >= 0xD800 && character <= 0xDFFF || character > 0x10FFFF)
 | 
			
		||||
            if (!IsHex(c))
 | 
			
		||||
            {
 | 
			
		||||
                return point;
 | 
			
		||||
            }
 | 
			
		||||
            character = (character << 4) + AsHex(c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            return char.ConvertFromUtf32(character);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static bool IsHex(char c)
 | 
			
		||||
        // Check the value and write the character.
 | 
			
		||||
 | 
			
		||||
        if (character >= 0xD800 && character <= 0xDFFF || character > 0x10FFFF)
 | 
			
		||||
        {
 | 
			
		||||
            return
 | 
			
		||||
                (c >= '0' && c <= '9') ||
 | 
			
		||||
                (c >= 'A' && c <= 'F') ||
 | 
			
		||||
                (c >= 'a' && c <= 'f');
 | 
			
		||||
            return point;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return char.ConvertFromUtf32(character);
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        public static int AsHex(char c)
 | 
			
		||||
    public static bool IsHex(char c)
 | 
			
		||||
    {
 | 
			
		||||
        return
 | 
			
		||||
            (c >= '0' && c <= '9') ||
 | 
			
		||||
            (c >= 'A' && c <= 'F') ||
 | 
			
		||||
            (c >= 'a' && c <= 'f');
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public static int AsHex(char c)
 | 
			
		||||
    {
 | 
			
		||||
        if (c <= '9')
 | 
			
		||||
        {
 | 
			
		||||
            if (c <= '9')
 | 
			
		||||
            {
 | 
			
		||||
                return c - '0';
 | 
			
		||||
            }
 | 
			
		||||
            if (c <= 'F')
 | 
			
		||||
            {
 | 
			
		||||
                return c - 'A' + 10;
 | 
			
		||||
            }
 | 
			
		||||
            return c - 'a' + 10;
 | 
			
		||||
            return c - '0';
 | 
			
		||||
        }
 | 
			
		||||
        if (c <= 'F')
 | 
			
		||||
        {
 | 
			
		||||
            return c - 'A' + 10;
 | 
			
		||||
        }
 | 
			
		||||
        return c - 'a' + 10;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +1,45 @@
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Db
 | 
			
		||||
namespace NadekoBot.Db;
 | 
			
		||||
 | 
			
		||||
public static class ClubExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class ClubExtensions
 | 
			
		||||
    {
 | 
			
		||||
        private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
 | 
			
		||||
            => clubs.Include(x => x.Owner)
 | 
			
		||||
                .Include(x => x.Applicants)
 | 
			
		||||
                    .ThenInclude(x => x.User)
 | 
			
		||||
                .Include(x => x.Bans)
 | 
			
		||||
                    .ThenInclude(x => x.User)
 | 
			
		||||
                .Include(x => x.Users)
 | 
			
		||||
                .AsQueryable();
 | 
			
		||||
        public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
            => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
 | 
			
		||||
    private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
 | 
			
		||||
        => clubs.Include(x => x.Owner)
 | 
			
		||||
            .Include(x => x.Applicants)
 | 
			
		||||
            .ThenInclude(x => x.User)
 | 
			
		||||
            .Include(x => x.Bans)
 | 
			
		||||
            .ThenInclude(x => x.User)
 | 
			
		||||
            .Include(x => x.Users)
 | 
			
		||||
            .AsQueryable();
 | 
			
		||||
    public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
        => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
 | 
			
		||||
        
 | 
			
		||||
        public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
            => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
 | 
			
		||||
                                                                || c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
 | 
			
		||||
    public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
        => Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
 | 
			
		||||
                                              || c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
 | 
			
		||||
 | 
			
		||||
        public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
            => Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
 | 
			
		||||
    public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
 | 
			
		||||
        => Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
 | 
			
		||||
 | 
			
		||||
        public static ClubInfo GetByName(this DbSet<ClubInfo> clubs, string name, int discrim)
 | 
			
		||||
            => Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
 | 
			
		||||
    public static ClubInfo GetByName(this DbSet<ClubInfo> clubs, string name, int discrim)
 | 
			
		||||
        => Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
 | 
			
		||||
 | 
			
		||||
        public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
 | 
			
		||||
            => Include(clubs)
 | 
			
		||||
                .Where(x => x.Name.ToUpper() == name.ToUpper())
 | 
			
		||||
                .Select(x => x.Discrim)
 | 
			
		||||
                .DefaultIfEmpty()
 | 
			
		||||
                .Max() + 1;
 | 
			
		||||
    public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
 | 
			
		||||
        => Include(clubs)
 | 
			
		||||
            .Where(x => x.Name.ToUpper() == name.ToUpper())
 | 
			
		||||
            .Select(x => x.Discrim)
 | 
			
		||||
            .DefaultIfEmpty()
 | 
			
		||||
            .Max() + 1;
 | 
			
		||||
 | 
			
		||||
        public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
 | 
			
		||||
        {
 | 
			
		||||
            return clubs
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .OrderByDescending(x => x.Xp)
 | 
			
		||||
                .Skip(page * 9)
 | 
			
		||||
                .Take(9)
 | 
			
		||||
                .ToList();
 | 
			
		||||
        }
 | 
			
		||||
    public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
 | 
			
		||||
    {
 | 
			
		||||
        return clubs
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .OrderByDescending(x => x.Xp)
 | 
			
		||||
            .Skip(page * 9)
 | 
			
		||||
            .Take(9)
 | 
			
		||||
            .ToList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,18 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Db
 | 
			
		||||
namespace NadekoBot.Db;
 | 
			
		||||
 | 
			
		||||
public static class CurrencyTransactionExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class CurrencyTransactionExtensions
 | 
			
		||||
    public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
 | 
			
		||||
    {
 | 
			
		||||
        public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
 | 
			
		||||
        {
 | 
			
		||||
            return set.AsQueryable()
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .Where(x => x.UserId == userId)
 | 
			
		||||
                .OrderByDescending(x => x.DateAdded)
 | 
			
		||||
                .Skip(15 * page)
 | 
			
		||||
                .Take(15)
 | 
			
		||||
                .ToList();
 | 
			
		||||
        }
 | 
			
		||||
        return set.AsQueryable()
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .Where(x => x.UserId == userId)
 | 
			
		||||
            .OrderByDescending(x => x.DateAdded)
 | 
			
		||||
            .Skip(15 * page)
 | 
			
		||||
            .Take(15)
 | 
			
		||||
            .ToList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +1,27 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using LinqToDB;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Db
 | 
			
		||||
namespace NadekoBot.Db;
 | 
			
		||||
 | 
			
		||||
public static class CustomReactionsExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class CustomReactionsExtensions
 | 
			
		||||
    public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
 | 
			
		||||
        {
 | 
			
		||||
            return crs.Delete(x => x.GuildId == guildId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
 | 
			
		||||
        {
 | 
			
		||||
            return crs
 | 
			
		||||
                .AsNoTracking()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => x.GuildId == id)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
 | 
			
		||||
        {
 | 
			
		||||
            return crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
 | 
			
		||||
        }
 | 
			
		||||
        return crs.Delete(x => x.GuildId == guildId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
 | 
			
		||||
    {
 | 
			
		||||
        return crs
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .AsQueryable()
 | 
			
		||||
            .Where(x => x.GuildId == id)
 | 
			
		||||
            .ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
 | 
			
		||||
    {
 | 
			
		||||
        return crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Db
 | 
			
		||||
namespace NadekoBot.Db;
 | 
			
		||||
 | 
			
		||||
public static class DbExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class DbExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static T GetById<T>(this DbSet<T> set, int id) where T: DbEntity
 | 
			
		||||
            => set.FirstOrDefault(x => x.Id == id);
 | 
			
		||||
    }
 | 
			
		||||
    public static T GetById<T>(this DbSet<T> set, int id) where T: DbEntity
 | 
			
		||||
        => set.FirstOrDefault(x => x.Id == id);
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user