Utility and nsfw work

This commit is contained in:
Master Kwoth 2017-05-23 01:59:31 +02:00
parent d08bc60be5
commit 2df415341c
83 changed files with 1495 additions and 1308 deletions

View File

@ -1,46 +1,13 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Services;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Attributes namespace NadekoBot.Attributes
{ {
[System.AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
sealed class NadekoModuleAttribute : GroupAttribute sealed class NadekoModuleAttribute : GroupAttribute
{ {
//modulename / prefix public NadekoModuleAttribute(string moduleName) : base("")
private static Dictionary<string, string> modulePrefixes = null;
public static Dictionary<string, string> ModulePrefixes {
get {
if (modulePrefixes != null)
return modulePrefixes;
using (var uow = DbHandler.UnitOfWork())
{ {
return (modulePrefixes = uow.BotConfig
.GetOrCreate()
.ModulePrefixes
.ToDictionary(p => p.ModuleName, p => p.Prefix));
}
}
}
public NadekoModuleAttribute(string moduleName, string defaultPrefix) : base(GetModulePrefix(moduleName, defaultPrefix), moduleName)
{
//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;
} }
} }
} }

View File

@ -1,11 +1,20 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using System;
using NadekoBot.Services.Impl;
using Discord;
using NadekoBot.Services;
namespace NadekoBot.Attributes namespace NadekoBot.Attributes
{ {
public class OwnerOnlyAttribute : PreconditionAttribute public class OwnerOnlyAttribute : PreconditionAttribute
{ {
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand,IDependencyMap depMap) => public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
Task.FromResult((NadekoBot.Credentials.IsOwner(context.User) || NadekoBot.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner"))); {
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")));
}
} }
} }

View File

@ -1,10 +1,4 @@
using Discord.Commands; 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 namespace NadekoBot.DataStructures
{ {

View File

@ -0,0 +1,23 @@
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class OldPermissionCache
{
public string PermRole { get; set; }
public bool Verbose { get; set; } = true;
public Permission RootPermission { get; set; }
}
public class PermissionCache
{
public string PermRole { get; set; }
public bool Verbose { get; set; } = true;
public PermissionsCollection<Permissionv2> Permissions { get; set; }
}
}

View File

@ -0,0 +1,68 @@
using Discord.Commands;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.TypeReaders
{
public class CommandTypeReader : TypeReader
{
private readonly CommandService _cmds;
public CommandTypeReader(CommandService cmds)
{
_cmds = cmds;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{
input = input.ToUpperInvariant();
var cmd = _cmds.Commands.FirstOrDefault(c =>
c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
if (cmd == null)
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
}
}
//public class CommandOrCrTypeReader : CommandTypeReader
//{
// public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
// {
// input = input.ToUpperInvariant();
// if (CustomReactions.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// }
// var guild = context.Guild;
// if (guild != null)
// {
// CustomReaction[] crs;
// if (CustomReactions.GuildReactions.TryGetValue(guild.Id, out crs))
// {
// if (crs.Any(x => x.Trigger.ToUpperInvariant() == input))
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// }
// }
// }
// var cmd = await base.Read(context, input);
// if (cmd.IsSuccess)
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Aliases.First()));
// }
// return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
// }
//}
public class CommandOrCrInfo
{
public string Name { get; set; }
public CommandOrCrInfo(string input)
{
this.Name = input;
}
}
}

View File

@ -1,4 +1,5 @@
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,10 +7,16 @@ namespace NadekoBot.TypeReaders
{ {
public class GuildTypeReader : TypeReader public class GuildTypeReader : TypeReader
{ {
private readonly DiscordShardedClient _client;
public GuildTypeReader(DiscordShardedClient client)
{
_client = client;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input) public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{ {
input = input.Trim().ToLowerInvariant(); 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 var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToLowerInvariant() == input) ?? //by id
guilds.FirstOrDefault(g => g.Name.Trim().ToLowerInvariant() == input); //by name guilds.FirstOrDefault(g => g.Name.Trim().ToLowerInvariant() == input); //by name

View File

@ -1,5 +1,6 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,10 +8,17 @@ namespace NadekoBot.TypeReaders
{ {
public class ModuleTypeReader : TypeReader public class ModuleTypeReader : TypeReader
{ {
private readonly CommandService _cmds;
public ModuleTypeReader(CommandService cmds)
{
_cmds = cmds;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input) public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{ {
input = input.ToUpperInvariant(); 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) if (module == null)
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found.")); return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
@ -20,10 +28,17 @@ namespace NadekoBot.TypeReaders
public class ModuleOrCrTypeReader : TypeReader public class ModuleOrCrTypeReader : TypeReader
{ {
private readonly CommandService _cmds;
public ModuleOrCrTypeReader(CommandService cmds)
{
_cmds = cmds;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input) public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{ {
input = input.ToLowerInvariant(); 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") if (module == null && input != "actualcustomreactions")
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found.")); return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -2,10 +2,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Music.Classes;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {

View File

@ -23,8 +23,6 @@ namespace NadekoBot.Modules.Music
[DontAutoLoad] [DontAutoLoad]
public class Music : NadekoTopLevelModule public class Music : NadekoTopLevelModule
{ {
public const string MusicDataPath = "data/musicdata";
private static MusicService music; private static MusicService music;
static Music() static Music()

View File

@ -11,15 +11,21 @@ using NadekoBot.Extensions;
using System.Xml; using System.Xml;
using System.Threading; using System.Threading;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.NSFW namespace NadekoBot.Modules.NSFW
{ {
[NadekoModule("NSFW", "~")] [NadekoModule("NSFW")]
public class NSFW : NadekoTopLevelModule public class NSFW : NadekoTopLevelModule
{ {
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>(); private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>(); private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private readonly SearchesService _service;
public NSFW(SearchesService service)
{
_service = service;
}
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
{ {
@ -142,11 +148,11 @@ namespace NadekoBot.Modules.NSFW
#endif #endif
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public Task Yandere([Remainder] string tag = null) public Task Yandere([Remainder] string tag = null)
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Yandere); => InternalDapiCommand(tag, DapiSearchType.Yandere);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public Task Konachan([Remainder] string tag = null) public Task Konachan([Remainder] string tag = null)
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Konachan); => InternalDapiCommand(tag, DapiSearchType.Konachan);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task E621([Remainder] string tag = null) public async Task E621([Remainder] string tag = null)
@ -167,7 +173,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public Task Rule34([Remainder] string tag = null) public Task Rule34([Remainder] string tag = null)
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Rule34); => InternalDapiCommand(tag, DapiSearchType.Rule34);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null) public async Task Danbooru([Remainder] string tag = null)
@ -210,7 +216,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public Task Gelbooru([Remainder] string tag = null) public Task Gelbooru([Remainder] string tag = null)
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Gelbooru); => InternalDapiCommand(tag, DapiSearchType.Gelbooru);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Boobs() public async Task Boobs()
@ -270,23 +276,23 @@ namespace NadekoBot.Modules.NSFW
} }
}); });
public static Task<string> GetRule34ImageLink(string tag) => public Task<string> GetRule34ImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34); _service.DapiSearch(tag, DapiSearchType.Rule34);
public static Task<string> GetYandereImageLink(string tag) => public Task<string> GetYandereImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Yandere); _service.DapiSearch(tag, DapiSearchType.Yandere);
public static Task<string> GetKonachanImageLink(string tag) => public Task<string> GetKonachanImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Konachan); _service.DapiSearch(tag, DapiSearchType.Konachan);
public static Task<string> GetGelbooruImageLink(string tag) => public Task<string> GetGelbooruImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru); _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() ?? ""; 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) if (url == null)
await ReplyErrorLocalized("not_found").ConfigureAwait(false); await ReplyErrorLocalized("not_found").ConfigureAwait(false);

View File

@ -1,6 +1,7 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog; using NLog;
using System; using System;
using System.Globalization; using System.Globalization;
@ -16,20 +17,23 @@ namespace NadekoBot.Modules
public readonly string ModuleTypeName; public readonly string ModuleTypeName;
public readonly string LowerModuleTypeName; public readonly string LowerModuleTypeName;
//todo :thinking:
public NadekoStrings _strings { get; set; }
public ILocalization _localization { get; set; }
protected NadekoTopLevelModule(bool isTopLevelModule = true) protected NadekoTopLevelModule(bool isTopLevelModule = true)
{ {
//if it's top level module //if it's top level module
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name; ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
LowerModuleTypeName = ModuleTypeName.ToLowerInvariant(); LowerModuleTypeName = ModuleTypeName.ToLowerInvariant();
Prefix = NadekoBot.Prefix;
if (!NadekoBot.ModulePrefixes.TryGetValue(ModuleTypeName, out Prefix))
Prefix = "?err?";
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
} }
protected override void BeforeExecute() protected override void BeforeExecute()
{ {
_cultureInfo = NadekoBot.Localization.GetCultureInfo(Context.Guild?.Id); _cultureInfo =_localization.GetCultureInfo(Context.Guild?.Id);
_log.Info("Culture info is {0}", _cultureInfo); _log.Info("Culture info is {0}", _cultureInfo);
} }
@ -54,45 +58,11 @@ namespace NadekoBot.Modules
// return Context.Channel.SendErrorAsync(title, text, url, footer); // return Context.Channel.SendErrorAsync(title, text, url, footer);
//} //}
/// <summary>
/// Used as failsafe in case response key doesn't exist in the selected or default language.
/// </summary>
private static readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
{
var text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, cultureInfo);
if (string.IsNullOrWhiteSpace(text))
{
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
if (string.IsNullOrWhiteSpace(text))
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
}
return text;
}
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
params object[] replacements)
{
try
{
return string.Format(GetTextStatic(key, cultureInfo, lowerModuleTypeName), replacements);
}
catch (FormatException)
{
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
}
}
protected string GetText(string key) => protected string GetText(string key) =>
GetTextStatic(key, _cultureInfo, LowerModuleTypeName); _strings.GetText(key, _cultureInfo, LowerModuleTypeName);
protected string GetText(string key, params object[] replacements) => protected string GetText(string key, params object[] replacements) =>
GetTextStatic(key, _cultureInfo, LowerModuleTypeName, replacements); _strings.GetText(key, _cultureInfo, LowerModuleTypeName, replacements);
public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements) public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements)
{ {

View File

@ -20,20 +20,6 @@ namespace NadekoBot.Modules.Permissions
[NadekoModule("Permissions", ";")] [NadekoModule("Permissions", ";")]
public partial class Permissions : NadekoTopLevelModule public partial class Permissions : NadekoTopLevelModule
{ {
public class OldPermissionCache
{
public string PermRole { get; set; }
public bool Verbose { get; set; } = true;
public Permission RootPermission { get; set; }
}
public class PermissionCache
{
public string PermRole { get; set; }
public bool Verbose { get; set; } = true;
public PermissionsCollection<Permissionv2> Permissions { get; set; }
}
//guildid, root permission //guildid, root permission
public static ConcurrentDictionary<ulong, PermissionCache> Cache { get; } = public static ConcurrentDictionary<ulong, PermissionCache> Cache { get; } =
new ConcurrentDictionary<ulong, PermissionCache>(); new ConcurrentDictionary<ulong, PermissionCache>();

View File

@ -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) public async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
{ {
@ -787,55 +779,6 @@ namespace NadekoBot.Modules.Searches
.WithImageUrl(url) .WithImageUrl(url)
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false); .WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
} }
public static async Task<string> InternalDapiSearch(string tag, DapiSearchType type)
{
tag = tag?.Replace(" ", "_");
var website = "";
switch (type)
{
case DapiSearchType.Safebooru:
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Gelbooru:
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Rule34:
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Konachan:
website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Yandere:
website = $"https://yande.re/post.xml?limit=100&tags={tag}";
break;
}
try
{
var toReturn = await Task.Run(async () =>
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync(website).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)];
var url = node.Attributes["file_url"].Value;
if (!url.StartsWith("http"))
url = "https:" + url;
return url;
}
}).ConfigureAwait(false);
return toReturn;
}
catch
{
return null;
}
}
public async Task<bool> ValidateQuery(IMessageChannel ch, string query) public async Task<bool> ValidateQuery(IMessageChannel ch, string query)
{ {
if (!string.IsNullOrWhiteSpace(query)) return true; if (!string.IsNullOrWhiteSpace(query)) return true;

View File

@ -1,48 +1,32 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
public partial class Utility public partial class Utility
{ {
public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
{
public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
}
[Group] [Group]
public class CommandMapCommands : NadekoSubmodule public class CommandMapCommands : NadekoSubmodule
{ {
//guildId, (trigger, mapping) private readonly UtilityService _service;
public static ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(); private readonly DbHandler _db;
private readonly DiscordShardedClient _client;
static CommandMapCommands() public CommandMapCommands(UtilityService service, DbHandler db, DiscordShardedClient client)
{ {
var eq = new CommandAliasEqualityComparer(); _service = service;
AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>( _db = db;
NadekoBot.AllGuildConfigs.ToDictionary( _client = client;
x => x.GuildId,
x => new ConcurrentDictionary<string, string>(x.CommandAliases
.Distinct(eq)
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
}
public static void Unload()
{
AliasMaps.Clear();
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -61,14 +45,14 @@ namespace NadekoBot.Modules.Utility
{ {
ConcurrentDictionary<string, string> maps; ConcurrentDictionary<string, string> maps;
string throwaway; string throwaway;
if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) || if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) ||
!maps.TryRemove(trigger, out throwaway)) !maps.TryRemove(trigger, out throwaway))
{ {
await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false); await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false);
return; 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 config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
var toAdd = new CommandAlias() var toAdd = new CommandAlias()
@ -83,9 +67,9 @@ namespace NadekoBot.Modules.Utility
await ReplyConfirmLocalized("alias_removed", Format.Code(trigger)).ConfigureAwait(false); await ReplyConfirmLocalized("alias_removed", Format.Code(trigger)).ConfigureAwait(false);
return; 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)); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
config.CommandAliases.Add(new CommandAlias() config.CommandAliases.Add(new CommandAlias()
@ -100,7 +84,7 @@ namespace NadekoBot.Modules.Utility
}); });
}, (_, map) => }, (_, 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 config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
var toAdd = new CommandAlias() var toAdd = new CommandAlias()
@ -131,7 +115,7 @@ namespace NadekoBot.Modules.Utility
return; return;
ConcurrentDictionary<string, string> maps; ConcurrentDictionary<string, string> maps;
if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any()) if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any())
{ {
await ReplyErrorLocalized("aliases_none").ConfigureAwait(false); await ReplyErrorLocalized("aliases_none").ConfigureAwait(false);
return; return;
@ -139,7 +123,7 @@ namespace NadekoBot.Modules.Utility
var arr = maps.ToArray(); var arr = maps.ToArray();
await Context.Channel.SendPaginatedConfirmAsync(page + 1, (curPage) => await Context.Channel.SendPaginatedConfirmAsync(_client, page + 1, (curPage) =>
{ {
return new EmbedBuilder().WithOkColor() return new EmbedBuilder().WithOkColor()
.WithTitle(GetText("alias_list")) .WithTitle(GetText("alias_list"))

View File

@ -15,60 +15,13 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class CrossServerTextChannel : NadekoSubmodule public class CrossServerTextChannel : NadekoSubmodule
{ {
static CrossServerTextChannel() private readonly UtilityService _service;
{
NadekoBot.Client.MessageReceived += Client_MessageReceived;
}
public static void Unload() public CrossServerTextChannel(UtilityService service)
{ {
NadekoBot.Client.MessageReceived -= Client_MessageReceived; _service = service;
} }
private static async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
{
try
{
if (imsg.Author.IsBot)
return;
var msg = imsg as IUserMessage;
if (msg == null)
return;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return;
if (msg.Author.Id == NadekoBot.Client.CurrentUser.Id) return;
foreach (var subscriber in Subscribers)
{
var set = subscriber.Value;
if (!set.Contains(channel))
continue;
foreach (var chan in set.Except(new[] { channel }))
{
try
{
await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
msg)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
catch
{
// ignored
}
}
private static string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[OwnerOnly] [OwnerOnly]
@ -76,7 +29,7 @@ namespace NadekoBot.Modules.Utility
{ {
var token = new NadekoRandom().Next(); var token = new NadekoRandom().Next();
var set = new ConcurrentHashSet<ITextChannel>(); var set = new ConcurrentHashSet<ITextChannel>();
if (Subscribers.TryAdd(token, set)) if (_service.Subscribers.TryAdd(token, set))
{ {
set.Add((ITextChannel) Context.Channel); set.Add((ITextChannel) Context.Channel);
await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString()) await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString())
@ -90,7 +43,7 @@ namespace NadekoBot.Modules.Utility
public async Task Jcsc(int token) public async Task Jcsc(int token)
{ {
ConcurrentHashSet<ITextChannel> set; ConcurrentHashSet<ITextChannel> set;
if (!Subscribers.TryGetValue(token, out set)) if (!_service.Subscribers.TryGetValue(token, out set))
return; return;
set.Add((ITextChannel) Context.Channel); set.Add((ITextChannel) Context.Channel);
await ReplyConfirmLocalized("csc_join").ConfigureAwait(false); await ReplyConfirmLocalized("csc_join").ConfigureAwait(false);
@ -101,7 +54,7 @@ namespace NadekoBot.Modules.Utility
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Lcsc() public async Task Lcsc()
{ {
foreach (var subscriber in Subscribers) foreach (var subscriber in _service.Subscribers)
{ {
subscriber.Value.TryRemove((ITextChannel) Context.Channel); subscriber.Value.TryRemove((ITextChannel) Context.Channel);
} }

View File

@ -1,7 +1,9 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -14,6 +16,17 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class InfoCommands : NadekoSubmodule 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] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task ServerInfo(string guildName = null) public async Task ServerInfo(string guildName = null)
@ -24,7 +37,7 @@ namespace NadekoBot.Modules.Utility
if (string.IsNullOrWhiteSpace(guildName)) if (string.IsNullOrWhiteSpace(guildName))
guild = channel.Guild; guild = channel.Guild;
else 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) if (guild == null)
return; return;
var ownername = await guild.GetUserAsync(guild.OwnerId); 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)) .AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
.WithImageUrl(guild.IconUrl) .WithImageUrl(guild.IconUrl)
.WithColor(NadekoBot.OkColor); .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); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
@ -101,7 +114,6 @@ namespace NadekoBot.Modules.Utility
embed.WithThumbnailUrl(user.RealAvatarUrl()); embed.WithThumbnailUrl(user.RealAvatarUrl());
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -117,20 +129,21 @@ namespace NadekoBot.Modules.Utility
int startCount = page * activityPerPage; int startCount = page * activityPerPage;
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
foreach (var kvp in NadekoBot.CommandHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page*activityPerPage).Take(activityPerPage)) foreach (var kvp in _ch.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage))
{ {
str.AppendLine(GetText("activity_line", str.AppendLine(GetText("activity_line",
++startCount, ++startCount,
Format.Bold(kvp.Key.ToString()), Format.Bold(kvp.Key.ToString()),
kvp.Value / NadekoBot.Stats.GetUptime().TotalSeconds, kvp.Value)); kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value));
} }
await Context.Channel.EmbedAsync(new EmbedBuilder() await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithTitle(GetText("activity_page", page + 1)) .WithTitle(GetText("activity_page", page + 1))
.WithOkColor() .WithOkColor()
.WithFooter(efb => efb.WithText(GetText("activity_users_total", .WithFooter(efb => efb.WithText(GetText("activity_users_total",
NadekoBot.CommandHandler.UserMessagesSent.Count))) _ch.UserMessagesSent.Count)))
.WithDescription(str.ToString())); .WithDescription(str.ToString()));
} }
} }
} }
}

View File

@ -1,19 +1,17 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.Net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Modules.Utility.Models;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
@ -22,140 +20,15 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class RepeatCommands : NadekoSubmodule public class RepeatCommands : NadekoSubmodule
{ {
//guildid/RepeatRunners private readonly UtilityService _service;
public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; } private readonly DiscordShardedClient _client;
private readonly DbHandler _db;
private static bool _ready; public RepeatCommands(UtilityService service, DiscordShardedClient client, DbHandler db)
public class RepeatRunner
{ {
private readonly Logger _log; _service = service;
_client = client;
private CancellationTokenSource source { get; set; } _db = db;
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
public SocketGuild Guild { get; }
public ITextChannel Channel { get; private set; }
private IUserMessage oldMsg = null;
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
Repeater = repeater;
Channel = channel;
Guild = NadekoBot.Client.GetGuild(repeater.GuildId);
if(Guild!=null)
Task.Run(Run);
}
private async Task Run()
{
source = new CancellationTokenSource();
token = source.Token;
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
await Trigger().ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
}
public async Task Trigger()
{
var toSend = "🔄 " + Repeater.Message;
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
// continue;
if (oldMsg != null)
try
{
await oldMsg.DeleteAsync();
}
catch
{
// ignored
}
try
{
if (Channel == null)
Channel = Guild.GetTextChannel(Repeater.ChannelId);
if (Channel != null)
oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
_log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
return;
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
{
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
return;
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
public void Reset()
{
source.Cancel();
var _ = Task.Run(Run);
}
public void Stop()
{
source.Cancel();
}
public override string ToString() =>
$"{Channel?.Mention ?? $"<#{Repeater.ChannelId}>" } " +
$"| {(int) Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
$"| {Repeater.Message.TrimTo(33)}";
}
static RepeatCommands()
{
var _ = Task.Run(async () =>
{
#if !GLOBAL_NADEKO
await Task.Delay(5000).ConfigureAwait(false);
#else
await Task.Delay(30000).ConfigureAwait(false);
#endif
//todo this is pretty terrible
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
.ToDictionary(gc => gc.GuildId,
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
.Select(gr => new RepeatRunner(gr))
.Where(x => x.Guild != null))));
_ready = true;
});
}
public static void Unload()
{
_ready = false;
foreach (var kvp in Repeaters)
{
RepeatRunner r;
while (kvp.Value.TryDequeue(out r))
{
r.Stop();
}
}
Repeaters.Clear();
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -163,11 +36,10 @@ namespace NadekoBot.Modules.Utility
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatInvoke(int index) public async Task RepeatInvoke(int index)
{ {
if (!_ready) if (!_service.RepeaterReady)
return; return;
index -= 1; index -= 1;
ConcurrentQueue<RepeatRunner> rep; if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
{ {
await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false); await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false);
return; return;
@ -193,14 +65,13 @@ namespace NadekoBot.Modules.Utility
[Priority(0)] [Priority(0)]
public async Task RepeatRemove(int index) public async Task RepeatRemove(int index)
{ {
if (!_ready) if (!_service.RepeaterReady)
return; return;
if (index < 1) if (index < 1)
return; return;
index -= 1; index -= 1;
ConcurrentQueue<RepeatRunner> rep; if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
return; return;
var repeaterList = rep.ToList(); var repeaterList = rep.ToList();
@ -215,7 +86,7 @@ namespace NadekoBot.Modules.Utility
repeater.Stop(); repeater.Stop();
repeaterList.RemoveAt(index); 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)); 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); await uow.CompleteAsync().ConfigureAwait(false);
} }
if (Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep)) if (_service.Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
await Context.Channel.SendConfirmAsync(GetText("message_repeater"), await Context.Channel.SendConfirmAsync(GetText("message_repeater"),
GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false); GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false);
} }
@ -234,7 +105,7 @@ namespace NadekoBot.Modules.Utility
[Priority(1)] [Priority(1)]
public async Task Repeat(int minutes, [Remainder] string message) public async Task Repeat(int minutes, [Remainder] string message)
{ {
if (!_ready) if (!_service.RepeaterReady)
return; return;
if (minutes < 1 || minutes > 10080) if (minutes < 1 || minutes > 10080)
return; return;
@ -250,7 +121,7 @@ namespace NadekoBot.Modules.Utility
Message = message 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)); 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); await uow.CompleteAsync().ConfigureAwait(false);
} }
var rep = new RepeatRunner(toAdd, (ITextChannel) Context.Channel); var rep = new RepeatRunner(_client, toAdd, (ITextChannel) Context.Channel);
Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) => _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
{ {
old.Enqueue(rep); old.Enqueue(rep);
return old; return old;
@ -282,10 +153,9 @@ namespace NadekoBot.Modules.Utility
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatList() public async Task RepeatList()
{ {
if (!_ready) if (!_service.RepeaterReady)
return; return;
ConcurrentQueue<RepeatRunner> repRunners; if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var repRunners))
if (!Repeaters.TryGetValue(Context.Guild.Id, out repRunners))
{ {
await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false); await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false);
return; return;

View File

@ -22,16 +22,20 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class PatreonCommands : NadekoSubmodule 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; _creds = creds;
} _config = config;
_db = db;
public static void Unload() _currency = currency;
{ patreon = PatreonThingy.GetInstance(creds, db, currency);
patreon.Updater.Change(Timeout.Infinite, Timeout.Infinite);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -46,7 +50,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task ClaimPatreonRewards() public async Task ClaimPatreonRewards()
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.PatreonAccessToken)) if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
return; return;
if (DateTime.UtcNow.Day < 5) if (DateTime.UtcNow.Day < 5)
{ {
@ -65,11 +69,11 @@ namespace NadekoBot.Modules.Utility
if (amount > 0) if (amount > 0)
{ {
await ReplyConfirmLocalized("clpa_success", amount + NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyConfirmLocalized("clpa_success", amount + _config.CurrencySign).ConfigureAwait(false);
return; return;
} }
var rem = (patreon.Interval - (DateTime.UtcNow - patreon.LastUpdate)); 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() await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(GetText("clpa_fail")) .WithDescription(GetText("clpa_fail"))
.AddField(efb => efb.WithName(GetText("clpa_fail_already_title")).WithValue(GetText("clpa_fail_already"))) .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 class PatreonThingy
{ {
public static PatreonThingy _instance = new PatreonThingy(); //todo quickly hacked while rewriting, fix this
public static PatreonThingy Instance => _instance; 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); private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
@ -96,10 +102,17 @@ namespace NadekoBot.Modules.Utility
private readonly Logger _log; private readonly Logger _log;
public readonly TimeSpan Interval = TimeSpan.FromHours(1); 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; return;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval); Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval);
@ -116,7 +129,7 @@ namespace NadekoBot.Modules.Utility
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + NadekoBot.Credentials.PatreonAccessToken); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
var data = new PatreonData() var data = new PatreonData()
{ {
Links = new PatreonDataLinks() Links = new PatreonDataLinks()
@ -170,7 +183,7 @@ namespace NadekoBot.Modules.Utility
var amount = data.Reward.attributes.amount_cents; var amount = data.Reward.attributes.amount_cents;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var users = uow._context.Set<RewardedUser>(); var users = uow._context.Set<RewardedUser>();
var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id); var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id);
@ -185,7 +198,7 @@ namespace NadekoBot.Modules.Utility
AmountRewardedThisMonth = amount, 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); await uow.CompleteAsync().ConfigureAwait(false);
return amount; return amount;
@ -197,7 +210,7 @@ namespace NadekoBot.Modules.Utility
usr.AmountRewardedThisMonth = amount; usr.AmountRewardedThisMonth = amount;
usr.PatreonUserId = data.User.id; 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); await uow.CompleteAsync().ConfigureAwait(false);
return amount; return amount;
@ -211,7 +224,7 @@ namespace NadekoBot.Modules.Utility
usr.AmountRewardedThisMonth = amount; usr.AmountRewardedThisMonth = amount;
usr.PatreonUserId = data.User.id; 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); await uow.CompleteAsync().ConfigureAwait(false);
return toAward; return toAward;

