Huge cleanup,
rewrite of the NadekoBot.cs, way services are loaded has changed. Updated discord.net.
This commit is contained in:
		@@ -9,17 +9,10 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class CommandTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
        private readonly CommandHandler _cmdHandler;
 | 
			
		||||
 | 
			
		||||
        public CommandTypeReader(CommandService cmds, CommandHandler cmdHandler)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
            _cmdHandler = cmdHandler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            var _cmds = ((INServiceProvider)services).GetService<CommandService>();
 | 
			
		||||
            var _cmdHandler = ((INServiceProvider)services).GetService<CommandHandler>();
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var prefix = _cmdHandler.GetPrefix(context.Guild);
 | 
			
		||||
            if (!input.StartsWith(prefix.ToUpperInvariant()))
 | 
			
		||||
@@ -38,17 +31,12 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
 | 
			
		||||
    public class CommandOrCrTypeReader : CommandTypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CustomReactionsService _crs;
 | 
			
		||||
 | 
			
		||||
        public CommandOrCrTypeReader(CustomReactionsService crs, CommandService cmds, CommandHandler cmdHandler) : base(cmds, cmdHandler)
 | 
			
		||||
        {
 | 
			
		||||
            _crs = crs;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
 | 
			
		||||
        public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
            var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>();
 | 
			
		||||
 | 
			
		||||
            if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
 | 
			
		||||
@@ -65,7 +53,7 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cmd = await base.Read(context, input, _);
 | 
			
		||||
            var cmd = await base.Read(context, input, services);
 | 
			
		||||
            if (cmd.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@@ -7,15 +8,9 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class GuildDateTimeTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly GuildTimezoneService _gts;
 | 
			
		||||
 | 
			
		||||
        public GuildDateTimeTypeReader(GuildTimezoneService gts)
 | 
			
		||||
        {
 | 
			
		||||
            _gts = gts;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            var _gts = (GuildTimezoneService)services.GetService(typeof(GuildTimezoneService));
 | 
			
		||||
            if (!DateTime.TryParse(input, out var dt))
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageRoles)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task SetMuteRole([Remainder] string name)
 | 
			
		||||
            {
 | 
			
		||||
                name = name.Trim();
 | 
			
		||||
@@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageRoles)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public Task SetMuteRole([Remainder] IRole role)
 | 
			
		||||
                => SetMuteRole(role.Name);
 | 
			
		||||
 | 
			
		||||
@@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageRoles)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.MuteMembers)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Mute(IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
@@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageRoles)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.MuteMembers)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Mute(int minutes, IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                if (minutes < 1 || minutes > 1440)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        public class PrefixCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public new async Task Prefix()
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalized("prefix_current", Format.Code(_cmdHandler.GetPrefix(Context.Guild))).ConfigureAwait(false);
 | 
			
		||||
@@ -21,7 +21,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.Administrator)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public new async Task Prefix([Remainder]string prefix)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(prefix))
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(ChannelPermission.ManageMessages)]
 | 
			
		||||
            [RequireBotPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Prune(int count)
 | 
			
		||||
            {
 | 
			
		||||
                count++;
 | 
			
		||||
@@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(ChannelPermission.ManageMessages)]
 | 
			
		||||
            [RequireBotPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Prune(IGuildUser user, int count = 100)
 | 
			
		||||
            {
 | 
			
		||||
                if (user.Id == Context.User.Id)
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task SlowmodeWhitelist(IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                var siu = new SlowmodeIgnoredUser
 | 
			
		||||
@@ -99,7 +99,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task SlowmodeWhitelist(IRole role)
 | 
			
		||||
            {
 | 
			
		||||
                var sir = new SlowmodeIgnoredRole
 | 
			
		||||
 
 | 
			
		||||
@@ -289,7 +289,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageNicknames)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task SetNick([Remainder] string newNick = null)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(newNick))
 | 
			
		||||
@@ -303,7 +303,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireBotPermission(GuildPermission.ManageNicknames)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageNicknames)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task SetNick(IGuildUser gu, [Remainder] string newNick = null)
 | 
			
		||||
            {
 | 
			
		||||
                await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -132,27 +132,27 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.BanMembers)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(2)]
 | 
			
		||||
            public Task Warnlog(int page, IGuildUser user)
 | 
			
		||||
                => Warnlog(page, user.Id);
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(3)]
 | 
			
		||||
            public Task Warnlog(IGuildUser user)
 | 
			
		||||
                => Context.User.Id == user.Id || ((IGuildUser)Context.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.BanMembers)]
 | 
			
		||||
            [Priority(3)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public Task Warnlog(int page, ulong userId)
 | 
			
		||||
                => InternalWarnlog(userId, page - 1);
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.BanMembers)]
 | 
			
		||||
            [Priority(2)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public Task Warnlog(ulong userId)
 | 
			
		||||
                => InternalWarnlog(userId, 0);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,239 +0,0 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.ClashOfClans;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.ClashOfClans
 | 
			
		||||
{
 | 
			
		||||
    public class ClashOfClans : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ClashOfClansService _service;
 | 
			
		||||
 | 
			
		||||
        public ClashOfClans(ClashOfClansService service)
 | 
			
		||||
        {
 | 
			
		||||
            _service = service;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
        public async Task CreateWar(int size, [Remainder] string enemyClan = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(enemyClan))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (size < 10 || size > 50 || size % 5 != 0)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("invalid_size").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            List<ClashWar> wars;
 | 
			
		||||
            if (!_service.ClashWars.TryGetValue(Context.Guild.Id, out wars))
 | 
			
		||||
            {
 | 
			
		||||
                wars = new List<ClashWar>();
 | 
			
		||||
                if (!_service.ClashWars.TryAdd(Context.Guild.Id, wars))
 | 
			
		||||
                    return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var cw = await _service.CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id);
 | 
			
		||||
 | 
			
		||||
            wars.Add(cw);
 | 
			
		||||
            await ReplyErrorLocalized("war_created", _service.ShortPrint(cw)).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task StartWar([Remainder] string number = null)
 | 
			
		||||
        {
 | 
			
		||||
            int num = 0;
 | 
			
		||||
            int.TryParse(number, out num);
 | 
			
		||||
 | 
			
		||||
            var warsInfo = _service.GetWarInfo(Context.Guild, num);
 | 
			
		||||
            if (warsInfo == null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var war = warsInfo.Item1[warsInfo.Item2];
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                war.Start();
 | 
			
		||||
                await ReplyConfirmLocalized("war_started", _service.ShortPrint(war)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_already_started", _service.ShortPrint(war)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            _service.SaveWar(war);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task ListWar([Remainder] string number = null)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            // if number is null, print all wars in a short way
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(number))
 | 
			
		||||
            {
 | 
			
		||||
                //check if there are any wars
 | 
			
		||||
                List<ClashWar> wars = null;
 | 
			
		||||
                _service.ClashWars.TryGetValue(Context.Guild.Id, out wars);
 | 
			
		||||
                if (wars == null || wars.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("no_active_wars").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var sb = new StringBuilder();
 | 
			
		||||
                sb.AppendLine("**-------------------------**");
 | 
			
		||||
                for (var i = 0; i < wars.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.AppendLine($"**#{i + 1}.**  `{GetText("enemy")}:` **{wars[i].EnemyClan}**");
 | 
			
		||||
                    sb.AppendLine($"\t\t`{GetText("size")}:` **{wars[i].Size} v {wars[i].Size}**");
 | 
			
		||||
                    sb.AppendLine("**-------------------------**");
 | 
			
		||||
                }
 | 
			
		||||
                await Context.Channel.SendConfirmAsync(GetText("list_active_wars"), sb.ToString()).ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var num = 0;
 | 
			
		||||
            int.TryParse(number, out num);
 | 
			
		||||
            //if number is not null, print the war needed
 | 
			
		||||
            var warsInfo = _service.GetWarInfo(Context.Guild, num);
 | 
			
		||||
            if (warsInfo == null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var war = warsInfo.Item1[warsInfo.Item2];
 | 
			
		||||
            await Context.Channel.SendConfirmAsync(_service.Localize(war, "info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), _service.ToPrettyString(war)).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task BaseCall(int number, int baseNumber, [Remainder] string other_name = null)
 | 
			
		||||
        {
 | 
			
		||||
            var warsInfo = _service.GetWarInfo(Context.Guild, number);
 | 
			
		||||
            if (warsInfo == null || warsInfo.Item1.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var usr =
 | 
			
		||||
                string.IsNullOrWhiteSpace(other_name) ?
 | 
			
		||||
                Context.User.Username :
 | 
			
		||||
                other_name;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var war = warsInfo.Item1[warsInfo.Item2];
 | 
			
		||||
                _service.Call(war, usr, baseNumber - 1);
 | 
			
		||||
                _service.SaveWar(war);
 | 
			
		||||
                await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, _service.ShortPrint(war)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task CallFinish1(int number, int baseNumber = 0)
 | 
			
		||||
        {
 | 
			
		||||
            await FinishClaim(number, baseNumber - 1, 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task CallFinish2(int number, int baseNumber = 0)
 | 
			
		||||
        {
 | 
			
		||||
            await FinishClaim(number, baseNumber - 1, 2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task CallFinish(int number, int baseNumber = 0)
 | 
			
		||||
        {
 | 
			
		||||
            await FinishClaim(number, baseNumber - 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task EndWar(int number)
 | 
			
		||||
        {
 | 
			
		||||
            var warsInfo = _service.GetWarInfo(Context.Guild, number);
 | 
			
		||||
            if (warsInfo == null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var war = warsInfo.Item1[warsInfo.Item2];
 | 
			
		||||
            war.End();
 | 
			
		||||
            _service.SaveWar(war);
 | 
			
		||||
            await ReplyConfirmLocalized("war_ended", _service.ShortPrint(warsInfo.Item1[warsInfo.Item2])).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            warsInfo.Item1.RemoveAt(warsInfo.Item2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task Unclaim(int number, [Remainder] string otherName = null)
 | 
			
		||||
        {
 | 
			
		||||
            var warsInfo = _service.GetWarInfo(Context.Guild, number);
 | 
			
		||||
            if (warsInfo == null || warsInfo.Item1.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var usr =
 | 
			
		||||
                string.IsNullOrWhiteSpace(otherName) ?
 | 
			
		||||
                Context.User.Username :
 | 
			
		||||
                otherName;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var war = warsInfo.Item1[warsInfo.Item2];
 | 
			
		||||
                var baseNumber = _service.Uncall(war, usr);
 | 
			
		||||
                _service.SaveWar(war);
 | 
			
		||||
                await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task FinishClaim(int number, int baseNumber, int stars = 3)
 | 
			
		||||
        {
 | 
			
		||||
            var warInfo = _service.GetWarInfo(Context.Guild, number);
 | 
			
		||||
            if (warInfo == null || warInfo.Item1.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var war = warInfo.Item1[warInfo.Item2];
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (baseNumber == -1)
 | 
			
		||||
                {
 | 
			
		||||
                    baseNumber = _service.FinishClaim(war, Context.User.Username, stars);
 | 
			
		||||
                    _service.SaveWar(war);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _service.FinishClaim(war, baseNumber, stars);
 | 
			
		||||
                }
 | 
			
		||||
                await ReplyConfirmLocalized("base_destroyed", baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -84,7 +84,7 @@ namespace NadekoBot.Modules.CustomReactions
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task ListCustReact(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0 || page > 999)
 | 
			
		||||
@@ -130,7 +130,7 @@ namespace NadekoBot.Modules.CustomReactions
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task ListCustReact(All x)
 | 
			
		||||
        {
 | 
			
		||||
            CustomReaction[] customReactions;
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Roll(int num)
 | 
			
		||||
            {
 | 
			
		||||
                await InternalRoll(num, true).ConfigureAwait(false);
 | 
			
		||||
@@ -66,21 +66,21 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Rolluo(int num = 1)
 | 
			
		||||
            {
 | 
			
		||||
                await InternalRoll(num, false).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Roll(string arg)
 | 
			
		||||
            {
 | 
			
		||||
                await InternallDndRoll(arg, true).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Rolluo(string arg)
 | 
			
		||||
            {
 | 
			
		||||
                await InternallDndRoll(arg, false).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -197,12 +197,12 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6);
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public Task Divorce([Remainder]IGuildUser target) => Divorce(target.Id);
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Divorce([Remainder]ulong targetId)
 | 
			
		||||
            {
 | 
			
		||||
                if (targetId == Context.User.Id)
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task Cash([Remainder] IUser user = null)
 | 
			
		||||
        {
 | 
			
		||||
            if(user == null)
 | 
			
		||||
@@ -62,7 +62,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task Cash(ulong userId)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyConfirmLocalized("has", Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false);
 | 
			
		||||
@@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public Task Award(int amount, [Remainder] IGuildUser usr) =>
 | 
			
		||||
            Award(amount, usr.Id);
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +107,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        [Priority(2)]
 | 
			
		||||
        public async Task Award(int amount, [Remainder] IRole role)
 | 
			
		||||
        {
 | 
			
		||||
            var users = (await Context.Guild.GetUsersAsync())
 | 
			
		||||
 
 | 
			
		||||
@@ -82,14 +82,14 @@ namespace NadekoBot.Modules.Help
 | 
			
		||||
            await ConfirmLocalized("commands_instr", Prefix).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task H([Remainder] string fail)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalized("command_not_found").ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task H([Remainder] CommandInfo com = null)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = Context.Channel;
 | 
			
		||||
 
 | 
			
		||||
@@ -377,7 +377,7 @@ namespace NadekoBot.Modules.Music
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async Task SongRemove(int index)
 | 
			
		||||
        {
 | 
			
		||||
            if (index < 1)
 | 
			
		||||
@@ -406,7 +406,7 @@ namespace NadekoBot.Modules.Music
 | 
			
		||||
        public enum All { All }
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async Task SongRemove(All all)
 | 
			
		||||
        {
 | 
			
		||||
            var mp = _music.GetPlayerOrDefault(Context.Guild.Id);
 | 
			
		||||
@@ -853,41 +853,6 @@ namespace NadekoBot.Modules.Music
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        //todo readd goto
 | 
			
		||||
        //[NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        //[RequireContext(ContextType.Guild)]
 | 
			
		||||
        //public async Task Goto(int time)
 | 
			
		||||
        //{
 | 
			
		||||
        //    MusicPlayer musicPlayer;
 | 
			
		||||
        //    if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
 | 
			
		||||
        //        return;
 | 
			
		||||
        //    if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
 | 
			
		||||
        //        return;
 | 
			
		||||
 | 
			
		||||
        //    if (time < 0)
 | 
			
		||||
        //        return;
 | 
			
		||||
 | 
			
		||||
        //    var currentSong = musicPlayer.CurrentSong;
 | 
			
		||||
 | 
			
		||||
        //    if (currentSong == null)
 | 
			
		||||
        //        return;
 | 
			
		||||
 | 
			
		||||
        //    //currentSong.PrintStatusMessage = false;
 | 
			
		||||
        //    var gotoSong = currentSong.Clone();
 | 
			
		||||
        //    gotoSong.SkipTo = time;
 | 
			
		||||
        //    musicPlayer.AddSong(gotoSong, 0);
 | 
			
		||||
        //    musicPlayer.Next();
 | 
			
		||||
 | 
			
		||||
        //    var minutes = (time / 60).ToString();
 | 
			
		||||
        //    var seconds = (time % 60).ToString();
 | 
			
		||||
 | 
			
		||||
        //    if (minutes.Length == 1)
 | 
			
		||||
        //        minutes = "0" + minutes;
 | 
			
		||||
        //    if (seconds.Length == 1)
 | 
			
		||||
        //        seconds = "0" + seconds;
 | 
			
		||||
 | 
			
		||||
        //    await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false);
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
 
 | 
			
		||||
@@ -192,10 +192,18 @@ namespace NadekoBot.Modules.NSFW
 | 
			
		||||
            if (imgObj == null)
 | 
			
		||||
                await ReplyErrorLocalized("not_found").ConfigureAwait(false);
 | 
			
		||||
            else
 | 
			
		||||
                await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor()
 | 
			
		||||
                    .WithDescription($"{Context.User} [{tag ?? "url"}]({imgObj}) ")
 | 
			
		||||
                    .WithImageUrl(imgObj.FileUrl)
 | 
			
		||||
                    .WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
 | 
			
		||||
                    .WithFooter(efb => efb.WithText(type.ToString()));
 | 
			
		||||
 | 
			
		||||
                if (Uri.IsWellFormedUriString(imgObj.FileUrl, UriKind.Absolute))
 | 
			
		||||
                    embed.WithImageUrl(imgObj.FileUrl);
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Error($"Image link from {type} is not a proper Url: {imgObj.FileUrl}");
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,7 +25,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Mal([Remainder] string name)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
@@ -132,7 +132,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public Task Mal(IGuildUser usr) => Mal(usr.Username);
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            private const string _xkcdUrl = "https://xkcd.com";
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Xkcd(string arg = null)
 | 
			
		||||
            {
 | 
			
		||||
                if (arg?.ToLowerInvariant().Trim() == "latest")
 | 
			
		||||
@@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Xkcd(int num)
 | 
			
		||||
            {
 | 
			
		||||
                if (num < 1)
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                ulong target;
 | 
			
		||||
@@ -47,7 +47,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel);
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,6 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task RepeatRemove(int index)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
@@ -103,7 +102,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task Repeat(int minutes, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
@@ -152,7 +151,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Repeat(GuildDateTime gt, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,20 +14,12 @@ using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
using NadekoBot.Services.ClashOfClans;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using NadekoBot.Services.CustomReactions;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
using NadekoBot.Services.Permissions;
 | 
			
		||||
using NadekoBot.Services.Utility;
 | 
			
		||||
using NadekoBot.Services.Help;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using NadekoBot.Services.Pokemon;
 | 
			
		||||
using NadekoBot.DataStructures.ShardCom;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
{
 | 
			
		||||
@@ -35,6 +27,15 @@ namespace NadekoBot
 | 
			
		||||
    {
 | 
			
		||||
        private Logger _log;
 | 
			
		||||
 | 
			
		||||
        public BotCredentials Credentials { get; }
 | 
			
		||||
 | 
			
		||||
        public DiscordSocketClient Client { get; }
 | 
			
		||||
        public CommandService CommandService { get; }
 | 
			
		||||
 | 
			
		||||
        public DbService Db { get; }
 | 
			
		||||
        public BotConfig BotConfig { get; }
 | 
			
		||||
        public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
 | 
			
		||||
 | 
			
		||||
        /* I don't know how to make this not be static
 | 
			
		||||
         * and keep the convenience of .WithOkColor
 | 
			
		||||
         * and .WithErrorColor extensions methods.
 | 
			
		||||
@@ -44,23 +45,9 @@ namespace NadekoBot
 | 
			
		||||
        public static Color OkColor { get; private set; }
 | 
			
		||||
        public static Color ErrorColor { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
 | 
			
		||||
        public BotConfig BotConfig { get; }
 | 
			
		||||
        public DbService Db { get; }
 | 
			
		||||
        public CommandService CommandService { get; }
 | 
			
		||||
        public CommandHandler CommandHandler { get; private set; }
 | 
			
		||||
        public Localization Localization { get; private set; }
 | 
			
		||||
        public NadekoStrings Strings { get; private set; }
 | 
			
		||||
        public StatsService Stats { get; private set; }
 | 
			
		||||
        public ImagesService Images { get; }
 | 
			
		||||
        public CurrencyService Currency { get; }
 | 
			
		||||
        public GoogleApiService GoogleApi { get; }
 | 
			
		||||
 | 
			
		||||
        public DiscordSocketClient Client { get; }
 | 
			
		||||
        public bool Ready { get; private set; }
 | 
			
		||||
        public TaskCompletionSource<bool> Ready { get; private set; } = new TaskCompletionSource<bool>();
 | 
			
		||||
 | 
			
		||||
        public INServiceProvider Services { get; private set; }
 | 
			
		||||
        public BotCredentials Credentials { get; }
 | 
			
		||||
 | 
			
		||||
        public int ShardId { get; }
 | 
			
		||||
        public ShardsCoordinator ShardCoord { get; private set; }
 | 
			
		||||
@@ -72,26 +59,14 @@ namespace NadekoBot
 | 
			
		||||
            if (shardId < 0)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(shardId));
 | 
			
		||||
 | 
			
		||||
            ShardId = shardId;
 | 
			
		||||
 | 
			
		||||
            LogSetup.SetupLogger();
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            TerribleElevatedPermissionCheck();
 | 
			
		||||
 | 
			
		||||
            ShardId = shardId;
 | 
			
		||||
 | 
			
		||||
            Credentials = new BotCredentials();
 | 
			
		||||
 | 
			
		||||
            port = port ?? Credentials.ShardRunPort;
 | 
			
		||||
            _comClient = new ShardComClient(port.Value);
 | 
			
		||||
 | 
			
		||||
            Db = new DbService(Credentials);
 | 
			
		||||
 | 
			
		||||
            using (var uow = Db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                BotConfig = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
 | 
			
		||||
                ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Client = new DiscordSocketClient(new DiscordSocketConfig
 | 
			
		||||
            {
 | 
			
		||||
                MessageCacheSize = 10,
 | 
			
		||||
@@ -101,16 +76,21 @@ namespace NadekoBot
 | 
			
		||||
                ShardId = shardId,
 | 
			
		||||
                AlwaysDownloadUsers = false,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            CommandService = new CommandService(new CommandServiceConfig()
 | 
			
		||||
            {
 | 
			
		||||
                CaseSensitiveCommands = false,
 | 
			
		||||
                DefaultRunMode = RunMode.Sync,
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            Images = new ImagesService();
 | 
			
		||||
            Currency = new CurrencyService(BotConfig, Db);
 | 
			
		||||
            GoogleApi = new GoogleApiService(Credentials);
 | 
			
		||||
 | 
			
		||||
            port = port ?? Credentials.ShardRunPort;
 | 
			
		||||
            _comClient = new ShardComClient(port.Value);
 | 
			
		||||
 | 
			
		||||
            using (var uow = Db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                BotConfig = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
 | 
			
		||||
                ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SetupShard(shardId, parentProcessId, port.Value);
 | 
			
		||||
 | 
			
		||||
@@ -145,141 +125,35 @@ namespace NadekoBot
 | 
			
		||||
            using (var uow = Db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
 | 
			
		||||
                
 | 
			
		||||
                Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
 | 
			
		||||
                Strings = new NadekoStrings(Localization);
 | 
			
		||||
                CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this);
 | 
			
		||||
                Stats = new StatsService(Client, CommandHandler, Credentials, ShardCoord);
 | 
			
		||||
 | 
			
		||||
                var soundcloudApiService = new SoundCloudApiService(Credentials);
 | 
			
		||||
 | 
			
		||||
                #region help
 | 
			
		||||
                var helpService = new HelpService(BotConfig, CommandHandler, Strings);
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                //module services
 | 
			
		||||
                //todo 90 - autodiscover, DI, and add instead of manual like this
 | 
			
		||||
                #region utility
 | 
			
		||||
                var remindService = new RemindService(Client, BotConfig, Db, startingGuildIdList, uow);
 | 
			
		||||
                var repeaterService = new MessageRepeaterService(this, Client, AllGuildConfigs);
 | 
			
		||||
                var converterService = new ConverterService(Client, Db);
 | 
			
		||||
                var commandMapService = new CommandMapService(AllGuildConfigs);
 | 
			
		||||
                var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency, Client);
 | 
			
		||||
                var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService);
 | 
			
		||||
                var pruneService = new PruneService();
 | 
			
		||||
                var streamRoleService = new StreamRoleService(Client, Db, AllGuildConfigs);
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                #region permissions
 | 
			
		||||
                var permissionsService = new PermissionService(Client, Db, BotConfig, CommandHandler, Strings);
 | 
			
		||||
                var blacklistService = new BlacklistService(BotConfig);
 | 
			
		||||
                var cmdcdsService = new CmdCdService(AllGuildConfigs);
 | 
			
		||||
                var filterService = new FilterService(Client, AllGuildConfigs);
 | 
			
		||||
                var globalPermsService = new GlobalPermissionService(BotConfig);
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                #region Searches
 | 
			
		||||
                var searchesService = new SearchesService(Client, GoogleApi, Db);
 | 
			
		||||
                var streamNotificationService = new StreamNotificationService(Db, Client, Strings);
 | 
			
		||||
                var animeSearchService = new AnimeSearchService();
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList);
 | 
			
		||||
                var musicService = new MusicService(Client, GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs);
 | 
			
		||||
                var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow);
 | 
			
		||||
 | 
			
		||||
                #region Games
 | 
			
		||||
                var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler);
 | 
			
		||||
                var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler, Strings);
 | 
			
		||||
                var pollService = new PollService(Client, Strings);
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                #region administration
 | 
			
		||||
                var administrationService = new AdministrationService(AllGuildConfigs, CommandHandler);
 | 
			
		||||
                var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, Db);
 | 
			
		||||
                var selfService = new SelfService(Client, this, CommandHandler, Db, BotConfig, Localization, Strings, Credentials);
 | 
			
		||||
                var vcRoleService = new VcRoleService(Client, AllGuildConfigs, Db);
 | 
			
		||||
                var vPlusTService = new VplusTService(Client, AllGuildConfigs, Strings, Db);
 | 
			
		||||
                var muteService = new MuteService(Client, AllGuildConfigs, Db);
 | 
			
		||||
                var ratelimitService = new SlowmodeService(Client, AllGuildConfigs);
 | 
			
		||||
                var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService);
 | 
			
		||||
                var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService, Db);
 | 
			
		||||
                var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs);
 | 
			
		||||
                var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs);
 | 
			
		||||
                var guildTimezoneService = new GuildTimezoneService(Client, AllGuildConfigs, Db);
 | 
			
		||||
                var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService, guildTimezoneService);
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                #region pokemon 
 | 
			
		||||
                var pokemonService = new PokemonService();
 | 
			
		||||
                #endregion
 | 
			
		||||
                var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
 | 
			
		||||
 | 
			
		||||
                //initialize Services
 | 
			
		||||
                Services = new NServiceProvider.ServiceProviderBuilder()
 | 
			
		||||
                    .Add<ILocalization>(Localization)
 | 
			
		||||
                    .Add<IStatsService>(Stats)
 | 
			
		||||
                    .Add<IImagesService>(Images)
 | 
			
		||||
                    .Add<IGoogleApiService>(GoogleApi)
 | 
			
		||||
                    .Add<IStatsService>(Stats)
 | 
			
		||||
                    .Add<IBotCredentials>(Credentials)
 | 
			
		||||
                    .Add<CommandService>(CommandService)
 | 
			
		||||
                    .Add<NadekoStrings>(Strings)
 | 
			
		||||
                    .Add<DiscordSocketClient>(Client)
 | 
			
		||||
                    .Add<BotConfig>(BotConfig)
 | 
			
		||||
                    .Add<CurrencyService>(Currency)
 | 
			
		||||
                    .Add<CommandHandler>(CommandHandler)
 | 
			
		||||
                    .Add<DbService>(Db)
 | 
			
		||||
                        //modules
 | 
			
		||||
                        .Add(commandMapService)
 | 
			
		||||
                        .Add(remindService)
 | 
			
		||||
                        .Add(repeaterService)
 | 
			
		||||
                        .Add(converterService)
 | 
			
		||||
                        .Add(verboseErrorsService)
 | 
			
		||||
                        .Add(patreonRewardsService)
 | 
			
		||||
                        .Add(pruneService)
 | 
			
		||||
                        .Add(streamRoleService)
 | 
			
		||||
                    .Add<SearchesService>(searchesService)
 | 
			
		||||
                        .Add(streamNotificationService)
 | 
			
		||||
                        .Add(animeSearchService)
 | 
			
		||||
                    .Add<ClashOfClansService>(clashService)
 | 
			
		||||
                    .Add<MusicService>(musicService)
 | 
			
		||||
                    .Add<GreetSettingsService>(greetSettingsService)
 | 
			
		||||
                    .Add<CustomReactionsService>(crService)
 | 
			
		||||
                    .Add<HelpService>(helpService)
 | 
			
		||||
                    .Add<GamesService>(gamesService)
 | 
			
		||||
                        .Add(chatterBotService)
 | 
			
		||||
                        .Add(pollService)
 | 
			
		||||
                    .Add<AdministrationService>(administrationService)
 | 
			
		||||
                        .Add(selfService)
 | 
			
		||||
                        .Add(vcRoleService)
 | 
			
		||||
                        .Add(vPlusTService)
 | 
			
		||||
                        .Add(muteService)
 | 
			
		||||
                        .Add(ratelimitService)
 | 
			
		||||
                        .Add(playingRotateService)
 | 
			
		||||
                        .Add(gameVcService)
 | 
			
		||||
                        .Add(autoAssignRoleService)
 | 
			
		||||
                        .Add(protectionService)
 | 
			
		||||
                        .Add(logCommandService)
 | 
			
		||||
                        .Add(guildTimezoneService)
 | 
			
		||||
                    .Add<PermissionService>(permissionsService)
 | 
			
		||||
                        .Add(blacklistService)
 | 
			
		||||
                        .Add(cmdcdsService)
 | 
			
		||||
                        .Add(filterService)
 | 
			
		||||
                        .Add(globalPermsService)
 | 
			
		||||
                    .Add<PokemonService>(pokemonService)
 | 
			
		||||
                    .Add<NadekoBot>(this)
 | 
			
		||||
                    .AddManual<IBotCredentials>(Credentials)
 | 
			
		||||
                    .AddManual(Db)
 | 
			
		||||
                    .AddManual(BotConfig)
 | 
			
		||||
                    .AddManual(Client)
 | 
			
		||||
                    .AddManual(CommandService)
 | 
			
		||||
                    .AddManual<ILocalization>(localization)
 | 
			
		||||
                    .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
 | 
			
		||||
                    .AddManual<NadekoBot>(this)
 | 
			
		||||
                    .AddManual<IUnitOfWork>(uow)
 | 
			
		||||
                    .AddManual(ShardCoord)
 | 
			
		||||
                    .LoadFrom(Assembly.GetEntryAssembly())
 | 
			
		||||
                    .Build();
 | 
			
		||||
 | 
			
		||||
                CommandHandler.AddServices(Services);
 | 
			
		||||
                var commandHandler = Services.GetService<CommandHandler>();
 | 
			
		||||
                commandHandler.AddServices(Services);
 | 
			
		||||
 | 
			
		||||
                //setup typereaders
 | 
			
		||||
                CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
 | 
			
		||||
                CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader(CommandService, CommandHandler));
 | 
			
		||||
                CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader(crService, CommandService, CommandHandler));
 | 
			
		||||
                CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
 | 
			
		||||
                CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
 | 
			
		||||
                CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService));
 | 
			
		||||
                CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
 | 
			
		||||
                CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
 | 
			
		||||
                CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader(guildTimezoneService));
 | 
			
		||||
                CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader());
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -382,7 +256,7 @@ namespace NadekoBot
 | 
			
		||||
                    .Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot)))
 | 
			
		||||
                    .ForEach(x => CommandService.RemoveModuleAsync(x));
 | 
			
		||||
 | 
			
		||||
            Ready = true;
 | 
			
		||||
            Ready.TrySetResult(true);
 | 
			
		||||
            _log.Info($"Shard {ShardId} ready.");
 | 
			
		||||
            //_log.Info(await stats.Print().ConfigureAwait(false));
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="AngleSharp" Version="0.9.9" />
 | 
			
		||||
    <PackageReference Include="Discord.Net" Version="1.0.1-build-00785" />
 | 
			
		||||
    <PackageReference Include="Discord.Net" Version="1.0.2-build-00797" />
 | 
			
		||||
    <PackageReference Include="libvideo" Version="1.0.1" />
 | 
			
		||||
    <PackageReference Include="CoreCLR-NCalc" Version="2.1.2" />
 | 
			
		||||
    <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" />
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class AdministrationService
 | 
			
		||||
    public class AdministrationService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly ConcurrentHashSet<ulong> DeleteMessagesOnCommand;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class AutoAssignRoleService
 | 
			
		||||
    public class AutoAssignRoleService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class GameVoiceChannelService
 | 
			
		||||
    public class GameVoiceChannelService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly ConcurrentHashSet<ulong> GameVoiceChannels = new ConcurrentHashSet<ulong>();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using Discord.WebSocket;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class GuildTimezoneService
 | 
			
		||||
    public class GuildTimezoneService : INService
 | 
			
		||||
    {
 | 
			
		||||
        //hack >.>
 | 
			
		||||
        public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class LogCommandService
 | 
			
		||||
    public class LogCommandService : INService
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ namespace NadekoBot.Services.Administration
 | 
			
		||||
        All
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class MuteService
 | 
			
		||||
    public class MuteService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class PlayingRotateService
 | 
			
		||||
    public class PlayingRotateService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Timer _t;
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class ProtectionService
 | 
			
		||||
    public class ProtectionService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly ConcurrentDictionary<ulong, AntiRaidStats> AntiRaidGuilds =
 | 
			
		||||
                new ConcurrentDictionary<ulong, AntiRaidStats>();
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class PruneService
 | 
			
		||||
    public class PruneService : INService
 | 
			
		||||
    {
 | 
			
		||||
        //channelids where prunes are currently occuring
 | 
			
		||||
        private ConcurrentHashSet<ulong> _pruningGuilds = new ConcurrentHashSet<ulong>();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class SlowmodeService : IEarlyBlocker
 | 
			
		||||
    public class SlowmodeService : IEarlyBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
 | 
			
		||||
        public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class SelfService : ILateExecutor
 | 
			
		||||
    public class SelfService : ILateExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        public volatile bool ForwardDMs;
 | 
			
		||||
        public volatile bool ForwardDMsToAllOwners;
 | 
			
		||||
@@ -44,8 +44,7 @@ namespace NadekoBot.Services.Administration
 | 
			
		||||
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                while (!bot.Ready)
 | 
			
		||||
                    await Task.Delay(1000);
 | 
			
		||||
                await bot.Ready.Task.ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                foreach (var cmd in bc.StartupCommands)
 | 
			
		||||
                {
 | 
			
		||||
@@ -56,8 +55,7 @@ namespace NadekoBot.Services.Administration
 | 
			
		||||
 | 
			
		||||
            var ___ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                while (!bot.Ready)
 | 
			
		||||
                    await Task.Delay(1000);
 | 
			
		||||
                await bot.Ready.Task.ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(5000);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class VcRoleService
 | 
			
		||||
    public class VcRoleService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class VplusTService
 | 
			
		||||
    public class VplusTService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,262 +0,0 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.ClashOfClans
 | 
			
		||||
{
 | 
			
		||||
    // todo 99 rewrite, just made this compile, it's a complete mess. A lot of the things here should actually be in the actual module. 
 | 
			
		||||
    // service should just handle the state, module should print out what happened, so anything that has to do with strings
 | 
			
		||||
    // shouldn't be here
 | 
			
		||||
    public class ClashOfClansService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private readonly ILocalization _localization;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
        private readonly Timer checkWarTimer;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
 | 
			
		||||
 | 
			
		||||
        public ClashOfClansService(DiscordSocketClient client, DbService db, 
 | 
			
		||||
            ILocalization localization, NadekoStrings strings, IUnitOfWork uow, 
 | 
			
		||||
            List<long> guilds)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _localization = localization;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
 | 
			
		||||
            ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
 | 
			
		||||
                uow.ClashOfClans
 | 
			
		||||
                    .GetAllWars(guilds)
 | 
			
		||||
                    .Select(cw =>
 | 
			
		||||
                    {
 | 
			
		||||
                        cw.Channel = _client.GetGuild(cw.GuildId)?
 | 
			
		||||
                                                        .GetTextChannel(cw.ChannelId);
 | 
			
		||||
                        return cw;
 | 
			
		||||
                    })
 | 
			
		||||
                    .Where(cw => cw.Channel != null)
 | 
			
		||||
                    .GroupBy(cw => cw.GuildId)
 | 
			
		||||
                    .ToDictionary(g => g.Key, g => g.ToList()));
 | 
			
		||||
 | 
			
		||||
            checkWarTimer = new Timer(async _ =>
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var kvp in ClashWars)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var war in kvp.Value)
 | 
			
		||||
                    {
 | 
			
		||||
                        try { await CheckWar(TimeSpan.FromHours(2), war).ConfigureAwait(false); } catch { }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task CheckWar(TimeSpan callExpire, ClashWar war)
 | 
			
		||||
        {
 | 
			
		||||
            var Bases = war.Bases;
 | 
			
		||||
            for (var i = 0; i < Bases.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var callUser = Bases[i].CallUser;
 | 
			
		||||
                if (callUser == null) continue;
 | 
			
		||||
                if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
 | 
			
		||||
                {
 | 
			
		||||
                    if (Bases[i].Stars != 3)
 | 
			
		||||
                        Bases[i].BaseDestroyed = true;
 | 
			
		||||
                    else
 | 
			
		||||
                        Bases[i] = null;
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        SaveWar(war);
 | 
			
		||||
                        await war.Channel.SendErrorAsync(_strings.GetText("claim_expired",
 | 
			
		||||
                                    _localization.GetCultureInfo(war.Channel.GuildId),
 | 
			
		||||
                                    typeof(ClashOfClansService).Name.ToLowerInvariant(),
 | 
			
		||||
                                    Format.Bold(Bases[i].CallUser),
 | 
			
		||||
                                    ShortPrint(war)));
 | 
			
		||||
                    }
 | 
			
		||||
                    catch { }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Tuple<List<ClashWar>, int> GetWarInfo(IGuild guild, int num)
 | 
			
		||||
        {
 | 
			
		||||
            List<ClashWar> wars = null;
 | 
			
		||||
            ClashWars.TryGetValue(guild.Id, out wars);
 | 
			
		||||
            if (wars == null || wars.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            // get the number of the war
 | 
			
		||||
            else if (num < 1 || num > wars.Count)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            num -= 1;
 | 
			
		||||
            //get the actual war
 | 
			
		||||
            return new Tuple<List<ClashWar>, int>(wars, num);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = _client.GetGuild(serverId)?.GetTextChannel(channelId);
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var cw = new ClashWar
 | 
			
		||||
                {
 | 
			
		||||
                    EnemyClan = enemyClan,
 | 
			
		||||
                    Size = size,
 | 
			
		||||
                    Bases = new List<ClashCaller>(size),
 | 
			
		||||
                    GuildId = serverId,
 | 
			
		||||
                    ChannelId = channelId,
 | 
			
		||||
                    Channel = channel,
 | 
			
		||||
                };
 | 
			
		||||
                cw.Bases.Capacity = size;
 | 
			
		||||
                for (int i = 0; i < size; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    cw.Bases.Add(new ClashCaller()
 | 
			
		||||
                    {
 | 
			
		||||
                        CallUser = null,
 | 
			
		||||
                        SequenceNumber = i,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                uow.ClashOfClans.Add(cw);
 | 
			
		||||
                await uow.CompleteAsync();
 | 
			
		||||
                return cw;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SaveWar(ClashWar cw)
 | 
			
		||||
        {
 | 
			
		||||
            if (cw.WarState == StateOfWar.Ended)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.ClashOfClans.Remove(cw);
 | 
			
		||||
                    uow.CompleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                uow.ClashOfClans.Update(cw);
 | 
			
		||||
                uow.CompleteAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Call(ClashWar cw, string u, int baseNumber)
 | 
			
		||||
        {
 | 
			
		||||
            if (baseNumber < 0 || baseNumber >= cw.Bases.Count)
 | 
			
		||||
                throw new ArgumentException(Localize(cw, "invalid_base_number"));
 | 
			
		||||
            if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3)
 | 
			
		||||
                throw new ArgumentException(Localize(cw, "base_already_claimed"));
 | 
			
		||||
            for (var i = 0; i < cw.Bases.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
 | 
			
		||||
                    throw new ArgumentException(Localize(cw, "claimed_other", u, i + 1));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cc = cw.Bases[baseNumber];
 | 
			
		||||
            cc.CallUser = u.Trim();
 | 
			
		||||
            cc.TimeAdded = DateTime.UtcNow;
 | 
			
		||||
            cc.BaseDestroyed = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int FinishClaim(ClashWar cw, string user, int stars = 3)
 | 
			
		||||
        {
 | 
			
		||||
            user = user.Trim();
 | 
			
		||||
            for (var i = 0; i < cw.Bases.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue;
 | 
			
		||||
                cw.Bases[i].BaseDestroyed = true;
 | 
			
		||||
                cw.Bases[i].Stars = stars;
 | 
			
		||||
                return i;
 | 
			
		||||
            }
 | 
			
		||||
            throw new InvalidOperationException(Localize(cw, "not_partic_or_destroyed", user));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void FinishClaim(ClashWar cw, int index, int stars = 3)
 | 
			
		||||
        {
 | 
			
		||||
            if (index < 0 || index > cw.Bases.Count)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(index));
 | 
			
		||||
            var toFinish = cw.Bases[index];
 | 
			
		||||
            if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(Localize(cw, "base_already_destroyed"));
 | 
			
		||||
            if (toFinish.CallUser == null) throw new InvalidOperationException(Localize(cw, "base_already_unclaimed"));
 | 
			
		||||
            toFinish.BaseDestroyed = true;
 | 
			
		||||
            toFinish.Stars = stars;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int Uncall(ClashWar cw, string user)
 | 
			
		||||
        {
 | 
			
		||||
            user = user.Trim();
 | 
			
		||||
            for (var i = 0; i < cw.Bases.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (cw.Bases[i]?.CallUser != user) continue;
 | 
			
		||||
                cw.Bases[i].CallUser = null;
 | 
			
		||||
                return i;
 | 
			
		||||
            }
 | 
			
		||||
            throw new InvalidOperationException(Localize(cw, "not_partic"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string ShortPrint(ClashWar cw) =>
 | 
			
		||||
            $"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})";
 | 
			
		||||
 | 
			
		||||
        public string ToPrettyString(ClashWar cw)
 | 
			
		||||
        {
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
            if (cw.WarState == StateOfWar.Created)
 | 
			
		||||
                sb.AppendLine("`not started`");
 | 
			
		||||
            var twoHours = new TimeSpan(2, 0, 0);
 | 
			
		||||
            for (var i = 0; i < cw.Bases.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (cw.Bases[i].CallUser == null)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.AppendLine($"`{i + 1}.` ❌*{Localize(cw, "not_claimed")}*");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (cw.Bases[i].BaseDestroyed)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}");
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours;
 | 
			
		||||
                        if (cw.Bases[i].Stars == 3)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left {new string('⭐', cw.Bases[i].Stars)} {string.Concat(Enumerable.Repeat("🔸", 3 - cw.Bases[i].Stars))}");
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Localize(ClashWar cw, string key, params object[] replacements)
 | 
			
		||||
        {
 | 
			
		||||
            return string.Format(Localize(cw, key), replacements);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Localize(ClashWar cw, string key)
 | 
			
		||||
        {
 | 
			
		||||
            return _strings.GetText(key,
 | 
			
		||||
                _localization.GetCultureInfo(cw.Channel?.GuildId),
 | 
			
		||||
                "ClashOfClans".ToLowerInvariant());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.ClashOfClans
 | 
			
		||||
{
 | 
			
		||||
    public static class Extensions
 | 
			
		||||
    {
 | 
			
		||||
        public static void ResetTime(this ClashCaller c)
 | 
			
		||||
        {
 | 
			
		||||
            c.TimeAdded = DateTime.UtcNow;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void Destroy(this ClashCaller c)
 | 
			
		||||
        {
 | 
			
		||||
            c.BaseDestroyed = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void End(this ClashWar cw)
 | 
			
		||||
        {
 | 
			
		||||
            //Ended = true;
 | 
			
		||||
            cw.WarState = StateOfWar.Ended;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void Start(this ClashWar cw)
 | 
			
		||||
        {
 | 
			
		||||
            if (cw.WarState == StateOfWar.Started)
 | 
			
		||||
                throw new InvalidOperationException("war_already_started");
 | 
			
		||||
            //if (Started)
 | 
			
		||||
            //    throw new InvalidOperationException();
 | 
			
		||||
            //Started = true;
 | 
			
		||||
            cw.WarState = StateOfWar.Started;
 | 
			
		||||
            cw.StartedAt = DateTime.UtcNow;
 | 
			
		||||
            foreach (var b in cw.Bases.Where(b => b.CallUser != null))
 | 
			
		||||
            {
 | 
			
		||||
                b.ResetTime();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -24,7 +24,8 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
        public int GetHashCode(IGuildUser obj) => obj.Id.GetHashCode();
 | 
			
		||||
    }
 | 
			
		||||
    public class CommandHandler
 | 
			
		||||
 | 
			
		||||
    public class CommandHandler : INService
 | 
			
		||||
    {
 | 
			
		||||
        public const int GlobalCommandsCooldown = 750;
 | 
			
		||||
 | 
			
		||||
@@ -189,7 +190,7 @@ namespace NadekoBot.Services
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (msg.Author.IsBot || !_bot.Ready) //no bots, wait until bot connected and initialized
 | 
			
		||||
                if (msg.Author.IsBot || !_bot.Ready.Task.IsCompleted) //no bots, wait until bot connected and initialized
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (!(msg is SocketUserMessage usrMsg))
 | 
			
		||||
@@ -296,83 +297,124 @@ namespace NadekoBot.Services
 | 
			
		||||
            => ExecuteCommand(context, input.Substring(argPos), serviceProvider, multiMatchHandling);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand(CommandContext context, string input, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
        public async Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommand(CommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
        {
 | 
			
		||||
            var searchResult = _commandService.Search(context, input);
 | 
			
		||||
            if (!searchResult.IsSuccess)
 | 
			
		||||
                return (false, null, null);
 | 
			
		||||
 | 
			
		||||
            var commands = searchResult.Commands;
 | 
			
		||||
            for (int i = commands.Count - 1; i >= 0; i--)
 | 
			
		||||
            var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
 | 
			
		||||
 | 
			
		||||
            foreach (var match in commands)
 | 
			
		||||
            {
 | 
			
		||||
                var preconditionResult = await commands[i].CheckPreconditionsAsync(context, serviceProvider).ConfigureAwait(false);
 | 
			
		||||
                if (!preconditionResult.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return (false, preconditionResult.ErrorReason, commands[i].Command);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false);
 | 
			
		||||
                if (!parseResult.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    if (parseResult.Error == CommandError.MultipleMatches)
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeReaderValue[] argList, paramList;
 | 
			
		||||
                        switch (multiMatchHandling)
 | 
			
		||||
                        {
 | 
			
		||||
                            case MultiMatchHandling.Best:
 | 
			
		||||
                                argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray();
 | 
			
		||||
                                paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray();
 | 
			
		||||
                                parseResult = ParseResult.FromSuccess(argList, paramList);
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!parseResult.IsSuccess)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (commands.Count == 1)
 | 
			
		||||
                            return (false, parseResult.ErrorReason, commands[i].Command);
 | 
			
		||||
                        else
 | 
			
		||||
                            continue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var cmd = commands[i].Command;
 | 
			
		||||
 | 
			
		||||
                // Bot will ignore commands which are ran more often than what specified by
 | 
			
		||||
                // GlobalCommandsCooldown constant (miliseconds)
 | 
			
		||||
                if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
 | 
			
		||||
                    return (false, null, commands[i].Command);
 | 
			
		||||
                //return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.");
 | 
			
		||||
 | 
			
		||||
                var commandName = cmd.Aliases.First();
 | 
			
		||||
                foreach (var svc in _services)
 | 
			
		||||
                {
 | 
			
		||||
                    if (svc is ILateBlocker exec &&
 | 
			
		||||
                        await exec.TryBlockLate(_client, context.Message, context.Guild, context.Channel, context.User, cmd.Module.GetTopLevelModule().Name, commandName).ConfigureAwait(false))
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Info("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName, svc.GetType().Name);
 | 
			
		||||
                        return (false, null, commands[i].Command);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var execResult = (ExecuteResult)(await commands[i].ExecuteAsync(context, parseResult, serviceProvider));
 | 
			
		||||
                if (execResult.Exception != null && (!(execResult.Exception is HttpException he) || he.DiscordCode != 50013))
 | 
			
		||||
                {
 | 
			
		||||
                    lock (errorLogLock)
 | 
			
		||||
                    {
 | 
			
		||||
                        var now = DateTime.Now;
 | 
			
		||||
                        File.AppendAllText($"./command_errors_{now:yyyy-MM-dd}.txt",
 | 
			
		||||
                            $"[{now:HH:mm-yyyy-MM-dd}]" + Environment.NewLine
 | 
			
		||||
                            + execResult.Exception.ToString() + Environment.NewLine
 | 
			
		||||
                            + "------" + Environment.NewLine);
 | 
			
		||||
                        _log.Warn(execResult.Exception);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return (true, null, commands[i].Command);
 | 
			
		||||
                preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (false, null, null);
 | 
			
		||||
            //return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."));
 | 
			
		||||
            var successfulPreconditions = preconditionResults
 | 
			
		||||
                .Where(x => x.Value.IsSuccess)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
 | 
			
		||||
            if (successfulPreconditions.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                //All preconditions failed, return the one from the highest priority command
 | 
			
		||||
                var bestCandidate = preconditionResults
 | 
			
		||||
                    .OrderByDescending(x => x.Key.Command.Priority)
 | 
			
		||||
                    .FirstOrDefault(x => !x.Value.IsSuccess);
 | 
			
		||||
                return (false, bestCandidate.Value.ErrorReason, commands[0].Command);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
 | 
			
		||||
            foreach (var pair in successfulPreconditions)
 | 
			
		||||
            {
 | 
			
		||||
                var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (parseResult.Error == CommandError.MultipleMatches)
 | 
			
		||||
                {
 | 
			
		||||
                    IReadOnlyList<TypeReaderValue> argList, paramList;
 | 
			
		||||
                    switch (multiMatchHandling)
 | 
			
		||||
                    {
 | 
			
		||||
                        case MultiMatchHandling.Best:
 | 
			
		||||
                            argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
 | 
			
		||||
                            paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
 | 
			
		||||
                            parseResult = ParseResult.FromSuccess(argList, paramList);
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                parseResultsDict[pair.Key] = parseResult;
 | 
			
		||||
            }
 | 
			
		||||
            // Calculates the 'score' of a command given a parse result
 | 
			
		||||
            float CalculateScore(CommandMatch match, ParseResult parseResult)
 | 
			
		||||
            {
 | 
			
		||||
                float argValuesScore = 0, paramValuesScore = 0;
 | 
			
		||||
 | 
			
		||||
                if (match.Command.Parameters.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
 | 
			
		||||
                    var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
 | 
			
		||||
 | 
			
		||||
                    argValuesScore = argValuesSum / match.Command.Parameters.Count;
 | 
			
		||||
                    paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
 | 
			
		||||
                return match.Command.Priority + totalArgsScore * 0.99f;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Order the parse results by their score so that we choose the most likely result to execute
 | 
			
		||||
            var parseResults = parseResultsDict
 | 
			
		||||
                .OrderByDescending(x => CalculateScore(x.Key, x.Value));
 | 
			
		||||
 | 
			
		||||
            var successfulParses = parseResults
 | 
			
		||||
                .Where(x => x.Value.IsSuccess)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
 | 
			
		||||
            if (successfulParses.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                //All parses failed, return the one from the highest priority command, using score as a tie breaker
 | 
			
		||||
                var bestMatch = parseResults
 | 
			
		||||
                    .FirstOrDefault(x => !x.Value.IsSuccess);
 | 
			
		||||
                return (false, bestMatch.Value.ErrorReason, commands[0].Command);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cmd = successfulParses[0].Key.Command;
 | 
			
		||||
 | 
			
		||||
            // Bot will ignore commands which are ran more often than what specified by
 | 
			
		||||
            // GlobalCommandsCooldown constant (miliseconds)
 | 
			
		||||
            if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
 | 
			
		||||
                return (false, null, cmd);
 | 
			
		||||
            //return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.");
 | 
			
		||||
 | 
			
		||||
            var commandName = cmd.Aliases.First();
 | 
			
		||||
            foreach (var svc in _services)
 | 
			
		||||
            {
 | 
			
		||||
                if (svc is ILateBlocker exec &&
 | 
			
		||||
                    await exec.TryBlockLate(_client, context.Message, context.Guild, context.Channel, context.User, cmd.Module.GetTopLevelModule().Name, commandName).ConfigureAwait(false))
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Info("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName, svc.GetType().Name);
 | 
			
		||||
                    return (false, null, cmd);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //If we get this far, at least one parse was successful. Execute the most likely overload.
 | 
			
		||||
            var chosenOverload = successfulParses[0];
 | 
			
		||||
            var execResult = (ExecuteResult)await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (execResult.Exception != null && (!(execResult.Exception is HttpException he) || he.DiscordCode != 50013))
 | 
			
		||||
            {
 | 
			
		||||
                lock (errorLogLock)
 | 
			
		||||
                {
 | 
			
		||||
                    var now = DateTime.Now;
 | 
			
		||||
                    File.AppendAllText($"./command_errors_{now:yyyy-MM-dd}.txt",
 | 
			
		||||
                        $"[{now:HH:mm-yyyy-MM-dd}]" + Environment.NewLine
 | 
			
		||||
                        + execResult.Exception.ToString() + Environment.NewLine
 | 
			
		||||
                        + "------" + Environment.NewLine);
 | 
			
		||||
                    _log.Warn(execResult.Exception);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (true, null, cmd);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly object errorLogLock = new object();
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ using NadekoBot.Services.Database;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class CurrencyService
 | 
			
		||||
    public class CurrencyService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly BotConfig _config;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ using NadekoBot.Services.Database;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.CustomReactions
 | 
			
		||||
{
 | 
			
		||||
    public class CustomReactionsService : IEarlyBlockingExecutor
 | 
			
		||||
    public class CustomReactionsService : IEarlyBlockingExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        public CustomReaction[] GlobalReactions = new CustomReaction[] { };
 | 
			
		||||
        public ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ Nadeko Support Server: https://discord.gg/nadekobot";
 | 
			
		||||
        public List<StartupCommand> StartupCommands { get; set; }
 | 
			
		||||
        public HashSet<BlockedCmdOrMdl> BlockedCommands { get; set; }
 | 
			
		||||
        public HashSet<BlockedCmdOrMdl> BlockedModules { get; set; }
 | 
			
		||||
        public int PermissionVersion { get; set; } = 1;
 | 
			
		||||
        public int PermissionVersion { get; set; }
 | 
			
		||||
        public string DefaultPrefix { get; set; } = ".";
 | 
			
		||||
        public bool CustomReactionsStartWith { get; set; } = false;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,6 @@ namespace NadekoBot.Services.Database.Repositories
 | 
			
		||||
{
 | 
			
		||||
    public interface IReminderRepository : IRepository<Reminder>
 | 
			
		||||
    {
 | 
			
		||||
        IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds);
 | 
			
		||||
        IEnumerable<Reminder> GetIncludedReminders(IEnumerable<long> guildIds);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds)
 | 
			
		||||
        public IEnumerable<Reminder> GetIncludedReminders(IEnumerable<long> guildIds)
 | 
			
		||||
        {
 | 
			
		||||
            return _set.Where(x => guildIds.Contains((long)x.ServerId)).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class ChatterBotService : IEarlyBlockingExecutor
 | 
			
		||||
    public class ChatterBotService : IEarlyBlockingExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class GamesService
 | 
			
		||||
    public class GamesService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly BotConfig _bc;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using NLog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class PollService : IEarlyBlockingExecutor
 | 
			
		||||
    public class PollService : IEarlyBlockingExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class GreetSettingsService
 | 
			
		||||
    public class GreetSettingsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ using NadekoBot.Attributes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Help
 | 
			
		||||
{
 | 
			
		||||
    public class HelpService : ILateExecutor
 | 
			
		||||
    public class HelpService : ILateExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly BotConfig _bc;
 | 
			
		||||
        private readonly CommandHandler _ch;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IGoogleApiService
 | 
			
		||||
    public interface IGoogleApiService : INService
 | 
			
		||||
    {
 | 
			
		||||
        IEnumerable<string> Languages { get; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using System.Collections.Immutable;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IImagesService
 | 
			
		||||
    public interface IImagesService : INService
 | 
			
		||||
    {
 | 
			
		||||
        ImmutableArray<byte> Heads { get; }
 | 
			
		||||
        ImmutableArray<byte> Tails { get; }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								src/NadekoBot/Services/INService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/NadekoBot/Services/INService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// All services must implement this interface in order to be auto-discovered by the DI system
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface INService
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IStatsService
 | 
			
		||||
    public interface IStatsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        string Author { get; }
 | 
			
		||||
        long CommandsRan { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class NadekoStrings
 | 
			
		||||
    public class NadekoStrings : INService
 | 
			
		||||
    {
 | 
			
		||||
        public const string stringsPath = @"_strings/";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Impl
 | 
			
		||||
{
 | 
			
		||||
    public class SoundCloudApiService
 | 
			
		||||
    public class SoundCloudApiService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								src/NadekoBot/Services/Impl/StartingGuildsListService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/NadekoBot/Services/Impl/StartingGuildsListService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Impl
 | 
			
		||||
{
 | 
			
		||||
    public class StartingGuildsService : IEnumerable<long>, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ImmutableList<long> _guilds;
 | 
			
		||||
 | 
			
		||||
        public StartingGuildsService(DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            this._guilds = client.Guilds.Select(x => (long)x.Id).ToImmutableList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<long> GetEnumerator() =>
 | 
			
		||||
            _guilds.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
            _guilds.GetEnumerator();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,7 +15,7 @@ using NadekoBot.Services.Impl;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public class MusicService
 | 
			
		||||
    public class MusicService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public const string MusicDataPath = "data/musicdata";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Permissions
 | 
			
		||||
{
 | 
			
		||||
    public class BlacklistService : IEarlyBlocker
 | 
			
		||||
    public class BlacklistService : IEarlyBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentHashSet<ulong> BlacklistedUsers { get; }
 | 
			
		||||
        public ConcurrentHashSet<ulong> BlacklistedGuilds { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Permissions
 | 
			
		||||
{
 | 
			
		||||
    public class CmdCdService : ILateBlocker
 | 
			
		||||
    public class CmdCdService : ILateBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> CommandCooldowns { get; }
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> ActiveCooldowns { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>>();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ using NLog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Permissions
 | 
			
		||||
{
 | 
			
		||||
    public class FilterService : IEarlyBlocker
 | 
			
		||||
    public class FilterService : IEarlyBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Permissions
 | 
			
		||||
{
 | 
			
		||||
    public class GlobalPermissionService : ILateBlocker
 | 
			
		||||
    public class GlobalPermissionService : ILateBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly ConcurrentHashSet<string> BlockedModules;
 | 
			
		||||
        public readonly ConcurrentHashSet<string> BlockedCommands;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,14 +11,14 @@ using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Permissions
 | 
			
		||||
{
 | 
			
		||||
    public class PermissionService : ILateBlocker
 | 
			
		||||
    public class PermissionService : ILateBlocker, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly CommandHandler _cmd;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
 | 
			
		||||
@@ -26,16 +26,15 @@ namespace NadekoBot.Services.Permissions
 | 
			
		||||
        public ConcurrentDictionary<ulong, PermissionCache> Cache { get; } =
 | 
			
		||||
            new ConcurrentDictionary<ulong, PermissionCache>();
 | 
			
		||||
 | 
			
		||||
        public PermissionService(DiscordSocketClient client, DbService db, BotConfig bc, CommandHandler cmd, NadekoStrings strings)
 | 
			
		||||
        public PermissionService(DiscordSocketClient client, DbService db, CommandHandler cmd, NadekoStrings strings)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _cmd = cmd;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
 | 
			
		||||
            var sw = Stopwatch.StartNew();
 | 
			
		||||
            if (client.ShardId == 0)
 | 
			
		||||
                TryMigratePermissions(bc);
 | 
			
		||||
                TryMigratePermissions();
 | 
			
		||||
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
@@ -49,9 +48,6 @@ namespace NadekoBot.Services.Permissions
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sw.Stop();
 | 
			
		||||
            _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public PermissionCache GetCache(ulong guildId)
 | 
			
		||||
@@ -71,12 +67,13 @@ namespace NadekoBot.Services.Permissions
 | 
			
		||||
            return pc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void TryMigratePermissions(BotConfig bc)
 | 
			
		||||
        private void TryMigratePermissions()
 | 
			
		||||
        {
 | 
			
		||||
            var log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            if (bc.PermissionVersion <= 1)
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                var bc = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                var log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                if (bc.PermissionVersion <= 1)
 | 
			
		||||
                {
 | 
			
		||||
                    log.Info("Permission version is 1, upgrading to 2.");
 | 
			
		||||
                    var oldCache = new ConcurrentDictionary<ulong, OldPermissionCache>(uow.GuildConfigs
 | 
			
		||||
@@ -134,25 +131,22 @@ namespace NadekoBot.Services.Permissions
 | 
			
		||||
                    bc.PermissionVersion = 2;
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (bc.PermissionVersion <= 2)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                if (bc.PermissionVersion <= 2)
 | 
			
		||||
                {
 | 
			
		||||
                    var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" };
 | 
			
		||||
                    uow._context.Database.ExecuteSqlCommand(
 | 
			
		||||
$@"UPDATE {nameof(Permissionv2)}
 | 
			
		||||
    $@"UPDATE {nameof(Permissionv2)}
 | 
			
		||||
SET secondaryTargetName=trim(substr(secondaryTargetName, 3))
 | 
			
		||||
WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%';
 | 
			
		||||
 | 
			
		||||
UPDATE {nameof(Permissionv2)}
 | 
			
		||||
SET secondaryTargetName=substr(secondaryTargetName, 2)
 | 
			
		||||
WHERE secondaryTargetName LIKE '.%' OR
 | 
			
		||||
    secondaryTargetName LIKE '~%' OR
 | 
			
		||||
    secondaryTargetName LIKE ';%' OR
 | 
			
		||||
    secondaryTargetName LIKE '>%' OR
 | 
			
		||||
    secondaryTargetName LIKE '-%' OR
 | 
			
		||||
    secondaryTargetName LIKE '!%';");
 | 
			
		||||
secondaryTargetName LIKE '~%' OR
 | 
			
		||||
secondaryTargetName LIKE ';%' OR
 | 
			
		||||
secondaryTargetName LIKE '>%' OR
 | 
			
		||||
secondaryTargetName LIKE '-%' OR
 | 
			
		||||
secondaryTargetName LIKE '!%';");
 | 
			
		||||
                    bc.PermissionVersion = 3;
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
@@ -229,4 +223,4 @@ WHERE secondaryTargetName LIKE '.%' OR
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ using System.IO;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Pokemon
 | 
			
		||||
{
 | 
			
		||||
    public class PokemonService
 | 
			
		||||
    public class PokemonService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly List<PokemonType> PokemonTypes = new List<PokemonType>();
 | 
			
		||||
        public readonly ConcurrentDictionary<ulong, PokeStats> Stats = new ConcurrentDictionary<ulong, PokeStats>();
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class AnimeSearchService
 | 
			
		||||
    public class AnimeSearchService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ using System.Xml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class SearchesService
 | 
			
		||||
    public class SearchesService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly IGoogleApiService _google;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class StreamNotificationService
 | 
			
		||||
    public class StreamNotificationService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Timer _streamCheckTimer;
 | 
			
		||||
        private bool firstStreamNotifPass { get; set; } = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,15 @@ using System.Collections;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Impl;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using NLog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
@@ -16,8 +25,14 @@ namespace NadekoBot.Services
 | 
			
		||||
        public class ServiceProviderBuilder
 | 
			
		||||
        {
 | 
			
		||||
            private ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();
 | 
			
		||||
            private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
            public ServiceProviderBuilder Add<T>(T obj)
 | 
			
		||||
            public ServiceProviderBuilder()
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public ServiceProviderBuilder AddManual<T>(T obj)
 | 
			
		||||
            {
 | 
			
		||||
                _dict.TryAdd(typeof(T), obj);
 | 
			
		||||
                return this;
 | 
			
		||||
@@ -27,6 +42,61 @@ namespace NadekoBot.Services
 | 
			
		||||
            {
 | 
			
		||||
                return new NServiceProvider(_dict);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public ServiceProviderBuilder LoadFrom(Assembly assembly)
 | 
			
		||||
            {
 | 
			
		||||
                var allTypes = assembly.GetTypes();
 | 
			
		||||
                var services = new Queue<Type>(allTypes
 | 
			
		||||
                        .Where(x => x.GetInterfaces().Contains(typeof(INService)) && !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract)
 | 
			
		||||
                        .ToArray());
 | 
			
		||||
 | 
			
		||||
                var interfaces = new HashSet<Type>(allTypes
 | 
			
		||||
                        .Where(x => x.GetInterfaces().Contains(typeof(INService)) && x.GetTypeInfo().IsInterface));
 | 
			
		||||
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
                var swInstance = new Stopwatch();
 | 
			
		||||
                while (services.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    var type = services.Dequeue(); //get a type i need to make an instance of
 | 
			
		||||
 | 
			
		||||
                    if (_dict.TryGetValue(type, out _)) // if that type is already instantiated, skip
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
                    var ctor = type.GetConstructors()[0];
 | 
			
		||||
                    var argTypes = ctor
 | 
			
		||||
                        .GetParameters()
 | 
			
		||||
                        .Select(x => x.ParameterType)
 | 
			
		||||
                        .ToArray(); // get constructor argument types i need to pass in
 | 
			
		||||
 | 
			
		||||
                    var args = new List<object>(argTypes.Length);
 | 
			
		||||
                    foreach (var arg in argTypes) //get constructor arguments from the dictionary of already instantiated types
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_dict.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on
 | 
			
		||||
                            args.Add(argObj);
 | 
			
		||||
                        else //if i failed getting it, add it to the end, and break
 | 
			
		||||
                        {
 | 
			
		||||
                            services.Enqueue(type);
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (args.Count != argTypes.Length)
 | 
			
		||||
                        continue;
 | 
			
		||||
                    swInstance.Restart();
 | 
			
		||||
                    var instance = ctor.Invoke(args.ToArray());
 | 
			
		||||
                    swInstance.Stop();
 | 
			
		||||
                    if (swInstance.Elapsed.TotalSeconds > 5)
 | 
			
		||||
                        _log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load.");
 | 
			
		||||
                    var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x));
 | 
			
		||||
                    if (interfaceType != null)
 | 
			
		||||
                        _dict.TryAdd(interfaceType, instance);
 | 
			
		||||
 | 
			
		||||
                    _dict.TryAdd(type, instance);
 | 
			
		||||
                }
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                _log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly ImmutableDictionary<Type, object> _services;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ using NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class CommandMapService : IInputTransformer
 | 
			
		||||
    public class CommandMapService : IInputTransformer, INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class ConverterService
 | 
			
		||||
    public class ConverterService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public List<ConvertUnit> Units { get; } = new List<ConvertUnit>();
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    //todo 50 rewrite
 | 
			
		||||
    public class MessageRepeaterService
 | 
			
		||||
    public class MessageRepeaterService : INService
 | 
			
		||||
    {
 | 
			
		||||
        //messagerepeater
 | 
			
		||||
        //guildid/RepeatRunners
 | 
			
		||||
@@ -19,8 +19,7 @@ namespace NadekoBot.Services.Utility
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                while (!bot.Ready)
 | 
			
		||||
                    await Task.Delay(1000);
 | 
			
		||||
                await bot.Ready.Task.ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(gcs
 | 
			
		||||
                    .ToDictionary(gc => gc.GuildId,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,12 @@ using System.Collections.Immutable;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class PatreonRewardsService
 | 
			
		||||
    public class PatreonRewardsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Impl;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
@@ -14,7 +14,7 @@ using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class RemindService
 | 
			
		||||
    public class RemindService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly Regex Regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
 | 
			
		||||
                                RegexOptions.Compiled | RegexOptions.Multiline);
 | 
			
		||||
@@ -28,8 +28,8 @@ namespace NadekoBot.Services.Utility
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
        public RemindService(DiscordSocketClient client, BotConfig config, DbService db, 
 | 
			
		||||
            List<long> guilds, IUnitOfWork uow)
 | 
			
		||||
        public RemindService(DiscordSocketClient client, BotConfig config, DbService db,
 | 
			
		||||
             StartingGuildsService guilds, IUnitOfWork uow)
 | 
			
		||||
        {
 | 
			
		||||
            _config = config;
 | 
			
		||||
            _client = client;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ using NLog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class StreamRoleService
 | 
			
		||||
    public class StreamRoleService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private readonly ConcurrentDictionary<ulong, StreamRoleSettings> guildSettings;
 | 
			
		||||
@@ -26,6 +26,7 @@ namespace NadekoBot.Services.Utility
 | 
			
		||||
            this._log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            guildSettings = gcs.ToDictionary(x => x.GuildId, x => x.StreamRole)
 | 
			
		||||
                .Where(x => x.Value.FromRoleId != 0 && x.Value.AddRoleId != 0)
 | 
			
		||||
               .ToConcurrent();
 | 
			
		||||
 | 
			
		||||
            client.GuildMemberUpdated += Client_GuildMemberUpdated;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class VerboseErrorsService
 | 
			
		||||
    public class VerboseErrorsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ConcurrentHashSet<ulong> guildsEnabled;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Impl;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user