diff --git a/src/NadekoBot/Attributes/NadekoModuleAttribute.cs b/src/NadekoBot/Attributes/NadekoModuleAttribute.cs index c5e21b26..fe09d3e5 100644 --- a/src/NadekoBot/Attributes/NadekoModuleAttribute.cs +++ b/src/NadekoBot/Attributes/NadekoModuleAttribute.cs @@ -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 modulePrefixes = null; - public static Dictionary 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; } } } diff --git a/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs b/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs index eefc489c..3791aefe 100644 --- a/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs +++ b/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs @@ -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 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 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"))); + } } } \ No newline at end of file diff --git a/src/NadekoBot/DataStructures/ExecuteCommandResult.cs b/src/NadekoBot/DataStructures/ExecuteCommandResult.cs index c8568c43..c7106f19 100644 --- a/src/NadekoBot/DataStructures/ExecuteCommandResult.cs +++ b/src/NadekoBot/DataStructures/ExecuteCommandResult.cs @@ -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 { diff --git a/src/NadekoBot/Services/NadekoRandom.cs b/src/NadekoBot/DataStructures/NadekoRandom.cs similarity index 100% rename from src/NadekoBot/Services/NadekoRandom.cs rename to src/NadekoBot/DataStructures/NadekoRandom.cs diff --git a/src/NadekoBot/Modules/Permissions/PermissionAction.cs b/src/NadekoBot/DataStructures/PermissionAction.cs similarity index 100% rename from src/NadekoBot/Modules/Permissions/PermissionAction.cs rename to src/NadekoBot/DataStructures/PermissionAction.cs diff --git a/src/NadekoBot/DataStructures/PermissionCache.cs b/src/NadekoBot/DataStructures/PermissionCache.cs new file mode 100644 index 00000000..9d2288bf --- /dev/null +++ b/src/NadekoBot/DataStructures/PermissionCache.cs @@ -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 Permissions { get; set; } + } +} diff --git a/src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs new file mode 100644 index 00000000..1e2ad01d --- /dev/null +++ b/src/NadekoBot/DataStructures/TypeReaders/BotCommandTypeReader.cs @@ -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 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 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; + } + } +} diff --git a/src/NadekoBot/TypeReaders/GuildTypeReader.cs b/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs similarity index 77% rename from src/NadekoBot/TypeReaders/GuildTypeReader.cs rename to src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs index a70eaa1e..63971f5a 100644 --- a/src/NadekoBot/TypeReaders/GuildTypeReader.cs +++ b/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs @@ -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 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 diff --git a/src/NadekoBot/TypeReaders/ModuleTypeReader.cs b/src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs similarity index 66% rename from src/NadekoBot/TypeReaders/ModuleTypeReader.cs rename to src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs index 10278060..88f953ad 100644 --- a/src/NadekoBot/TypeReaders/ModuleTypeReader.cs +++ b/src/NadekoBot/DataStructures/TypeReaders/ModuleTypeReader.cs @@ -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 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 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.")); diff --git a/src/NadekoBot/TypeReaders/PermissionActionTypeReader.cs b/src/NadekoBot/DataStructures/TypeReaders/PermissionActionTypeReader.cs similarity index 100% rename from src/NadekoBot/TypeReaders/PermissionActionTypeReader.cs rename to src/NadekoBot/DataStructures/TypeReaders/PermissionActionTypeReader.cs diff --git a/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs index 51d8ca7f..8a9f83eb 100644 --- a/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs +++ b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20161127233843_PokeGame.Designer.cs b/src/NadekoBot/Migrations/20161127233843_PokeGame.Designer.cs index 47f998c4..b1856225 100644 --- a/src/NadekoBot/Migrations/20161127233843_PokeGame.Designer.cs +++ b/src/NadekoBot/Migrations/20161127233843_PokeGame.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20161213025624_mutedusers.Designer.cs b/src/NadekoBot/Migrations/20161213025624_mutedusers.Designer.cs index af021dfc..f142b4fc 100644 --- a/src/NadekoBot/Migrations/20161213025624_mutedusers.Designer.cs +++ b/src/NadekoBot/Migrations/20161213025624_mutedusers.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20161224032833_logsettings.Designer.cs b/src/NadekoBot/Migrations/20161224032833_logsettings.Designer.cs index a2691bdb..6f288421 100644 --- a/src/NadekoBot/Migrations/20161224032833_logsettings.Designer.cs +++ b/src/NadekoBot/Migrations/20161224032833_logsettings.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170110111159_repeater-drop.Designer.cs b/src/NadekoBot/Migrations/20170110111159_repeater-drop.Designer.cs index db95f728..2a3fb271 100644 --- a/src/NadekoBot/Migrations/20170110111159_repeater-drop.Designer.cs +++ b/src/NadekoBot/Migrations/20170110111159_repeater-drop.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170110111302_repeater-new.Designer.cs b/src/NadekoBot/Migrations/20170110111302_repeater-new.Designer.cs index 09bee367..b8a72c04 100644 --- a/src/NadekoBot/Migrations/20170110111302_repeater-new.Designer.cs +++ b/src/NadekoBot/Migrations/20170110111302_repeater-new.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170110180534_protection.Designer.cs b/src/NadekoBot/Migrations/20170110180534_protection.Designer.cs index ecc134cd..f85c57c4 100644 --- a/src/NadekoBot/Migrations/20170110180534_protection.Designer.cs +++ b/src/NadekoBot/Migrations/20170110180534_protection.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170112185538_currency-modifications.Designer.cs b/src/NadekoBot/Migrations/20170112185538_currency-modifications.Designer.cs index 2f585147..6550cc1d 100644 --- a/src/NadekoBot/Migrations/20170112185538_currency-modifications.Designer.cs +++ b/src/NadekoBot/Migrations/20170112185538_currency-modifications.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs index b13f8275..8bfa7330 100644 --- a/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs +++ b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs index 46eebfab..0db0f6a2 100644 --- a/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs +++ b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170213164350_guild-timezone-and-locale.Designer.cs b/src/NadekoBot/Migrations/20170213164350_guild-timezone-and-locale.Designer.cs index 1dc832d9..e5515381 100644 --- a/src/NadekoBot/Migrations/20170213164350_guild-timezone-and-locale.Designer.cs +++ b/src/NadekoBot/Migrations/20170213164350_guild-timezone-and-locale.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170222162505_dateadded.Designer.cs b/src/NadekoBot/Migrations/20170222162505_dateadded.Designer.cs index 89102dc7..9ab4340d 100644 --- a/src/NadekoBot/Migrations/20170222162505_dateadded.Designer.cs +++ b/src/NadekoBot/Migrations/20170222162505_dateadded.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170308033058_permsv2.Designer.cs b/src/NadekoBot/Migrations/20170308033058_permsv2.Designer.cs index ea8eaee1..e0717137 100644 --- a/src/NadekoBot/Migrations/20170308033058_permsv2.Designer.cs +++ b/src/NadekoBot/Migrations/20170308033058_permsv2.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170310210952_unmute-timers.Designer.cs b/src/NadekoBot/Migrations/20170310210952_unmute-timers.Designer.cs index 96d944e6..34517ca8 100644 --- a/src/NadekoBot/Migrations/20170310210952_unmute-timers.Designer.cs +++ b/src/NadekoBot/Migrations/20170310210952_unmute-timers.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170311054632_vcrole.Designer.cs b/src/NadekoBot/Migrations/20170311054632_vcrole.Designer.cs index a4cd628d..99dcee74 100644 --- a/src/NadekoBot/Migrations/20170311054632_vcrole.Designer.cs +++ b/src/NadekoBot/Migrations/20170311054632_vcrole.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170318190018_crad-and-crdm.Designer.cs b/src/NadekoBot/Migrations/20170318190018_crad-and-crdm.Designer.cs index 1bfcd350..3b25cdab 100644 --- a/src/NadekoBot/Migrations/20170318190018_crad-and-crdm.Designer.cs +++ b/src/NadekoBot/Migrations/20170318190018_crad-and-crdm.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170320090138_command-aliasing.Designer.cs b/src/NadekoBot/Migrations/20170320090138_command-aliasing.Designer.cs index 0017cd76..7fe32ff1 100644 --- a/src/NadekoBot/Migrations/20170320090138_command-aliasing.Designer.cs +++ b/src/NadekoBot/Migrations/20170320090138_command-aliasing.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170330000613_warning-commands.Designer.cs b/src/NadekoBot/Migrations/20170330000613_warning-commands.Designer.cs index 28a99d1d..5cb6a428 100644 --- a/src/NadekoBot/Migrations/20170330000613_warning-commands.Designer.cs +++ b/src/NadekoBot/Migrations/20170330000613_warning-commands.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170331093025_startup-commands.Designer.cs b/src/NadekoBot/Migrations/20170331093025_startup-commands.Designer.cs index a91943d7..1ba8cc51 100644 --- a/src/NadekoBot/Migrations/20170331093025_startup-commands.Designer.cs +++ b/src/NadekoBot/Migrations/20170331093025_startup-commands.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170401161600_slowmode-whitelist.Designer.cs b/src/NadekoBot/Migrations/20170401161600_slowmode-whitelist.Designer.cs index 7409d096..a6cdb622 100644 --- a/src/NadekoBot/Migrations/20170401161600_slowmode-whitelist.Designer.cs +++ b/src/NadekoBot/Migrations/20170401161600_slowmode-whitelist.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170401205753_patreon-rewards.Designer.cs b/src/NadekoBot/Migrations/20170401205753_patreon-rewards.Designer.cs index e693300d..2c60d6c8 100644 --- a/src/NadekoBot/Migrations/20170401205753_patreon-rewards.Designer.cs +++ b/src/NadekoBot/Migrations/20170401205753_patreon-rewards.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170405161814_flower-shop.Designer.cs b/src/NadekoBot/Migrations/20170405161814_flower-shop.Designer.cs index 5acf9f8e..a9836330 100644 --- a/src/NadekoBot/Migrations/20170405161814_flower-shop.Designer.cs +++ b/src/NadekoBot/Migrations/20170405161814_flower-shop.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170408162851_game-voice-channel.Designer.cs b/src/NadekoBot/Migrations/20170408162851_game-voice-channel.Designer.cs index fa371c1d..ff8d5d8d 100644 --- a/src/NadekoBot/Migrations/20170408162851_game-voice-channel.Designer.cs +++ b/src/NadekoBot/Migrations/20170408162851_game-voice-channel.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170409193757_gmod-and-cmod.Designer.cs b/src/NadekoBot/Migrations/20170409193757_gmod-and-cmod.Designer.cs index fe9da269..98eb17ff 100644 --- a/src/NadekoBot/Migrations/20170409193757_gmod-and-cmod.Designer.cs +++ b/src/NadekoBot/Migrations/20170409193757_gmod-and-cmod.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/20170501103455_patreon-id.Designer.cs b/src/NadekoBot/Migrations/20170501103455_patreon-id.Designer.cs index 0309c44b..3ca47b2c 100644 --- a/src/NadekoBot/Migrations/20170501103455_patreon-id.Designer.cs +++ b/src/NadekoBot/Migrations/20170501103455_patreon-id.Designer.cs @@ -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 { diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 80a2fadb..78c14cc6 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -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 { diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index db7d0aae..9bca0a41 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -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() diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 614c1308..16834288 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -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 _autoHentaiTimers = new ConcurrentDictionary(); private static readonly ConcurrentHashSet _hentaiBombBlacklist = new ConcurrentHashSet(); + 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 GetRule34ImageLink(string tag) => - Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34); + public Task GetRule34ImageLink(string tag) => + _service.DapiSearch(tag, DapiSearchType.Rule34); - public static Task GetYandereImageLink(string tag) => - Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Yandere); + public Task GetYandereImageLink(string tag) => + _service.DapiSearch(tag, DapiSearchType.Yandere); - public static Task GetKonachanImageLink(string tag) => - Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Konachan); + public Task GetKonachanImageLink(string tag) => + _service.DapiSearch(tag, DapiSearchType.Konachan); - public static Task GetGelbooruImageLink(string tag) => - Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru); + public Task 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); diff --git a/src/NadekoBot/Modules/NadekoModule.cs b/src/NadekoBot/Modules/NadekoModule.cs index 400d1c21..6310f574 100644 --- a/src/NadekoBot/Modules/NadekoModule.cs +++ b/src/NadekoBot/Modules/NadekoModule.cs @@ -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); //} - - /// - /// Used as failsafe in case response key doesn't exist in the selected or default language. - /// - 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 ErrorLocalized(string textKey, params object[] replacements) { diff --git a/src/NadekoBot/Modules/Permissions/Permissions.cs b/src/NadekoBot/Modules/Permissions/Permissions.cs index e9f92c66..d51218ba 100644 --- a/src/NadekoBot/Modules/Permissions/Permissions.cs +++ b/src/NadekoBot/Modules/Permissions/Permissions.cs @@ -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 Permissions { get; set; } - } - //guildid, root permission public static ConcurrentDictionary Cache { get; } = new ConcurrentDictionary(); diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 7bfd8368..1c78855f 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -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 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 ValidateQuery(IMessageChannel ch, string query) { if (!string.IsNullOrWhiteSpace(query)) return true; diff --git a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs b/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs index 11eb5017..a50bf026 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs @@ -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 - { - 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> AliasMaps { get; } = new ConcurrentDictionary>(); + 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>( - NadekoBot.AllGuildConfigs.ToDictionary( - x => x.GuildId, - x => new ConcurrentDictionary(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 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 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")) diff --git a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs index 0f1a78b5..5e198107 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs +++ b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs @@ -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> Subscribers = - new ConcurrentDictionary>(); - [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(); - 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 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); } diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 92abd1f3..f59cc49b 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -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())); + } } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs index 4b05391d..6e0007ce 100644 --- a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs +++ b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs @@ -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> 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>(NadekoBot.AllGuildConfigs - .ToDictionary(gc => gc.GuildId, - gc => new ConcurrentQueue(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 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 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(repeaterList), rep)) + if (_service.Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue(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(new[] {rep}), (key, old) => + _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue(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 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; diff --git a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs b/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs index 9db51b46..96becc9f 100644 --- a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs @@ -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(); 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; diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs index a11ad7c9..913f3906 100644 --- a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs @@ -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 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()); diff --git a/src/NadekoBot/Modules/Utility/Commands/Remind.cs b/src/NadekoBot/Modules/Utility/Commands/Remind.cs index 4ffd9469..eafd5f39 100644 --- a/src/NadekoBot/Modules/Utility/Commands/Remind.cs +++ b/src/NadekoBot/Modules/Utility/Commands/Remind.cs @@ -19,89 +19,13 @@ namespace NadekoBot.Modules.Utility [Group] public class RemindCommands : NadekoSubmodule { - readonly Regex _regex = new Regex(@"^(?:(?\d)mo)?(?:(?\d)w)?(?:(?\d{1,2})d)?(?:(?\d{1,2})h)?(?:(?\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> _replacements = new Dictionary> + 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 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(); - 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); diff --git a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs index 87283de3..ea16953f 100644 --- a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs +++ b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs @@ -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 Units { get; set; } = new List(); - 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>(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 UpdateCurrencyRates() - { - using (var http = new HttpClient()) - { - var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false); - return JsonConvert.DeserializeObject(res); - } - } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs b/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs deleted file mode 100644 index 017ea8b4..00000000 --- a/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace NadekoBot.Modules.Utility.Commands.Models -{ - public class MeasurementUnit - { - public List Triggers { get; set; } - public string UnitType { get; set; } - public decimal Modifier { get; set; } - } -} diff --git a/src/NadekoBot/Modules/Utility/Models/Rates.cs b/src/NadekoBot/Modules/Utility/Models/Rates.cs deleted file mode 100644 index b460a3c5..00000000 --- a/src/NadekoBot/Modules/Utility/Models/Rates.cs +++ /dev/null @@ -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 ConversionRates { get; set; } - } -} diff --git a/src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs b/src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs new file mode 100644 index 00000000..c9d7ad15 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs @@ -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)}"; + } +} diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index af782973..928c304f 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -20,15 +20,23 @@ using System.Diagnostics; namespace NadekoBot.Modules.Utility { - [NadekoModule("Utility", ".")] + [NadekoModule("Utility")] public partial class Utility : NadekoTopLevelModule { private static ConcurrentDictionary _rotatingRoleColors = new ConcurrentDictionary(); + 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()) { diff --git a/src/NadekoBot/Modules/Utility/UtilityService.cs b/src/NadekoBot/Modules/Utility/UtilityService.cs new file mode 100644 index 00000000..5bb6baa1 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/UtilityService.cs @@ -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> AliasMaps { get; } = new ConcurrentDictionary>(); + + //messagerepeater + //guildid/RepeatRunners + public ConcurrentDictionary> Repeaters { get; set; } + public bool RepeaterReady { get; private set; } + + //remind + public RemindService Remind { get; } + + //unit conversion + public ConverterService Converter { get; } + + public UtilityService(IEnumerable guildConfigs, DiscordShardedClient client, BotConfig config, DbHandler db) + { + //commandmap + AliasMaps = new ConcurrentDictionary>( + guildConfigs.ToDictionary( + x => x.GuildId, + x => new ConcurrentDictionary(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>(guildConfigs + .ToDictionary(gc => gc.GuildId, + gc => new ConcurrentQueue(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> Subscribers = + new ConcurrentDictionary>(); + private DiscordShardedClient _client; + } + + public class ConverterService + { + public class MeasurementUnit + { + public List 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 ConversionRates { get; set; } + } + + public List Units { get; set; } = new List(); + 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>(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 UpdateCurrencyRates() + { + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false); + return JsonConvert.DeserializeObject(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(@"^(?:(?\d)mo)?(?:(?\d)w)?(?:(?\d{1,2})d)?(?:(?\d{1,2})h)?(?:(?\d{1,2})m)?$", + RegexOptions.Compiled | RegexOptions.Multiline); + + public string RemindMessageFormat { get; } + + public readonly IDictionary> _replacements = new Dictionary> + { + { "%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 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 + { + public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger; + + public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode(); + } +} diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 8e9f9aa3..52b491d5 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -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 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 ModulePrefixes { get; private set; } - public static bool Ready { get; private set; } - - public static ImmutableArray 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(localization) + .Add(stats) + .Add(images) + .Add(google) + .Add(stats) + .Add(credentials) + .Add(commandService) + .Add(strings) + .Add(Client) + .Add(greetSettingsService) + //.Add(musicService) + .Add(commandHandler) + .Add(db) + //modules + .Add(utilityService) + .Add(searchesService) + .Build(); + + commandHandler.AddServices(Services); + + //setup typereaders + commandService.AddTypeReader(new PermissionActionTypeReader()); + commandService.AddTypeReader(new CommandTypeReader(commandService)); + //commandService.AddTypeReader(new CommandOrCrTypeReader()); + commandService.AddTypeReader(new ModuleTypeReader(commandService)); + commandService.AddTypeReader(new ModuleOrCrTypeReader(commandService)); + commandService.AddTypeReader(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(Localizer); - //depMap.Add(Client); - //depMap.Add(CommandService); - //depMap.Add(Google); - - - //setup typereaders - CommandService.AddTypeReader(new PermissionActionTypeReader()); - CommandService.AddTypeReader(new CommandTypeReader()); - CommandService.AddTypeReader(new CommandOrCrTypeReader()); - CommandService.AddTypeReader(new ModuleTypeReader()); - CommandService.AddTypeReader(new ModuleOrCrTypeReader()); - CommandService.AddTypeReader(new GuildTypeReader()); + public async Task RunAsync(params string[] args) + { + var creds = Services.GetService(); + var stats = Services.GetService(); + var commandHandler = Services.GetService(); + var commandService = Services.GetService(); + _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(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().ConfigureAwait(false); + //todo uncomment this + //await commandService.AddModuleAsync().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) diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 2d574796..b829edd5 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -27,7 +27,37 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest @@ -44,7 +74,7 @@ - + @@ -58,10 +88,10 @@ - - + + - + diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 12125a20..94571167 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -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> ownerChannels { get; set; } = new ImmutableArray>(); @@ -46,10 +42,13 @@ namespace NadekoBot.Services public ConcurrentHashSet UsersOnShortCooldown { get; } = new ConcurrentHashSet(); 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(NadekoBot.Credentials.OwnerIds); + var hs = new HashSet(_creds.OwnerIds); var channels = new Dictionary>(); 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 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 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 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 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 WordFiltered(IGuild guild, IUserMessage usrMsg) - { - var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet(); - var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet(); - 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 WordFiltered(IGuild guild, IUserMessage usrMsg) + //{ + // var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet(); + // var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet(); + // 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 maps)) - { + ////todo alias mapping + // if (guild != null) + // { + // if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary 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 ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); + public Task ExecuteCommandAsync(CommandContext context, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => ExecuteCommand(context, context.Message.Content.Substring(argPos), serviceProvider, multiMatchHandling); - public async Task ExecuteCommand(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public async Task 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.")); diff --git a/src/NadekoBot/Services/CurrencyHandler.cs b/src/NadekoBot/Services/CurrencyHandler.cs index 7e2939e4..27fc98ee 100644 --- a/src/NadekoBot/Services/CurrencyHandler.cs +++ b/src/NadekoBot/Services/CurrencyHandler.cs @@ -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 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 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 RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null) + public async Task 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); diff --git a/src/NadekoBot/Services/Database/Models/PlaylistSong.cs b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs index 06cbb919..d1b09f9d 100644 --- a/src/NadekoBot/Services/Database/Models/PlaylistSong.cs +++ b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs @@ -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 + } } diff --git a/src/NadekoBot/Services/DbHandler.cs b/src/NadekoBot/Services/DbHandler.cs index 9a4ab212..2cec74f3 100644 --- a/src/NadekoBot/Services/DbHandler.cs +++ b/src/NadekoBot/Services/DbHandler.cs @@ -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(); } } \ No newline at end of file diff --git a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs index 3fba8864..52c9668f 100644 --- a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs +++ b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs @@ -15,13 +15,14 @@ namespace NadekoBot.Services.Discord public event Action 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 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) diff --git a/src/NadekoBot/Services/GreetSettingsService.cs b/src/NadekoBot/Services/GreetSettingsService.cs index 253a4a14..ba866433 100644 --- a/src/NadekoBot/Services/GreetSettingsService.cs +++ b/src/NadekoBot/Services/GreetSettingsService.cs @@ -12,11 +12,14 @@ namespace NadekoBot.Services { public class GreetSettingsService { - public ConcurrentDictionary GuildConfigsCache { get; } + private readonly DbHandler _db; - public GreetSettingsService() + public readonly ConcurrentDictionary GuildConfigsCache; + + public GreetSettingsService(IEnumerable guildConfigs, DbHandler db) { - GuildConfigsCache = new ConcurrentDictionary(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create)); + _db = db; + GuildConfigsCache = new ConcurrentDictionary(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 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 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 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; diff --git a/src/NadekoBot/Services/IBotCredentials.cs b/src/NadekoBot/Services/IBotCredentials.cs index 0a89b471..82299319 100644 --- a/src/NadekoBot/Services/IBotCredentials.cs +++ b/src/NadekoBot/Services/IBotCredentials.cs @@ -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); } diff --git a/src/NadekoBot/Services/IImagesService.cs b/src/NadekoBot/Services/IImagesService.cs index 3b635ca7..0398c574 100644 --- a/src/NadekoBot/Services/IImagesService.cs +++ b/src/NadekoBot/Services/IImagesService.cs @@ -24,6 +24,6 @@ namespace NadekoBot.Services ImmutableArray WifeMatrix { get; } ImmutableArray RategirlDot { get; } - Task Reload(); + TimeSpan Reload(); } } diff --git a/src/NadekoBot/Services/ILocalization.cs b/src/NadekoBot/Services/ILocalization.cs new file mode 100644 index 00000000..b9fa898d --- /dev/null +++ b/src/NadekoBot/Services/ILocalization.cs @@ -0,0 +1,21 @@ +using System.Collections.Concurrent; +using System.Globalization; +using Discord; + +namespace NadekoBot.Services +{ + public interface ILocalization + { + CultureInfo DefaultCultureInfo { get; } + ConcurrentDictionary 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); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/IStatsService.cs b/src/NadekoBot/Services/IStatsService.cs index 5634790d..a7735fc8 100644 --- a/src/NadekoBot/Services/IStatsService.cs +++ b/src/NadekoBot/Services/IStatsService.cs @@ -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 Print(); } } diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index 187bf272..c4afec9f 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -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\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); + private readonly IBotCredentials _creds; public async Task> 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 diff --git a/src/NadekoBot/Services/Impl/ImagesService.cs b/src/NadekoBot/Services/Impl/ImagesService.cs index 97e1c129..734178eb 100644 --- a/src/NadekoBot/Services/Impl/ImagesService.cs +++ b/src/NadekoBot/Services/Impl/ImagesService.cs @@ -44,19 +44,13 @@ namespace NadekoBot.Services.Impl public ImmutableArray WifeMatrix { get; private set; } public ImmutableArray RategirlDot { get; private set; } - private ImagesService() + public ImagesService() { _log = LogManager.GetCurrentClassLogger(); + this.Reload(); } - public static async Task Create() - { - var srvc = new ImagesService(); - await srvc.Reload().ConfigureAwait(false); - return srvc; - } - - public Task Reload() => Task.Run(() => + public TimeSpan Reload() { try { @@ -101,6 +95,6 @@ namespace NadekoBot.Services.Impl _log.Error(ex); throw; } - }); + } } } \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs index 53718ebf..8f29adb5 100644 --- a/src/NadekoBot/Services/Impl/Localization.cs +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -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 GuildCultureInfos { get; } public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; private Localization() { } - public Localization(string defaultCulture, IDictionary cultureInfoNames) + public Localization(string defaultCulture, IDictionary 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; diff --git a/src/NadekoBot/Services/NadekoStrings.cs b/src/NadekoBot/Services/Impl/NadekoStrings.cs similarity index 50% rename from src/NadekoBot/Services/NadekoStrings.cs rename to src/NadekoBot/Services/Impl/NadekoStrings.cs index 5bad0214..9eaede58 100644 --- a/src/NadekoBot/Services/NadekoStrings.cs +++ b/src/NadekoBot/Services/Impl/NadekoStrings.cs @@ -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> responseStrings; private readonly Logger _log; + /// + /// Used as failsafe in case response key doesn't exist in the selected or default language. + /// + 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 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."; + } + } } } diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index c99afb2f..adfb34ba 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -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 { { "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} diff --git a/src/NadekoBot/Modules/Music/Classes/Exceptions.cs b/src/NadekoBot/Services/Music/Exceptions.cs similarity index 91% rename from src/NadekoBot/Modules/Music/Classes/Exceptions.cs rename to src/NadekoBot/Services/Music/Exceptions.cs index 3dd355f9..1dbe8ad7 100644 --- a/src/NadekoBot/Modules/Music/Classes/Exceptions.cs +++ b/src/NadekoBot/Services/Music/Exceptions.cs @@ -1,6 +1,6 @@ using System; -namespace NadekoBot.Modules.Music.Classes +namespace NadekoBot.Services.Music { class PlaylistFullException : Exception { diff --git a/src/NadekoBot/Services/Music/MusicControls.cs b/src/NadekoBot/Services/Music/MusicControls.cs index 9eb0abc1..2687e99b 100644 --- a/src/NadekoBot/Services/Music/MusicControls.cs +++ b/src/NadekoBot/Services/Music/MusicControls.cs @@ -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; } /// @@ -58,6 +54,7 @@ namespace NadekoBot.Modules.Music.Classes private readonly List _playlist = new List(); private readonly Logger _log; + private readonly IGoogleApiService _google; public IReadOnlyCollection Playlist => _playlist; @@ -88,9 +85,10 @@ namespace NadekoBot.Modules.Music.Classes public event Action 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() diff --git a/src/NadekoBot/Services/Music/MusicService.cs b/src/NadekoBot/Services/Music/MusicService.cs index 8913def1..3c85607b 100644 --- a/src/NadekoBot/Services/Music/MusicService.cs +++ b/src/NadekoBot/Services/Music/MusicService.cs @@ -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 MusicPlayers { get; } = new ConcurrentDictionary(); - 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) { diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Services/Music/Song.cs similarity index 98% rename from src/NadekoBot/Modules/Music/Classes/Song.cs rename to src/NadekoBot/Services/Music/Song.cs index b7c16266..77394349 100644 --- a/src/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Services/Music/Song.cs @@ -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); diff --git a/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs b/src/NadekoBot/Services/Music/SongBuffer.cs similarity index 99% rename from src/NadekoBot/Modules/Music/Classes/SongBuffer.cs rename to src/NadekoBot/Services/Music/SongBuffer.cs index 87c01ff6..ff3a0ed2 100644 --- a/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs +++ b/src/NadekoBot/Services/Music/SongBuffer.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Music.Classes +namespace NadekoBot.Services.Music { /// /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. diff --git a/src/NadekoBot/Modules/Music/Classes/SongHandler.cs b/src/NadekoBot/Services/Music/SongHandler.cs similarity index 98% rename from src/NadekoBot/Modules/Music/Classes/SongHandler.cs rename to src/NadekoBot/Services/Music/SongHandler.cs index e374279f..9ef4aae0 100644 --- a/src/NadekoBot/Modules/Music/Classes/SongHandler.cs +++ b/src/NadekoBot/Services/Music/SongHandler.cs @@ -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); diff --git a/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/src/NadekoBot/Services/Music/SoundCloud.cs similarity index 80% rename from src/NadekoBot/Modules/Music/Classes/SoundCloud.cs rename to src/NadekoBot/Services/Music/SoundCloud.cs index 98399363..9d18dc2f 100644 --- a/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/src/NadekoBot/Services/Music/SoundCloud.cs @@ -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 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(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 { diff --git a/src/NadekoBot/Services/Searches/SearchesService.cs b/src/NadekoBot/Services/Searches/SearchesService.cs new file mode 100644 index 00000000..a65d5120 --- /dev/null +++ b/src/NadekoBot/Services/Searches/SearchesService.cs @@ -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 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 + } +} diff --git a/src/NadekoBot/Services/ServiceProvider.cs b/src/NadekoBot/Services/ServiceProvider.cs new file mode 100644 index 00000000..b90be9aa --- /dev/null +++ b/src/NadekoBot/Services/ServiceProvider.cs @@ -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(); + } + + public class NServiceProvider : INServiceProvider + { + public class ServiceProviderBuilder + { + private ConcurrentDictionary _dict = new ConcurrentDictionary(); + + public ServiceProviderBuilder Add(T obj) + { + _dict.TryAdd(typeof(T), obj); + return this; + } + + public NServiceProvider Build() + { + return new NServiceProvider(_dict); + } + } + + private readonly ImmutableDictionary _services; + + private NServiceProvider() { } + public NServiceProvider(IDictionary services) + { + this._services = services.ToImmutableDictionary(); + } + + public T GetService() + { + return (T)((IServiceProvider)(this)).GetService(typeof(T)); + } + + object IServiceProvider.GetService(Type serviceType) + { + _services.TryGetValue(serviceType, out var toReturn); + return toReturn; + } + } +} diff --git a/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs deleted file mode 100644 index 0699ae43..00000000 --- a/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs +++ /dev/null @@ -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 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 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; - } - } -} diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index bc72a25a..1b4519b8 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -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 bytes, bool canWrite = false) { @@ -40,7 +40,7 @@ namespace NadekoBot.Extensions /// /// danny kamisama /// - public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) + public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func 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 reactionAdded, Action reactionRemoved = null) + public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action reactionAdded, Action 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 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 Members(this IRole role) => role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty(); diff --git a/src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs b/src/NadekoBot/_Extensions/MusicExtensions.cs similarity index 58% rename from src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs rename to src/NadekoBot/_Extensions/MusicExtensions.cs index 2840fd16..0a3409b9 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs +++ b/src/NadekoBot/_Extensions/MusicExtensions.cs @@ -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 {