Utility and nsfw work
This commit is contained in:
		@@ -1,46 +1,13 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Attributes
 | 
			
		||||
{
 | 
			
		||||
    [System.AttributeUsage(AttributeTargets.Class)]
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Class)]
 | 
			
		||||
    sealed class NadekoModuleAttribute : GroupAttribute
 | 
			
		||||
    {
 | 
			
		||||
        //modulename / prefix
 | 
			
		||||
        private static Dictionary<string, string> modulePrefixes = null;
 | 
			
		||||
        public static Dictionary<string, string> ModulePrefixes {
 | 
			
		||||
            get {
 | 
			
		||||
                if (modulePrefixes != null)
 | 
			
		||||
                    return modulePrefixes;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    return (modulePrefixes = uow.BotConfig
 | 
			
		||||
                                                .GetOrCreate()
 | 
			
		||||
                                                .ModulePrefixes
 | 
			
		||||
                                                .ToDictionary(p => p.ModuleName, p => p.Prefix));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public NadekoModuleAttribute(string moduleName, string defaultPrefix) : base(GetModulePrefix(moduleName, defaultPrefix), moduleName)
 | 
			
		||||
        public NadekoModuleAttribute(string moduleName) : base("")
 | 
			
		||||
        {
 | 
			
		||||
            //AppendSpace = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string GetModulePrefix(string moduleName, string defaultPrefix)
 | 
			
		||||
        {
 | 
			
		||||
            if (!ModulePrefixes.TryGetValue(moduleName, out string prefix))
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.ModulePrefixes.TryAdd(moduleName, defaultPrefix);
 | 
			
		||||
                NLog.LogManager.GetCurrentClassLogger().Warn("Prefix not found for {0}. Will use default one: {1}", moduleName, defaultPrefix);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return prefix ?? defaultPrefix;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,20 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System;
 | 
			
		||||
using NadekoBot.Services.Impl;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Attributes
 | 
			
		||||
{
 | 
			
		||||
    public class OwnerOnlyAttribute : PreconditionAttribute
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand,IDependencyMap depMap) =>
 | 
			
		||||
            Task.FromResult((NadekoBot.Credentials.IsOwner(context.User) || NadekoBot.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
 | 
			
		||||
        public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            var creds = (IBotCredentials)services.GetService(typeof(IBotCredentials));
 | 
			
		||||
            var client = (IDiscordClient)services.GetService(typeof(IDiscordClient));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult((creds.IsOwner(context.User) || client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,4 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using static NadekoBot.Modules.Permissions.Permissions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.DataStructures
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/NadekoBot/DataStructures/PermissionCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/NadekoBot/DataStructures/PermissionCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.DataStructures
 | 
			
		||||
{
 | 
			
		||||
    public class OldPermissionCache
 | 
			
		||||
    {
 | 
			
		||||
        public string PermRole { get; set; }
 | 
			
		||||
        public bool Verbose { get; set; } = true;
 | 
			
		||||
        public Permission RootPermission { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class PermissionCache
 | 
			
		||||
    {
 | 
			
		||||
        public string PermRole { get; set; }
 | 
			
		||||
        public bool Verbose { get; set; } = true;
 | 
			
		||||
        public PermissionsCollection<Permissionv2> Permissions { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,68 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class CommandTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
        public CommandTypeReader(CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var cmd = _cmds.Commands.FirstOrDefault(c => 
 | 
			
		||||
                c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
 | 
			
		||||
            if (cmd == null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //public class CommandOrCrTypeReader : CommandTypeReader
 | 
			
		||||
    //{
 | 
			
		||||
    //    public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
    //    {
 | 
			
		||||
    //        input = input.ToUpperInvariant();
 | 
			
		||||
            
 | 
			
		||||
    //        if (CustomReactions.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
 | 
			
		||||
    //        {
 | 
			
		||||
    //            return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
 | 
			
		||||
    //        }
 | 
			
		||||
    //        var guild = context.Guild;
 | 
			
		||||
    //        if (guild != null)
 | 
			
		||||
    //        {
 | 
			
		||||
    //            CustomReaction[] crs;
 | 
			
		||||
    //            if (CustomReactions.GuildReactions.TryGetValue(guild.Id, out crs))
 | 
			
		||||
    //            {
 | 
			
		||||
    //                if (crs.Any(x => x.Trigger.ToUpperInvariant() == input))
 | 
			
		||||
    //                {
 | 
			
		||||
    //                    return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
 | 
			
		||||
    //                }
 | 
			
		||||
    //            }
 | 
			
		||||
    //        }
 | 
			
		||||
 | 
			
		||||
    //        var cmd = await base.Read(context, input);
 | 
			
		||||
    //        if (cmd.IsSuccess)
 | 
			
		||||
    //        {
 | 
			
		||||
    //            return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Aliases.First()));
 | 
			
		||||
    //        }
 | 
			
		||||
    //        return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
 | 
			
		||||
    //    }
 | 
			
		||||
    //}
 | 
			
		||||
 | 
			
		||||
    public class CommandOrCrInfo
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        public CommandOrCrInfo(string input)
 | 
			
		||||
        {
 | 
			
		||||
            this.Name = input;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -6,10 +7,16 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class GuildTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
        public GuildTypeReader(DiscordShardedClient client)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
        }
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.Trim().ToLowerInvariant();
 | 
			
		||||
            var guilds = NadekoBot.Client.Guilds;
 | 
			
		||||
            var guilds = _client.Guilds;
 | 
			
		||||
            var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToLowerInvariant() == input) ?? //by id
 | 
			
		||||
                        guilds.FirstOrDefault(g => g.Name.Trim().ToLowerInvariant() == input); //by name
 | 
			
		||||
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -7,10 +8,17 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class ModuleTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
        public ModuleTypeReader(CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var module = NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
            var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
 | 
			
		||||
            if (module == null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
@@ -20,10 +28,17 @@ namespace NadekoBot.TypeReaders
 | 
			
		||||
 | 
			
		||||
    public class ModuleOrCrTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CommandService _cmds;
 | 
			
		||||
 | 
			
		||||
        public ModuleOrCrTypeReader(CommandService cmds)
 | 
			
		||||
        {
 | 
			
		||||
            _cmds = cmds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToLowerInvariant();
 | 
			
		||||
            var module = NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToLowerInvariant() == input)?.Key;
 | 
			
		||||
            var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToLowerInvariant() == input)?.Key;
 | 
			
		||||
            if (module == null && input != "actualcustomreactions")
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
 | 
			
		||||
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,7 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Metadata;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,6 @@ namespace NadekoBot.Modules.Music
 | 
			
		||||
    [DontAutoLoad]
 | 
			
		||||
    public class Music : NadekoTopLevelModule 
 | 
			
		||||
    {
 | 
			
		||||
        public const string MusicDataPath = "data/musicdata";
 | 
			
		||||
 | 
			
		||||
        private static MusicService music;
 | 
			
		||||
 | 
			
		||||
        static Music()
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,21 @@ using NadekoBot.Extensions;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.NSFW
 | 
			
		||||
{
 | 
			
		||||
    [NadekoModule("NSFW", "~")]
 | 
			
		||||
    [NadekoModule("NSFW")]
 | 
			
		||||
    public class NSFW : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
 | 
			
		||||
        private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
 | 
			
		||||
        private readonly SearchesService _service;
 | 
			
		||||
 | 
			
		||||
        public NSFW(SearchesService service)
 | 
			
		||||
        {
 | 
			
		||||
            _service = service;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
 | 
			
		||||
        {
 | 
			
		||||
@@ -142,11 +148,11 @@ namespace NadekoBot.Modules.NSFW
 | 
			
		||||
#endif
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public Task Yandere([Remainder] string tag = null)
 | 
			
		||||
            => InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Yandere);
 | 
			
		||||
            => InternalDapiCommand(tag, DapiSearchType.Yandere);
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public Task Konachan([Remainder] string tag = null)
 | 
			
		||||
            => InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Konachan);
 | 
			
		||||
            => InternalDapiCommand(tag, DapiSearchType.Konachan);
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task E621([Remainder] string tag = null)
 | 
			
		||||
@@ -167,7 +173,7 @@ namespace NadekoBot.Modules.NSFW
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public Task Rule34([Remainder] string tag = null)
 | 
			
		||||
            => InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Rule34);
 | 
			
		||||
            => InternalDapiCommand(tag, DapiSearchType.Rule34);
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Danbooru([Remainder] string tag = null)
 | 
			
		||||
@@ -210,7 +216,7 @@ namespace NadekoBot.Modules.NSFW
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public Task Gelbooru([Remainder] string tag = null)
 | 
			
		||||
            => InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Gelbooru);
 | 
			
		||||
            => InternalDapiCommand(tag, DapiSearchType.Gelbooru);
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Boobs()
 | 
			
		||||
@@ -270,23 +276,23 @@ namespace NadekoBot.Modules.NSFW
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        public static Task<string> GetRule34ImageLink(string tag) =>
 | 
			
		||||
            Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34);
 | 
			
		||||
        public Task<string> GetRule34ImageLink(string tag) =>
 | 
			
		||||
            _service.DapiSearch(tag, DapiSearchType.Rule34);
 | 
			
		||||
 | 
			
		||||
        public static Task<string> GetYandereImageLink(string tag) =>
 | 
			
		||||
            Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Yandere);
 | 
			
		||||
        public Task<string> GetYandereImageLink(string tag) =>
 | 
			
		||||
            _service.DapiSearch(tag, DapiSearchType.Yandere);
 | 
			
		||||
 | 
			
		||||
        public static Task<string> GetKonachanImageLink(string tag) =>
 | 
			
		||||
            Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Konachan);
 | 
			
		||||
        public Task<string> GetKonachanImageLink(string tag) =>
 | 
			
		||||
            _service.DapiSearch(tag, DapiSearchType.Konachan);
 | 
			
		||||
 | 
			
		||||
        public static Task<string> GetGelbooruImageLink(string tag) =>
 | 
			
		||||
            Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru);
 | 
			
		||||
        public Task<string> GetGelbooruImageLink(string tag) =>
 | 
			
		||||
            _service.DapiSearch(tag, DapiSearchType.Gelbooru);
 | 
			
		||||
 | 
			
		||||
        public async Task InternalDapiCommand(string tag, Searches.Searches.DapiSearchType type)
 | 
			
		||||
        public async Task InternalDapiCommand(string tag, DapiSearchType type)
 | 
			
		||||
        {
 | 
			
		||||
            tag = tag?.Trim() ?? "";
 | 
			
		||||
 | 
			
		||||
            var url = await Searches.Searches.InternalDapiSearch(tag, type).ConfigureAwait(false);
 | 
			
		||||
            var url = await _service.DapiSearch(tag, type).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (url == null)
 | 
			
		||||
                await ReplyErrorLocalized("not_found").ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
@@ -16,20 +17,23 @@ namespace NadekoBot.Modules
 | 
			
		||||
        public readonly string ModuleTypeName;
 | 
			
		||||
        public readonly string LowerModuleTypeName;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        //todo :thinking:
 | 
			
		||||
        public NadekoStrings _strings { get; set; }
 | 
			
		||||
        public ILocalization _localization { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected NadekoTopLevelModule(bool isTopLevelModule = true)
 | 
			
		||||
        {
 | 
			
		||||
            //if it's top level module
 | 
			
		||||
            ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
 | 
			
		||||
            LowerModuleTypeName = ModuleTypeName.ToLowerInvariant();
 | 
			
		||||
 | 
			
		||||
            if (!NadekoBot.ModulePrefixes.TryGetValue(ModuleTypeName, out Prefix))
 | 
			
		||||
                Prefix = "?err?";
 | 
			
		||||
            Prefix = NadekoBot.Prefix;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void BeforeExecute()
 | 
			
		||||
        {
 | 
			
		||||
            _cultureInfo = NadekoBot.Localization.GetCultureInfo(Context.Guild?.Id);
 | 
			
		||||
            _cultureInfo =_localization.GetCultureInfo(Context.Guild?.Id);
 | 
			
		||||
 | 
			
		||||
            _log.Info("Culture info is {0}", _cultureInfo);
 | 
			
		||||
        }
 | 
			
		||||
@@ -53,46 +57,12 @@ namespace NadekoBot.Modules
 | 
			
		||||
        //    var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
 | 
			
		||||
        //    return Context.Channel.SendErrorAsync(title, text, url, footer);
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Used as failsafe in case response key doesn't exist in the selected or default language.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private static readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
 | 
			
		||||
 | 
			
		||||
        public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
 | 
			
		||||
        {
 | 
			
		||||
            var text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, cultureInfo);
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
            {
 | 
			
		||||
                LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
 | 
			
		||||
                text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                    return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
 | 
			
		||||
                        lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
 | 
			
		||||
            }
 | 
			
		||||
            return text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
 | 
			
		||||
            params object[] replacements)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return string.Format(GetTextStatic(key, cultureInfo, lowerModuleTypeName), replacements);
 | 
			
		||||
            }
 | 
			
		||||
            catch (FormatException)
 | 
			
		||||
            {
 | 
			
		||||
                return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
 | 
			
		||||
                       lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        protected string GetText(string key) =>
 | 
			
		||||
            GetTextStatic(key, _cultureInfo, LowerModuleTypeName);
 | 
			
		||||
            _strings.GetText(key, _cultureInfo, LowerModuleTypeName);
 | 
			
		||||
 | 
			
		||||
        protected string GetText(string key, params object[] replacements) =>
 | 
			
		||||
            GetTextStatic(key, _cultureInfo, LowerModuleTypeName, replacements);
 | 
			
		||||
            _strings.GetText(key, _cultureInfo, LowerModuleTypeName, replacements);
 | 
			
		||||
 | 
			
		||||
        public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,20 +20,6 @@ namespace NadekoBot.Modules.Permissions
 | 
			
		||||
    [NadekoModule("Permissions", ";")]
 | 
			
		||||
    public partial class Permissions : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        public class OldPermissionCache
 | 
			
		||||
        {
 | 
			
		||||
            public string PermRole { get; set; }
 | 
			
		||||
            public bool Verbose { get; set; } = true;
 | 
			
		||||
            public Permission RootPermission { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class PermissionCache
 | 
			
		||||
        {
 | 
			
		||||
            public string PermRole { get; set; }
 | 
			
		||||
            public bool Verbose { get; set; } = true;
 | 
			
		||||
            public PermissionsCollection<Permissionv2> Permissions { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //guildid, root permission
 | 
			
		||||
        public static ConcurrentDictionary<ulong, PermissionCache> Cache { get; } =
 | 
			
		||||
            new ConcurrentDictionary<ulong, PermissionCache>();
 | 
			
		||||
 
 | 
			
		||||
@@ -762,14 +762,6 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        //    }
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        public enum DapiSearchType
 | 
			
		||||
        {
 | 
			
		||||
            Safebooru,
 | 
			
		||||
            Gelbooru,
 | 
			
		||||
            Konachan,
 | 
			
		||||
            Rule34,
 | 
			
		||||
            Yandere
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
 | 
			
		||||
        {
 | 
			
		||||
@@ -787,55 +779,6 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    .WithImageUrl(url)
 | 
			
		||||
                    .WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<string> InternalDapiSearch(string tag, DapiSearchType type)
 | 
			
		||||
        {
 | 
			
		||||
            tag = tag?.Replace(" ", "_");
 | 
			
		||||
            var website = "";
 | 
			
		||||
            switch (type)
 | 
			
		||||
            {
 | 
			
		||||
                case DapiSearchType.Safebooru:
 | 
			
		||||
                    website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Gelbooru:
 | 
			
		||||
                    website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Rule34:
 | 
			
		||||
                    website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Konachan:
 | 
			
		||||
                    website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Yandere:
 | 
			
		||||
                    website = $"https://yande.re/post.xml?limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var toReturn = await Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        http.AddFakeHeaders();
 | 
			
		||||
                        var data = await http.GetStreamAsync(website).ConfigureAwait(false);
 | 
			
		||||
                        var doc = new XmlDocument();
 | 
			
		||||
                        doc.Load(data);
 | 
			
		||||
 | 
			
		||||
                        var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)];
 | 
			
		||||
 | 
			
		||||
                        var url = node.Attributes["file_url"].Value;
 | 
			
		||||
                        if (!url.StartsWith("http"))
 | 
			
		||||
                            url = "https:" + url;
 | 
			
		||||
                        return url;
 | 
			
		||||
                    }
 | 
			
		||||
                }).ConfigureAwait(false);
 | 
			
		||||
                return toReturn;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public async Task<bool> ValidateQuery(IMessageChannel ch, string query)
 | 
			
		||||
        {
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(query)) return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +1,32 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
{
 | 
			
		||||
    public partial class Utility
 | 
			
		||||
    {
 | 
			
		||||
        public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
 | 
			
		||||
        {
 | 
			
		||||
            public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
 | 
			
		||||
 | 
			
		||||
            public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class CommandMapCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            //guildId, (trigger, mapping)
 | 
			
		||||
            public static ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
 | 
			
		||||
            private readonly UtilityService _service;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            static CommandMapCommands()
 | 
			
		||||
            public CommandMapCommands(UtilityService service, DbHandler db, DiscordShardedClient client)
 | 
			
		||||
            {
 | 
			
		||||
                var eq = new CommandAliasEqualityComparer();
 | 
			
		||||
                AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
 | 
			
		||||
                    NadekoBot.AllGuildConfigs.ToDictionary(
 | 
			
		||||
                        x => x.GuildId,
 | 
			
		||||
                        x => new ConcurrentDictionary<string, string>(x.CommandAliases
 | 
			
		||||
                            .Distinct(eq)
 | 
			
		||||
                            .ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                AliasMaps.Clear();
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -61,14 +45,14 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                {
 | 
			
		||||
                    ConcurrentDictionary<string, string> maps;
 | 
			
		||||
                    string throwaway;
 | 
			
		||||
                    if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) ||
 | 
			
		||||
                    if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) ||
 | 
			
		||||
                        !maps.TryRemove(trigger, out throwaway))
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
                        var toAdd = new CommandAlias()
 | 
			
		||||
@@ -83,9 +67,9 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    await ReplyConfirmLocalized("alias_removed", Format.Code(trigger)).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                AliasMaps.AddOrUpdate(Context.Guild.Id, (_) =>
 | 
			
		||||
                _service.AliasMaps.AddOrUpdate(Context.Guild.Id, (_) =>
 | 
			
		||||
                {
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
                        config.CommandAliases.Add(new CommandAlias()
 | 
			
		||||
@@ -100,7 +84,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    });
 | 
			
		||||
                }, (_, map) =>
 | 
			
		||||
                {
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
                        var toAdd = new CommandAlias()
 | 
			
		||||
@@ -131,7 +115,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                ConcurrentDictionary<string, string> maps;
 | 
			
		||||
                if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any())
 | 
			
		||||
                if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("aliases_none").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -139,7 +123,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
                var arr = maps.ToArray();
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.SendPaginatedConfirmAsync(page + 1, (curPage) =>
 | 
			
		||||
                await Context.Channel.SendPaginatedConfirmAsync(_client, page + 1, (curPage) =>
 | 
			
		||||
                {
 | 
			
		||||
                    return new EmbedBuilder().WithOkColor()
 | 
			
		||||
                    .WithTitle(GetText("alias_list"))
 | 
			
		||||
 
 | 
			
		||||
@@ -15,60 +15,13 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class CrossServerTextChannel : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            static CrossServerTextChannel()
 | 
			
		||||
            private readonly UtilityService _service;
 | 
			
		||||
 | 
			
		||||
            public CrossServerTextChannel(UtilityService service)
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += Client_MessageReceived;
 | 
			
		||||
                _service = service;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (imsg.Author.IsBot)
 | 
			
		||||
                        return;
 | 
			
		||||
                    var msg = imsg as IUserMessage;
 | 
			
		||||
                    if (msg == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    var channel = imsg.Channel as ITextChannel;
 | 
			
		||||
                    if (channel == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (msg.Author.Id == NadekoBot.Client.CurrentUser.Id) return;
 | 
			
		||||
                    foreach (var subscriber in Subscribers)
 | 
			
		||||
                    {
 | 
			
		||||
                        var set = subscriber.Value;
 | 
			
		||||
                        if (!set.Contains(channel))
 | 
			
		||||
                            continue;
 | 
			
		||||
                        foreach (var chan in set.Except(new[] { channel }))
 | 
			
		||||
                        {
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
 | 
			
		||||
                                    msg)).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            catch
 | 
			
		||||
                            {
 | 
			
		||||
                                // ignored
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
 | 
			
		||||
                $"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
 | 
			
		||||
 | 
			
		||||
            public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
 | 
			
		||||
                new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
@@ -76,7 +29,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            {
 | 
			
		||||
                var token = new NadekoRandom().Next();
 | 
			
		||||
                var set = new ConcurrentHashSet<ITextChannel>();
 | 
			
		||||
                if (Subscribers.TryAdd(token, set))
 | 
			
		||||
                if (_service.Subscribers.TryAdd(token, set))
 | 
			
		||||
                {
 | 
			
		||||
                    set.Add((ITextChannel) Context.Channel);
 | 
			
		||||
                    await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString())
 | 
			
		||||
@@ -90,7 +43,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            public async Task Jcsc(int token)
 | 
			
		||||
            {
 | 
			
		||||
                ConcurrentHashSet<ITextChannel> set;
 | 
			
		||||
                if (!Subscribers.TryGetValue(token, out set))
 | 
			
		||||
                if (!_service.Subscribers.TryGetValue(token, out set))
 | 
			
		||||
                    return;
 | 
			
		||||
                set.Add((ITextChannel) Context.Channel);
 | 
			
		||||
                await ReplyConfirmLocalized("csc_join").ConfigureAwait(false);
 | 
			
		||||
@@ -101,7 +54,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageGuild)]
 | 
			
		||||
            public async Task Lcsc()
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var subscriber in Subscribers)
 | 
			
		||||
                foreach (var subscriber in _service.Subscribers)
 | 
			
		||||
                {
 | 
			
		||||
                    subscriber.Value.TryRemove((ITextChannel) Context.Channel);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@@ -14,6 +16,17 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class InfoCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly IStatsService _stats;
 | 
			
		||||
            private readonly CommandHandler _ch;
 | 
			
		||||
 | 
			
		||||
            public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _stats = stats;
 | 
			
		||||
                _ch = ch;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public async Task ServerInfo(string guildName = null)
 | 
			
		||||
@@ -24,7 +37,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(guildName))
 | 
			
		||||
                    guild = channel.Guild;
 | 
			
		||||
                else
 | 
			
		||||
                    guild = NadekoBot.Client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
 | 
			
		||||
                    guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
 | 
			
		||||
                if (guild == null)
 | 
			
		||||
                    return;
 | 
			
		||||
                var ownername = await guild.GetUserAsync(guild.OwnerId);
 | 
			
		||||
@@ -50,9 +63,9 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    .AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
 | 
			
		||||
                    .WithImageUrl(guild.IconUrl)
 | 
			
		||||
                    .WithColor(NadekoBot.OkColor);
 | 
			
		||||
                if (guild.Emojis.Any())
 | 
			
		||||
                if (guild.Emotes.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emojis.Count})").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
 | 
			
		||||
                    embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emotes.Count})").WithValue(string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
 | 
			
		||||
                }
 | 
			
		||||
                await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -101,36 +114,36 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    embed.WithThumbnailUrl(user.RealAvatarUrl());
 | 
			
		||||
                await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async Task Activity(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            const int activityPerPage = 15;
 | 
			
		||||
            page -= 1;
 | 
			
		||||
 | 
			
		||||
            if (page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            int startCount = page * activityPerPage;
 | 
			
		||||
 | 
			
		||||
            StringBuilder str = new StringBuilder();
 | 
			
		||||
            foreach (var kvp in NadekoBot.CommandHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page*activityPerPage).Take(activityPerPage))
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task Activity(int page = 1)
 | 
			
		||||
            {
 | 
			
		||||
                str.AppendLine(GetText("activity_line", 
 | 
			
		||||
                    ++startCount, 
 | 
			
		||||
                    Format.Bold(kvp.Key.ToString()),
 | 
			
		||||
                    kvp.Value / NadekoBot.Stats.GetUptime().TotalSeconds, kvp.Value));
 | 
			
		||||
            }
 | 
			
		||||
                const int activityPerPage = 15;
 | 
			
		||||
                page -= 1;
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                .WithTitle(GetText("activity_page", page + 1))
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithFooter(efb => efb.WithText(GetText("activity_users_total",
 | 
			
		||||
                    NadekoBot.CommandHandler.UserMessagesSent.Count)))
 | 
			
		||||
                .WithDescription(str.ToString()));
 | 
			
		||||
                if (page < 0)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                int startCount = page * activityPerPage;
 | 
			
		||||
 | 
			
		||||
                StringBuilder str = new StringBuilder();
 | 
			
		||||
                foreach (var kvp in _ch.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage))
 | 
			
		||||
                {
 | 
			
		||||
                    str.AppendLine(GetText("activity_line",
 | 
			
		||||
                        ++startCount,
 | 
			
		||||
                        Format.Bold(kvp.Key.ToString()),
 | 
			
		||||
                        kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                    .WithTitle(GetText("activity_page", page + 1))
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithFooter(efb => efb.WithText(GetText("activity_users_total",
 | 
			
		||||
                        _ch.UserMessagesSent.Count)))
 | 
			
		||||
                    .WithDescription(str.ToString()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,17 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.Net;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Modules.Utility.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
{
 | 
			
		||||
@@ -22,140 +20,15 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class RepeatCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            //guildid/RepeatRunners
 | 
			
		||||
            public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
 | 
			
		||||
            private readonly UtilityService _service;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            private static bool _ready;
 | 
			
		||||
 | 
			
		||||
            public class RepeatRunner
 | 
			
		||||
            public RepeatCommands(UtilityService service, DiscordShardedClient client, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
                private CancellationTokenSource source { get; set; }
 | 
			
		||||
                private CancellationToken token { get; set; }
 | 
			
		||||
                public Repeater Repeater { get; }
 | 
			
		||||
                public SocketGuild Guild { get; }
 | 
			
		||||
                public ITextChannel Channel { get; private set; }
 | 
			
		||||
                private IUserMessage oldMsg = null;
 | 
			
		||||
 | 
			
		||||
                public RepeatRunner(Repeater repeater, ITextChannel channel = null)
 | 
			
		||||
                {
 | 
			
		||||
                    _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                    Repeater = repeater;
 | 
			
		||||
                    Channel = channel;
 | 
			
		||||
 | 
			
		||||
                    Guild = NadekoBot.Client.GetGuild(repeater.GuildId);
 | 
			
		||||
                    if(Guild!=null)
 | 
			
		||||
                        Task.Run(Run);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                private async Task Run()
 | 
			
		||||
                {
 | 
			
		||||
                    source = new CancellationTokenSource();
 | 
			
		||||
                    token = source.Token;
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        while (!token.IsCancellationRequested)
 | 
			
		||||
                        {
 | 
			
		||||
                            await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            await Trigger().ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (OperationCanceledException)
 | 
			
		||||
                    {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public async Task Trigger()
 | 
			
		||||
                {
 | 
			
		||||
                    var toSend = "🔄 " + Repeater.Message;
 | 
			
		||||
                    //var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
 | 
			
		||||
                    // if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
 | 
			
		||||
                    //     continue;
 | 
			
		||||
 | 
			
		||||
                    if (oldMsg != null)
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await oldMsg.DeleteAsync();
 | 
			
		||||
                        }
 | 
			
		||||
                        catch
 | 
			
		||||
                        {
 | 
			
		||||
                            // ignored
 | 
			
		||||
                        }
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        if (Channel == null)
 | 
			
		||||
                            Channel = Guild.GetTextChannel(Repeater.ChannelId);
 | 
			
		||||
 | 
			
		||||
                        if (Channel != null)
 | 
			
		||||
                            oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public void Reset()
 | 
			
		||||
                {
 | 
			
		||||
                    source.Cancel();
 | 
			
		||||
                    var _ = Task.Run(Run);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public void Stop()
 | 
			
		||||
                {
 | 
			
		||||
                    source.Cancel();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public override string ToString() => 
 | 
			
		||||
                    $"{Channel?.Mention ?? $"⚠<#{Repeater.ChannelId}>" } " +
 | 
			
		||||
                    $"| {(int) Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
 | 
			
		||||
                    $"| {Repeater.Message.TrimTo(33)}";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static RepeatCommands()
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
                    await Task.Delay(5000).ConfigureAwait(false);
 | 
			
		||||
#else
 | 
			
		||||
                    await Task.Delay(30000).ConfigureAwait(false);
 | 
			
		||||
#endif
 | 
			
		||||
                    //todo this is pretty terrible
 | 
			
		||||
                    Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
 | 
			
		||||
                        .ToDictionary(gc => gc.GuildId,
 | 
			
		||||
                            gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
 | 
			
		||||
                                .Select(gr => new RepeatRunner(gr))
 | 
			
		||||
                                .Where(x => x.Guild != null))));
 | 
			
		||||
                    _ready = true;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                _ready = false;
 | 
			
		||||
                foreach (var kvp in Repeaters)
 | 
			
		||||
                {
 | 
			
		||||
                    RepeatRunner r;
 | 
			
		||||
                    while (kvp.Value.TryDequeue(out r))
 | 
			
		||||
                    {
 | 
			
		||||
                        r.Stop();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Repeaters.Clear();
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -163,11 +36,10 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            public async Task RepeatInvoke(int index)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_ready)
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
                    return;
 | 
			
		||||
                index -= 1;
 | 
			
		||||
                ConcurrentQueue<RepeatRunner> rep;
 | 
			
		||||
                if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
 | 
			
		||||
                if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -193,14 +65,13 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [Priority(0)]
 | 
			
		||||
            public async Task RepeatRemove(int index)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_ready)
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (index < 1)
 | 
			
		||||
                    return;
 | 
			
		||||
                index -= 1;
 | 
			
		||||
 | 
			
		||||
                ConcurrentQueue<RepeatRunner> rep;
 | 
			
		||||
                if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
 | 
			
		||||
                
 | 
			
		||||
                if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var repeaterList = rep.ToList();
 | 
			
		||||
@@ -215,7 +86,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                repeater.Stop();
 | 
			
		||||
                repeaterList.RemoveAt(index);
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var guildConfig = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.GuildRepeaters));
 | 
			
		||||
 | 
			
		||||
@@ -223,7 +94,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
 | 
			
		||||
                if (_service.Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
 | 
			
		||||
                    await Context.Channel.SendConfirmAsync(GetText("message_repeater"),
 | 
			
		||||
                        GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -234,7 +105,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [Priority(1)]
 | 
			
		||||
            public async Task Repeat(int minutes, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_ready)
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (minutes < 1 || minutes > 10080)
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -250,7 +121,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    Message = message
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
 | 
			
		||||
 | 
			
		||||
@@ -261,9 +132,9 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var rep = new RepeatRunner(toAdd, (ITextChannel) Context.Channel);
 | 
			
		||||
                var rep = new RepeatRunner(_client, toAdd, (ITextChannel) Context.Channel);
 | 
			
		||||
 | 
			
		||||
                Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
 | 
			
		||||
                _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
 | 
			
		||||
                {
 | 
			
		||||
                    old.Enqueue(rep);
 | 
			
		||||
                    return old;
 | 
			
		||||
@@ -282,10 +153,9 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
            public async Task RepeatList()
 | 
			
		||||
            {
 | 
			
		||||
                if (!_ready)
 | 
			
		||||
                if (!_service.RepeaterReady)
 | 
			
		||||
                    return;
 | 
			
		||||
                ConcurrentQueue<RepeatRunner> repRunners;
 | 
			
		||||
                if (!Repeaters.TryGetValue(Context.Guild.Id, out repRunners))
 | 
			
		||||
                if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var repRunners))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,16 +22,20 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class PatreonCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static readonly PatreonThingy patreon;
 | 
			
		||||
            //todo rename patreon thingy and move it to be a service, or a part of utility service
 | 
			
		||||
            private readonly PatreonThingy patreon;
 | 
			
		||||
            private readonly IBotCredentials _creds;
 | 
			
		||||
            private readonly BotConfig _config;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly CurrencyHandler _currency;
 | 
			
		||||
 | 
			
		||||
            static PatreonCommands()
 | 
			
		||||
            public PatreonCommands(IBotCredentials creds, BotConfig config, DbHandler db, CurrencyHandler currency)
 | 
			
		||||
            {
 | 
			
		||||
                patreon = PatreonThingy.Instance;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                patreon.Updater.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                _creds = creds;
 | 
			
		||||
                _config = config;
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _currency = currency;
 | 
			
		||||
                patreon = PatreonThingy.GetInstance(creds, db, currency);                
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -46,7 +50,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task ClaimPatreonRewards()
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.PatreonAccessToken))
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
 | 
			
		||||
                    return;
 | 
			
		||||
                if (DateTime.UtcNow.Day < 5)
 | 
			
		||||
                {
 | 
			
		||||
@@ -65,11 +69,11 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
                if (amount > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyConfirmLocalized("clpa_success", amount + NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("clpa_success", amount + _config.CurrencySign).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var rem = (patreon.Interval - (DateTime.UtcNow - patreon.LastUpdate));
 | 
			
		||||
                var helpcmd = Format.Code(NadekoBot.ModulePrefixes[typeof(Help.Help).Name] + "donate");
 | 
			
		||||
                var helpcmd = Format.Code(NadekoBot.Prefix + "donate");
 | 
			
		||||
                await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
                    .WithDescription(GetText("clpa_fail"))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("clpa_fail_already_title")).WithValue(GetText("clpa_fail_already")))
 | 
			
		||||
@@ -83,8 +87,10 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
        public class PatreonThingy
 | 
			
		||||
        {
 | 
			
		||||
            public static PatreonThingy _instance = new PatreonThingy();
 | 
			
		||||
            public static PatreonThingy Instance => _instance;
 | 
			
		||||
            //todo quickly hacked while rewriting, fix this
 | 
			
		||||
            private static PatreonThingy _instance = null;
 | 
			
		||||
            public static PatreonThingy GetInstance(IBotCredentials creds, DbHandler db, CurrencyHandler cur) 
 | 
			
		||||
                => _instance ?? (_instance = new PatreonThingy(creds, db, cur));
 | 
			
		||||
 | 
			
		||||
            private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
 | 
			
		||||
 | 
			
		||||
@@ -96,10 +102,17 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
            public readonly TimeSpan Interval = TimeSpan.FromHours(1);
 | 
			
		||||
            private IBotCredentials _creds;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly CurrencyHandler _currency;
 | 
			
		||||
 | 
			
		||||
            private PatreonThingy()
 | 
			
		||||
            static PatreonThingy() { }
 | 
			
		||||
            private PatreonThingy(IBotCredentials creds, DbHandler db, CurrencyHandler currency)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.PatreonAccessToken))
 | 
			
		||||
                _creds = creds;
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _currency = currency;
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
 | 
			
		||||
                    return;
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval);
 | 
			
		||||
@@ -116,7 +129,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        http.DefaultRequestHeaders.Clear();
 | 
			
		||||
                        http.DefaultRequestHeaders.Add("Authorization", "Bearer " + NadekoBot.Credentials.PatreonAccessToken);
 | 
			
		||||
                        http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
 | 
			
		||||
                        var data = new PatreonData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Links = new PatreonDataLinks()
 | 
			
		||||
@@ -170,7 +183,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
                    var amount = data.Reward.attributes.amount_cents;
 | 
			
		||||
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var users = uow._context.Set<RewardedUser>();
 | 
			
		||||
                        var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id);
 | 
			
		||||
@@ -185,7 +198,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                                AmountRewardedThisMonth = amount,
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            await CurrencyHandler.AddCurrencyAsync(userId, "Patreon reward - new", amount, uow).ConfigureAwait(false);
 | 
			
		||||
                            await _currency.AddCurrencyAsync(userId, "Patreon reward - new", amount, uow).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                            return amount;
 | 
			
		||||
@@ -197,7 +210,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                            usr.AmountRewardedThisMonth = amount;
 | 
			
		||||
                            usr.PatreonUserId = data.User.id;
 | 
			
		||||
 | 
			
		||||
                            await CurrencyHandler.AddCurrencyAsync(userId, "Patreon reward - recurring", amount, uow).ConfigureAwait(false);
 | 
			
		||||
                            await _currency.AddCurrencyAsync(userId, "Patreon reward - recurring", amount, uow).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                            return amount;
 | 
			
		||||
@@ -211,7 +224,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                            usr.AmountRewardedThisMonth = amount;
 | 
			
		||||
                            usr.PatreonUserId = data.User.id;
 | 
			
		||||
 | 
			
		||||
                            await CurrencyHandler.AddCurrencyAsync(usr.UserId, "Patreon reward - update", toAward, uow).ConfigureAwait(false);
 | 
			
		||||
                            await _currency.AddCurrencyAsync(usr.UserId, "Patreon reward - update", toAward, uow).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                            return toAward;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,13 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class QuoteCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            public QuoteCommands(DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public async Task ListQuotes(int page = 1)
 | 
			
		||||
@@ -27,7 +34,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                IEnumerable<Quote> quotes;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    quotes = uow.Quotes.GetGroup(Context.Guild.Id, page * 16, 16);
 | 
			
		||||
                }
 | 
			
		||||
@@ -50,7 +57,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                keyword = keyword.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
                Quote quote;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    quote =
 | 
			
		||||
                        await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false);
 | 
			
		||||
@@ -87,7 +94,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                keyword = keyword.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
                Quote keywordquote;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    keywordquote =
 | 
			
		||||
                        await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text)
 | 
			
		||||
@@ -108,7 +115,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                if (id < 0)
 | 
			
		||||
                    return;
 | 
			
		||||
                
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                { 
 | 
			
		||||
                    var qfromid = uow.Quotes.Get(id);
 | 
			
		||||
                    CREmbed crembed;
 | 
			
		||||
@@ -146,7 +153,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
                keyword = keyword.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Quotes.Add(new Quote
 | 
			
		||||
                    {
 | 
			
		||||
@@ -169,7 +176,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                
 | 
			
		||||
                var success = false;
 | 
			
		||||
                string response;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var q = uow.Quotes.Get(id);
 | 
			
		||||
 | 
			
		||||
@@ -201,7 +208,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
                keyword = keyword.ToUpperInvariant();
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Quotes.RemoveAllByKeyword(Context.Guild.Id, keyword.ToUpperInvariant());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,89 +19,13 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class RemindCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            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);
 | 
			
		||||
            private readonly UtilityService _service;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            private static string remindMessageFormat { get; }
 | 
			
		||||
 | 
			
		||||
            private static readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
 | 
			
		||||
            public RemindCommands(UtilityService service, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                { "%message%" , (r) => r.Message },
 | 
			
		||||
                { "%user%", (r) => $"<@!{r.UserId}>" },
 | 
			
		||||
                { "%target%", (r) =>  r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
            private static readonly CancellationTokenSource cancelSource;
 | 
			
		||||
            private static readonly CancellationToken cancelAllToken;
 | 
			
		||||
 | 
			
		||||
            static RemindCommands()
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
                cancelSource = new CancellationTokenSource();
 | 
			
		||||
                cancelAllToken = cancelSource.Token;
 | 
			
		||||
                List<Reminder> reminders;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    reminders = uow.Reminders.GetAll().ToList();
 | 
			
		||||
                }
 | 
			
		||||
                remindMessageFormat = NadekoBot.BotConfig.RemindMessageFormat;
 | 
			
		||||
 | 
			
		||||
                foreach (var r in reminders)
 | 
			
		||||
                {
 | 
			
		||||
                    Task.Run(() => StartReminder(r, cancelAllToken));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                if (!cancelSource.IsCancellationRequested)
 | 
			
		||||
                    cancelSource.Cancel();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task StartReminder(Reminder r, CancellationToken t)
 | 
			
		||||
            {
 | 
			
		||||
                var now = DateTime.Now;
 | 
			
		||||
 | 
			
		||||
                var time = r.When - now;
 | 
			
		||||
 | 
			
		||||
                if (time.TotalMilliseconds > int.MaxValue)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(time, t).ConfigureAwait(false);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    IMessageChannel ch;
 | 
			
		||||
                    if (r.IsPrivate)
 | 
			
		||||
                    {
 | 
			
		||||
                        var user = NadekoBot.Client.GetGuild(r.ServerId).GetUser(r.ChannelId);
 | 
			
		||||
                        if(user == null)
 | 
			
		||||
                            return;
 | 
			
		||||
                        ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (ch == null)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    await ch.SendMessageAsync(
 | 
			
		||||
                        _replacements.Aggregate(remindMessageFormat,
 | 
			
		||||
                            (cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
 | 
			
		||||
                            .SanitizeMentions()
 | 
			
		||||
                            ).ConfigureAwait(false); //it works trust me
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    {
 | 
			
		||||
                        uow.Reminders.Remove(r);
 | 
			
		||||
                        await uow.CompleteAsync();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public enum MeOrHere
 | 
			
		||||
@@ -139,7 +63,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
            public async Task RemindInternal(ulong targetId, bool isPrivate, string timeStr, [Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                var m = _regex.Match(timeStr);
 | 
			
		||||
                var m = _service.Remind.Regex.Match(timeStr);
 | 
			
		||||
 | 
			
		||||
                if (m.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
@@ -150,7 +74,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                string output = "";
 | 
			
		||||
                var namesAndValues = new Dictionary<string, int>();
 | 
			
		||||
 | 
			
		||||
                foreach (var groupName in _regex.GetGroupNames())
 | 
			
		||||
                foreach (var groupName in _service.Remind.Regex.GetGroupNames())
 | 
			
		||||
                {
 | 
			
		||||
                    if (groupName == "0") continue;
 | 
			
		||||
                    int value;
 | 
			
		||||
@@ -191,7 +115,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    ServerId = Context.Guild.Id
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Reminders.Add(rem);
 | 
			
		||||
                    await uow.CompleteAsync();
 | 
			
		||||
@@ -210,7 +134,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
                await StartReminder(rem, cancelAllToken);
 | 
			
		||||
                await _service.Remind.StartReminder(rem);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -220,7 +144,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(arg))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,17 +2,8 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Utility.Commands.Models;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
@@ -22,108 +13,17 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class UnitConverterCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            public static List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
            private static Timer _timer;
 | 
			
		||||
            private static readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
 | 
			
		||||
            private readonly UtilityService _service;
 | 
			
		||||
 | 
			
		||||
            static UnitConverterCommands()
 | 
			
		||||
            public UnitConverterCommands(UtilityService service)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
 | 
			
		||||
                    {
 | 
			
		||||
                        Modifier = u.Modifier,
 | 
			
		||||
                        UnitType = u.UnitType,
 | 
			
		||||
                        InternalTrigger = string.Join("|", u.Triggers)
 | 
			
		||||
                    }).ToArray();
 | 
			
		||||
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    {
 | 
			
		||||
                        if (uow.ConverterUnits.Empty())
 | 
			
		||||
                        {
 | 
			
		||||
                            uow.ConverterUnits.AddRange(data);
 | 
			
		||||
                            uow.Complete();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Units = data.ToList();
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn("Could not load units: " + ex.Message);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
 | 
			
		||||
                _service = service;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void Unload()
 | 
			
		||||
            {
 | 
			
		||||
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task UpdateCurrency()
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var currencyRates = await UpdateCurrencyRates();
 | 
			
		||||
                    var unitTypeString = "currency";
 | 
			
		||||
                    var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
 | 
			
		||||
                    {
 | 
			
		||||
                        InternalTrigger = u.Key,
 | 
			
		||||
                        Modifier = u.Value,
 | 
			
		||||
                        UnitType = unitTypeString
 | 
			
		||||
                    }).ToArray();
 | 
			
		||||
                    var baseType = new ConvertUnit()
 | 
			
		||||
                    {
 | 
			
		||||
                        Triggers = new[] { currencyRates.Base },
 | 
			
		||||
                        Modifier = decimal.One,
 | 
			
		||||
                        UnitType = unitTypeString
 | 
			
		||||
                    };
 | 
			
		||||
                    var toRemove = Units.Where(u => u.UnitType == unitTypeString);
 | 
			
		||||
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    {
 | 
			
		||||
                        uow.ConverterUnits.RemoveRange(toRemove.ToArray());
 | 
			
		||||
                        uow.ConverterUnits.Add(baseType);
 | 
			
		||||
                        uow.ConverterUnits.AddRange(range);
 | 
			
		||||
 | 
			
		||||
                        await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    Units.RemoveAll(u => u.UnitType == unitTypeString);
 | 
			
		||||
                    Units.Add(baseType);
 | 
			
		||||
                    Units.AddRange(range);
 | 
			
		||||
                    _log.Info("Updated Currency");
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn("Failed updating currency. Ignore this.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //[NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            //[RequireContext(ContextType.Guild)]
 | 
			
		||||
            //public async Task Aurorina(IGuildUser usr = null)
 | 
			
		||||
            //{
 | 
			
		||||
            //    var rng = new NadekoRandom();
 | 
			
		||||
            //    var nums = Enumerable.Range(48, 10)
 | 
			
		||||
            //        .Concat(Enumerable.Range(65, 26))
 | 
			
		||||
            //        .Concat(Enumerable.Range(97, 26))
 | 
			
		||||
            //        .Concat(new[] {45, 46, 95})
 | 
			
		||||
            //        .ToArray();
 | 
			
		||||
 | 
			
		||||
            //    var token = String.Concat(new int[59]
 | 
			
		||||
            //        .Select(x => (char) nums[rng.Next(0, nums.Length)]));
 | 
			
		||||
            //    if (usr == null)
 | 
			
		||||
            //        await Context.Channel.SendConfirmAsync(token).ConfigureAwait(false);
 | 
			
		||||
            //    else
 | 
			
		||||
            //        await Context.Channel.SendConfirmAsync($"Token of user {usr} is `{token}`").ConfigureAwait(false);
 | 
			
		||||
            //}
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task ConvertList()
 | 
			
		||||
            {
 | 
			
		||||
                var res = Units.GroupBy(x => x.UnitType)
 | 
			
		||||
                var res = _service.Converter.Units.GroupBy(x => x.UnitType)
 | 
			
		||||
                               .Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
 | 
			
		||||
                                                            .WithColor(NadekoBot.OkColor),
 | 
			
		||||
                                          (embed, g) => embed.AddField(efb =>
 | 
			
		||||
@@ -135,8 +35,8 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Convert(string origin, string target, decimal value)
 | 
			
		||||
            {
 | 
			
		||||
                var originUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
 | 
			
		||||
                var targetUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
 | 
			
		||||
                var originUnit = _service.Converter.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
 | 
			
		||||
                var targetUnit = _service.Converter.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
 | 
			
		||||
                if (originUnit == null || targetUnit == null)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
 | 
			
		||||
@@ -189,14 +89,5 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<Rates> UpdateCurrencyRates()
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
 | 
			
		||||
                return JsonConvert.DeserializeObject<Rates>(res);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility.Commands.Models
 | 
			
		||||
{
 | 
			
		||||
    public class MeasurementUnit
 | 
			
		||||
    {
 | 
			
		||||
        public List<string> Triggers { get; set; }
 | 
			
		||||
        public string UnitType { get; set; }
 | 
			
		||||
        public decimal Modifier { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility.Commands.Models
 | 
			
		||||
{
 | 
			
		||||
    public class Rates
 | 
			
		||||
    {
 | 
			
		||||
        public string Base { get; set; }
 | 
			
		||||
        public DateTime Date { get; set; }
 | 
			
		||||
        [JsonProperty("rates")]
 | 
			
		||||
        public Dictionary<string, decimal> ConversionRates { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Net;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility.Models
 | 
			
		||||
{
 | 
			
		||||
    public class RepeatRunner
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        private CancellationTokenSource source { get; set; }
 | 
			
		||||
        private CancellationToken token { get; set; }
 | 
			
		||||
        public Repeater Repeater { get; }
 | 
			
		||||
        public SocketGuild Guild { get; }
 | 
			
		||||
        public ITextChannel Channel { get; private set; }
 | 
			
		||||
        private IUserMessage oldMsg = null;
 | 
			
		||||
 | 
			
		||||
        public RepeatRunner(DiscordShardedClient client, Repeater repeater, ITextChannel channel = null)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            Repeater = repeater;
 | 
			
		||||
            Channel = channel;
 | 
			
		||||
 | 
			
		||||
            //todo @.@ fix all of this
 | 
			
		||||
            Guild = client.GetGuild(repeater.GuildId);
 | 
			
		||||
            if (Guild != null)
 | 
			
		||||
                Task.Run(Run);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Run()
 | 
			
		||||
        {
 | 
			
		||||
            source = new CancellationTokenSource();
 | 
			
		||||
            token = source.Token;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                while (!token.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await Trigger().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (OperationCanceledException)
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Trigger()
 | 
			
		||||
        {
 | 
			
		||||
            var toSend = "🔄 " + Repeater.Message;
 | 
			
		||||
            //var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
 | 
			
		||||
            // if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
 | 
			
		||||
            //     continue;
 | 
			
		||||
 | 
			
		||||
            if (oldMsg != null)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await oldMsg.DeleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (Channel == null)
 | 
			
		||||
                    Channel = Guild.GetTextChannel(Repeater.ChannelId);
 | 
			
		||||
 | 
			
		||||
                if (Channel != null)
 | 
			
		||||
                    oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn(ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Reset()
 | 
			
		||||
        {
 | 
			
		||||
            source.Cancel();
 | 
			
		||||
            var _ = Task.Run(Run);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Stop()
 | 
			
		||||
        {
 | 
			
		||||
            source.Cancel();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ToString() =>
 | 
			
		||||
            $"{Channel?.Mention ?? $"⚠<#{Repeater.ChannelId}>" } " +
 | 
			
		||||
            $"| {(int)Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
 | 
			
		||||
            $"| {Repeater.Message.TrimTo(33)}";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,15 +20,23 @@ using System.Diagnostics;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
{
 | 
			
		||||
    [NadekoModule("Utility", ".")]
 | 
			
		||||
    [NadekoModule("Utility")]
 | 
			
		||||
    public partial class Utility : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly IStatsService _stats;
 | 
			
		||||
        private readonly UtilityService _service;
 | 
			
		||||
        //private readonly MusicService _music;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
        public static void Unload()
 | 
			
		||||
        public Utility(UtilityService service, DiscordShardedClient client, IStatsService stats, IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _rotatingRoleColors.ForEach(x => x.Value?.Change(Timeout.Infinite, Timeout.Infinite));
 | 
			
		||||
            _rotatingRoleColors.Clear();
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _stats = stats;
 | 
			
		||||
            _service = service;
 | 
			
		||||
            //_music = music;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //[NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -355,11 +363,11 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            if (page < 1)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var status = string.Join(", ", NadekoBot.Client.Shards.GroupBy(x => x.ConnectionState)
 | 
			
		||||
            var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState)
 | 
			
		||||
                .Select(x => $"{x.Count()} {x.Key}")
 | 
			
		||||
                .ToArray());
 | 
			
		||||
 | 
			
		||||
            var allShardStrings = NadekoBot.Client.Shards
 | 
			
		||||
            var allShardStrings = _client.Shards
 | 
			
		||||
                .Select(x =>
 | 
			
		||||
                    GetText("shard_stats_txt", x.ShardId.ToString(),
 | 
			
		||||
                        Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString())))
 | 
			
		||||
@@ -367,7 +375,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
 | 
			
		||||
            await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                var str = string.Join("\n", allShardStrings.Skip(25 * (curPage - 1)).Take(25));
 | 
			
		||||
@@ -386,7 +394,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task ShardId(IGuild guild)
 | 
			
		||||
        {
 | 
			
		||||
            var shardId = NadekoBot.Client.GetShardIdFor(guild);
 | 
			
		||||
            var shardId = _client.GetShardIdFor(guild);
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
@@ -394,10 +402,8 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Stats()
 | 
			
		||||
        {
 | 
			
		||||
            var stats = NadekoBot.Stats;
 | 
			
		||||
 | 
			
		||||
            var shardId = Context.Guild != null
 | 
			
		||||
                ? NadekoBot.Client.GetShardIdFor(Context.Guild)
 | 
			
		||||
                ? _client.GetShardIdFor(Context.Guild)
 | 
			
		||||
                : 0;
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.EmbedAsync(
 | 
			
		||||
@@ -405,21 +411,21 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    .WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
 | 
			
		||||
                                          .WithUrl("http://nadekobot.readthedocs.io/en/latest/")
 | 
			
		||||
                                          .WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("author")).WithValue(stats.Author).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("botid")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {NadekoBot.Client.Shards.Count}").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", _creds.OwnerIds)).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
 | 
			
		||||
                    .AddField(efb => efb.WithName(GetText("presence")).WithValue(
 | 
			
		||||
                        GetText("presence_txt",
 | 
			
		||||
                            NadekoBot.Client.Guilds.Count, stats.TextChannels, stats.VoiceChannels)).WithIsInline(true))
 | 
			
		||||
                            _client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
                    .WithFooter(efb => efb.WithText(GetText("stats_songs",
 | 
			
		||||
                        NadekoBot.MusicService.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
 | 
			
		||||
                        NadekoBot.MusicService.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
 | 
			
		||||
                    //.WithFooter(efb => efb.WithText(GetText("stats_songs",
 | 
			
		||||
                    //    _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
 | 
			
		||||
                    //    _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
 | 
			
		||||
#endif
 | 
			
		||||
                    );
 | 
			
		||||
        }
 | 
			
		||||
@@ -427,7 +433,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Showemojis([Remainder] string emojis)
 | 
			
		||||
        {
 | 
			
		||||
            var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emoji)t.Value);
 | 
			
		||||
            var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value);
 | 
			
		||||
 | 
			
		||||
            var result = string.Join("\n", tags.Select(m => GetText("showemojis", m, m.Url)));
 | 
			
		||||
 | 
			
		||||
@@ -446,7 +452,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
            if (page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var guilds = await Task.Run(() => NadekoBot.Client.Guilds.OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
 | 
			
		||||
            var guilds = await Task.Run(() => _client.Guilds.OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (!guilds.Any())
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										319
									
								
								src/NadekoBot/Modules/Utility/UtilityService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								src/NadekoBot/Modules/Utility/UtilityService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,319 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Utility.Models;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
{
 | 
			
		||||
    public class UtilityService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
 | 
			
		||||
 | 
			
		||||
        //messagerepeater
 | 
			
		||||
        //guildid/RepeatRunners
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
 | 
			
		||||
        public bool RepeaterReady { get; private set; }
 | 
			
		||||
 | 
			
		||||
        //remind
 | 
			
		||||
        public RemindService Remind { get; }
 | 
			
		||||
 | 
			
		||||
        //unit conversion
 | 
			
		||||
        public ConverterService Converter { get; }
 | 
			
		||||
 | 
			
		||||
        public UtilityService(IEnumerable<GuildConfig> guildConfigs, DiscordShardedClient client, BotConfig config, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            //commandmap
 | 
			
		||||
            AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
 | 
			
		||||
                    guildConfigs.ToDictionary(
 | 
			
		||||
                        x => x.GuildId,
 | 
			
		||||
                        x => new ConcurrentDictionary<string, string>(x.CommandAliases
 | 
			
		||||
                            .Distinct(new CommandAliasEqualityComparer())
 | 
			
		||||
                            .ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
 | 
			
		||||
 | 
			
		||||
            //crossesrver
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _client.MessageReceived += Client_MessageReceived;
 | 
			
		||||
 | 
			
		||||
            //messagerepeater
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
                await Task.Delay(5000).ConfigureAwait(false);
 | 
			
		||||
#else
 | 
			
		||||
                    await Task.Delay(30000).ConfigureAwait(false);
 | 
			
		||||
#endif
 | 
			
		||||
                //todo this is pretty terrible :kms: no time
 | 
			
		||||
                Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(guildConfigs
 | 
			
		||||
                    .ToDictionary(gc => gc.GuildId,
 | 
			
		||||
                        gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
 | 
			
		||||
                            .Select(gr => new RepeatRunner(client, gr))
 | 
			
		||||
                            .Where(x => x.Guild != null))));
 | 
			
		||||
                RepeaterReady = true;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            //reminder
 | 
			
		||||
            Remind = new RemindService(client, config, db);
 | 
			
		||||
 | 
			
		||||
            //unit converter
 | 
			
		||||
            Converter = new ConverterService(db);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (imsg.Author.IsBot)
 | 
			
		||||
                    return;
 | 
			
		||||
                var msg = imsg as IUserMessage;
 | 
			
		||||
                if (msg == null)
 | 
			
		||||
                    return;
 | 
			
		||||
                var channel = imsg.Channel as ITextChannel;
 | 
			
		||||
                if (channel == null)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (msg.Author.Id == _client.CurrentUser.Id) return;
 | 
			
		||||
                foreach (var subscriber in Subscribers)
 | 
			
		||||
                {
 | 
			
		||||
                    var set = subscriber.Value;
 | 
			
		||||
                    if (!set.Contains(channel))
 | 
			
		||||
                        continue;
 | 
			
		||||
                    foreach (var chan in set.Except(new[] { channel }))
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
 | 
			
		||||
                                msg)).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch
 | 
			
		||||
                        {
 | 
			
		||||
                            // ignored
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                // ignored
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
 | 
			
		||||
            $"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
 | 
			
		||||
 | 
			
		||||
        public readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
 | 
			
		||||
            new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
 | 
			
		||||
        private DiscordShardedClient _client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class ConverterService
 | 
			
		||||
    {
 | 
			
		||||
        public class MeasurementUnit
 | 
			
		||||
        {
 | 
			
		||||
            public List<string> Triggers { get; set; }
 | 
			
		||||
            public string UnitType { get; set; }
 | 
			
		||||
            public decimal Modifier { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class Rates
 | 
			
		||||
        {
 | 
			
		||||
            public string Base { get; set; }
 | 
			
		||||
            public DateTime Date { get; set; }
 | 
			
		||||
            [JsonProperty("rates")]
 | 
			
		||||
            public Dictionary<string, decimal> ConversionRates { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private Timer _timer;
 | 
			
		||||
        private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public ConverterService(DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _db = db;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
 | 
			
		||||
                {
 | 
			
		||||
                    Modifier = u.Modifier,
 | 
			
		||||
                    UnitType = u.UnitType,
 | 
			
		||||
                    InternalTrigger = string.Join("|", u.Triggers)
 | 
			
		||||
                }).ToArray();
 | 
			
		||||
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    if (uow.ConverterUnits.Empty())
 | 
			
		||||
                    {
 | 
			
		||||
                        uow.ConverterUnits.AddRange(data);
 | 
			
		||||
                        uow.Complete();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Units = data.ToList();
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn("Could not load units: " + ex.Message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static async Task<Rates> UpdateCurrencyRates()
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
 | 
			
		||||
                return JsonConvert.DeserializeObject<Rates>(res);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task UpdateCurrency()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var currencyRates = await UpdateCurrencyRates();
 | 
			
		||||
                var unitTypeString = "currency";
 | 
			
		||||
                var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
 | 
			
		||||
                {
 | 
			
		||||
                    InternalTrigger = u.Key,
 | 
			
		||||
                    Modifier = u.Value,
 | 
			
		||||
                    UnitType = unitTypeString
 | 
			
		||||
                }).ToArray();
 | 
			
		||||
                var baseType = new ConvertUnit()
 | 
			
		||||
                {
 | 
			
		||||
                    Triggers = new[] { currencyRates.Base },
 | 
			
		||||
                    Modifier = decimal.One,
 | 
			
		||||
                    UnitType = unitTypeString
 | 
			
		||||
                };
 | 
			
		||||
                var toRemove = Units.Where(u => u.UnitType == unitTypeString);
 | 
			
		||||
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.ConverterUnits.RemoveRange(toRemove.ToArray());
 | 
			
		||||
                    uow.ConverterUnits.Add(baseType);
 | 
			
		||||
                    uow.ConverterUnits.AddRange(range);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                Units.RemoveAll(u => u.UnitType == unitTypeString);
 | 
			
		||||
                Units.Add(baseType);
 | 
			
		||||
                Units.AddRange(range);
 | 
			
		||||
                _log.Info("Updated Currency");
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn("Failed updating currency. Ignore this.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class RemindService
 | 
			
		||||
    {
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        public string RemindMessageFormat { get; }
 | 
			
		||||
 | 
			
		||||
        public readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
 | 
			
		||||
            {
 | 
			
		||||
                { "%message%" , (r) => r.Message },
 | 
			
		||||
                { "%user%", (r) => $"<@!{r.UserId}>" },
 | 
			
		||||
                { "%target%", (r) =>  r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly CancellationTokenSource cancelSource;
 | 
			
		||||
        private readonly CancellationToken cancelAllToken;
 | 
			
		||||
        private readonly BotConfig _config;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public RemindService(DiscordShardedClient client, BotConfig config, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _config = config;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _db = db;
 | 
			
		||||
 | 
			
		||||
            cancelSource = new CancellationTokenSource();
 | 
			
		||||
            cancelAllToken = cancelSource.Token;
 | 
			
		||||
            List<Reminder> reminders;
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                reminders = uow.Reminders.GetAll().ToList();
 | 
			
		||||
            }
 | 
			
		||||
            RemindMessageFormat = _config.RemindMessageFormat;
 | 
			
		||||
 | 
			
		||||
            foreach (var r in reminders)
 | 
			
		||||
            {
 | 
			
		||||
                Task.Run(() => StartReminder(r));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task StartReminder(Reminder r)
 | 
			
		||||
        {
 | 
			
		||||
            var t = cancelAllToken;
 | 
			
		||||
            var now = DateTime.Now;
 | 
			
		||||
 | 
			
		||||
            var time = r.When - now;
 | 
			
		||||
 | 
			
		||||
            if (time.TotalMilliseconds > int.MaxValue)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(time, t).ConfigureAwait(false);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                IMessageChannel ch;
 | 
			
		||||
                if (r.IsPrivate)
 | 
			
		||||
                {
 | 
			
		||||
                    var user = _client.GetGuild(r.ServerId).GetUser(r.ChannelId);
 | 
			
		||||
                    if (user == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
 | 
			
		||||
                }
 | 
			
		||||
                if (ch == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await ch.SendMessageAsync(
 | 
			
		||||
                    _replacements.Aggregate(RemindMessageFormat,
 | 
			
		||||
                        (cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
 | 
			
		||||
                        .SanitizeMentions()
 | 
			
		||||
                        ).ConfigureAwait(false); //it works trust me
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Reminders.Remove(r);
 | 
			
		||||
                    await uow.CompleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
 | 
			
		||||
    {
 | 
			
		||||
        public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
 | 
			
		||||
 | 
			
		||||
        public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,13 +12,12 @@ using System.Reflection;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Modules.Permissions;
 | 
			
		||||
using NadekoBot.TypeReaders;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using NadekoBot.Modules.Music;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using NadekoBot.Modules.Utility;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
{
 | 
			
		||||
@@ -26,109 +25,122 @@ namespace NadekoBot
 | 
			
		||||
    {
 | 
			
		||||
        private Logger _log;
 | 
			
		||||
        
 | 
			
		||||
        public static Color OkColor { get; }
 | 
			
		||||
        public static Color ErrorColor { get; }
 | 
			
		||||
        /* I don't know how to make this not be static
 | 
			
		||||
         * and keep the convenience of .WithOkColor
 | 
			
		||||
         * and .WithErrorColor extensions methods.
 | 
			
		||||
         * I don't want to pass botconfig every time I 
 | 
			
		||||
         * want to send a confirm or error message, so
 | 
			
		||||
         * I'll keep this for now */
 | 
			
		||||
        public static Color OkColor { get; private set; }
 | 
			
		||||
        public static Color ErrorColor { get; private set; }
 | 
			
		||||
        
 | 
			
		||||
        //todo placeholder, will be guild-based
 | 
			
		||||
        public static string Prefix { get; } = ".";
 | 
			
		||||
 | 
			
		||||
        public static CommandService CommandService { get; private set; }
 | 
			
		||||
        public static CommandHandler CommandHandler { get; private set; }
 | 
			
		||||
        public static DiscordShardedClient Client { get; private set; }
 | 
			
		||||
        public static BotCredentials Credentials { get; }
 | 
			
		||||
        public ImmutableArray<GuildConfig> AllGuildConfigs { get; }
 | 
			
		||||
        public BotConfig BotConfig { get; }
 | 
			
		||||
 | 
			
		||||
        public static Localization Localization { get; private set; }
 | 
			
		||||
        public static NadekoStrings Strings { get; private set; }
 | 
			
		||||
        public DiscordShardedClient Client { get; }
 | 
			
		||||
        public bool Ready { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public static GoogleApiService Google { get; private set; }
 | 
			
		||||
        public static StatsService Stats { get; private set; }
 | 
			
		||||
        public static IImagesService Images { get; private set; }
 | 
			
		||||
        public INServiceProvider Services { get; }
 | 
			
		||||
 | 
			
		||||
        public static ConcurrentDictionary<string, string> ModulePrefixes { get; private set; }
 | 
			
		||||
        public static bool Ready { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public static ImmutableArray<GuildConfig> AllGuildConfigs { get; }
 | 
			
		||||
        public static BotConfig BotConfig { get; }
 | 
			
		||||
 | 
			
		||||
        //services
 | 
			
		||||
        //todo DI in the future
 | 
			
		||||
        public static GreetSettingsService GreetSettingsService { get; private set; }
 | 
			
		||||
        public static MusicService MusicService { get; private set; }
 | 
			
		||||
 | 
			
		||||
        static NadekoBot()
 | 
			
		||||
        public NadekoBot()
 | 
			
		||||
        {
 | 
			
		||||
            SetupLogger();
 | 
			
		||||
            Credentials = new BotCredentials();
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            var credentials = new BotCredentials();
 | 
			
		||||
            var db = new DbHandler(credentials);
 | 
			
		||||
            using (var uow = db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray();
 | 
			
		||||
                BotConfig = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
 | 
			
		||||
                ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Google = new GoogleApiService();
 | 
			
		||||
            GreetSettingsService = new GreetSettingsService();
 | 
			
		||||
            MusicService = new MusicService(Google);
 | 
			
		||||
 | 
			
		||||
            //ImageSharp.Configuration.Default.AddImageFormat(new ImageSharp.Formats.PngFormat());
 | 
			
		||||
            //ImageSharp.Configuration.Default.AddImageFormat(new ImageSharp.Formats.JpegFormat());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task RunAsync(params string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            _log.Info("Starting NadekoBot v" + StatsService.BotVersion);
 | 
			
		||||
 | 
			
		||||
            //create client
 | 
			
		||||
            
 | 
			
		||||
            Client = new DiscordShardedClient(new DiscordSocketConfig
 | 
			
		||||
            {
 | 
			
		||||
                MessageCacheSize = 10,
 | 
			
		||||
                LogLevel = LogSeverity.Warning,
 | 
			
		||||
                TotalShards = Credentials.TotalShards,
 | 
			
		||||
                TotalShards = credentials.TotalShards,
 | 
			
		||||
                ConnectionTimeout = int.MaxValue,
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
                //AlwaysDownloadUsers = true,
 | 
			
		||||
#endif
 | 
			
		||||
                AlwaysDownloadUsers = true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var google = new GoogleApiService(credentials);
 | 
			
		||||
            var strings = new NadekoStrings();
 | 
			
		||||
 | 
			
		||||
            var greetSettingsService = new GreetSettingsService(AllGuildConfigs, db);
 | 
			
		||||
 | 
			
		||||
            var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db);
 | 
			
		||||
            //var musicService = new MusicService(google, strings, localization);
 | 
			
		||||
 | 
			
		||||
            var commandService = new CommandService(new CommandServiceConfig()
 | 
			
		||||
            {
 | 
			
		||||
                CaseSensitiveCommands = false,
 | 
			
		||||
                DefaultRunMode = RunMode.Sync,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var commandHandler = new CommandHandler(Client, commandService, credentials, this);
 | 
			
		||||
 | 
			
		||||
            var stats = new StatsService(Client, commandHandler, credentials);
 | 
			
		||||
 | 
			
		||||
            var images = new ImagesService();
 | 
			
		||||
 | 
			
		||||
            //module services
 | 
			
		||||
            var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
 | 
			
		||||
            var searchesService = new SearchesService();
 | 
			
		||||
 | 
			
		||||
            //initialize Services
 | 
			
		||||
            Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
 | 
			
		||||
                .Add<ILocalization>(localization)
 | 
			
		||||
                .Add<IStatsService>(stats)
 | 
			
		||||
                .Add<IImagesService>(images)
 | 
			
		||||
                .Add<IGoogleApiService>(google)
 | 
			
		||||
                .Add<IStatsService>(stats)
 | 
			
		||||
                .Add<IBotCredentials>(credentials)
 | 
			
		||||
                .Add<CommandService>(commandService)
 | 
			
		||||
                .Add<NadekoStrings>(strings)
 | 
			
		||||
                .Add<DiscordShardedClient>(Client)
 | 
			
		||||
                .Add<GreetSettingsService>(greetSettingsService)
 | 
			
		||||
                //.Add(musicService)
 | 
			
		||||
                .Add<CommandHandler>(commandHandler)
 | 
			
		||||
                .Add<DbHandler>(db)
 | 
			
		||||
                //modules
 | 
			
		||||
                .Add<UtilityService>(utilityService)
 | 
			
		||||
                .Add<SearchesService>(searchesService)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            commandHandler.AddServices(Services);
 | 
			
		||||
 | 
			
		||||
            //setup typereaders
 | 
			
		||||
            commandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
 | 
			
		||||
            commandService.AddTypeReader<CommandInfo>(new CommandTypeReader(commandService));
 | 
			
		||||
            //commandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
 | 
			
		||||
            commandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(commandService));
 | 
			
		||||
            commandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(commandService));
 | 
			
		||||
            commandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
 | 
			
		||||
 | 
			
		||||
#if GLOBAL_NADEKO
 | 
			
		||||
            Client.Log += Client_Log;
 | 
			
		||||
#endif
 | 
			
		||||
            // initialize response strings
 | 
			
		||||
            Strings = new NadekoStrings();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            //initialize Services
 | 
			
		||||
            Localization = new Localization(NadekoBot.BotConfig.Locale, NadekoBot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale));
 | 
			
		||||
            CommandService = new CommandService(new CommandServiceConfig() {
 | 
			
		||||
                CaseSensitiveCommands = false,
 | 
			
		||||
                DefaultRunMode = RunMode.Sync
 | 
			
		||||
            });
 | 
			
		||||
            CommandHandler = new CommandHandler(Client, CommandService);
 | 
			
		||||
            Stats = new StatsService(Client, CommandHandler);
 | 
			
		||||
            Images = await ImagesService.Create().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            ////setup DI
 | 
			
		||||
            //var depMap = new DependencyMap();
 | 
			
		||||
            //depMap.Add<ILocalization>(Localizer);
 | 
			
		||||
            //depMap.Add<ShardedDiscordClient>(Client);
 | 
			
		||||
            //depMap.Add<CommandService>(CommandService);
 | 
			
		||||
            //depMap.Add<IGoogleApiService>(Google);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //setup typereaders
 | 
			
		||||
            CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
 | 
			
		||||
            CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
 | 
			
		||||
            CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
 | 
			
		||||
            CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader());
 | 
			
		||||
            CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader());
 | 
			
		||||
            CommandService.AddTypeReader<IGuild>(new GuildTypeReader());
 | 
			
		||||
        public async Task RunAsync(params string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            var creds = Services.GetService<IBotCredentials>();
 | 
			
		||||
            var stats = Services.GetService<IStatsService>();
 | 
			
		||||
            var commandHandler = Services.GetService<CommandHandler>();
 | 
			
		||||
            var commandService = Services.GetService<CommandService>();
 | 
			
		||||
 | 
			
		||||
            _log.Info("Starting NadekoBot v" + StatsService.BotVersion);
 | 
			
		||||
 | 
			
		||||
            var sw = Stopwatch.StartNew();
 | 
			
		||||
            //connect
 | 
			
		||||
            await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false);
 | 
			
		||||
            await Client.LoginAsync(TokenType.Bot, creds.Token).ConfigureAwait(false);
 | 
			
		||||
            await Client.StartAsync().ConfigureAwait(false);
 | 
			
		||||
            //await Client.DownloadAllUsersAsync().ConfigureAwait(false);
 | 
			
		||||
            
 | 
			
		||||
            // wait for all shards to be ready
 | 
			
		||||
            int readyCount = 0;
 | 
			
		||||
@@ -138,25 +150,21 @@ namespace NadekoBot
 | 
			
		||||
            while (readyCount < Client.Shards.Count)
 | 
			
		||||
                await Task.Delay(100).ConfigureAwait(false);
 | 
			
		||||
            
 | 
			
		||||
            Stats.Initialize();
 | 
			
		||||
            stats.Initialize();
 | 
			
		||||
 | 
			
		||||
            sw.Stop();
 | 
			
		||||
            _log.Info("Connected in " + sw.Elapsed.TotalSeconds.ToString("F2"));
 | 
			
		||||
 | 
			
		||||
            //load commands and prefixes
 | 
			
		||||
 | 
			
		||||
            ModulePrefixes = new ConcurrentDictionary<string, string>(NadekoBot.BotConfig.ModulePrefixes.OrderByDescending(mp => mp.Prefix.Length).ToDictionary(m => m.ModuleName, m => m.Prefix));
 | 
			
		||||
 | 
			
		||||
            // start handling messages received in commandhandler
 | 
			
		||||
            
 | 
			
		||||
            await CommandHandler.StartHandling().ConfigureAwait(false);
 | 
			
		||||
            await commandHandler.StartHandling().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var _ = await Task.Run(() => CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly)).ConfigureAwait(false);
 | 
			
		||||
            var _ = await Task.Run(() => commandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly)).ConfigureAwait(false);
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
            await CommandService.AddModuleAsync<Music>().ConfigureAwait(false);
 | 
			
		||||
            //todo uncomment this
 | 
			
		||||
            //await commandService.AddModuleAsync<Music>().ConfigureAwait(false);
 | 
			
		||||
#endif
 | 
			
		||||
            Ready = true;
 | 
			
		||||
            _log.Info(await Stats.Print().ConfigureAwait(false));
 | 
			
		||||
            _log.Info(await stats.Print().ConfigureAwait(false));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Client_Log(LogMessage arg)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,37 @@
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Compile Remove="data\**\*;credentials.json;credentials_example.json;Modules\Music\Classes\PlaylistFullException.cs" />
 | 
			
		||||
    <Compile Remove="data\**\*;credentials.json;credentials_example.json" />
 | 
			
		||||
    <Compile Remove="Modules\Administration\**" />
 | 
			
		||||
    <Compile Remove="Modules\ClashOfClans\**" />
 | 
			
		||||
    <Compile Remove="Modules\CustomReactions\**" />
 | 
			
		||||
    <Compile Remove="Modules\Gambling\**" />
 | 
			
		||||
    <Compile Remove="Modules\Games\**" />
 | 
			
		||||
    <Compile Remove="Modules\Help\**" />
 | 
			
		||||
    <Compile Remove="Modules\Music\**" />
 | 
			
		||||
    <Compile Remove="Modules\Permissions\**" />
 | 
			
		||||
    <Compile Remove="Modules\Searches\**" />
 | 
			
		||||
    <Compile Remove="Services\Music\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Administration\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\ClashOfClans\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\CustomReactions\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Gambling\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Games\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Help\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Music\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Permissions\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Searches\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Services\Music\**" />
 | 
			
		||||
    <None Remove="Modules\Administration\**" />
 | 
			
		||||
    <None Remove="Modules\ClashOfClans\**" />
 | 
			
		||||
    <None Remove="Modules\CustomReactions\**" />
 | 
			
		||||
    <None Remove="Modules\Gambling\**" />
 | 
			
		||||
    <None Remove="Modules\Games\**" />
 | 
			
		||||
    <None Remove="Modules\Help\**" />
 | 
			
		||||
    <None Remove="Modules\Music\**" />
 | 
			
		||||
    <None Remove="Modules\Permissions\**" />
 | 
			
		||||
    <None Remove="Modules\Searches\**" />
 | 
			
		||||
    <None Remove="Services\Music\**" />
 | 
			
		||||
    <None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </None>
 | 
			
		||||
@@ -44,7 +74,7 @@
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="AngleSharp" Version="0.9.9" />
 | 
			
		||||
    <PackageReference Include="Discord.Net" Version="1.0.0-rc2-00014" />
 | 
			
		||||
    <PackageReference Include="Discord.Net" Version="1.0.0-rc3-00743" />
 | 
			
		||||
    <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" />
 | 
			
		||||
@@ -58,10 +88,10 @@
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
 | 
			
		||||
    <PackageReference Include="Newtonsoft.Json" Version="9.0.2-beta1" />
 | 
			
		||||
    <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
 | 
			
		||||
    <PackageReference Include="NLog" Version="5.0.0-beta03" />
 | 
			
		||||
    <PackageReference Include="System.Xml.XPath" Version="4.3.0" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,11 @@ using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NLog;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Modules.Permissions;
 | 
			
		||||
using Discord.Net;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using static NadekoBot.Modules.Permissions.Permissions;
 | 
			
		||||
using NadekoBot.Modules.Help;
 | 
			
		||||
using static NadekoBot.Modules.Administration.Administration;
 | 
			
		||||
using NadekoBot.Modules.CustomReactions;
 | 
			
		||||
using NadekoBot.Modules.Games;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
@@ -35,6 +28,9 @@ namespace NadekoBot.Services
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly CommandService _commandService;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly NadekoBot _bot;
 | 
			
		||||
        private INServiceProvider _services;
 | 
			
		||||
 | 
			
		||||
        private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>();
 | 
			
		||||
 | 
			
		||||
@@ -46,10 +42,13 @@ namespace NadekoBot.Services
 | 
			
		||||
        public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
 | 
			
		||||
        private readonly Timer _clearUsersOnShortCooldown;
 | 
			
		||||
 | 
			
		||||
        public CommandHandler(DiscordShardedClient client, CommandService commandService)
 | 
			
		||||
        public CommandHandler(DiscordShardedClient client, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _commandService = commandService;
 | 
			
		||||
            _creds = credentials;
 | 
			
		||||
            _bot = bot;
 | 
			
		||||
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            _clearUsersOnShortCooldown = new Timer(_ =>
 | 
			
		||||
@@ -58,11 +57,16 @@ namespace NadekoBot.Services
 | 
			
		||||
            }, null, GlobalCommandsCooldown, GlobalCommandsCooldown);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddServices(INServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            _services = services;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText)
 | 
			
		||||
        {
 | 
			
		||||
            if (guildId != null)
 | 
			
		||||
            {
 | 
			
		||||
                var guild = NadekoBot.Client.GetGuild(guildId.Value);
 | 
			
		||||
                var guild = _client.GetGuild(guildId.Value);
 | 
			
		||||
                var channel = guild?.GetChannel(channelId) as SocketTextChannel;
 | 
			
		||||
                if (channel == null)
 | 
			
		||||
                {
 | 
			
		||||
@@ -87,28 +91,28 @@ namespace NadekoBot.Services
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(5000).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                _client.GetGuilds().SelectMany(g => g.Users);
 | 
			
		||||
                _client.Guilds.SelectMany(g => g.Users);
 | 
			
		||||
 | 
			
		||||
                LoadOwnerChannels();
 | 
			
		||||
 | 
			
		||||
                if (!ownerChannels.Any())
 | 
			
		||||
                    _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Info($"Created {ownerChannels.Length} out of {NadekoBot.Credentials.OwnerIds.Length} owner message channels.");
 | 
			
		||||
                    _log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            _client.MessageReceived += MessageReceivedHandler;
 | 
			
		||||
            _client.MessageUpdated += (oldmsg, newMsg, channel) =>
 | 
			
		||||
            {
 | 
			
		||||
                var ignore = Task.Run(async () =>
 | 
			
		||||
                var ignore = Task.Run(() =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var usrMsg = newMsg as SocketUserMessage;
 | 
			
		||||
                        var guild = (usrMsg?.Channel as ITextChannel)?.Guild;
 | 
			
		||||
 | 
			
		||||
                        if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
                            await WordFiltered(guild, usrMsg).ConfigureAwait(false);
 | 
			
		||||
                        ////todo invite filtering
 | 
			
		||||
                        //if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
                        //    await WordFiltered(guild, usrMsg).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
@@ -124,7 +128,7 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
        private void LoadOwnerChannels()
 | 
			
		||||
        {
 | 
			
		||||
            var hs = new HashSet<ulong>(NadekoBot.Credentials.OwnerIds);
 | 
			
		||||
            var hs = new HashSet<ulong>(_creds.OwnerIds);
 | 
			
		||||
            var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
 | 
			
		||||
 | 
			
		||||
            foreach (var s in _client.Shards)
 | 
			
		||||
@@ -148,57 +152,59 @@ namespace NadekoBot.Services
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ownerChannels = channels.OrderBy(x => NadekoBot.Credentials.OwnerIds.IndexOf(x.Key))
 | 
			
		||||
            ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key))
 | 
			
		||||
                    .Select(x => x.Value)
 | 
			
		||||
                    .ToImmutableArray();
 | 
			
		||||
        }
 | 
			
		||||
        private async Task<bool> TryRunCleverbot(IUserMessage usrMsg, SocketGuild guild)
 | 
			
		||||
        {
 | 
			
		||||
            if (guild == null)
 | 
			
		||||
                return false;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var message = Games.CleverBotCommands.PrepareMessage(usrMsg, out Games.ChatterBotSession cbs);
 | 
			
		||||
                if (message == null || cbs == null)
 | 
			
		||||
                    return false;
 | 
			
		||||
        ////todo cleverbot
 | 
			
		||||
        //private async Task<bool> TryRunCleverbot(IUserMessage usrMsg, SocketGuild guild)
 | 
			
		||||
        //{
 | 
			
		||||
        //    if (guild == null)
 | 
			
		||||
        //        return false;
 | 
			
		||||
        //    try
 | 
			
		||||
        //    {
 | 
			
		||||
        //        var message = Games.CleverBotCommands.PrepareMessage(usrMsg, out Games.ChatterBotSession cbs);
 | 
			
		||||
        //        if (message == null || cbs == null)
 | 
			
		||||
        //            return false;
 | 
			
		||||
 | 
			
		||||
                PermissionCache pc = Permissions.GetCache(guild.Id);
 | 
			
		||||
                if (!pc.Permissions.CheckPermissions(usrMsg,
 | 
			
		||||
                    NadekoBot.ModulePrefixes[typeof(Games).Name] + "cleverbot",
 | 
			
		||||
                    typeof(Games).Name,
 | 
			
		||||
                    out int index))
 | 
			
		||||
                {
 | 
			
		||||
                    //todo print in guild actually
 | 
			
		||||
                    var returnMsg =
 | 
			
		||||
                        $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
 | 
			
		||||
                    _log.Info(returnMsg);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
        //        PermissionCache pc = Permissions.GetCache(guild.Id);
 | 
			
		||||
        //        if (!pc.Permissions.CheckPermissions(usrMsg,
 | 
			
		||||
        //            NadekoBot.Prefix + "cleverbot",
 | 
			
		||||
        //            typeof(Games).Name,
 | 
			
		||||
        //            out int index))
 | 
			
		||||
        //        {
 | 
			
		||||
        //            //todo print in guild actually
 | 
			
		||||
        //            var returnMsg =
 | 
			
		||||
        //                $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
 | 
			
		||||
        //            _log.Info(returnMsg);
 | 
			
		||||
        //            return true;
 | 
			
		||||
        //        }
 | 
			
		||||
 | 
			
		||||
                var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(cbs, (ITextChannel)usrMsg.Channel, message).ConfigureAwait(false);
 | 
			
		||||
                if (cleverbotExecuted)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Info($@"CleverBot Executed
 | 
			
		||||
        Server: {guild.Name} [{guild.Id}]
 | 
			
		||||
        Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}]
 | 
			
		||||
        UserId: {usrMsg.Author} [{usrMsg.Author.Id}]
 | 
			
		||||
        Message: {usrMsg.Content}");
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsBlacklisted(IGuild guild, IUserMessage usrMsg) =>
 | 
			
		||||
            (guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) ||
 | 
			
		||||
            BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) ||
 | 
			
		||||
            BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
 | 
			
		||||
        //        var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(cbs, (ITextChannel)usrMsg.Channel, message).ConfigureAwait(false);
 | 
			
		||||
        //        if (cleverbotExecuted)
 | 
			
		||||
        //        {
 | 
			
		||||
        //            _log.Info($@"CleverBot Executed
 | 
			
		||||
        //Server: {guild.Name} [{guild.Id}]
 | 
			
		||||
        //Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}]
 | 
			
		||||
        //UserId: {usrMsg.Author} [{usrMsg.Author.Id}]
 | 
			
		||||
        //Message: {usrMsg.Content}");
 | 
			
		||||
        //            return true;
 | 
			
		||||
        //        }
 | 
			
		||||
        //    }
 | 
			
		||||
        //    catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); }
 | 
			
		||||
        //    return false;
 | 
			
		||||
        //}
 | 
			
		||||
        ////todo blacklisting
 | 
			
		||||
        //private bool IsBlacklisted(IGuild guild, IUserMessage usrMsg) =>
 | 
			
		||||
        //    (guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) ||
 | 
			
		||||
        //    BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) ||
 | 
			
		||||
        //    BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
 | 
			
		||||
 | 
			
		||||
        private const float _oneThousandth = 1.0f / 1000;
 | 
			
		||||
        private Task LogSuccessfulExecution(IUserMessage usrMsg, ExecuteCommandResult exec, ITextChannel channel, int exec1, int exec2, int exec3, int total)
 | 
			
		||||
 | 
			
		||||
        private Task LogSuccessfulExecution(IUserMessage usrMsg, ExecuteCommandResult exec, ITextChannel channel, params int[] execPoints)
 | 
			
		||||
        {
 | 
			
		||||
            _log.Info("Command Executed after {4}/{5}/{6}/{7}s\n\t" +
 | 
			
		||||
            _log.Info("Command Executed after " + string.Join("/", execPoints.Select(x => x * _oneThousandth)) + "s\n\t" +
 | 
			
		||||
                        "User: {0}\n\t" +
 | 
			
		||||
                        "Server: {1}\n\t" +
 | 
			
		||||
                        "Channel: {2}\n\t" +
 | 
			
		||||
@@ -206,18 +212,14 @@ namespace NadekoBot.Services
 | 
			
		||||
                        usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
 | 
			
		||||
                        (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
 | 
			
		||||
                        (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
 | 
			
		||||
                        usrMsg.Content, // {3}
 | 
			
		||||
                        exec1 * _oneThousandth, // {4}
 | 
			
		||||
                        exec2 * _oneThousandth, // {5}
 | 
			
		||||
                        exec3 * _oneThousandth, // {6}
 | 
			
		||||
                        total * _oneThousandth // {7}
 | 
			
		||||
                        usrMsg.Content // {3}
 | 
			
		||||
                        );
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void LogErroredExecution(IUserMessage usrMsg, ExecuteCommandResult exec, ITextChannel channel, int exec1, int exec2, int exec3, int total)
 | 
			
		||||
        private void LogErroredExecution(IUserMessage usrMsg, ExecuteCommandResult exec, ITextChannel channel, params int[] execPoints)
 | 
			
		||||
        {
 | 
			
		||||
            _log.Warn("Command Errored after {5}/{6}/{7}/{8}s\n\t" +
 | 
			
		||||
            _log.Warn("Command Errored after " + string.Join("/", execPoints.Select(x => x * _oneThousandth)) + "s\n\t" +
 | 
			
		||||
                        "User: {0}\n\t" +
 | 
			
		||||
                        "Server: {1}\n\t" +
 | 
			
		||||
                        "Channel: {2}\n\t" +
 | 
			
		||||
@@ -227,60 +229,57 @@ namespace NadekoBot.Services
 | 
			
		||||
                        (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
 | 
			
		||||
                        (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
 | 
			
		||||
                        usrMsg.Content,// {3}
 | 
			
		||||
                        exec.Result.ErrorReason, // {4}
 | 
			
		||||
                        exec1 * _oneThousandth, // {5}
 | 
			
		||||
                        exec2 * _oneThousandth, // {6}
 | 
			
		||||
                        exec3 * _oneThousandth, // {7}
 | 
			
		||||
                        total * _oneThousandth // {8}
 | 
			
		||||
                        exec.Result.ErrorReason // {4}
 | 
			
		||||
                        );
 | 
			
		||||
        }
 | 
			
		||||
        ////todo invite filtering
 | 
			
		||||
        //private async Task<bool> InviteFiltered(IGuild guild, IUserMessage usrMsg)
 | 
			
		||||
        //{
 | 
			
		||||
        //    if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) ||
 | 
			
		||||
        //        Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) &&
 | 
			
		||||
        //            usrMsg.Content.IsDiscordInvite())
 | 
			
		||||
        //    {
 | 
			
		||||
        //        try
 | 
			
		||||
        //        {
 | 
			
		||||
        //            await usrMsg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
        //            return true;
 | 
			
		||||
        //        }
 | 
			
		||||
        //        catch (HttpException ex)
 | 
			
		||||
        //        {
 | 
			
		||||
        //            _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex);
 | 
			
		||||
        //            return true;
 | 
			
		||||
        //        }
 | 
			
		||||
        //    }
 | 
			
		||||
        //    return false;
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        private async Task<bool> InviteFiltered(IGuild guild, IUserMessage usrMsg)
 | 
			
		||||
        {
 | 
			
		||||
            if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) ||
 | 
			
		||||
                Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) &&
 | 
			
		||||
                    usrMsg.Content.IsDiscordInvite())
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await usrMsg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                catch (HttpException ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<bool> WordFiltered(IGuild guild, IUserMessage usrMsg)
 | 
			
		||||
        {
 | 
			
		||||
            var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>();
 | 
			
		||||
            var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>();
 | 
			
		||||
            var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
 | 
			
		||||
            if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var word in wordsInMessage)
 | 
			
		||||
                {
 | 
			
		||||
                    if (filteredChannelWords.Contains(word) ||
 | 
			
		||||
                        filteredServerWords.Contains(word))
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await usrMsg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (HttpException ex)
 | 
			
		||||
                        {
 | 
			
		||||
                            _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        ////todo word filtering
 | 
			
		||||
        //private async Task<bool> WordFiltered(IGuild guild, IUserMessage usrMsg)
 | 
			
		||||
        //{
 | 
			
		||||
        //    var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>();
 | 
			
		||||
        //    var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>();
 | 
			
		||||
        //    var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
 | 
			
		||||
        //    if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
 | 
			
		||||
        //    {
 | 
			
		||||
        //        foreach (var word in wordsInMessage)
 | 
			
		||||
        //        {
 | 
			
		||||
        //            if (filteredChannelWords.Contains(word) ||
 | 
			
		||||
        //                filteredServerWords.Contains(word))
 | 
			
		||||
        //            {
 | 
			
		||||
        //                try
 | 
			
		||||
        //                {
 | 
			
		||||
        //                    await usrMsg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
        //                }
 | 
			
		||||
        //                catch (HttpException ex)
 | 
			
		||||
        //                {
 | 
			
		||||
        //                    _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex);
 | 
			
		||||
        //                }
 | 
			
		||||
        //                return true;
 | 
			
		||||
        //            }
 | 
			
		||||
        //        }
 | 
			
		||||
        //    }
 | 
			
		||||
        //    return false;
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        private Task MessageReceivedHandler(SocketMessage msg)
 | 
			
		||||
        {
 | 
			
		||||
@@ -288,7 +287,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized
 | 
			
		||||
                    if (msg.Author.IsBot || !_bot.Ready) //no bots, wait until bot connected and initialized
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    var usrMsg = msg as SocketUserMessage;
 | 
			
		||||
@@ -325,140 +324,144 @@ namespace NadekoBot.Services
 | 
			
		||||
        {
 | 
			
		||||
            var execTime = Environment.TickCount;
 | 
			
		||||
 | 
			
		||||
            if (guild != null && guild.OwnerId != usrMsg.Author.Id)
 | 
			
		||||
            {
 | 
			
		||||
                if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
                    return;
 | 
			
		||||
            ////todo word and invite filtering
 | 
			
		||||
            //if (guild != null && guild.OwnerId != usrMsg.Author.Id)
 | 
			
		||||
            //{
 | 
			
		||||
            //    if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
            //        return;
 | 
			
		||||
 | 
			
		||||
                if (await WordFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
                    return;
 | 
			
		||||
            }
 | 
			
		||||
            //    if (await WordFiltered(guild, usrMsg).ConfigureAwait(false))
 | 
			
		||||
            //        return;
 | 
			
		||||
            //}
 | 
			
		||||
 | 
			
		||||
            if (IsBlacklisted(guild, usrMsg))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var exec1 = Environment.TickCount - execTime;
 | 
			
		||||
            ////todo blacklisting
 | 
			
		||||
            //if (IsBlacklisted(guild, usrMsg))
 | 
			
		||||
            //    return;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false);
 | 
			
		||||
            if (cleverBotRan)
 | 
			
		||||
                return;
 | 
			
		||||
            //var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false);
 | 
			
		||||
            //if (cleverBotRan)
 | 
			
		||||
            //    return;
 | 
			
		||||
 | 
			
		||||
            var exec2 = Environment.TickCount - execTime;
 | 
			
		||||
 | 
			
		||||
            // maybe this message is a custom reaction
 | 
			
		||||
            // todo log custom reaction executions. return struct with info
 | 
			
		||||
            var cr = await Task.Run(() => CustomReactions.TryGetCustomReaction(usrMsg)).ConfigureAwait(false);
 | 
			
		||||
            if (cr != null) //if it was, don't execute the command
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (guild != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        PermissionCache pc = Permissions.GetCache(guild.Id);
 | 
			
		||||
            ////todo custom reactions
 | 
			
		||||
            //// maybe this message is a custom reaction
 | 
			
		||||
            //// todo log custom reaction executions. return struct with info
 | 
			
		||||
            //var cr = await Task.Run(() => CustomReactions.TryGetCustomReaction(usrMsg)).ConfigureAwait(false);
 | 
			
		||||
            //if (cr != null) //if it was, don't execute the command
 | 
			
		||||
            //{
 | 
			
		||||
            //    try
 | 
			
		||||
            //    {
 | 
			
		||||
            //        if (guild != null)
 | 
			
		||||
            //        {
 | 
			
		||||
            //            PermissionCache pc = Permissions.GetCache(guild.Id);
 | 
			
		||||
 | 
			
		||||
                        if (!pc.Permissions.CheckPermissions(usrMsg, cr.Trigger, "ActualCustomReactions",
 | 
			
		||||
                            out int index))
 | 
			
		||||
                        {
 | 
			
		||||
                            //todo print in guild actually
 | 
			
		||||
                            var returnMsg =
 | 
			
		||||
                                $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
 | 
			
		||||
                            _log.Info(returnMsg);
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    await cr.Send(usrMsg).ConfigureAwait(false);
 | 
			
		||||
            //            if (!pc.Permissions.CheckPermissions(usrMsg, cr.Trigger, "ActualCustomReactions",
 | 
			
		||||
            //                out int index))
 | 
			
		||||
            //            {
 | 
			
		||||
            //                //todo print in guild actually
 | 
			
		||||
            //                var returnMsg =
 | 
			
		||||
            //                    $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
 | 
			
		||||
            //                _log.Info(returnMsg);
 | 
			
		||||
            //                return;
 | 
			
		||||
            //            }
 | 
			
		||||
            //        }
 | 
			
		||||
            //        await cr.Send(usrMsg).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    if (cr.AutoDeleteTrigger)
 | 
			
		||||
                    {
 | 
			
		||||
                        try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn("Sending CREmbed failed");
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            //        if (cr.AutoDeleteTrigger)
 | 
			
		||||
            //        {
 | 
			
		||||
            //            try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
            //        }
 | 
			
		||||
            //    }
 | 
			
		||||
            //    catch (Exception ex)
 | 
			
		||||
            //    {
 | 
			
		||||
            //        _log.Warn("Sending CREmbed failed");
 | 
			
		||||
            //        _log.Warn(ex);
 | 
			
		||||
            //    }
 | 
			
		||||
            //    return;
 | 
			
		||||
            //}
 | 
			
		||||
 | 
			
		||||
            var exec3 = Environment.TickCount - execTime;
 | 
			
		||||
 | 
			
		||||
            string messageContent = usrMsg.Content;
 | 
			
		||||
            if (guild != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps))
 | 
			
		||||
                {
 | 
			
		||||
            ////todo alias mapping
 | 
			
		||||
            //        if (guild != null)
 | 
			
		||||
            //        {
 | 
			
		||||
            //            if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps))
 | 
			
		||||
            //            {
 | 
			
		||||
 | 
			
		||||
                    var keys = maps.Keys
 | 
			
		||||
                        .OrderByDescending(x => x.Length);
 | 
			
		||||
            //                var keys = maps.Keys
 | 
			
		||||
            //                    .OrderByDescending(x => x.Length);
 | 
			
		||||
 | 
			
		||||
                    var lowerMessageContent = messageContent.ToLowerInvariant();
 | 
			
		||||
                    foreach (var k in keys)
 | 
			
		||||
                    {
 | 
			
		||||
                        string newMessageContent;
 | 
			
		||||
                        if (lowerMessageContent.StartsWith(k + " "))
 | 
			
		||||
                            newMessageContent = maps[k] + messageContent.Substring(k.Length, messageContent.Length - k.Length);
 | 
			
		||||
                        else if (lowerMessageContent == k)
 | 
			
		||||
                            newMessageContent = maps[k];
 | 
			
		||||
                        else
 | 
			
		||||
                            continue;
 | 
			
		||||
            //                var lowerMessageContent = messageContent.ToLowerInvariant();
 | 
			
		||||
            //                foreach (var k in keys)
 | 
			
		||||
            //                {
 | 
			
		||||
            //                    string newMessageContent;
 | 
			
		||||
            //                    if (lowerMessageContent.StartsWith(k + " "))
 | 
			
		||||
            //                        newMessageContent = maps[k] + messageContent.Substring(k.Length, messageContent.Length - k.Length);
 | 
			
		||||
            //                    else if (lowerMessageContent == k)
 | 
			
		||||
            //                        newMessageContent = maps[k];
 | 
			
		||||
            //                    else
 | 
			
		||||
            //                        continue;
 | 
			
		||||
 | 
			
		||||
                        _log.Info(@"--Mapping Command--
 | 
			
		||||
    GuildId: {0}
 | 
			
		||||
    Trigger: {1}
 | 
			
		||||
    Mapping: {2}", guild.Id, messageContent, newMessageContent);
 | 
			
		||||
                        var oldMessageContent = messageContent;
 | 
			
		||||
                        messageContent = newMessageContent;
 | 
			
		||||
            //                    _log.Info(@"--Mapping Command--
 | 
			
		||||
            //GuildId: {0}
 | 
			
		||||
            //Trigger: {1}
 | 
			
		||||
            //Mapping: {2}", guild.Id, messageContent, newMessageContent);
 | 
			
		||||
            //                    var oldMessageContent = messageContent;
 | 
			
		||||
            //                    messageContent = newMessageContent;
 | 
			
		||||
 | 
			
		||||
                        try { await usrMsg.Channel.SendConfirmAsync($"{oldMessageContent} => {newMessageContent}").ConfigureAwait(false); } catch { }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //                    try { await usrMsg.Channel.SendConfirmAsync($"{oldMessageContent} => {newMessageContent}").ConfigureAwait(false); } catch { }
 | 
			
		||||
            //                    break;
 | 
			
		||||
            //                }
 | 
			
		||||
            //            }
 | 
			
		||||
            //        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // execute the command and measure the time it took
 | 
			
		||||
            var exec = await Task.Run(() => ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best)).ConfigureAwait(false);
 | 
			
		||||
            execTime = Environment.TickCount - execTime;
 | 
			
		||||
            if (messageContent.StartsWith(NadekoBot.Prefix))
 | 
			
		||||
            {
 | 
			
		||||
                var exec = await Task.Run(() => ExecuteCommandAsync(new CommandContext(_client, usrMsg), NadekoBot.Prefix.Length, _services, MultiMatchHandling.Best)).ConfigureAwait(false);
 | 
			
		||||
                execTime = Environment.TickCount - execTime;
 | 
			
		||||
 | 
			
		||||
            if (exec.Result.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
 | 
			
		||||
                await LogSuccessfulExecution(usrMsg, exec, channel, exec1, exec2, exec3, execTime).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
 | 
			
		||||
            {
 | 
			
		||||
                LogErroredExecution(usrMsg, exec, channel, exec1, exec2, exec3, execTime);
 | 
			
		||||
                if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
 | 
			
		||||
                if (exec.Result.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
 | 
			
		||||
                        try { await usrMsg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { }
 | 
			
		||||
                    await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
 | 
			
		||||
                    await LogSuccessfulExecution(usrMsg, exec, channel, exec2, exec3, execTime).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
 | 
			
		||||
                {
 | 
			
		||||
                    LogErroredExecution(usrMsg, exec, channel, exec2, exec3, execTime);
 | 
			
		||||
                    if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
 | 
			
		||||
                            try { await usrMsg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { }
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
 | 
			
		||||
            if (usrMsg.Channel is IPrivateChannel)
 | 
			
		||||
            {
 | 
			
		||||
                if (usrMsg.Channel is IPrivateChannel)
 | 
			
		||||
                {
 | 
			
		||||
                    // rofl, gotta do this to prevent dm help message being sent to 
 | 
			
		||||
                    // users who are voting on private polls (sending a number in a DM)
 | 
			
		||||
                    if (int.TryParse(usrMsg.Content, out int vote)) return;
 | 
			
		||||
                // rofl, gotta do this to prevent dm help message being sent to 
 | 
			
		||||
                // users who are voting on private polls (sending a number in a DM)
 | 
			
		||||
                if (int.TryParse(usrMsg.Content, out int vote)) return;
 | 
			
		||||
 | 
			
		||||
                    await usrMsg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
 | 
			
		||||
                ////todo help
 | 
			
		||||
                //await usrMsg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await SelfCommands.HandleDmForwarding(usrMsg, ownerChannels).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                ////todo selfcommands
 | 
			
		||||
                //await SelfCommands.HandleDmForwarding(usrMsg, ownerChannels).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
            => ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling);
 | 
			
		||||
        public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
            => ExecuteCommand(context, context.Message.Content.Substring(argPos), serviceProvider, multiMatchHandling);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public async Task<ExecuteCommandResult> ExecuteCommand(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
        public async Task<ExecuteCommandResult> ExecuteCommand(CommandContext context, string input, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
 | 
			
		||||
        {
 | 
			
		||||
            dependencyMap = dependencyMap ?? DependencyMap.Empty;
 | 
			
		||||
 | 
			
		||||
            var searchResult = _commandService.Search(context, input);
 | 
			
		||||
            if (!searchResult.IsSuccess)
 | 
			
		||||
                return new ExecuteCommandResult(null, null, searchResult);
 | 
			
		||||
@@ -505,50 +508,55 @@ namespace NadekoBot.Services
 | 
			
		||||
                var module = cmd.Module.GetTopLevelModule();
 | 
			
		||||
                if (context.Guild != null)
 | 
			
		||||
                {
 | 
			
		||||
                    PermissionCache pc = Permissions.GetCache(context.Guild.Id);
 | 
			
		||||
                    if (!resetCommand && !pc.Permissions.CheckPermissions(context.Message, cmd.Aliases.First(), module.Name, out int index))
 | 
			
		||||
                    {
 | 
			
		||||
                        var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand((SocketGuild)context.Guild)}** is preventing this action.";
 | 
			
		||||
                        return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    if (module.Name == typeof(Permissions).Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        var guildUser = (IGuildUser)context.User;
 | 
			
		||||
                        if (!guildUser.GetRoles().Any(r => r.Name.Trim().ToLowerInvariant() == pc.PermRole.Trim().ToLowerInvariant()) && guildUser.Id != guildUser.Guild.OwnerId)
 | 
			
		||||
                        {
 | 
			
		||||
                            return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands."));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    //int price;
 | 
			
		||||
                    //if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0)
 | 
			
		||||
                    ////todo perms
 | 
			
		||||
                    //PermissionCache pc = Permissions.GetCache(context.Guild.Id);
 | 
			
		||||
                    //if (!resetCommand && !pc.Permissions.CheckPermissions(context.Message, cmd.Aliases.First(), module.Name, out int index))
 | 
			
		||||
                    //{
 | 
			
		||||
                    //    var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false);
 | 
			
		||||
                    //    if (!success)
 | 
			
		||||
                    //    var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand((SocketGuild)context.Guild)}** is preventing this action.";
 | 
			
		||||
                    //    return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg));
 | 
			
		||||
                    //}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    //if (module.Name == typeof(Permissions).Name)
 | 
			
		||||
                    //{
 | 
			
		||||
                    //    var guildUser = (IGuildUser)context.User;
 | 
			
		||||
                    //    if (!guildUser.GetRoles().Any(r => r.Name.Trim().ToLowerInvariant() == pc.PermRole.Trim().ToLowerInvariant()) && guildUser.Id != guildUser.Guild.OwnerId)
 | 
			
		||||
                    //    {
 | 
			
		||||
                    //        return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command."));
 | 
			
		||||
                    //        return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands."));
 | 
			
		||||
                    //    }
 | 
			
		||||
                    //}
 | 
			
		||||
 | 
			
		||||
                    ////////future
 | 
			
		||||
                    //////int price;
 | 
			
		||||
                    //////if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0)
 | 
			
		||||
                    //////{
 | 
			
		||||
                    //////    var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false);
 | 
			
		||||
                    //////    if (!success)
 | 
			
		||||
                    //////    {
 | 
			
		||||
                    //////        return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command."));
 | 
			
		||||
                    //////    }
 | 
			
		||||
                    //////}
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (cmd.Name != "resetglobalperms" && 
 | 
			
		||||
                    (GlobalPermissionCommands.BlockedCommands.Contains(cmd.Aliases.First().ToLowerInvariant()) ||
 | 
			
		||||
                    GlobalPermissionCommands.BlockedModules.Contains(module.Name.ToLowerInvariant())))
 | 
			
		||||
                {
 | 
			
		||||
                    return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"Command or module is blocked globally by the bot owner."));
 | 
			
		||||
                }
 | 
			
		||||
                ////todo perms
 | 
			
		||||
                //if (cmd.Name != "resetglobalperms" && 
 | 
			
		||||
                //    (GlobalPermissionCommands.BlockedCommands.Contains(cmd.Aliases.First().ToLowerInvariant()) ||
 | 
			
		||||
                //    GlobalPermissionCommands.BlockedModules.Contains(module.Name.ToLowerInvariant())))
 | 
			
		||||
                //{
 | 
			
		||||
                //    return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"Command or module is blocked globally by the bot owner."));
 | 
			
		||||
                //}
 | 
			
		||||
 | 
			
		||||
                // Bot will ignore commands which are ran more often than what specified by
 | 
			
		||||
                // GlobalCommandsCooldown constant (miliseconds)
 | 
			
		||||
                if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
 | 
			
		||||
                    return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."));
 | 
			
		||||
 | 
			
		||||
                if (CmdCdsCommands.HasCooldown(cmd, context.Guild, context.User))
 | 
			
		||||
                    return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "That command is on a cooldown for you."));
 | 
			
		||||
                ////todo cmdcds
 | 
			
		||||
                //if (CmdCdsCommands.HasCooldown(cmd, context.Guild, context.User))
 | 
			
		||||
                //    return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "That command is on a cooldown for you."));
 | 
			
		||||
 | 
			
		||||
                return new ExecuteCommandResult(cmd, null, await commands[i].ExecuteAsync(context, parseResult, dependencyMap));
 | 
			
		||||
                return new ExecuteCommandResult(cmd, null, await commands[i].ExecuteAsync(context, parseResult, serviceProvider));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."));
 | 
			
		||||
 
 | 
			
		||||
@@ -2,25 +2,33 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Gambling;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public static class CurrencyHandler
 | 
			
		||||
    public class CurrencyHandler
 | 
			
		||||
    {
 | 
			
		||||
        public static async Task<bool> RemoveCurrencyAsync(IUser author, string reason, long amount, bool sendMessage)
 | 
			
		||||
        private readonly BotConfig _config;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public CurrencyHandler(BotConfig config, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _config = config;
 | 
			
		||||
            _db = db;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> RemoveCurrencyAsync(IUser author, string reason, long amount, bool sendMessage)
 | 
			
		||||
        {
 | 
			
		||||
            var success = await RemoveCurrencyAsync(author.Id, reason, amount);
 | 
			
		||||
 | 
			
		||||
            if (success && sendMessage)
 | 
			
		||||
                try { await author.SendErrorAsync($"`You lost:` {amount} {NadekoBot.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { }
 | 
			
		||||
                try { await author.SendErrorAsync($"`You lost:` {amount} {_config.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { }
 | 
			
		||||
 | 
			
		||||
            return success;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<bool> RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null)
 | 
			
		||||
        public async Task<bool> RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (amount < 0)
 | 
			
		||||
                throw new ArgumentNullException(nameof(amount));
 | 
			
		||||
@@ -28,7 +36,7 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
            if (uow == null)
 | 
			
		||||
            {
 | 
			
		||||
                using (uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var toReturn = InternalRemoveCurrency(authorId, reason, amount, uow);
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
@@ -39,7 +47,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            return InternalRemoveCurrency(authorId, reason, amount, uow);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool InternalRemoveCurrency(ulong authorId, string reason, long amount, IUnitOfWork uow)
 | 
			
		||||
        private bool InternalRemoveCurrency(ulong authorId, string reason, long amount, IUnitOfWork uow)
 | 
			
		||||
        {
 | 
			
		||||
            var success = uow.Currency.TryUpdateState(authorId, -amount);
 | 
			
		||||
            if (!success)
 | 
			
		||||
@@ -53,15 +61,15 @@ namespace NadekoBot.Services
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task AddCurrencyAsync(IUser author, string reason, long amount, bool sendMessage)
 | 
			
		||||
        public async Task AddCurrencyAsync(IUser author, string reason, long amount, bool sendMessage)
 | 
			
		||||
        {
 | 
			
		||||
            await AddCurrencyAsync(author.Id, reason, amount);
 | 
			
		||||
 | 
			
		||||
            if (sendMessage)
 | 
			
		||||
                try { await author.SendConfirmAsync($"`You received:` {amount} {NadekoBot.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { }
 | 
			
		||||
                try { await author.SendConfirmAsync($"`You received:` {amount} {_config.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount, IUnitOfWork uow = null)
 | 
			
		||||
        public async Task AddCurrencyAsync(ulong receiverId, string reason, long amount, IUnitOfWork uow = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (amount < 0)
 | 
			
		||||
                throw new ArgumentNullException(nameof(amount));
 | 
			
		||||
@@ -74,7 +82,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (uow == null)
 | 
			
		||||
                using (uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.Currency.TryUpdateState(receiverId, amount);
 | 
			
		||||
                    uow.CurrencyTransactions.Add(transaction);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Database.Models
 | 
			
		||||
namespace NadekoBot.Services.Database.Models
 | 
			
		||||
{
 | 
			
		||||
    public class PlaylistSong : DbEntity
 | 
			
		||||
    {
 | 
			
		||||
@@ -10,4 +8,12 @@ namespace NadekoBot.Services.Database.Models
 | 
			
		||||
        public string Uri { get; set; }
 | 
			
		||||
        public string Query { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum MusicType
 | 
			
		||||
    {
 | 
			
		||||
        Radio,
 | 
			
		||||
        Normal,
 | 
			
		||||
        Local,
 | 
			
		||||
        Soundcloud
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,17 @@ namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class DbHandler
 | 
			
		||||
    {
 | 
			
		||||
        private static DbHandler _instance = null;
 | 
			
		||||
        public static DbHandler Instance = _instance ?? (_instance = new DbHandler());
 | 
			
		||||
        private readonly DbContextOptions options;
 | 
			
		||||
 | 
			
		||||
        private string connectionString { get; }
 | 
			
		||||
 | 
			
		||||
        static DbHandler() { }
 | 
			
		||||
 | 
			
		||||
        private DbHandler()
 | 
			
		||||
        public DbHandler(IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            connectionString = NadekoBot.Credentials.Db.ConnectionString;
 | 
			
		||||
            connectionString = creds.Db.ConnectionString;
 | 
			
		||||
            var optionsBuilder = new DbContextOptionsBuilder();
 | 
			
		||||
            optionsBuilder.UseSqlite(NadekoBot.Credentials.Db.ConnectionString);
 | 
			
		||||
            optionsBuilder.UseSqlite(creds.Db.ConnectionString);
 | 
			
		||||
            options = optionsBuilder.Options;
 | 
			
		||||
            //switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant())
 | 
			
		||||
            //{
 | 
			
		||||
@@ -44,10 +42,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            return context;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IUnitOfWork GetUnitOfWork() =>
 | 
			
		||||
        public IUnitOfWork UnitOfWork =>
 | 
			
		||||
            new UnitOfWork(GetDbContext());
 | 
			
		||||
 | 
			
		||||
        public static IUnitOfWork UnitOfWork() =>
 | 
			
		||||
            DbHandler.Instance.GetUnitOfWork();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,13 +15,14 @@ namespace NadekoBot.Services.Discord
 | 
			
		||||
        public event Action<SocketReaction> OnReactionRemoved = delegate { };
 | 
			
		||||
        public event Action OnReactionsCleared = delegate { };
 | 
			
		||||
 | 
			
		||||
        public ReactionEventWrapper(IUserMessage msg)
 | 
			
		||||
        public ReactionEventWrapper(DiscordShardedClient client, IUserMessage msg)
 | 
			
		||||
        {
 | 
			
		||||
            Message = msg ?? throw new ArgumentNullException(nameof(msg));
 | 
			
		||||
            _client = client;
 | 
			
		||||
 | 
			
		||||
            NadekoBot.Client.ReactionAdded += Discord_ReactionAdded;
 | 
			
		||||
            NadekoBot.Client.ReactionRemoved += Discord_ReactionRemoved;
 | 
			
		||||
            NadekoBot.Client.ReactionsCleared += Discord_ReactionsCleared;
 | 
			
		||||
            _client.ReactionAdded += Discord_ReactionAdded;
 | 
			
		||||
            _client.ReactionRemoved += Discord_ReactionRemoved;
 | 
			
		||||
            _client.ReactionsCleared += Discord_ReactionsCleared;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
 | 
			
		||||
@@ -62,15 +63,17 @@ namespace NadekoBot.Services.Discord
 | 
			
		||||
 | 
			
		||||
        public void UnsubAll()
 | 
			
		||||
        {
 | 
			
		||||
            NadekoBot.Client.ReactionAdded -= Discord_ReactionAdded;
 | 
			
		||||
            NadekoBot.Client.ReactionRemoved -= Discord_ReactionRemoved;
 | 
			
		||||
            NadekoBot.Client.ReactionsCleared -= Discord_ReactionsCleared;
 | 
			
		||||
            _client.ReactionAdded -= Discord_ReactionAdded;
 | 
			
		||||
            _client.ReactionRemoved -= Discord_ReactionRemoved;
 | 
			
		||||
            _client.ReactionsCleared -= Discord_ReactionsCleared;
 | 
			
		||||
            OnReactionAdded = null;
 | 
			
		||||
            OnReactionRemoved = null;
 | 
			
		||||
            OnReactionsCleared = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool disposing = false;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (disposing)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,14 @@ namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class GreetSettingsService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache { get; }
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public GreetSettingsService()
 | 
			
		||||
        public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache;
 | 
			
		||||
 | 
			
		||||
        public GreetSettingsService(IEnumerable<GuildConfig> guildConfigs, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
 | 
			
		||||
            _db = db;
 | 
			
		||||
            GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(guildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
 | 
			
		||||
@@ -27,7 +30,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            if (settings != null)
 | 
			
		||||
                return settings;
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                settings = GreetSettings.Create(gc);
 | 
			
		||||
@@ -47,7 +50,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                conf.DmGreetMessageText = settings.DmGreetMessageText?.SanitizeMentions();
 | 
			
		||||
@@ -78,7 +81,7 @@ namespace NadekoBot.Services
 | 
			
		||||
        public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
 | 
			
		||||
        {
 | 
			
		||||
            bool enabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
 | 
			
		||||
@@ -100,7 +103,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
            bool greetMsgEnabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                conf.ChannelGreetMessageText = message;
 | 
			
		||||
@@ -117,7 +120,7 @@ namespace NadekoBot.Services
 | 
			
		||||
        public async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
 | 
			
		||||
        {
 | 
			
		||||
            bool enabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
 | 
			
		||||
@@ -138,7 +141,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
            bool greetMsgEnabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId);
 | 
			
		||||
                conf.DmGreetMessageText = message;
 | 
			
		||||
@@ -155,7 +158,7 @@ namespace NadekoBot.Services
 | 
			
		||||
        public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
 | 
			
		||||
        {
 | 
			
		||||
            bool enabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
 | 
			
		||||
@@ -177,7 +180,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                throw new ArgumentNullException(nameof(message));
 | 
			
		||||
 | 
			
		||||
            bool byeMsgEnabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                conf.ChannelByeMessageText = message;
 | 
			
		||||
@@ -196,7 +199,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            if (timer < 0 || timer > 600)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                conf.AutoDeleteByeMessagesTimer = timer;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,10 @@ namespace NadekoBot.Services
 | 
			
		||||
        string MashapeKey { get; }
 | 
			
		||||
        string LoLApiKey { get; }
 | 
			
		||||
        string PatreonAccessToken { get; }
 | 
			
		||||
        string CarbonKey { get; }
 | 
			
		||||
 | 
			
		||||
        DBConfig Db { get; }
 | 
			
		||||
        string SoundCloudClientId { get; set; }
 | 
			
		||||
 | 
			
		||||
        bool IsOwner(IUser u);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,6 @@ namespace NadekoBot.Services
 | 
			
		||||
        ImmutableArray<byte> WifeMatrix { get; }
 | 
			
		||||
        ImmutableArray<byte> RategirlDot { get; }
 | 
			
		||||
 | 
			
		||||
        Task<TimeSpan> Reload();
 | 
			
		||||
        TimeSpan Reload();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/NadekoBot/Services/ILocalization.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/NadekoBot/Services/ILocalization.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface ILocalization
 | 
			
		||||
    {
 | 
			
		||||
        CultureInfo DefaultCultureInfo { get; }
 | 
			
		||||
        ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
 | 
			
		||||
 | 
			
		||||
        CultureInfo GetCultureInfo(IGuild guild);
 | 
			
		||||
        CultureInfo GetCultureInfo(ulong? guildId);
 | 
			
		||||
        void RemoveGuildCulture(IGuild guild);
 | 
			
		||||
        void RemoveGuildCulture(ulong guildId);
 | 
			
		||||
        void ResetDefaultCulture();
 | 
			
		||||
        void SetDefaultCulture(CultureInfo ci);
 | 
			
		||||
        void SetGuildCulture(IGuild guild, CultureInfo ci);
 | 
			
		||||
        void SetGuildCulture(ulong guildId, CultureInfo ci);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,22 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IStatsService
 | 
			
		||||
    {
 | 
			
		||||
        string Author { get; }
 | 
			
		||||
        long CommandsRan { get; }
 | 
			
		||||
        string Heap { get; }
 | 
			
		||||
        string Library { get; }
 | 
			
		||||
        long MessageCounter { get; }
 | 
			
		||||
        double MessagesPerSecond { get; }
 | 
			
		||||
        long TextChannels { get; }
 | 
			
		||||
        long VoiceChannels { get; }
 | 
			
		||||
 | 
			
		||||
        TimeSpan GetUptime();
 | 
			
		||||
        string GetUptimeString(string separator = ", ");
 | 
			
		||||
        void Initialize();
 | 
			
		||||
        Task<string> Print();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,12 +23,14 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
        
 | 
			
		||||
        private Logger _log { get; }
 | 
			
		||||
 | 
			
		||||
        public GoogleApiService()
 | 
			
		||||
        public GoogleApiService(IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
 | 
			
		||||
            var bcs = new BaseClientService.Initializer
 | 
			
		||||
            {
 | 
			
		||||
                ApplicationName = "Nadeko Bot",
 | 
			
		||||
                ApiKey = NadekoBot.Credentials.GoogleApiKey,
 | 
			
		||||
                ApiKey = _creds.GoogleApiKey,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
@@ -59,6 +61,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?<id>[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled);
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
        public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
 | 
			
		||||
        {
 | 
			
		||||
@@ -110,7 +113,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(url))
 | 
			
		||||
                throw new ArgumentNullException(nameof(url));
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.GoogleApiKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
 | 
			
		||||
                return url;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
 
 | 
			
		||||
@@ -44,19 +44,13 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
        public ImmutableArray<byte> WifeMatrix { get; private set; }
 | 
			
		||||
        public ImmutableArray<byte> RategirlDot { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private ImagesService()
 | 
			
		||||
        public ImagesService()
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            this.Reload();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<IImagesService> Create()
 | 
			
		||||
        {
 | 
			
		||||
            var srvc = new ImagesService();
 | 
			
		||||
            await srvc.Reload().ConfigureAwait(false);
 | 
			
		||||
            return srvc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<TimeSpan> Reload() => Task.Run(() =>
 | 
			
		||||
        public TimeSpan Reload()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -101,6 +95,6 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
                _log.Error(ex);
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,17 +12,19 @@ using NLog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class Localization
 | 
			
		||||
    public class Localization : ILocalization
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
 | 
			
		||||
        public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
 | 
			
		||||
 | 
			
		||||
        private Localization() { }
 | 
			
		||||
        public Localization(string defaultCulture, IDictionary<ulong, string> cultureInfoNames)
 | 
			
		||||
        public Localization(string defaultCulture, IDictionary<ulong, string> cultureInfoNames, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _db = db;
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(defaultCulture))
 | 
			
		||||
                DefaultCultureInfo = new CultureInfo("en-US");
 | 
			
		||||
            else
 | 
			
		||||
@@ -62,7 +64,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                gc.Locale = ci.Name;
 | 
			
		||||
@@ -80,7 +82,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            CultureInfo throwaway;
 | 
			
		||||
            if (GuildCultureInfos.TryRemove(guildId, out throwaway))
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var gc = uow.GuildConfigs.For(guildId, set => set);
 | 
			
		||||
                    gc.Locale = null;
 | 
			
		||||
@@ -91,7 +93,7 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
        public void SetDefaultCulture(CultureInfo ci)
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var bc = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                bc.Locale = ci.Name;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ using System.Xml.Linq;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
@@ -15,6 +16,10 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
        private readonly ImmutableDictionary<string, ImmutableDictionary<string, string>> responseStrings;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Used as failsafe in case response key doesn't exist in the selected or default language.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
 | 
			
		||||
 | 
			
		||||
        public NadekoStrings()
 | 
			
		||||
        {
 | 
			
		||||
@@ -44,7 +49,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            return fileName.Substring(dotIndex, secondDotINdex - dotIndex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetString(string text, CultureInfo cultureInfo)
 | 
			
		||||
        private string GetString(string text, CultureInfo cultureInfo)
 | 
			
		||||
        {
 | 
			
		||||
            if (!responseStrings.TryGetValue(cultureInfo.Name.ToLowerInvariant(), out ImmutableDictionary<string, string> strings))
 | 
			
		||||
                return null;
 | 
			
		||||
@@ -52,5 +57,34 @@ namespace NadekoBot.Services
 | 
			
		||||
            strings.TryGetValue(text, out string val);
 | 
			
		||||
            return val;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
 | 
			
		||||
        {
 | 
			
		||||
            var text = GetString(lowerModuleTypeName + "_" + key, cultureInfo);
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
            {
 | 
			
		||||
                LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
 | 
			
		||||
                text = GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                    return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
 | 
			
		||||
                        lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
 | 
			
		||||
            }
 | 
			
		||||
            return text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
 | 
			
		||||
            params object[] replacements)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return string.Format(GetText(key, cultureInfo, lowerModuleTypeName), replacements);
 | 
			
		||||
            }
 | 
			
		||||
            catch (FormatException)
 | 
			
		||||
            {
 | 
			
		||||
                return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
 | 
			
		||||
                       lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,6 +15,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
    public class StatsService : IStatsService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly DateTime _started;
 | 
			
		||||
 | 
			
		||||
        public const string BotVersion = "1.4";
 | 
			
		||||
@@ -36,10 +37,10 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
 | 
			
		||||
        private readonly Timer _carbonitexTimer;
 | 
			
		||||
 | 
			
		||||
        public StatsService(DiscordShardedClient client, CommandHandler cmdHandler)
 | 
			
		||||
        public StatsService(DiscordShardedClient client, CommandHandler cmdHandler, IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
 | 
			
		||||
            _started = DateTime.Now;
 | 
			
		||||
            _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
 | 
			
		||||
@@ -117,7 +118,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
 | 
			
		||||
            _carbonitexTimer = new Timer(async (state) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey))
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(_creds.CarbonKey))
 | 
			
		||||
                    return;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
@@ -126,7 +127,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
                        using (var content = new FormUrlEncodedContent(
 | 
			
		||||
                            new Dictionary<string, string> {
 | 
			
		||||
                                { "servercount", _client.Guilds.Count.ToString() },
 | 
			
		||||
                                { "key", NadekoBot.Credentials.CarbonKey }}))
 | 
			
		||||
                                { "key", _creds.CarbonKey }}))
 | 
			
		||||
                        {
 | 
			
		||||
                            content.Headers.Clear();
 | 
			
		||||
                            content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
 | 
			
		||||
@@ -158,7 +159,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
Author: [{Author}] | Library: [{Library}]
 | 
			
		||||
Bot Version: [{BotVersion}]
 | 
			
		||||
Bot ID: {curUser.Id}
 | 
			
		||||
Owner ID(s): {string.Join(", ", NadekoBot.Credentials.OwnerIds)}
 | 
			
		||||
Owner ID(s): {string.Join(", ", _creds.OwnerIds)}
 | 
			
		||||
Uptime: {GetUptimeString()}
 | 
			
		||||
Servers: {_client.Guilds.Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
 | 
			
		||||
Commands Ran this session: {CommandsRan}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    class PlaylistFullException : Exception
 | 
			
		||||
    {
 | 
			
		||||
@@ -8,16 +8,11 @@ using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public enum MusicType
 | 
			
		||||
    {
 | 
			
		||||
        Radio,
 | 
			
		||||
        Normal,
 | 
			
		||||
        Local,
 | 
			
		||||
        Soundcloud
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum StreamState
 | 
			
		||||
    {
 | 
			
		||||
@@ -29,6 +24,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
 | 
			
		||||
    public class MusicPlayer
 | 
			
		||||
    {
 | 
			
		||||
        public const string MusicDataPath = "data/musicdata";
 | 
			
		||||
        private IAudioClient AudioClient { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -58,6 +54,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
 | 
			
		||||
        private readonly List<Song> _playlist = new List<Song>();
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly IGoogleApiService _google;
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyCollection<Song> Playlist => _playlist;
 | 
			
		||||
 | 
			
		||||
@@ -88,9 +85,10 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
 | 
			
		||||
        public event Action<Song, int> SongRemoved = delegate { };
 | 
			
		||||
 | 
			
		||||
        public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume)
 | 
			
		||||
        public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume, IGoogleApiService google)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _google = google;
 | 
			
		||||
 | 
			
		||||
            OutputTextChannel = outputChannel;
 | 
			
		||||
            Volume = defaultVolume ?? 1.0f;
 | 
			
		||||
@@ -324,7 +322,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
            var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
 | 
			
		||||
                              .Distinct();
 | 
			
		||||
 | 
			
		||||
            var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids);
 | 
			
		||||
            var durations = await _google.GetVideoDurationsAsync(ids);
 | 
			
		||||
 | 
			
		||||
            toUpdate.ForEach(s =>
 | 
			
		||||
            {
 | 
			
		||||
@@ -337,7 +335,6 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Destroy()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,28 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Modules.Music.Classes;
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules;
 | 
			
		||||
using NadekoBot.Services.Impl;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public class MusicService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IGoogleApiService _google;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
        private readonly ILocalization _localization;
 | 
			
		||||
        private GoogleApiService google;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
 | 
			
		||||
 | 
			
		||||
        public MusicService(IGoogleApiService google)
 | 
			
		||||
        public MusicService(IGoogleApiService google, NadekoStrings strings, ILocalization localization)
 | 
			
		||||
        {
 | 
			
		||||
            _google = google;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
            _localization = localization;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public MusicPlayer GetPlayer(ulong guildId)
 | 
			
		||||
@@ -30,7 +34,7 @@ namespace NadekoBot.Services.Music
 | 
			
		||||
        public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
 | 
			
		||||
        {
 | 
			
		||||
            string GetText(string text, params object[] replacements) =>
 | 
			
		||||
                NadekoTopLevelModule.GetTextStatic(text, NadekoBot.Localization.GetCultureInfo(textCh.Guild), nameof(Modules.Music.Music).ToLowerInvariant(), replacements);
 | 
			
		||||
                _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
 | 
			
		||||
 | 
			
		||||
            return MusicPlayers.GetOrAdd(guildId, server =>
 | 
			
		||||
            {
 | 
			
		||||
@@ -40,7 +44,7 @@ namespace NadekoBot.Services.Music
 | 
			
		||||
                    //todo move to cached variable
 | 
			
		||||
                    vol = uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
 | 
			
		||||
                }
 | 
			
		||||
                var mp = new MusicPlayer(voiceCh, textCh, vol);
 | 
			
		||||
                var mp = new MusicPlayer(voiceCh, textCh, vol, _google);
 | 
			
		||||
                IUserMessage playingMessage = null;
 | 
			
		||||
                IUserMessage lastFinishedMessage = null;
 | 
			
		||||
                mp.OnCompleted += async (s, song) =>
 | 
			
		||||
@@ -64,7 +68,7 @@ namespace NadekoBot.Services.Music
 | 
			
		||||
 | 
			
		||||
                        if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal)
 | 
			
		||||
                        {
 | 
			
		||||
                            var relatedVideos = (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList();
 | 
			
		||||
                            var relatedVideos = (await _google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList();
 | 
			
		||||
                            if (relatedVideos.Count > 0)
 | 
			
		||||
                                await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
 | 
			
		||||
                                    textCh,
 | 
			
		||||
@@ -148,7 +152,7 @@ namespace NadekoBot.Services.Music
 | 
			
		||||
        public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
 | 
			
		||||
        {
 | 
			
		||||
            string GetText(string text, params object[] replacements) => 
 | 
			
		||||
                NadekoTopLevelModule.GetTextStatic(text, NadekoBot.Localization.GetCultureInfo(textCh.Guild), nameof(Modules.Music.Music).ToLowerInvariant(), replacements);
 | 
			
		||||
                _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
 | 
			
		||||
 | 
			
		||||
            if (voiceCh == null || voiceCh.Guild != textCh.Guild)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,9 @@ using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public class SongInfo
 | 
			
		||||
    {
 | 
			
		||||
@@ -29,7 +30,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
 | 
			
		||||
        private string _queuerName;
 | 
			
		||||
        public string QueuerName { get{
 | 
			
		||||
            return Discord.Format.Sanitize(_queuerName);
 | 
			
		||||
            return Format.Sanitize(_queuerName);
 | 
			
		||||
        } set { _queuerName = value; } }
 | 
			
		||||
 | 
			
		||||
        public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
 | 
			
		||||
@@ -143,7 +144,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
        public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
 | 
			
		||||
        {
 | 
			
		||||
            BytesSent = (ulong) SkipTo * 3840 * 50;
 | 
			
		||||
            var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
 | 
			
		||||
            var filename = Path.Combine(MusicPlayer.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
 | 
			
		||||
 | 
			
		||||
            var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
 | 
			
		||||
            var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
 | 
			
		||||
@@ -6,7 +6,7 @@ using System.IO;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
 | 
			
		||||
@@ -1,15 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NLog;
 | 
			
		||||
using VideoLibrary;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public static class SongHandler
 | 
			
		||||
    {
 | 
			
		||||
@@ -49,6 +47,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
                        })
 | 
			
		||||
                        { TotalTime = TimeSpan.MaxValue };
 | 
			
		||||
                }
 | 
			
		||||
                var sc = SoundCloud.GetInstance(_creds);
 | 
			
		||||
                if (SoundCloud.Default.IsSoundCloudLink(query))
 | 
			
		||||
                {
 | 
			
		||||
                    var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false);
 | 
			
		||||
@@ -4,28 +4,34 @@ using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Services.Music
 | 
			
		||||
{
 | 
			
		||||
    public class SoundCloud
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly SoundCloud _instance = new SoundCloud();
 | 
			
		||||
        public static SoundCloud Default => _instance;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
        //todo make a service
 | 
			
		||||
        private static SoundCloud _instance = null;
 | 
			
		||||
        public static SoundCloud GetInstance(IBotCredentials creds) => _instance ?? (_instance = new SoundCloud(creds));
 | 
			
		||||
 | 
			
		||||
        static SoundCloud() { }
 | 
			
		||||
        public SoundCloud() { }
 | 
			
		||||
        public SoundCloud(IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(url))
 | 
			
		||||
                throw new ArgumentNullException(nameof(url));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId))
 | 
			
		||||
                throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId))
 | 
			
		||||
                throw new ArgumentNullException(nameof(_creds.SoundCloudClientId));
 | 
			
		||||
 | 
			
		||||
            string response = "";
 | 
			
		||||
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false);
 | 
			
		||||
                response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={_creds.SoundCloudClientId}").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
                
 | 
			
		||||
@@ -44,13 +50,13 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
                throw new ArgumentNullException(nameof(query));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId))
 | 
			
		||||
                throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId))
 | 
			
		||||
                throw new ArgumentNullException(nameof(_creds.SoundCloudClientId));
 | 
			
		||||
 | 
			
		||||
            var response = "";
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false);
 | 
			
		||||
                response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={_creds.SoundCloudClientId}").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault();
 | 
			
		||||
@@ -74,8 +80,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
        [JsonProperty("permalink_url")]
 | 
			
		||||
        public string TrackLink { get; set; } = "";
 | 
			
		||||
        public string artwork_url { get; set; } = "";
 | 
			
		||||
        [JsonIgnore]
 | 
			
		||||
        public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Credentials.SoundCloudClientId}";
 | 
			
		||||
        public string GetStreamLink(IBotCredentials creds) => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={creds.SoundCloudClientId}";
 | 
			
		||||
    }
 | 
			
		||||
    public class SoundCloudUser
 | 
			
		||||
    {
 | 
			
		||||
							
								
								
									
										68
									
								
								src/NadekoBot/Services/Searches/SearchesService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/NadekoBot/Services/Searches/SearchesService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class SearchesService
 | 
			
		||||
    {
 | 
			
		||||
        public async Task<string> DapiSearch(string tag, DapiSearchType type)
 | 
			
		||||
        {
 | 
			
		||||
            tag = tag?.Replace(" ", "_");
 | 
			
		||||
            var website = "";
 | 
			
		||||
            switch (type)
 | 
			
		||||
            {
 | 
			
		||||
                case DapiSearchType.Safebooru:
 | 
			
		||||
                    website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Gelbooru:
 | 
			
		||||
                    website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Rule34:
 | 
			
		||||
                    website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Konachan:
 | 
			
		||||
                    website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
                case DapiSearchType.Yandere:
 | 
			
		||||
                    website = $"https://yande.re/post.xml?limit=100&tags={tag}";
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var toReturn = await Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        http.AddFakeHeaders();
 | 
			
		||||
                        var data = await http.GetStreamAsync(website).ConfigureAwait(false);
 | 
			
		||||
                        var doc = new XmlDocument();
 | 
			
		||||
                        doc.Load(data);
 | 
			
		||||
 | 
			
		||||
                        var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)];
 | 
			
		||||
 | 
			
		||||
                        var url = node.Attributes["file_url"].Value;
 | 
			
		||||
                        if (!url.StartsWith("http"))
 | 
			
		||||
                            url = "https:" + url;
 | 
			
		||||
                        return url;
 | 
			
		||||
                    }
 | 
			
		||||
                }).ConfigureAwait(false);
 | 
			
		||||
                return toReturn;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum DapiSearchType
 | 
			
		||||
    {
 | 
			
		||||
        Safebooru,
 | 
			
		||||
        Gelbooru,
 | 
			
		||||
        Konachan,
 | 
			
		||||
        Rule34,
 | 
			
		||||
        Yandere
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/NadekoBot/Services/ServiceProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/NadekoBot/Services/ServiceProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface INServiceProvider : IServiceProvider
 | 
			
		||||
    {
 | 
			
		||||
        T GetService<T>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class NServiceProvider : INServiceProvider
 | 
			
		||||
    {
 | 
			
		||||
        public class ServiceProviderBuilder
 | 
			
		||||
        {
 | 
			
		||||
            private ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();
 | 
			
		||||
 | 
			
		||||
            public ServiceProviderBuilder Add<T>(T obj)
 | 
			
		||||
            {
 | 
			
		||||
                _dict.TryAdd(typeof(T), obj);
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public NServiceProvider Build()
 | 
			
		||||
            {
 | 
			
		||||
                return new NServiceProvider(_dict);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly ImmutableDictionary<Type, object> _services;
 | 
			
		||||
 | 
			
		||||
        private NServiceProvider() { }
 | 
			
		||||
        public NServiceProvider(IDictionary<Type, object> services)
 | 
			
		||||
        {
 | 
			
		||||
            this._services = services.ToImmutableDictionary();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public T GetService<T>()
 | 
			
		||||
        {
 | 
			
		||||
            return (T)((IServiceProvider)(this)).GetService(typeof(T));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        object IServiceProvider.GetService(Type serviceType)
 | 
			
		||||
        {
 | 
			
		||||
            _services.TryGetValue(serviceType, out var toReturn);
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Modules.CustomReactions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.TypeReaders
 | 
			
		||||
{
 | 
			
		||||
    public class CommandTypeReader : TypeReader
 | 
			
		||||
    {
 | 
			
		||||
        public override Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            var cmd = NadekoBot.CommandService.Commands.FirstOrDefault(c => 
 | 
			
		||||
                c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
 | 
			
		||||
            if (cmd == null)
 | 
			
		||||
                return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class CommandOrCrTypeReader : CommandTypeReader
 | 
			
		||||
    {
 | 
			
		||||
        public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
 | 
			
		||||
        {
 | 
			
		||||
            input = input.ToUpperInvariant();
 | 
			
		||||
            
 | 
			
		||||
            if (CustomReactions.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
 | 
			
		||||
            }
 | 
			
		||||
            var guild = context.Guild;
 | 
			
		||||
            if (guild != null)
 | 
			
		||||
            {
 | 
			
		||||
                CustomReaction[] crs;
 | 
			
		||||
                if (CustomReactions.GuildReactions.TryGetValue(guild.Id, out crs))
 | 
			
		||||
                {
 | 
			
		||||
                    if (crs.Any(x => x.Trigger.ToUpperInvariant() == input))
 | 
			
		||||
                    {
 | 
			
		||||
                        return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cmd = await base.Read(context, input);
 | 
			
		||||
            if (cmd.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Aliases.First()));
 | 
			
		||||
            }
 | 
			
		||||
            return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class CommandOrCrInfo
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        public CommandOrCrInfo(string input)
 | 
			
		||||
        {
 | 
			
		||||
            this.Name = input;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -18,8 +18,8 @@ namespace NadekoBot.Extensions
 | 
			
		||||
{
 | 
			
		||||
    public static class Extensions
 | 
			
		||||
    {
 | 
			
		||||
        private const string arrow_left = "⬅";
 | 
			
		||||
        private const string arrow_right = "➡";
 | 
			
		||||
        private static readonly IEmote arrow_left = Emote.Parse("⬅");
 | 
			
		||||
        private static readonly IEmote arrow_right = Emote.Parse("➡");
 | 
			
		||||
 | 
			
		||||
        public static string ToBase64(this string plainText)
 | 
			
		||||
        {
 | 
			
		||||
@@ -27,8 +27,8 @@ namespace NadekoBot.Extensions
 | 
			
		||||
            return Convert.ToBase64String(plainTextBytes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string RealSummary(this CommandInfo cmd) => string.Format(cmd.Summary, cmd.Module.GetTopLevelModule().Prefix);
 | 
			
		||||
        public static string RealRemarks(this CommandInfo cmd) => string.Format(cmd.Remarks, cmd.Module.GetTopLevelModule().Prefix);
 | 
			
		||||
        public static string RealSummary(this CommandInfo cmd) => string.Format(cmd.Summary, ".");
 | 
			
		||||
        public static string RealRemarks(this CommandInfo cmd) => string.Format(cmd.Remarks, ".");
 | 
			
		||||
 | 
			
		||||
        public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
 | 
			
		||||
        {
 | 
			
		||||
@@ -40,7 +40,7 @@ namespace NadekoBot.Extensions
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// danny kamisama
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true)
 | 
			
		||||
        public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true)
 | 
			
		||||
        {
 | 
			
		||||
            lastPage += 1;
 | 
			
		||||
            var embed = pageFunc(currentPage);
 | 
			
		||||
@@ -53,7 +53,8 @@ namespace NadekoBot.Extensions
 | 
			
		||||
            if (currentPage >= lastPage && lastPage == 1)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
 | 
			
		||||
            
 | 
			
		||||
            await msg.AddReactionAsync( arrow_left).ConfigureAwait(false);
 | 
			
		||||
            await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(2000).ConfigureAwait(false);
 | 
			
		||||
@@ -62,7 +63,7 @@ namespace NadekoBot.Extensions
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (r.Emoji.Name == arrow_left)
 | 
			
		||||
                    if (r.Emote.Name == arrow_left.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (currentPage == 1)
 | 
			
		||||
                            return;
 | 
			
		||||
@@ -71,7 +72,7 @@ namespace NadekoBot.Extensions
 | 
			
		||||
                            toSend.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
                        await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (r.Emoji.Name == arrow_right)
 | 
			
		||||
                    else if (r.Emote.Name == arrow_right.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (lastPage == null || lastPage > currentPage)
 | 
			
		||||
                        {
 | 
			
		||||
@@ -85,7 +86,7 @@ namespace NadekoBot.Extensions
 | 
			
		||||
                catch (Exception ex) { Console.WriteLine(ex); }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            using (msg.OnReaction(changePage, changePage))
 | 
			
		||||
            using (msg.OnReaction(client, changePage, changePage))
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(30000).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -101,12 +102,12 @@ namespace NadekoBot.Extensions
 | 
			
		||||
                return embed.WithFooter(efb => efb.WithText(curPage.ToString()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static ReactionEventWrapper OnReaction(this IUserMessage msg, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
 | 
			
		||||
        public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (reactionRemoved == null)
 | 
			
		||||
                reactionRemoved = delegate { };
 | 
			
		||||
 | 
			
		||||
            var wrap = new ReactionEventWrapper(msg);
 | 
			
		||||
            var wrap = new ReactionEventWrapper(client, msg);
 | 
			
		||||
            wrap.OnReactionAdded += reactionAdded;
 | 
			
		||||
            wrap.OnReactionRemoved += reactionRemoved;
 | 
			
		||||
            return wrap;
 | 
			
		||||
@@ -141,8 +142,6 @@ namespace NadekoBot.Extensions
 | 
			
		||||
            return msg;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetPrefix(this ModuleInfo module) => NadekoBot.ModulePrefixes[module.GetTopLevelModule().Name];
 | 
			
		||||
 | 
			
		||||
        public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
 | 
			
		||||
        {
 | 
			
		||||
            while (module.Parent != null)
 | 
			
		||||
@@ -221,9 +220,6 @@ namespace NadekoBot.Extensions
 | 
			
		||||
        public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) =>
 | 
			
		||||
            await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        public static bool IsAuthor(this IUserMessage msg) =>
 | 
			
		||||
            NadekoBot.Client.CurrentUser.Id == msg.Author.Id;
 | 
			
		||||
 | 
			
		||||
        public static IEnumerable<IUser> Members(this IRole role) =>
 | 
			
		||||
            role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IUser>();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,6 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
{
 | 
			
		||||
    public static class MusicExtensions
 | 
			
		||||
    {
 | 
			
		||||
		Reference in New Issue
	
	Block a user