View File

@ -17,6 +17,13 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class QuoteCommands : NadekoSubmodule public class QuoteCommands : NadekoSubmodule
{ {
private readonly DbHandler _db;
public QuoteCommands(DbHandler db)
{
_db = db;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task ListQuotes(int page = 1) public async Task ListQuotes(int page = 1)
@ -27,7 +34,7 @@ namespace NadekoBot.Modules.Utility
return; return;
IEnumerable<Quote> quotes; IEnumerable<Quote> quotes;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
quotes = uow.Quotes.GetGroup(Context.Guild.Id, page * 16, 16); quotes = uow.Quotes.GetGroup(Context.Guild.Id, page * 16, 16);
} }
@ -50,7 +57,7 @@ namespace NadekoBot.Modules.Utility
keyword = keyword.ToUpperInvariant(); keyword = keyword.ToUpperInvariant();
Quote quote; Quote quote;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
quote = quote =
await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false); await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false);
@ -87,7 +94,7 @@ namespace NadekoBot.Modules.Utility
keyword = keyword.ToUpperInvariant(); keyword = keyword.ToUpperInvariant();
Quote keywordquote; Quote keywordquote;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
keywordquote = keywordquote =
await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text) await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text)
@ -108,7 +115,7 @@ namespace NadekoBot.Modules.Utility
if (id < 0) if (id < 0)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var qfromid = uow.Quotes.Get(id); var qfromid = uow.Quotes.Get(id);
CREmbed crembed; CREmbed crembed;
@ -146,7 +153,7 @@ namespace NadekoBot.Modules.Utility
keyword = keyword.ToUpperInvariant(); keyword = keyword.ToUpperInvariant();
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.Quotes.Add(new Quote uow.Quotes.Add(new Quote
{ {
@ -169,7 +176,7 @@ namespace NadekoBot.Modules.Utility
var success = false; var success = false;
string response; string response;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var q = uow.Quotes.Get(id); var q = uow.Quotes.Get(id);
@ -201,7 +208,7 @@ namespace NadekoBot.Modules.Utility
keyword = keyword.ToUpperInvariant(); keyword = keyword.ToUpperInvariant();
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.Quotes.RemoveAllByKeyword(Context.Guild.Id, keyword.ToUpperInvariant()); uow.Quotes.RemoveAllByKeyword(Context.Guild.Id, keyword.ToUpperInvariant());

View File

@ -19,89 +19,13 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class RemindCommands : NadekoSubmodule public class RemindCommands : NadekoSubmodule
{ {
readonly Regex _regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$", private readonly UtilityService _service;
RegexOptions.Compiled | RegexOptions.Multiline); private readonly DbHandler _db;
private static string remindMessageFormat { get; } public RemindCommands(UtilityService service, DbHandler db)
private static readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
{ {
{ "%message%" , (r) => r.Message }, _service = service;
{ "%user%", (r) => $"<@!{r.UserId}>" }, _db = db;
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
};
private new static readonly Logger _log;
private static readonly CancellationTokenSource cancelSource;
private static readonly CancellationToken cancelAllToken;
static RemindCommands()
{
_log = LogManager.GetCurrentClassLogger();
cancelSource = new CancellationTokenSource();
cancelAllToken = cancelSource.Token;
List<Reminder> reminders;
using (var uow = DbHandler.UnitOfWork())
{
reminders = uow.Reminders.GetAll().ToList();
}
remindMessageFormat = NadekoBot.BotConfig.RemindMessageFormat;
foreach (var r in reminders)
{
Task.Run(() => StartReminder(r, cancelAllToken));
}
}
public static void Unload()
{
if (!cancelSource.IsCancellationRequested)
cancelSource.Cancel();
}
private static async Task StartReminder(Reminder r, CancellationToken t)
{
var now = DateTime.Now;
var time = r.When - now;
if (time.TotalMilliseconds > int.MaxValue)
return;
await Task.Delay(time, t).ConfigureAwait(false);
try
{
IMessageChannel ch;
if (r.IsPrivate)
{
var user = NadekoBot.Client.GetGuild(r.ServerId).GetUser(r.ChannelId);
if(user == null)
return;
ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
}
else
{
ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
}
if (ch == null)
return;
await ch.SendMessageAsync(
_replacements.Aggregate(remindMessageFormat,
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
.SanitizeMentions()
).ConfigureAwait(false); //it works trust me
}
catch (Exception ex) { _log.Warn(ex); }
finally
{
using (var uow = DbHandler.UnitOfWork())
{
uow.Reminders.Remove(r);
await uow.CompleteAsync();
}
}
} }
public enum MeOrHere public enum MeOrHere
@ -139,7 +63,7 @@ namespace NadekoBot.Modules.Utility
public async Task RemindInternal(ulong targetId, bool isPrivate, string timeStr, [Remainder] string message) 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) if (m.Length == 0)
{ {
@ -150,7 +74,7 @@ namespace NadekoBot.Modules.Utility
string output = ""; string output = "";
var namesAndValues = new Dictionary<string, int>(); var namesAndValues = new Dictionary<string, int>();
foreach (var groupName in _regex.GetGroupNames()) foreach (var groupName in _service.Remind.Regex.GetGroupNames())
{ {
if (groupName == "0") continue; if (groupName == "0") continue;
int value; int value;
@ -191,7 +115,7 @@ namespace NadekoBot.Modules.Utility
ServerId = Context.Guild.Id ServerId = Context.Guild.Id
}; };
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.Reminders.Add(rem); uow.Reminders.Add(rem);
await uow.CompleteAsync(); await uow.CompleteAsync();
@ -210,7 +134,7 @@ namespace NadekoBot.Modules.Utility
{ {
// ignored // ignored
} }
await StartReminder(rem, cancelAllToken); await _service.Remind.StartReminder(rem);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -220,7 +144,7 @@ namespace NadekoBot.Modules.Utility
if (string.IsNullOrWhiteSpace(arg)) if (string.IsNullOrWhiteSpace(arg))
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim(); uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);

View File

@ -2,17 +2,8 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; 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;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
@ -22,108 +13,17 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class UnitConverterCommands : NadekoSubmodule public class UnitConverterCommands : NadekoSubmodule
{ {
public static List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>(); private readonly UtilityService _service;
private new static readonly Logger _log;
private static Timer _timer;
private static readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
static UnitConverterCommands() public UnitConverterCommands(UtilityService service)
{ {
_log = LogManager.GetCurrentClassLogger(); _service = service;
try
{
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
{
Modifier = u.Modifier,
UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
}).ToArray();
using (var uow = DbHandler.UnitOfWork())
{
if (uow.ConverterUnits.Empty())
{
uow.ConverterUnits.AddRange(data);
uow.Complete();
} }
}
Units = data.ToList();
}
catch (Exception ex)
{
_log.Warn("Could not load units: " + ex.Message);
}
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
}
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] [NadekoCommand, Usage, Description, Aliases]
public async Task ConvertList() 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")) .Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
.WithColor(NadekoBot.OkColor), .WithColor(NadekoBot.OkColor),
(embed, g) => embed.AddField(efb => (embed, g) => embed.AddField(efb =>
@ -135,8 +35,8 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Convert(string origin, string target, decimal value) 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 originUnit = _service.Converter.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 targetUnit = _service.Converter.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
if (originUnit == null || targetUnit == null) if (originUnit == null || targetUnit == null)
{ {
await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false); 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))); await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
} }
} }
public static async Task<Rates> UpdateCurrencyRates()
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
return JsonConvert.DeserializeObject<Rates>(res);
}
}
} }
} }

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace NadekoBot.Modules.Utility.Commands.Models
{
public class MeasurementUnit
{
public List<string> Triggers { get; set; }
public string UnitType { get; set; }
public decimal Modifier { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NadekoBot.Modules.Utility.Commands.Models
{
public class Rates
{
public string Base { get; set; }
public DateTime Date { get; set; }
[JsonProperty("rates")]
public Dictionary<string, decimal> ConversionRates { get; set; }
}
}

View File

@ -0,0 +1,114 @@
using Discord;
using Discord.Net;
using Discord.WebSocket;
using Microsoft.Extensions.Logging;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility.Models
{
public class RepeatRunner
{
private readonly Logger _log;
private CancellationTokenSource source { get; set; }
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
public SocketGuild Guild { get; }
public ITextChannel Channel { get; private set; }
private IUserMessage oldMsg = null;
public RepeatRunner(DiscordShardedClient client, Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
Repeater = repeater;
Channel = channel;
//todo @.@ fix all of this
Guild = client.GetGuild(repeater.GuildId);
if (Guild != null)
Task.Run(Run);
}
private async Task Run()
{
source = new CancellationTokenSource();
token = source.Token;
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
await Trigger().ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
}
public async Task Trigger()
{
var toSend = "🔄 " + Repeater.Message;
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
// continue;
if (oldMsg != null)
try
{
await oldMsg.DeleteAsync();
}
catch
{
// ignored
}
try
{
if (Channel == null)
Channel = Guild.GetTextChannel(Repeater.ChannelId);
if (Channel != null)
oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
_log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
return;
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
{
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
return;
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
public void Reset()
{
source.Cancel();
var _ = Task.Run(Run);
}
public void Stop()
{
source.Cancel();
}
public override string ToString() =>
$"{Channel?.Mention ?? $"<#{Repeater.ChannelId}>" } " +
$"| {(int)Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
$"| {Repeater.Message.TrimTo(33)}";
}
}

View File

@ -20,15 +20,23 @@ using System.Diagnostics;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
[NadekoModule("Utility", ".")] [NadekoModule("Utility")]
public partial class Utility : NadekoTopLevelModule public partial class Utility : NadekoTopLevelModule
{ {
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>(); private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
private readonly DiscordShardedClient _client;
private readonly IStatsService _stats;
private readonly UtilityService _service;
//private readonly MusicService _music;
private readonly IBotCredentials _creds;
public static void Unload() public Utility(UtilityService service, DiscordShardedClient client, IStatsService stats, IBotCredentials creds)
{ {
_rotatingRoleColors.ForEach(x => x.Value?.Change(Timeout.Infinite, Timeout.Infinite)); _client = client;
_rotatingRoleColors.Clear(); _stats = stats;
_service = service;
//_music = music;
_creds = creds;
} }
//[NadekoCommand, Usage, Description, Aliases] //[NadekoCommand, Usage, Description, Aliases]
@ -355,11 +363,11 @@ namespace NadekoBot.Modules.Utility
if (page < 1) if (page < 1)
return; 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}") .Select(x => $"{x.Count()} {x.Key}")
.ToArray()); .ToArray());
var allShardStrings = NadekoBot.Client.Shards var allShardStrings = _client.Shards
.Select(x => .Select(x =>
GetText("shard_stats_txt", x.ShardId.ToString(), GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.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)); var str = string.Join("\n", allShardStrings.Skip(25 * (curPage - 1)).Take(25));
@ -386,7 +394,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task ShardId(IGuild guild) 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); await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
} }
@ -394,10 +402,8 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Stats() public async Task Stats()
{ {
var stats = NadekoBot.Stats;
var shardId = Context.Guild != null var shardId = Context.Guild != null
? NadekoBot.Client.GetShardIdFor(Context.Guild) ? _client.GetShardIdFor(Context.Guild)
: 0; : 0;
await Context.Channel.EmbedAsync( await Context.Channel.EmbedAsync(
@ -405,21 +411,21 @@ namespace NadekoBot.Modules.Utility
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}") .WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
.WithUrl("http://nadekobot.readthedocs.io/en/latest/") .WithUrl("http://nadekobot.readthedocs.io/en/latest/")
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg")) .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("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("botid")).WithValue(_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("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("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("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("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("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("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("presence")).WithValue( .AddField(efb => efb.WithName(GetText("presence")).WithValue(
GetText("presence_txt", 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 #if !GLOBAL_NADEKO
.WithFooter(efb => efb.WithText(GetText("stats_songs", //.WithFooter(efb => efb.WithText(GetText("stats_songs",
NadekoBot.MusicService.MusicPlayers.Count(mp => mp.Value.CurrentSong != null), // _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
NadekoBot.MusicService.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)))) // _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
#endif #endif
); );
} }
@ -427,7 +433,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Showemojis([Remainder] string emojis) 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))); var result = string.Join("\n", tags.Select(m => GetText("showemojis", m, m.Url)));
@ -446,7 +452,7 @@ namespace NadekoBot.Modules.Utility
if (page < 0) if (page < 0)
return; 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()) if (!guilds.Any())
{ {

View File

@ -0,0 +1,319 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Modules.Utility.Models;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility
{
public class UtilityService
{
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
//messagerepeater
//guildid/RepeatRunners
public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
public bool RepeaterReady { get; private set; }
//remind
public RemindService Remind { get; }
//unit conversion
public ConverterService Converter { get; }
public UtilityService(IEnumerable<GuildConfig> guildConfigs, DiscordShardedClient client, BotConfig config, DbHandler db)
{
//commandmap
AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
guildConfigs.ToDictionary(
x => x.GuildId,
x => new ConcurrentDictionary<string, string>(x.CommandAliases
.Distinct(new CommandAliasEqualityComparer())
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
//crossesrver
_client = client;
_client.MessageReceived += Client_MessageReceived;
//messagerepeater
var _ = Task.Run(async () =>
{
#if !GLOBAL_NADEKO
await Task.Delay(5000).ConfigureAwait(false);
#else
await Task.Delay(30000).ConfigureAwait(false);
#endif
//todo this is pretty terrible :kms: no time
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(guildConfigs
.ToDictionary(gc => gc.GuildId,
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
.Select(gr => new RepeatRunner(client, gr))
.Where(x => x.Guild != null))));
RepeaterReady = true;
});
//reminder
Remind = new RemindService(client, config, db);
//unit converter
Converter = new ConverterService(db);
}
private async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
{
try
{
if (imsg.Author.IsBot)
return;
var msg = imsg as IUserMessage;
if (msg == null)
return;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return;
if (msg.Author.Id == _client.CurrentUser.Id) return;
foreach (var subscriber in Subscribers)
{
var set = subscriber.Value;
if (!set.Contains(channel))
continue;
foreach (var chan in set.Except(new[] { channel }))
{
try
{
await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
msg)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
catch
{
// ignored
}
}
private string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
public readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
private DiscordShardedClient _client;
}
public class ConverterService
{
public class MeasurementUnit
{
public List<string> Triggers { get; set; }
public string UnitType { get; set; }
public decimal Modifier { get; set; }
}
public class Rates
{
public string Base { get; set; }
public DateTime Date { get; set; }
[JsonProperty("rates")]
public Dictionary<string, decimal> ConversionRates { get; set; }
}
public List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
private readonly Logger _log;
private Timer _timer;
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
private readonly DbHandler _db;
public ConverterService(DbHandler db)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
try
{
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
{
Modifier = u.Modifier,
UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
}).ToArray();
using (var uow = _db.UnitOfWork)
{
if (uow.ConverterUnits.Empty())
{
uow.ConverterUnits.AddRange(data);
uow.Complete();
}
}
Units = data.ToList();
}
catch (Exception ex)
{
_log.Warn("Could not load units: " + ex.Message);
}
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
}
public static async Task<Rates> UpdateCurrencyRates()
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
return JsonConvert.DeserializeObject<Rates>(res);
}
}
public async Task UpdateCurrency()
{
try
{
var currencyRates = await UpdateCurrencyRates();
var unitTypeString = "currency";
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
{
InternalTrigger = u.Key,
Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
using (var uow = _db.UnitOfWork)
{
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
uow.ConverterUnits.Add(baseType);
uow.ConverterUnits.AddRange(range);
await uow.CompleteAsync().ConfigureAwait(false);
}
Units.RemoveAll(u => u.UnitType == unitTypeString);
Units.Add(baseType);
Units.AddRange(range);
_log.Info("Updated Currency");
}
catch
{
_log.Warn("Failed updating currency. Ignore this.");
}
}
}
public class RemindService
{
public readonly Regex Regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
RegexOptions.Compiled | RegexOptions.Multiline);
public string RemindMessageFormat { get; }
public readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
{
{ "%message%" , (r) => r.Message },
{ "%user%", (r) => $"<@!{r.UserId}>" },
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
};
private readonly Logger _log;
private readonly CancellationTokenSource cancelSource;
private readonly CancellationToken cancelAllToken;
private readonly BotConfig _config;
private readonly DiscordShardedClient _client;
private readonly DbHandler _db;
public RemindService(DiscordShardedClient client, BotConfig config, DbHandler db)
{
_config = config;
_client = client;
_log = LogManager.GetCurrentClassLogger();
_db = db;
cancelSource = new CancellationTokenSource();
cancelAllToken = cancelSource.Token;
List<Reminder> reminders;
using (var uow = _db.UnitOfWork)
{
reminders = uow.Reminders.GetAll().ToList();
}
RemindMessageFormat = _config.RemindMessageFormat;
foreach (var r in reminders)
{
Task.Run(() => StartReminder(r));
}
}
public async Task StartReminder(Reminder r)
{
var t = cancelAllToken;
var now = DateTime.Now;
var time = r.When - now;
if (time.TotalMilliseconds > int.MaxValue)
return;
await Task.Delay(time, t).ConfigureAwait(false);
try
{
IMessageChannel ch;
if (r.IsPrivate)
{
var user = _client.GetGuild(r.ServerId).GetUser(r.ChannelId);
if (user == null)
return;
ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
}
else
{
ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
}
if (ch == null)
return;
await ch.SendMessageAsync(
_replacements.Aggregate(RemindMessageFormat,
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
.SanitizeMentions()
).ConfigureAwait(false); //it works trust me
}
catch (Exception ex) { _log.Warn(ex); }
finally
{
using (var uow = _db.UnitOfWork)
{
uow.Reminders.Remove(r);
await uow.CompleteAsync();
}
}
}
}
public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
{
public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
}
}

View File

@ -12,13 +12,12 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Modules.Permissions; using NadekoBot.Modules.Permissions;
using NadekoBot.TypeReaders; using NadekoBot.TypeReaders;
using System.Collections.Concurrent;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using NadekoBot.Modules.Music;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Threading; using System.Threading;
using NadekoBot.Services.Music; using NadekoBot.Modules.Utility;
using NadekoBot.Services.Searches;
namespace NadekoBot namespace NadekoBot
{ {
@ -26,38 +25,34 @@ namespace NadekoBot
{ {
private Logger _log; private Logger _log;
public static Color OkColor { get; } /* I don't know how to make this not be static
public static Color ErrorColor { get; } * 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; }
public static CommandService CommandService { get; private set; } //todo placeholder, will be guild-based
public static CommandHandler CommandHandler { get; private set; } public static string Prefix { get; } = ".";
public static DiscordShardedClient Client { get; private set; }
public static BotCredentials Credentials { get; }
public static Localization Localization { get; private set; } public ImmutableArray<GuildConfig> AllGuildConfigs { get; }
public static NadekoStrings Strings { get; private set; } public BotConfig BotConfig { get; }
public static GoogleApiService Google { get; private set; } public DiscordShardedClient Client { get; }
public static StatsService Stats { get; private set; } public bool Ready { get; private set; }
public static IImagesService Images { get; private set; }
public static ConcurrentDictionary<string, string> ModulePrefixes { get; private set; } public INServiceProvider Services { get; }
public static bool Ready { get; private set; }
public static ImmutableArray<GuildConfig> AllGuildConfigs { get; } public NadekoBot()
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()
{ {
SetupLogger(); 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(); AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray();
BotConfig = uow.BotConfig.GetOrCreate(); BotConfig = uow.BotConfig.GetOrCreate();
@ -65,70 +60,87 @@ namespace NadekoBot
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 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 Client = new DiscordShardedClient(new DiscordSocketConfig
{ {
MessageCacheSize = 10, MessageCacheSize = 10,
LogLevel = LogSeverity.Warning, LogLevel = LogSeverity.Warning,
TotalShards = Credentials.TotalShards, TotalShards = credentials.TotalShards,
ConnectionTimeout = int.MaxValue, ConnectionTimeout = int.MaxValue,
#if !GLOBAL_NADEKO AlwaysDownloadUsers = true,
//AlwaysDownloadUsers = true,
#endif
}); });
var google = new GoogleApiService(credentials);
var strings = new NadekoStrings();
var greetSettingsService = new GreetSettingsService(AllGuildConfigs, db);
var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db);
//var musicService = new MusicService(google, strings, localization);
var commandService = new CommandService(new CommandServiceConfig()
{
CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync,
});
var commandHandler = new CommandHandler(Client, commandService, credentials, this);
var stats = new StatsService(Client, commandHandler, credentials);
var images = new ImagesService();
//module services
var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
var searchesService = new SearchesService();
//initialize Services
Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
.Add<ILocalization>(localization)
.Add<IStatsService>(stats)
.Add<IImagesService>(images)
.Add<IGoogleApiService>(google)
.Add<IStatsService>(stats)
.Add<IBotCredentials>(credentials)
.Add<CommandService>(commandService)
.Add<NadekoStrings>(strings)
.Add<DiscordShardedClient>(Client)
.Add<GreetSettingsService>(greetSettingsService)
//.Add(musicService)
.Add<CommandHandler>(commandHandler)
.Add<DbHandler>(db)
//modules
.Add<UtilityService>(utilityService)
.Add<SearchesService>(searchesService)
.Build();
commandHandler.AddServices(Services);
//setup typereaders
commandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
commandService.AddTypeReader<CommandInfo>(new CommandTypeReader(commandService));
//commandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
commandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(commandService));
commandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(commandService));
commandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
Client.Log += Client_Log; Client.Log += Client_Log;
#endif #endif
// initialize response strings }
Strings = new NadekoStrings();
//initialize Services public async Task RunAsync(params string[] args)
Localization = new Localization(NadekoBot.BotConfig.Locale, NadekoBot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale)); {
CommandService = new CommandService(new CommandServiceConfig() { var creds = Services.GetService<IBotCredentials>();
CaseSensitiveCommands = false, var stats = Services.GetService<IStatsService>();
DefaultRunMode = RunMode.Sync var commandHandler = Services.GetService<CommandHandler>();
}); var commandService = Services.GetService<CommandService>();
CommandHandler = new CommandHandler(Client, CommandService);
Stats = new StatsService(Client, CommandHandler);
Images = await ImagesService.Create().ConfigureAwait(false);
////setup DI
//var depMap = new DependencyMap();
//depMap.Add<ILocalization>(Localizer);
//depMap.Add<ShardedDiscordClient>(Client);
//depMap.Add<CommandService>(CommandService);
//depMap.Add<IGoogleApiService>(Google);
//setup typereaders
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader());
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader());
CommandService.AddTypeReader<IGuild>(new GuildTypeReader());
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
//connect //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.StartAsync().ConfigureAwait(false);
//await Client.DownloadAllUsersAsync().ConfigureAwait(false);
// wait for all shards to be ready // wait for all shards to be ready
int readyCount = 0; int readyCount = 0;
@ -138,25 +150,21 @@ namespace NadekoBot
while (readyCount < Client.Shards.Count) while (readyCount < Client.Shards.Count)
await Task.Delay(100).ConfigureAwait(false); await Task.Delay(100).ConfigureAwait(false);
Stats.Initialize(); stats.Initialize();
sw.Stop(); sw.Stop();
_log.Info("Connected in " + sw.Elapsed.TotalSeconds.ToString("F2")); _log.Info("Connected in " + sw.Elapsed.TotalSeconds.ToString("F2"));
//load commands and prefixes
ModulePrefixes = new ConcurrentDictionary<string, string>(NadekoBot.BotConfig.ModulePrefixes.OrderByDescending(mp => mp.Prefix.Length).ToDictionary(m => m.ModuleName, m => m.Prefix));
// start handling messages received in commandhandler // 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 #if !GLOBAL_NADEKO
await CommandService.AddModuleAsync<Music>().ConfigureAwait(false); //todo uncomment this
//await commandService.AddModuleAsync<Music>().ConfigureAwait(false);
#endif #endif
Ready = true; Ready = true;
_log.Info(await Stats.Print().ConfigureAwait(false)); _log.Info(await stats.Print().ConfigureAwait(false));
} }
private Task Client_Log(LogMessage arg) private Task Client_Log(LogMessage arg)

View File

@ -27,7 +27,37 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="data\**\*;credentials.json;credentials_example.json;Modules\Music\Classes\PlaylistFullException.cs" /> <Compile Remove="data\**\*;credentials.json;credentials_example.json" />
<Compile Remove="Modules\Administration\**" />
<Compile Remove="Modules\ClashOfClans\**" />
<Compile Remove="Modules\CustomReactions\**" />
<Compile Remove="Modules\Gambling\**" />
<Compile Remove="Modules\Games\**" />
<Compile Remove="Modules\Help\**" />
<Compile Remove="Modules\Music\**" />
<Compile Remove="Modules\Permissions\**" />
<Compile Remove="Modules\Searches\**" />
<Compile Remove="Services\Music\**" />
<EmbeddedResource Remove="Modules\Administration\**" />
<EmbeddedResource Remove="Modules\ClashOfClans\**" />
<EmbeddedResource Remove="Modules\CustomReactions\**" />
<EmbeddedResource Remove="Modules\Gambling\**" />
<EmbeddedResource Remove="Modules\Games\**" />
<EmbeddedResource Remove="Modules\Help\**" />
<EmbeddedResource Remove="Modules\Music\**" />
<EmbeddedResource Remove="Modules\Permissions\**" />
<EmbeddedResource Remove="Modules\Searches\**" />
<EmbeddedResource Remove="Services\Music\**" />
<None Remove="Modules\Administration\**" />
<None Remove="Modules\ClashOfClans\**" />
<None Remove="Modules\CustomReactions\**" />
<None Remove="Modules\Gambling\**" />
<None Remove="Modules\Games\**" />
<None Remove="Modules\Help\**" />
<None Remove="Modules\Music\**" />
<None Remove="Modules\Permissions\**" />
<None Remove="Modules\Searches\**" />
<None Remove="Services\Music\**" />
<None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so"> <None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
@ -44,7 +74,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" /> <PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="Discord.Net" Version="1.0.0-rc2-00014" /> <PackageReference Include="Discord.Net" Version="1.0.0-rc3-00743" />
<PackageReference Include="libvideo" Version="1.0.1" /> <PackageReference Include="libvideo" Version="1.0.1" />
<PackageReference Include="CoreCLR-NCalc" Version="2.1.2" /> <PackageReference Include="CoreCLR-NCalc" Version="2.1.2" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" /> <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" />
@ -58,10 +88,10 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.2-beta1" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="NLog" Version="5.0.0-beta03" /> <PackageReference Include="NLog" Version="5.0.0-beta03" />
<PackageReference Include="System.Xml.XPath" Version="4.3.0" /> <PackageReference Include="System.Xml.XPath" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View File

@ -6,18 +6,11 @@ using System.Threading.Tasks;
using Discord; using Discord;
using NLog; using NLog;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Modules.Permissions;
using Discord.Net; using Discord.Net;
using NadekoBot.Extensions; 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.Collections.Concurrent;
using System.Threading; using System.Threading;
using NadekoBot.DataStructures; using NadekoBot.DataStructures;
using System.Diagnostics;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace NadekoBot.Services namespace NadekoBot.Services
@ -35,6 +28,9 @@ namespace NadekoBot.Services
private readonly DiscordShardedClient _client; private readonly DiscordShardedClient _client;
private readonly CommandService _commandService; private readonly CommandService _commandService;
private readonly Logger _log; private readonly Logger _log;
private readonly IBotCredentials _creds;
private readonly NadekoBot _bot;
private INServiceProvider _services;
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>(); private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>();
@ -46,10 +42,13 @@ namespace NadekoBot.Services
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>(); public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private readonly Timer _clearUsersOnShortCooldown; private readonly Timer _clearUsersOnShortCooldown;
public CommandHandler(DiscordShardedClient client, CommandService commandService) public CommandHandler(DiscordShardedClient client, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
{ {
_client = client; _client = client;
_commandService = commandService; _commandService = commandService;
_creds = credentials;
_bot = bot;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_clearUsersOnShortCooldown = new Timer(_ => _clearUsersOnShortCooldown = new Timer(_ =>
@ -58,11 +57,16 @@ namespace NadekoBot.Services
}, null, GlobalCommandsCooldown, GlobalCommandsCooldown); }, null, GlobalCommandsCooldown, GlobalCommandsCooldown);
} }
public void AddServices(INServiceProvider services)
{
_services = services;
}
public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText) public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText)
{ {
if (guildId != null) if (guildId != null)
{ {
var guild = NadekoBot.Client.GetGuild(guildId.Value); var guild = _client.GetGuild(guildId.Value);
var channel = guild?.GetChannel(channelId) as SocketTextChannel; var channel = guild?.GetChannel(channelId) as SocketTextChannel;
if (channel == null) if (channel == null)
{ {
@ -87,28 +91,28 @@ namespace NadekoBot.Services
{ {
await Task.Delay(5000).ConfigureAwait(false); await Task.Delay(5000).ConfigureAwait(false);
_client.GetGuilds().SelectMany(g => g.Users); _client.Guilds.SelectMany(g => g.Users);
LoadOwnerChannels(); LoadOwnerChannels();
if (!ownerChannels.Any()) if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else 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.MessageReceived += MessageReceivedHandler;
_client.MessageUpdated += (oldmsg, newMsg, channel) => _client.MessageUpdated += (oldmsg, newMsg, channel) =>
{ {
var ignore = Task.Run(async () => var ignore = Task.Run(() =>
{ {
try try
{ {
var usrMsg = newMsg as SocketUserMessage; var usrMsg = newMsg as SocketUserMessage;
var guild = (usrMsg?.Channel as ITextChannel)?.Guild; var guild = (usrMsg?.Channel as ITextChannel)?.Guild;
////todo invite filtering
if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false)) //if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
await WordFiltered(guild, usrMsg).ConfigureAwait(false); // await WordFiltered(guild, usrMsg).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@ -124,7 +128,7 @@ namespace NadekoBot.Services
private void LoadOwnerChannels() private void LoadOwnerChannels()
{ {
var hs = new HashSet<ulong>(NadekoBot.Credentials.OwnerIds); var hs = new HashSet<ulong>(_creds.OwnerIds);
var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>(); var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
foreach (var s in _client.Shards) 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) .Select(x => x.Value)
.ToImmutableArray(); .ToImmutableArray();
} }
private async Task<bool> TryRunCleverbot(IUserMessage usrMsg, SocketGuild guild) ////todo cleverbot
{ //private async Task<bool> TryRunCleverbot(IUserMessage usrMsg, SocketGuild guild)
if (guild == null) //{
return false; // if (guild == null)
try // return false;
{ // try
var message = Games.CleverBotCommands.PrepareMessage(usrMsg, out Games.ChatterBotSession cbs); // {
if (message == null || cbs == null) // var message = Games.CleverBotCommands.PrepareMessage(usrMsg, out Games.ChatterBotSession cbs);
return false; // if (message == null || cbs == null)
// return false;
PermissionCache pc = Permissions.GetCache(guild.Id); // PermissionCache pc = Permissions.GetCache(guild.Id);
if (!pc.Permissions.CheckPermissions(usrMsg, // if (!pc.Permissions.CheckPermissions(usrMsg,
NadekoBot.ModulePrefixes[typeof(Games).Name] + "cleverbot", // NadekoBot.Prefix + "cleverbot",
typeof(Games).Name, // typeof(Games).Name,
out int index)) // out int index))
{ // {
//todo print in guild actually // //todo print in guild actually
var returnMsg = // var returnMsg =
$"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action."; // $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
_log.Info(returnMsg); // _log.Info(returnMsg);
return true; // return true;
} // }
var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(cbs, (ITextChannel)usrMsg.Channel, message).ConfigureAwait(false); // var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(cbs, (ITextChannel)usrMsg.Channel, message).ConfigureAwait(false);
if (cleverbotExecuted) // if (cleverbotExecuted)
{ // {
_log.Info($@"CleverBot Executed // _log.Info($@"CleverBot Executed
Server: {guild.Name} [{guild.Id}] //Server: {guild.Name} [{guild.Id}]
Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}] //Channel: {usrMsg.Channel?.Name} [{usrMsg.Channel?.Id}]
UserId: {usrMsg.Author} [{usrMsg.Author.Id}] //UserId: {usrMsg.Author} [{usrMsg.Author.Id}]
Message: {usrMsg.Content}"); //Message: {usrMsg.Content}");
return true; // return true;
} // }
} // }
catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); } // catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); }
return false; // return false;
} //}
////todo blacklisting
private bool IsBlacklisted(IGuild guild, IUserMessage usrMsg) => //private bool IsBlacklisted(IGuild guild, IUserMessage usrMsg) =>
(guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) || // (guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) ||
BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) || // BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) ||
BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id); // BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
private const float _oneThousandth = 1.0f / 1000; 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" + "User: {0}\n\t" +
"Server: {1}\n\t" + "Server: {1}\n\t" +
"Channel: {2}\n\t" + "Channel: {2}\n\t" +
@ -206,18 +212,14 @@ namespace NadekoBot.Services
usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content, // {3} usrMsg.Content // {3}
exec1 * _oneThousandth, // {4}
exec2 * _oneThousandth, // {5}
exec3 * _oneThousandth, // {6}
total * _oneThousandth // {7}
); );
return Task.CompletedTask; 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" + "User: {0}\n\t" +
"Server: {1}\n\t" + "Server: {1}\n\t" +
"Channel: {2}\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.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content,// {3} usrMsg.Content,// {3}
exec.Result.ErrorReason, // {4} exec.Result.ErrorReason // {4}
exec1 * _oneThousandth, // {5}
exec2 * _oneThousandth, // {6}
exec3 * _oneThousandth, // {7}
total * _oneThousandth // {8}
); );
} }
////todo invite filtering
//private async Task<bool> InviteFiltered(IGuild guild, IUserMessage usrMsg)
//{
// if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) ||
// Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) &&
// usrMsg.Content.IsDiscordInvite())
// {
// try
// {
// await usrMsg.DeleteAsync().ConfigureAwait(false);
// return true;
// }
// catch (HttpException ex)
// {
// _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex);
// return true;
// }
// }
// return false;
//}
private async Task<bool> InviteFiltered(IGuild guild, IUserMessage usrMsg) ////todo word filtering
{ //private async Task<bool> WordFiltered(IGuild guild, IUserMessage usrMsg)
if ((Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) || //{
Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) && // var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>();
usrMsg.Content.IsDiscordInvite()) // var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>();
{ // var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
try // if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
{ // {
await usrMsg.DeleteAsync().ConfigureAwait(false); // foreach (var word in wordsInMessage)
return true; // {
} // if (filteredChannelWords.Contains(word) ||
catch (HttpException ex) // filteredServerWords.Contains(word))
{ // {
_log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex); // try
return true; // {
} // await usrMsg.DeleteAsync().ConfigureAwait(false);
} // }
return false; // catch (HttpException ex)
} // {
// _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex);
private async Task<bool> WordFiltered(IGuild guild, IUserMessage usrMsg) // }
{ // return true;
var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>(); // }
var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>(); // }
var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); // }
if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0) // return false;
{ //}
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) private Task MessageReceivedHandler(SocketMessage msg)
{ {
@ -288,7 +287,7 @@ namespace NadekoBot.Services
{ {
try 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; return;
var usrMsg = msg as SocketUserMessage; var usrMsg = msg as SocketUserMessage;
@ -325,140 +324,144 @@ namespace NadekoBot.Services
{ {
var execTime = Environment.TickCount; var execTime = Environment.TickCount;
if (guild != null && guild.OwnerId != usrMsg.Author.Id) ////todo word and invite filtering
{ //if (guild != null && guild.OwnerId != usrMsg.Author.Id)
if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false)) //{
return; // if (await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
// return;
if (await WordFiltered(guild, usrMsg).ConfigureAwait(false)) // if (await WordFiltered(guild, usrMsg).ConfigureAwait(false))
return; // return;
} //}
if (IsBlacklisted(guild, usrMsg)) ////todo blacklisting
return; //if (IsBlacklisted(guild, usrMsg))
// return;
var exec1 = Environment.TickCount - execTime; //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; var exec2 = Environment.TickCount - execTime;
// maybe this message is a custom reaction ////todo custom reactions
// todo log custom reaction executions. return struct with info //// maybe this message is a custom reaction
var cr = await Task.Run(() => CustomReactions.TryGetCustomReaction(usrMsg)).ConfigureAwait(false); //// todo log custom reaction executions. return struct with info
if (cr != null) //if it was, don't execute the command //var cr = await Task.Run(() => CustomReactions.TryGetCustomReaction(usrMsg)).ConfigureAwait(false);
{ //if (cr != null) //if it was, don't execute the command
try //{
{ // try
if (guild != null) // {
{ // if (guild != null)
PermissionCache pc = Permissions.GetCache(guild.Id); // {
// PermissionCache pc = Permissions.GetCache(guild.Id);
if (!pc.Permissions.CheckPermissions(usrMsg, cr.Trigger, "ActualCustomReactions", // if (!pc.Permissions.CheckPermissions(usrMsg, cr.Trigger, "ActualCustomReactions",
out int index)) // out int index))
{ // {
//todo print in guild actually // //todo print in guild actually
var returnMsg = // var returnMsg =
$"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action."; // $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(guild)}** is preventing this action.";
_log.Info(returnMsg); // _log.Info(returnMsg);
return; // return;
} // }
} // }
await cr.Send(usrMsg).ConfigureAwait(false); // await cr.Send(usrMsg).ConfigureAwait(false);
if (cr.AutoDeleteTrigger) // if (cr.AutoDeleteTrigger)
{ // {
try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { } // try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { }
} // }
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
_log.Warn("Sending CREmbed failed"); // _log.Warn("Sending CREmbed failed");
_log.Warn(ex); // _log.Warn(ex);
} // }
return; // return;
} //}
var exec3 = Environment.TickCount - execTime; var exec3 = Environment.TickCount - execTime;
string messageContent = usrMsg.Content; string messageContent = usrMsg.Content;
if (guild != null) ////todo alias mapping
{ // if (guild != null)
if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps)) // {
{ // if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps))
// {
var keys = maps.Keys // var keys = maps.Keys
.OrderByDescending(x => x.Length); // .OrderByDescending(x => x.Length);
var lowerMessageContent = messageContent.ToLowerInvariant(); // var lowerMessageContent = messageContent.ToLowerInvariant();
foreach (var k in keys) // foreach (var k in keys)
{ // {
string newMessageContent; // string newMessageContent;
if (lowerMessageContent.StartsWith(k + " ")) // if (lowerMessageContent.StartsWith(k + " "))
newMessageContent = maps[k] + messageContent.Substring(k.Length, messageContent.Length - k.Length); // newMessageContent = maps[k] + messageContent.Substring(k.Length, messageContent.Length - k.Length);
else if (lowerMessageContent == k) // else if (lowerMessageContent == k)
newMessageContent = maps[k]; // newMessageContent = maps[k];
else // else
continue; // continue;
_log.Info(@"--Mapping Command-- // _log.Info(@"--Mapping Command--
GuildId: {0} //GuildId: {0}
Trigger: {1} //Trigger: {1}
Mapping: {2}", guild.Id, messageContent, newMessageContent); //Mapping: {2}", guild.Id, messageContent, newMessageContent);
var oldMessageContent = messageContent; // var oldMessageContent = messageContent;
messageContent = newMessageContent; // messageContent = newMessageContent;
try { await usrMsg.Channel.SendConfirmAsync($"{oldMessageContent} => {newMessageContent}").ConfigureAwait(false); } catch { } // try { await usrMsg.Channel.SendConfirmAsync($"{oldMessageContent} => {newMessageContent}").ConfigureAwait(false); } catch { }
break; // break;
} // }
} // }
} // }
// execute the command and measure the time it took // 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); 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; execTime = Environment.TickCount - execTime;
if (exec.Result.IsSuccess) if (exec.Result.IsSuccess)
{ {
await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false); await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
await LogSuccessfulExecution(usrMsg, exec, channel, exec1, exec2, exec3, execTime).ConfigureAwait(false); await LogSuccessfulExecution(usrMsg, exec, channel, exec2, exec3, execTime).ConfigureAwait(false);
return;
} }
else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand) else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
{ {
LogErroredExecution(usrMsg, exec, channel, exec1, exec2, exec3, execTime); LogErroredExecution(usrMsg, exec, channel, exec2, exec3, execTime);
if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception) if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
{ {
if (exec.PermissionCache != null && exec.PermissionCache.Verbose) if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
try { await usrMsg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { } 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 // 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) // users who are voting on private polls (sending a number in a DM)
if (int.TryParse(usrMsg.Content, out int vote)) return; if (int.TryParse(usrMsg.Content, out int vote)) return;
await usrMsg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false); ////todo help
//await usrMsg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
await SelfCommands.HandleDmForwarding(usrMsg, ownerChannels).ConfigureAwait(false); ////todo selfcommands
} //await SelfCommands.HandleDmForwarding(usrMsg, ownerChannels).ConfigureAwait(false);
} }
} }
public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) public Task<ExecuteCommandResult> ExecuteCommandAsync(CommandContext context, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> ExecuteCommand(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); => ExecuteCommand(context, context.Message.Content.Substring(argPos), serviceProvider, multiMatchHandling);
public async Task<ExecuteCommandResult> ExecuteCommand(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) public async Task<ExecuteCommandResult> ExecuteCommand(CommandContext context, string input, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{ {
dependencyMap = dependencyMap ?? DependencyMap.Empty;
var searchResult = _commandService.Search(context, input); var searchResult = _commandService.Search(context, input);
if (!searchResult.IsSuccess) if (!searchResult.IsSuccess)
return new ExecuteCommandResult(null, null, searchResult); return new ExecuteCommandResult(null, null, searchResult);
@ -505,50 +508,55 @@ namespace NadekoBot.Services
var module = cmd.Module.GetTopLevelModule(); var module = cmd.Module.GetTopLevelModule();
if (context.Guild != null) 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));
}
////todo perms
if (module.Name == typeof(Permissions).Name) //PermissionCache pc = Permissions.GetCache(context.Guild.Id);
{ //if (!resetCommand && !pc.Permissions.CheckPermissions(context.Message, cmd.Aliases.First(), module.Name, out int index))
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)
//{ //{
// var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false); // var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand((SocketGuild)context.Guild)}** is preventing this action.";
// if (!success) // return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg));
//}
//if (module.Name == typeof(Permissions).Name)
//{ //{
// return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command.")); // 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."));
// } // }
//} //}
////////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" && ////todo perms
(GlobalPermissionCommands.BlockedCommands.Contains(cmd.Aliases.First().ToLowerInvariant()) || //if (cmd.Name != "resetglobalperms" &&
GlobalPermissionCommands.BlockedModules.Contains(module.Name.ToLowerInvariant()))) // (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.")); //{
} // 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 // Bot will ignore commands which are ran more often than what specified by
// GlobalCommandsCooldown constant (miliseconds) // GlobalCommandsCooldown constant (miliseconds)
if (!UsersOnShortCooldown.Add(context.Message.Author.Id)) if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.")); return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."));
if (CmdCdsCommands.HasCooldown(cmd, context.Guild, context.User)) ////todo cmdcds
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "That command is on a cooldown for you.")); //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.")); return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."));

View File

@ -2,25 +2,33 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public static class CurrencyHandler public class CurrencyHandler
{ {
public static async Task<bool> RemoveCurrencyAsync(IUser author, string reason, long amount, bool sendMessage) private readonly BotConfig _config;
private readonly DbHandler _db;
public CurrencyHandler(BotConfig config, DbHandler db)
{
_config = config;
_db = db;
}
public async Task<bool> RemoveCurrencyAsync(IUser author, string reason, long amount, bool sendMessage)
{ {
var success = await RemoveCurrencyAsync(author.Id, reason, amount); var success = await RemoveCurrencyAsync(author.Id, reason, amount);
if (success && sendMessage) 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; return success;
} }
public static async Task<bool> RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null) public async Task<bool> RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null)
{ {
if (amount < 0) if (amount < 0)
throw new ArgumentNullException(nameof(amount)); throw new ArgumentNullException(nameof(amount));
@ -28,7 +36,7 @@ namespace NadekoBot.Services
if (uow == null) if (uow == null)
{ {
using (uow = DbHandler.UnitOfWork()) using (uow = _db.UnitOfWork)
{ {
var toReturn = InternalRemoveCurrency(authorId, reason, amount, uow); var toReturn = InternalRemoveCurrency(authorId, reason, amount, uow);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
@ -39,7 +47,7 @@ namespace NadekoBot.Services
return InternalRemoveCurrency(authorId, reason, amount, uow); 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); var success = uow.Currency.TryUpdateState(authorId, -amount);
if (!success) if (!success)
@ -53,15 +61,15 @@ namespace NadekoBot.Services
return true; 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); await AddCurrencyAsync(author.Id, reason, amount);
if (sendMessage) 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) if (amount < 0)
throw new ArgumentNullException(nameof(amount)); throw new ArgumentNullException(nameof(amount));
@ -74,7 +82,7 @@ namespace NadekoBot.Services
}; };
if (uow == null) if (uow == null)
using (uow = DbHandler.UnitOfWork()) using (uow = _db.UnitOfWork)
{ {
uow.Currency.TryUpdateState(receiverId, amount); uow.Currency.TryUpdateState(receiverId, amount);
uow.CurrencyTransactions.Add(transaction); uow.CurrencyTransactions.Add(transaction);

View File

@ -1,6 +1,4 @@
using NadekoBot.Modules.Music.Classes; namespace NadekoBot.Services.Database.Models
namespace NadekoBot.Services.Database.Models
{ {
public class PlaylistSong : DbEntity public class PlaylistSong : DbEntity
{ {
@ -10,4 +8,12 @@ namespace NadekoBot.Services.Database.Models
public string Uri { get; set; } public string Uri { get; set; }
public string Query { get; set; } public string Query { get; set; }
} }
public enum MusicType
{
Radio,
Normal,
Local,
Soundcloud
}
} }

View File

@ -7,19 +7,17 @@ namespace NadekoBot.Services
{ {
public class DbHandler public class DbHandler
{ {
private static DbHandler _instance = null;
public static DbHandler Instance = _instance ?? (_instance = new DbHandler());
private readonly DbContextOptions options; private readonly DbContextOptions options;
private string connectionString { get; } private string connectionString { get; }
static DbHandler() { } static DbHandler() { }
private DbHandler() public DbHandler(IBotCredentials creds)
{ {
connectionString = NadekoBot.Credentials.Db.ConnectionString; connectionString = creds.Db.ConnectionString;
var optionsBuilder = new DbContextOptionsBuilder(); var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite(NadekoBot.Credentials.Db.ConnectionString); optionsBuilder.UseSqlite(creds.Db.ConnectionString);
options = optionsBuilder.Options; options = optionsBuilder.Options;
//switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant()) //switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant())
//{ //{
@ -44,10 +42,7 @@ namespace NadekoBot.Services
return context; return context;
} }
private IUnitOfWork GetUnitOfWork() => public IUnitOfWork UnitOfWork =>
new UnitOfWork(GetDbContext()); new UnitOfWork(GetDbContext());
public static IUnitOfWork UnitOfWork() =>
DbHandler.Instance.GetUnitOfWork();
} }
} }

View File

@ -15,13 +15,14 @@ namespace NadekoBot.Services.Discord
public event Action<SocketReaction> OnReactionRemoved = delegate { }; public event Action<SocketReaction> OnReactionRemoved = delegate { };
public event Action OnReactionsCleared = delegate { }; public event Action OnReactionsCleared = delegate { };
public ReactionEventWrapper(IUserMessage msg) public ReactionEventWrapper(DiscordShardedClient client, IUserMessage msg)
{ {
Message = msg ?? throw new ArgumentNullException(nameof(msg)); Message = msg ?? throw new ArgumentNullException(nameof(msg));
_client = client;
NadekoBot.Client.ReactionAdded += Discord_ReactionAdded; _client.ReactionAdded += Discord_ReactionAdded;
NadekoBot.Client.ReactionRemoved += Discord_ReactionRemoved; _client.ReactionRemoved += Discord_ReactionRemoved;
NadekoBot.Client.ReactionsCleared += Discord_ReactionsCleared; _client.ReactionsCleared += Discord_ReactionsCleared;
} }
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel) private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
@ -62,15 +63,17 @@ namespace NadekoBot.Services.Discord
public void UnsubAll() public void UnsubAll()
{ {
NadekoBot.Client.ReactionAdded -= Discord_ReactionAdded; _client.ReactionAdded -= Discord_ReactionAdded;
NadekoBot.Client.ReactionRemoved -= Discord_ReactionRemoved; _client.ReactionRemoved -= Discord_ReactionRemoved;
NadekoBot.Client.ReactionsCleared -= Discord_ReactionsCleared; _client.ReactionsCleared -= Discord_ReactionsCleared;
OnReactionAdded = null; OnReactionAdded = null;
OnReactionRemoved = null; OnReactionRemoved = null;
OnReactionsCleared = null; OnReactionsCleared = null;
} }
private bool disposing = false; private bool disposing = false;
private readonly DiscordShardedClient _client;
public void Dispose() public void Dispose()
{ {
if (disposing) if (disposing)

View File

@ -12,11 +12,14 @@ namespace NadekoBot.Services
{ {
public class GreetSettingsService public class GreetSettingsService
{ {
public ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache { get; } private readonly DbHandler _db;
public GreetSettingsService() public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache;
public GreetSettingsService(IEnumerable<GuildConfig> guildConfigs, DbHandler db)
{ {
GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create)); _db = db;
GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(guildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
} }
public GreetSettings GetOrAddSettingsForGuild(ulong guildId) public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
@ -27,7 +30,7 @@ namespace NadekoBot.Services
if (settings != null) if (settings != null)
return settings; return settings;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(guildId, set => set); var gc = uow.GuildConfigs.For(guildId, set => set);
settings = GreetSettings.Create(gc); settings = GreetSettings.Create(gc);
@ -47,7 +50,7 @@ namespace NadekoBot.Services
return false; return false;
} }
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
conf.DmGreetMessageText = settings.DmGreetMessageText?.SanitizeMentions(); conf.DmGreetMessageText = settings.DmGreetMessageText?.SanitizeMentions();
@ -78,7 +81,7 @@ namespace NadekoBot.Services
public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null) public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
{ {
bool enabled; bool enabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
@ -100,7 +103,7 @@ namespace NadekoBot.Services
throw new ArgumentNullException(nameof(message)); throw new ArgumentNullException(nameof(message));
bool greetMsgEnabled; bool greetMsgEnabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
conf.ChannelGreetMessageText = message; conf.ChannelGreetMessageText = message;
@ -117,7 +120,7 @@ namespace NadekoBot.Services
public async Task<bool> SetGreetDm(ulong guildId, bool? value = null) public async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
{ {
bool enabled; bool enabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
@ -138,7 +141,7 @@ namespace NadekoBot.Services
throw new ArgumentNullException(nameof(message)); throw new ArgumentNullException(nameof(message));
bool greetMsgEnabled; bool greetMsgEnabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId); var conf = uow.GuildConfigs.For(guildId);
conf.DmGreetMessageText = message; conf.DmGreetMessageText = message;
@ -155,7 +158,7 @@ namespace NadekoBot.Services
public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null) public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
{ {
bool enabled; bool enabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
@ -177,7 +180,7 @@ namespace NadekoBot.Services
throw new ArgumentNullException(nameof(message)); throw new ArgumentNullException(nameof(message));
bool byeMsgEnabled; bool byeMsgEnabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
conf.ChannelByeMessageText = message; conf.ChannelByeMessageText = message;
@ -196,7 +199,7 @@ namespace NadekoBot.Services
if (timer < 0 || timer > 600) if (timer < 0 || timer > 600)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guildId, set => set); var conf = uow.GuildConfigs.For(guildId, set => set);
conf.AutoDeleteByeMessagesTimer = timer; conf.AutoDeleteByeMessagesTimer = timer;

View File

@ -14,8 +14,10 @@ namespace NadekoBot.Services
string MashapeKey { get; } string MashapeKey { get; }
string LoLApiKey { get; } string LoLApiKey { get; }
string PatreonAccessToken { get; } string PatreonAccessToken { get; }
string CarbonKey { get; }
DBConfig Db { get; } DBConfig Db { get; }
string SoundCloudClientId { get; set; }
bool IsOwner(IUser u); bool IsOwner(IUser u);
} }

View File

@ -24,6 +24,6 @@ namespace NadekoBot.Services
ImmutableArray<byte> WifeMatrix { get; } ImmutableArray<byte> WifeMatrix { get; }
ImmutableArray<byte> RategirlDot { get; } ImmutableArray<byte> RategirlDot { get; }
Task<TimeSpan> Reload(); TimeSpan Reload();
} }
} }

View File

@ -0,0 +1,21 @@
using System.Collections.Concurrent;
using System.Globalization;
using Discord;
namespace NadekoBot.Services
{
public interface ILocalization
{
CultureInfo DefaultCultureInfo { get; }
ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
CultureInfo GetCultureInfo(IGuild guild);
CultureInfo GetCultureInfo(ulong? guildId);
void RemoveGuildCulture(IGuild guild);
void RemoveGuildCulture(ulong guildId);
void ResetDefaultCulture();
void SetDefaultCulture(CultureInfo ci);
void SetGuildCulture(IGuild guild, CultureInfo ci);
void SetGuildCulture(ulong guildId, CultureInfo ci);
}
}

View File

@ -1,9 +1,22 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public interface IStatsService public interface IStatsService
{ {
string Author { get; }
long CommandsRan { get; }
string Heap { get; }
string Library { get; }
long MessageCounter { get; }
double MessagesPerSecond { get; }
long TextChannels { get; }
long VoiceChannels { get; }
TimeSpan GetUptime();
string GetUptimeString(string separator = ", ");
void Initialize();
Task<string> Print(); Task<string> Print();
} }
} }

View File

@ -23,12 +23,14 @@ namespace NadekoBot.Services.Impl
private Logger _log { get; } private Logger _log { get; }
public GoogleApiService() public GoogleApiService(IBotCredentials creds)
{ {
_creds = creds;
var bcs = new BaseClientService.Initializer var bcs = new BaseClientService.Initializer
{ {
ApplicationName = "Nadeko Bot", ApplicationName = "Nadeko Bot",
ApiKey = NadekoBot.Credentials.GoogleApiKey, ApiKey = _creds.GoogleApiKey,
}; };
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
@ -59,6 +61,7 @@ namespace NadekoBot.Services.Impl
} }
private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?<id>[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?<id>[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled);
private readonly IBotCredentials _creds;
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1) public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
{ {
@ -110,7 +113,7 @@ namespace NadekoBot.Services.Impl
if (string.IsNullOrWhiteSpace(url)) if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); throw new ArgumentNullException(nameof(url));
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.GoogleApiKey)) if (string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
return url; return url;
try try

View File

@ -44,19 +44,13 @@ namespace NadekoBot.Services.Impl
public ImmutableArray<byte> WifeMatrix { get; private set; } public ImmutableArray<byte> WifeMatrix { get; private set; }
public ImmutableArray<byte> RategirlDot { get; private set; } public ImmutableArray<byte> RategirlDot { get; private set; }
private ImagesService() public ImagesService()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
this.Reload();
} }
public static async Task<IImagesService> Create() public TimeSpan Reload()
{
var srvc = new ImagesService();
await srvc.Reload().ConfigureAwait(false);
return srvc;
}
public Task<TimeSpan> Reload() => Task.Run(() =>
{ {
try try
{ {
@ -101,6 +95,6 @@ namespace NadekoBot.Services.Impl
_log.Error(ex); _log.Error(ex);
throw; throw;
} }
}); }
} }
} }

View File

@ -12,17 +12,19 @@ using NLog;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public class Localization public class Localization : ILocalization
{ {
private readonly Logger _log; private readonly Logger _log;
private readonly DbHandler _db;
public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; } public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
private Localization() { } private Localization() { }
public Localization(string defaultCulture, IDictionary<ulong, string> cultureInfoNames) public Localization(string defaultCulture, IDictionary<ulong, string> cultureInfoNames, DbHandler db)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db;
if (string.IsNullOrWhiteSpace(defaultCulture)) if (string.IsNullOrWhiteSpace(defaultCulture))
DefaultCultureInfo = new CultureInfo("en-US"); DefaultCultureInfo = new CultureInfo("en-US");
else else
@ -62,7 +64,7 @@ namespace NadekoBot.Services
return; return;
} }
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(guildId, set => set); var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = ci.Name; gc.Locale = ci.Name;
@ -80,7 +82,7 @@ namespace NadekoBot.Services
CultureInfo throwaway; CultureInfo throwaway;
if (GuildCultureInfos.TryRemove(guildId, out 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); var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = null; gc.Locale = null;
@ -91,7 +93,7 @@ namespace NadekoBot.Services
public void SetDefaultCulture(CultureInfo ci) public void SetDefaultCulture(CultureInfo ci)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var bc = uow.BotConfig.GetOrCreate(); var bc = uow.BotConfig.GetOrCreate();
bc.Locale = ci.Name; bc.Locale = ci.Name;

View File

@ -6,6 +6,7 @@ using System.Xml.Linq;
using NLog; using NLog;
using System.Diagnostics; using System.Diagnostics;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
@ -15,6 +16,10 @@ namespace NadekoBot.Services
private readonly ImmutableDictionary<string, ImmutableDictionary<string, string>> responseStrings; private readonly ImmutableDictionary<string, ImmutableDictionary<string, string>> responseStrings;
private readonly Logger _log; private readonly Logger _log;
/// <summary>
/// Used as failsafe in case response key doesn't exist in the selected or default language.
/// </summary>
private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
public NadekoStrings() public NadekoStrings()
{ {
@ -44,7 +49,7 @@ namespace NadekoBot.Services
return fileName.Substring(dotIndex, secondDotINdex - dotIndex); return fileName.Substring(dotIndex, secondDotINdex - dotIndex);
} }
public string GetString(string text, CultureInfo cultureInfo) private string GetString(string text, CultureInfo cultureInfo)
{ {
if (!responseStrings.TryGetValue(cultureInfo.Name.ToLowerInvariant(), out ImmutableDictionary<string, string> strings)) if (!responseStrings.TryGetValue(cultureInfo.Name.ToLowerInvariant(), out ImmutableDictionary<string, string> strings))
return null; return null;
@ -52,5 +57,34 @@ namespace NadekoBot.Services
strings.TryGetValue(text, out string val); strings.TryGetValue(text, out string val);
return 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.";
}
}
} }
} }

View File

@ -15,6 +15,7 @@ namespace NadekoBot.Services.Impl
public class StatsService : IStatsService public class StatsService : IStatsService
{ {
private readonly DiscordShardedClient _client; private readonly DiscordShardedClient _client;
private readonly IBotCredentials _creds;
private readonly DateTime _started; private readonly DateTime _started;
public const string BotVersion = "1.4"; public const string BotVersion = "1.4";
@ -36,10 +37,10 @@ namespace NadekoBot.Services.Impl
private readonly Timer _carbonitexTimer; private readonly Timer _carbonitexTimer;
public StatsService(DiscordShardedClient client, CommandHandler cmdHandler) public StatsService(DiscordShardedClient client, CommandHandler cmdHandler, IBotCredentials creds)
{ {
_client = client; _client = client;
_creds = creds;
_started = DateTime.Now; _started = DateTime.Now;
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter)); _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
@ -117,7 +118,7 @@ namespace NadekoBot.Services.Impl
_carbonitexTimer = new Timer(async (state) => _carbonitexTimer = new Timer(async (state) =>
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey)) if (string.IsNullOrWhiteSpace(_creds.CarbonKey))
return; return;
try try
{ {
@ -126,7 +127,7 @@ namespace NadekoBot.Services.Impl
using (var content = new FormUrlEncodedContent( using (var content = new FormUrlEncodedContent(
new Dictionary<string, string> { new Dictionary<string, string> {
{ "servercount", _client.Guilds.Count.ToString() }, { "servercount", _client.Guilds.Count.ToString() },
{ "key", NadekoBot.Credentials.CarbonKey }})) { "key", _creds.CarbonKey }}))
{ {
content.Headers.Clear(); content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
@ -158,7 +159,7 @@ namespace NadekoBot.Services.Impl
Author: [{Author}] | Library: [{Library}] Author: [{Author}] | Library: [{Library}]
Bot Version: [{BotVersion}] Bot Version: [{BotVersion}]
Bot ID: {curUser.Id} Bot ID: {curUser.Id}
Owner ID(s): {string.Join(", ", NadekoBot.Credentials.OwnerIds)} Owner ID(s): {string.Join(", ", _creds.OwnerIds)}
Uptime: {GetUptimeString()} Uptime: {GetUptimeString()}
Servers: {_client.Guilds.Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels} Servers: {_client.Guilds.Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
Commands Ran this session: {CommandsRan} Commands Ran this session: {CommandsRan}

View File

@ -1,6 +1,6 @@
using System; using System;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Services.Music
{ {
class PlaylistFullException : Exception class PlaylistFullException : Exception
{ {

View File

@ -8,16 +8,11 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; 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 public enum StreamState
{ {
@ -29,6 +24,7 @@ namespace NadekoBot.Modules.Music.Classes
public class MusicPlayer public class MusicPlayer
{ {
public const string MusicDataPath = "data/musicdata";
private IAudioClient AudioClient { get; set; } private IAudioClient AudioClient { get; set; }
/// <summary> /// <summary>
@ -58,6 +54,7 @@ namespace NadekoBot.Modules.Music.Classes
private readonly List<Song> _playlist = new List<Song>(); private readonly List<Song> _playlist = new List<Song>();
private readonly Logger _log; private readonly Logger _log;
private readonly IGoogleApiService _google;
public IReadOnlyCollection<Song> Playlist => _playlist; public IReadOnlyCollection<Song> Playlist => _playlist;
@ -88,9 +85,10 @@ namespace NadekoBot.Modules.Music.Classes
public event Action<Song, int> SongRemoved = delegate { }; public event Action<Song, int> SongRemoved = delegate { };
public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume) public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume, IGoogleApiService google)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_google = google;
OutputTextChannel = outputChannel; OutputTextChannel = outputChannel;
Volume = defaultVolume ?? 1.0f; 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)) var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
.Distinct(); .Distinct();
var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids); var durations = await _google.GetVideoDurationsAsync(ids);
toUpdate.ForEach(s => toUpdate.ForEach(s =>
{ {
@ -337,7 +335,6 @@ namespace NadekoBot.Modules.Music.Classes
} }
} }
}); });
} }
public void Destroy() public void Destroy()

View File

@ -1,24 +1,28 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Modules.Music.Classes;
using Discord; using Discord;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules; using NadekoBot.Modules;
using NadekoBot.Services.Impl;
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {
public class MusicService public class MusicService
{ {
private readonly IGoogleApiService _google; private readonly IGoogleApiService _google;
private readonly NadekoStrings _strings;
private readonly ILocalization _localization;
private GoogleApiService google;
public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>(); public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
public MusicService(IGoogleApiService google) public MusicService(IGoogleApiService google, NadekoStrings strings, ILocalization localization)
{ {
_google = google; _google = google;
_strings = strings;
_localization = localization;
} }
public MusicPlayer GetPlayer(ulong guildId) public MusicPlayer GetPlayer(ulong guildId)
@ -30,7 +34,7 @@ namespace NadekoBot.Services.Music
public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh) public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
{ {
string GetText(string text, params object[] replacements) => 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 => return MusicPlayers.GetOrAdd(guildId, server =>
{ {
@ -40,7 +44,7 @@ namespace NadekoBot.Services.Music
//todo move to cached variable //todo move to cached variable
vol = uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume; 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 playingMessage = null;
IUserMessage lastFinishedMessage = null; IUserMessage lastFinishedMessage = null;
mp.OnCompleted += async (s, song) => 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) 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) if (relatedVideos.Count > 0)
await QueueSong(await textCh.Guild.GetCurrentUserAsync(), await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
textCh, 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) 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) => 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) if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{ {

View File

@ -9,8 +9,9 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net; using System.Net;
using Discord;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Services.Music
{ {
public class SongInfo public class SongInfo
{ {
@ -29,7 +30,7 @@ namespace NadekoBot.Modules.Music.Classes
private string _queuerName; private string _queuerName;
public string QueuerName { get{ public string QueuerName { get{
return Discord.Format.Sanitize(_queuerName); return Format.Sanitize(_queuerName);
} set { _queuerName = value; } } } set { _queuerName = value; } }
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
@ -143,7 +144,7 @@ namespace NadekoBot.Modules.Music.Classes
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{ {
BytesSent = (ulong) SkipTo * 3840 * 50; 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 inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false); var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);

View File

@ -6,7 +6,7 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Services.Music
{ {
/// <summary> /// <summary>
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.

View File

@ -1,15 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using VideoLibrary; using VideoLibrary;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Services.Music
{ {
public static class SongHandler public static class SongHandler
{ {
@ -49,6 +47,7 @@ namespace NadekoBot.Modules.Music.Classes
}) })
{ TotalTime = TimeSpan.MaxValue }; { TotalTime = TimeSpan.MaxValue };
} }
var sc = SoundCloud.GetInstance(_creds);
if (SoundCloud.Default.IsSoundCloudLink(query)) if (SoundCloud.Default.IsSoundCloudLink(query))
{ {
var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false);

View File

@ -4,28 +4,34 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Services.Music
{ {
public class SoundCloud public class SoundCloud
{ {
private static readonly SoundCloud _instance = new SoundCloud(); private readonly IBotCredentials _creds;
public static SoundCloud Default => _instance;
//todo make a service
private static SoundCloud _instance = null;
public static SoundCloud GetInstance(IBotCredentials creds) => _instance ?? (_instance = new SoundCloud(creds));
static SoundCloud() { } static SoundCloud() { }
public SoundCloud() { } public SoundCloud(IBotCredentials creds)
{
_creds = creds;
}
public async Task<SoundCloudVideo> ResolveVideoAsync(string url) public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
{ {
if (string.IsNullOrWhiteSpace(url)) if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); throw new ArgumentNullException(nameof(url));
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId))
throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); throw new ArgumentNullException(nameof(_creds.SoundCloudClientId));
string response = ""; string response = "";
using (var http = new HttpClient()) 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)) if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) if (string.IsNullOrWhiteSpace(_creds.SoundCloudClientId))
throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); throw new ArgumentNullException(nameof(_creds.SoundCloudClientId));
var response = ""; var response = "";
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false); response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={_creds.SoundCloudClientId}").ConfigureAwait(false);
} }
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault(); var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault();
@ -74,8 +80,7 @@ namespace NadekoBot.Modules.Music.Classes
[JsonProperty("permalink_url")] [JsonProperty("permalink_url")]
public string TrackLink { get; set; } = ""; public string TrackLink { get; set; } = "";
public string artwork_url { get; set; } = ""; public string artwork_url { get; set; } = "";
[JsonIgnore] public string GetStreamLink(IBotCredentials creds) => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={creds.SoundCloudClientId}";
public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Credentials.SoundCloudClientId}";
} }
public class SoundCloudUser public class SoundCloudUser
{ {

View File

@ -0,0 +1,68 @@
using NadekoBot.Extensions;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;
namespace NadekoBot.Services.Searches
{
public class SearchesService
{
public async Task<string> DapiSearch(string tag, DapiSearchType type)
{
tag = tag?.Replace(" ", "_");
var website = "";
switch (type)
{
case DapiSearchType.Safebooru:
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Gelbooru:
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Rule34:
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Konachan:
website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Yandere:
website = $"https://yande.re/post.xml?limit=100&tags={tag}";
break;
}
try
{
var toReturn = await Task.Run(async () =>
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync(website).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)];
var url = node.Attributes["file_url"].Value;
if (!url.StartsWith("http"))
url = "https:" + url;
return url;
}
}).ConfigureAwait(false);
return toReturn;
}
catch
{
return null;
}
}
}
public enum DapiSearchType
{
Safebooru,
Gelbooru,
Konachan,
Rule34,
Yandere
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services
{
public interface INServiceProvider : IServiceProvider
{
T GetService<T>();
}
public class NServiceProvider : INServiceProvider
{
public class ServiceProviderBuilder
{
private ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();
public ServiceProviderBuilder Add<T>(T obj)
{
_dict.TryAdd(typeof(T), obj);
return this;
}
public NServiceProvider Build()
{
return new NServiceProvider(_dict);
}
}
private readonly ImmutableDictionary<Type, object> _services;
private NServiceProvider() { }
public NServiceProvider(IDictionary<Type, object> services)
{
this._services = services.ToImmutableDictionary();
}
public T GetService<T>()
{
return (T)((IServiceProvider)(this)).GetService(typeof(T));
}
object IServiceProvider.GetService(Type serviceType)
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
}
}
}

View File

@ -1,64 +0,0 @@
using Discord.Commands;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.CustomReactions;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.TypeReaders
{
public class CommandTypeReader : TypeReader
{
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{
input = input.ToUpperInvariant();
var cmd = NadekoBot.CommandService.Commands.FirstOrDefault(c =>
c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
if (cmd == null)
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
}
}
public class CommandOrCrTypeReader : CommandTypeReader
{
public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
{
input = input.ToUpperInvariant();
if (CustomReactions.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
}
var guild = context.Guild;
if (guild != null)
{
CustomReaction[] crs;
if (CustomReactions.GuildReactions.TryGetValue(guild.Id, out crs))
{
if (crs.Any(x => x.Trigger.ToUpperInvariant() == input))
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
}
}
}
var cmd = await base.Read(context, input);
if (cmd.IsSuccess)
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Aliases.First()));
}
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
}
}
public class CommandOrCrInfo
{
public string Name { get; set; }
public CommandOrCrInfo(string input)
{
this.Name = input;
}
}
}

View File

@ -18,8 +18,8 @@ namespace NadekoBot.Extensions
{ {
public static class Extensions public static class Extensions
{ {
private const string arrow_left = "⬅"; private static readonly IEmote arrow_left = Emote.Parse("⬅");
private const string arrow_right = "➡"; private static readonly IEmote arrow_right = Emote.Parse("➡");
public static string ToBase64(this string plainText) public static string ToBase64(this string plainText)
{ {
@ -27,8 +27,8 @@ namespace NadekoBot.Extensions
return Convert.ToBase64String(plainTextBytes); return Convert.ToBase64String(plainTextBytes);
} }
public static string RealSummary(this CommandInfo cmd) => string.Format(cmd.Summary, 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, cmd.Module.GetTopLevelModule().Prefix); public static string RealRemarks(this CommandInfo cmd) => string.Format(cmd.Remarks, ".");
public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false) public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
{ {
@ -40,7 +40,7 @@ namespace NadekoBot.Extensions
/// <summary> /// <summary>
/// danny kamisama /// danny kamisama
/// </summary> /// </summary>
public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true) public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true)
{ {
lastPage += 1; lastPage += 1;
var embed = pageFunc(currentPage); var embed = pageFunc(currentPage);
@ -53,6 +53,7 @@ namespace NadekoBot.Extensions
if (currentPage >= lastPage && lastPage == 1) if (currentPage >= lastPage && lastPage == 1)
return; return;
await msg.AddReactionAsync( arrow_left).ConfigureAwait(false); await msg.AddReactionAsync( arrow_left).ConfigureAwait(false);
await msg.AddReactionAsync(arrow_right).ConfigureAwait(false); await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
@ -62,7 +63,7 @@ namespace NadekoBot.Extensions
{ {
try try
{ {
if (r.Emoji.Name == arrow_left) if (r.Emote.Name == arrow_left.Name)
{ {
if (currentPage == 1) if (currentPage == 1)
return; return;
@ -71,7 +72,7 @@ namespace NadekoBot.Extensions
toSend.AddPaginatedFooter(currentPage, lastPage); toSend.AddPaginatedFooter(currentPage, lastPage);
await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); 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) if (lastPage == null || lastPage > currentPage)
{ {
@ -85,7 +86,7 @@ namespace NadekoBot.Extensions
catch (Exception ex) { Console.WriteLine(ex); } catch (Exception ex) { Console.WriteLine(ex); }
}; };
using (msg.OnReaction(changePage, changePage)) using (msg.OnReaction(client, changePage, changePage))
{ {
await Task.Delay(30000).ConfigureAwait(false); await Task.Delay(30000).ConfigureAwait(false);
} }
@ -101,12 +102,12 @@ namespace NadekoBot.Extensions
return embed.WithFooter(efb => efb.WithText(curPage.ToString())); return embed.WithFooter(efb => efb.WithText(curPage.ToString()));
} }
public static ReactionEventWrapper OnReaction(this IUserMessage msg, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null) public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
{ {
if (reactionRemoved == null) if (reactionRemoved == null)
reactionRemoved = delegate { }; reactionRemoved = delegate { };
var wrap = new ReactionEventWrapper(msg); var wrap = new ReactionEventWrapper(client, msg);
wrap.OnReactionAdded += reactionAdded; wrap.OnReactionAdded += reactionAdded;
wrap.OnReactionRemoved += reactionRemoved; wrap.OnReactionRemoved += reactionRemoved;
return wrap; return wrap;
@ -141,8 +142,6 @@ namespace NadekoBot.Extensions
return msg; return msg;
} }
public static string GetPrefix(this ModuleInfo module) => NadekoBot.ModulePrefixes[module.GetTopLevelModule().Name];
public static ModuleInfo GetTopLevelModule(this ModuleInfo module) public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
{ {
while (module.Parent != null) while (module.Parent != null)
@ -221,9 +220,6 @@ namespace NadekoBot.Extensions
public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) => public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) =>
await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false); await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false);
public static bool IsAuthor(this IUserMessage msg) =>
NadekoBot.Client.CurrentUser.Id == msg.Author.Id;
public static IEnumerable<IUser> Members(this IRole role) => public static IEnumerable<IUser> Members(this IRole role) =>
role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IUser>(); role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IUser>();

View File

@ -1,11 +1,6 @@
using Discord; 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 public static class MusicExtensions
{ {