Typereaders will be autoloaded when module loads

This commit is contained in:
Master Kwoth 2017-10-09 02:52:46 +02:00
parent 72f36270dc
commit f3513779b7
60 changed files with 316 additions and 140 deletions

View File

@ -4,11 +4,17 @@ using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Modules.CustomReactions.Services; using NadekoBot.Modules.CustomReactions.Services;
using NadekoBot.Core.Common.TypeReaders;
using Discord.WebSocket;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class CommandTypeReader : TypeReader public class CommandTypeReader : NadekoTypeReader
{ {
public CommandTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
{ {
var _cmds = ((INServiceProvider)services).GetService<CommandService>(); var _cmds = ((INServiceProvider)services).GetService<CommandService>();
@ -28,9 +34,13 @@ namespace NadekoBot.Common.TypeReaders
return Task.FromResult(TypeReaderResult.FromSuccess(cmd)); return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
} }
} }
//todo dependency on the module
public class CommandOrCrTypeReader : CommandTypeReader public class CommandOrCrTypeReader : CommandTypeReader
{ {
public CommandOrCrTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{
}
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
{ {
input = input.ToUpperInvariant(); input = input.ToUpperInvariant();

View File

@ -1,40 +1,46 @@
//using System; using System;
//using System.Threading.Tasks; using System.Threading.Tasks;
//using Discord.Commands; using Discord.Commands;
//using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Administration.Services;
using NadekoBot.Core.Common.TypeReaders;
using Discord.WebSocket;
//namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
//{ {
// public class GuildDateTimeTypeReader : TypeReader public class GuildDateTimeTypeReader : NadekoTypeReader
// { {
// public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) public GuildDateTimeTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
// { {
// var _gts = (GuildTimezoneService)services.GetService(typeof(GuildTimezoneService)); }
// if (!DateTime.TryParse(input, out var dt))
// return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
// var tz = _gts.GetTimeZoneOrUtc(context.Guild.Id); public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
{
var _gts = (GuildTimezoneService)services.GetService(typeof(GuildTimezoneService));
if (!DateTime.TryParse(input, out var dt))
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
// return Task.FromResult(TypeReaderResult.FromSuccess(new GuildDateTime(tz, dt))); var tz = _gts.GetTimeZoneOrUtc(context.Guild.Id);
// }
// }
// public class GuildDateTime return Task.FromResult(TypeReaderResult.FromSuccess(new GuildDateTime(tz, dt)));
// { }
// public TimeZoneInfo Timezone { get; } }
// public DateTime CurrentGuildTime { get; }
// public DateTime InputTime { get; }
// public DateTime InputTimeUtc { get; }
// private GuildDateTime() { } public class GuildDateTime
{
public TimeZoneInfo Timezone { get; }
public DateTime CurrentGuildTime { get; }
public DateTime InputTime { get; }
public DateTime InputTimeUtc { get; }
// public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime) private GuildDateTime() { }
// {
// var now = DateTime.UtcNow; public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
// Timezone = guildTimezone; {
// CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone); var now = DateTime.UtcNow;
// InputTime = inputTime; Timezone = guildTimezone;
// InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc); CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
// } InputTime = inputTime;
// } InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
//} }
}
}

View File

@ -3,17 +3,19 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Core.Common.TypeReaders;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class GuildTypeReader : TypeReader public class GuildTypeReader : NadekoTypeReader
{ {
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
public GuildTypeReader(DiscordSocketClient client) public GuildTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{ {
_client = client; _client = client;
} }
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _) public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{ {
input = input.Trim().ToLowerInvariant(); input = input.Trim().ToLowerInvariant();

View File

@ -3,14 +3,16 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Core.Common.TypeReaders;
using Discord.WebSocket;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class ModuleTypeReader : TypeReader public class ModuleTypeReader : NadekoTypeReader
{ {
private readonly CommandService _cmds; private readonly CommandService _cmds;
public ModuleTypeReader(CommandService cmds) public ModuleTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{ {
_cmds = cmds; _cmds = cmds;
} }
@ -26,11 +28,11 @@ namespace NadekoBot.Common.TypeReaders
} }
} }
public class ModuleOrCrTypeReader : TypeReader public class ModuleOrCrTypeReader : NadekoTypeReader
{ {
private readonly CommandService _cmds; private readonly CommandService _cmds;
public ModuleOrCrTypeReader(CommandService cmds) public ModuleOrCrTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{ {
_cmds = cmds; _cmds = cmds;
} }

View File

@ -0,0 +1,18 @@
using Discord.Commands;
using Discord.WebSocket;
namespace NadekoBot.Core.Common.TypeReaders
{
public abstract class NadekoTypeReader : TypeReader
{
private readonly DiscordSocketClient _client;
private readonly CommandService _cmds;
private NadekoTypeReader() { }
public NadekoTypeReader(DiscordSocketClient client, CommandService cmds)
{
_client = client;
_cmds = cmds;
}
}
}

View File

@ -1,15 +1,21 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.TypeReaders.Models; using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Core.Common.TypeReaders;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
/// <summary> /// <summary>
/// Used instead of bool for more flexible keywords for true/false only in the permission module /// Used instead of bool for more flexible keywords for true/false only in the permission module
/// </summary> /// </summary>
public class PermissionActionTypeReader : TypeReader public class PermissionActionTypeReader : NadekoTypeReader
{ {
public PermissionActionTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _) public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{ {
input = input.ToUpperInvariant(); input = input.ToUpperInvariant();

View File

@ -308,7 +308,6 @@ namespace NadekoBot.Services.Database
.WithOne(x => x.XpSettings); .WithOne(x => x.XpSettings);
#endregion #endregion
//todo major bug
#region XpRoleReward #region XpRoleReward
modelBuilder.Entity<XpRoleReward>() modelBuilder.Entity<XpRoleReward>()
.HasIndex(x => new { x.XpSettingsId, x.Level }) .HasIndex(x => new { x.XpSettingsId, x.Level })

View File

@ -23,6 +23,7 @@ using NadekoBot.Services.Database;
using StackExchange.Redis; using StackExchange.Redis;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Runtime.Loader; using System.Runtime.Loader;
using NadekoBot.Core.Common.TypeReaders;
namespace NadekoBot namespace NadekoBot
{ {
@ -138,6 +139,8 @@ namespace NadekoBot
.AddManual(Client) .AddManual(Client)
.AddManual(CommandService) .AddManual(CommandService)
.AddManual(botConfigProvider) .AddManual(botConfigProvider)
//todo this needs to reload whenever a new service is supposed to be loaded
//except at startup for obvious reasons
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this) .AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow) .AddManual<IUnitOfWork>(uow)
@ -147,19 +150,50 @@ namespace NadekoBot
var commandHandler = Services.GetService<CommandHandler>(); var commandHandler = Services.GetService<CommandHandler>();
commandHandler.AddServices(Services); commandHandler.AddServices(Services);
LoadTypeReaders(typeof(NadekoBot).Assembly);
//setup typereaders //setup typereaders
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader()); CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader(Client, CommandService));
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader()); CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader(Client, CommandService));
//todo module dependency //todo module dependency
CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader()); CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader(Client, CommandService));
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService)); CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(Client, CommandService));
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService)); CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(Client, CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client)); CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client, CommandService));
//CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader()); //CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader());
} }
Services.Unload(typeof(IUnitOfWork)); // unload it after the startup Services.Unload(typeof(IUnitOfWork)); // unload it after the startup
} }
private IEnumerable<NadekoTypeReader> LoadTypeReaders(Assembly assembly)
{
Type[] allTypes;
try
{
allTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<NadekoTypeReader>();
}
var filteredTypes = allTypes
.Where(x => x.IsSubclassOf(typeof(NadekoTypeReader))
&& !x.IsAbstract);
var toReturn = new List<NadekoTypeReader>();
foreach (var ft in filteredTypes)
{
//:yayyy:
var x = (NadekoTypeReader)Activator.CreateInstance(ft, Client, CommandService);
CommandService.AddTypeReader(x.GetType(), x);
toReturn.Add(x);
_log.Info("Loaded {0} typereader.", x.GetType().Name);
}
return toReturn;
}
private async Task LoginAsync(string token) private async Task LoginAsync(string token)
{ {
var clientReady = new TaskCompletionSource<bool>(); var clientReady = new TaskCompletionSource<bool>();
@ -254,7 +288,6 @@ namespace NadekoBot
Ready.TrySetResult(true); Ready.TrySetResult(true);
HandleStatusChanges(); HandleStatusChanges();
_log.Info($"Shard {Client.ShardId} ready."); _log.Info($"Shard {Client.ShardId} ready.");
//_log.Info(await stats.Print().ConfigureAwait(false));
} }
private Task Client_Log(LogMessage arg) private Task Client_Log(LogMessage arg)
@ -400,6 +433,7 @@ namespace NadekoBot
_log.Info("Unloaded {0} types.", i); _log.Info("Unloaded {0} types.", i);
} }
return true; return true;
} }
finally finally
@ -426,6 +460,13 @@ namespace NadekoBot
$"NadekoBot.Modules.{name}.dll")); $"NadekoBot.Modules.{name}.dll"));
var types = Services.LoadFrom(package); var types = Services.LoadFrom(package);
var added = await CommandService.AddModulesAsync(package).ConfigureAwait(false); var added = await CommandService.AddModulesAsync(package).ConfigureAwait(false);
var trs = LoadTypeReaders(package);
/* i don't have to unload typereaders
* (and there's no api for it)
* because they get overwritten anyway, and since
* the only time I'd unload typereaders, is when unloading a module
* which means they won't have a chance to be used
* */
_log.Info("Loaded {0} modules and {1} types.", added.Count(), types.Count()); _log.Info("Loaded {0} modules and {1} types.", added.Count(), types.Count());
_packageModules.Add(name, added); _packageModules.Add(name, added);
_packageTypes.Add(name, types); _packageTypes.Add(name, types);

View File

@ -15,4 +15,8 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
</Project> </Project>

View File

@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Gambling
Depraved, Depraved,
Harlot Harlot
} }
//todo unclaimed waifus should lose 5% of their value a day //todo unclaimed waifus should lose 3% of their value a day
[Group] [Group]
public class WaifuClaimCommands : NadekoSubmodule public class WaifuClaimCommands : NadekoSubmodule
{ {

View File

@ -11,6 +11,7 @@ namespace NadekoBot.Modules.Games.Common.Hangman
public class Hangman : IDisposable public class Hangman : IDisposable
{ {
public string TermType { get; } public string TermType { get; }
public TermPool TermPool { get; }
public HangmanObject Term { get; } public HangmanObject Term { get; }
public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c => public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c =>
@ -56,10 +57,11 @@ namespace NadekoBot.Modules.Games.Common.Hangman
public Task EndedTask => _endingCompletionSource.Task; public Task EndedTask => _endingCompletionSource.Task;
public Hangman(string type) public Hangman(string type, TermPool tp = null)
{ {
this.TermType = type.Trim().ToLowerInvariant().ToTitleCase(); this.TermType = type.Trim().ToLowerInvariant().ToTitleCase();
this.Term = TermPool.GetTerm(type); this.TermPool = tp ?? new TermPool();
this.Term = this.TermPool.GetTerm(type);
} }
private void AddError() private void AddError()

View File

@ -5,27 +5,35 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog;
namespace NadekoBot.Modules.Games.Common.Hangman namespace NadekoBot.Modules.Games.Common.Hangman
{ {
public class TermPool public class TermPool
{ {
const string termsPath = "data/hangman3.json"; const string termsPath = "data/hangman3.json";
public static IReadOnlyDictionary<string, HangmanObject[]> Data { get; } = new Dictionary<string, HangmanObject[]>(); private readonly Logger _log;
static TermPool()
public IReadOnlyDictionary<string, HangmanObject[]> Data { get; } = new Dictionary<string, HangmanObject[]>();
public TermPool()
{ {
_log = LogManager.GetCurrentClassLogger();
try try
{ {
Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath)); Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
Data = Data.ToDictionary(
x => x.Key.ToLowerInvariant(),
x => x.Value);
} }
catch (Exception) catch (Exception ex)
{ {
//ignored _log.Warn(ex);
} }
} }
public static HangmanObject GetTerm(string type) public HangmanObject GetTerm(string type)
{ {
type = type?.Trim().ToLowerInvariant();
var rng = new NadekoRandom(); var rng = new NadekoRandom();
if (type == "random") if (type == "random")

View File

@ -26,14 +26,14 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Hangmanlist() public async Task Hangmanlist()
{ {
await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join("\n", TermPool.Data.Keys)); await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join("\n", _service.TermPool.Data.Keys));
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Hangman([Remainder]string type = "random") public async Task Hangman([Remainder]string type = "random")
{ {
var hm = new Hangman(type); var hm = new Hangman(type, _service.TermPool);
if (!_service.HangmanGames.TryAdd(Context.Channel.Id, hm)) if (!_service.HangmanGames.TryAdd(Context.Channel.Id, hm))
{ {

View File

@ -19,4 +19,8 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
</Project> </Project>

View File

@ -46,7 +46,10 @@ namespace NadekoBot.Modules.Games.Services
//channelId, game //channelId, game
public ConcurrentDictionary<ulong, Acrophobia> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, Acrophobia>(); public ConcurrentDictionary<ulong, Acrophobia> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, Acrophobia>();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>(); public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
public ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>(); public ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>();
public TermPool TermPool { get; } = new TermPool();
public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>(); public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>(); public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>(); public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();

View File

@ -16,4 +16,8 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
</Project> </Project>

View File

@ -2,11 +2,4 @@
namespace NadekoBot.Modules.NSFW.Exceptions namespace NadekoBot.Modules.NSFW.Exceptions
{ {
public class TagBlacklistedException : Exception
{
public TagBlacklistedException() : base("Tag you used is blacklisted.")
{
}
}
} }

View File

@ -12,6 +12,7 @@ using NadekoBot.Common.Collections;
using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Common;
using NadekoBot.Modules.Searches.Services; using NadekoBot.Modules.Searches.Services;
using NadekoBot.Modules.NSFW.Exceptions; using NadekoBot.Modules.NSFW.Exceptions;
using NadekoBot.Modules.Searches.Exceptions;
namespace NadekoBot.Modules.NSFW namespace NadekoBot.Modules.NSFW
{ {
@ -85,7 +86,7 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0) if (interval == 0)
{ {
if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; if (!_service.AutoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("stopped").ConfigureAwait(false); await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
@ -112,7 +113,7 @@ namespace NadekoBot.Modules.NSFW
} }
}, null, interval * 1000, interval * 1000); }, null, interval * 1000, interval * 1000);
_autoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => _service.AutoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{ {
old.Change(Timeout.Infinite, Timeout.Infinite); old.Change(Timeout.Infinite, Timeout.Infinite);
return t; return t;
@ -131,7 +132,7 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0) if (interval == 0)
{ {
if (!_autoBoobTimers.TryRemove(Context.Channel.Id, out t)) return; if (!_service.AutoBoobTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("stopped").ConfigureAwait(false); await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
@ -153,7 +154,7 @@ namespace NadekoBot.Modules.NSFW
} }
}, null, interval * 1000, interval * 1000); }, null, interval * 1000, interval * 1000);
_autoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => _service.AutoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{ {
old.Change(Timeout.Infinite, Timeout.Infinite); old.Change(Timeout.Infinite, Timeout.Infinite);
return t; return t;
@ -170,7 +171,7 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0) if (interval == 0)
{ {
if (!_autoButtTimers.TryRemove(Context.Channel.Id, out t)) return; if (!_service.AutoButtTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("stopped").ConfigureAwait(false); await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
@ -192,7 +193,7 @@ namespace NadekoBot.Modules.NSFW
} }
}, null, interval * 1000, interval * 1000); }, null, interval * 1000, interval * 1000);
_autoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => _service.AutoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{ {
old.Change(Timeout.Infinite, Timeout.Infinite); old.Change(Timeout.Infinite, Timeout.Infinite);
return t; return t;

View File

@ -17,7 +17,11 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
<ProjectReference Include="..\NadekoBot.Module.Searches\NadekoBot.Modules.Searches.csproj" /> <ProjectReference Include="..\NadekoBot.Modules.Searches\NadekoBot.Modules.Searches.csproj" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -15,4 +15,8 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,12 @@
using System;
namespace NadekoBot.Modules.Searches.Exceptions
{
public class TagBlacklistedException : Exception
{
public TagBlacklistedException() : base("Tag you used is blacklisted.")
{
}
}
}

View File

@ -17,6 +17,7 @@ using System.Net.Http;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using AngleSharp; using AngleSharp;
using System.Threading; using System.Threading;
using NadekoBot.Modules.Searches.Exceptions;
namespace NadekoBot.Modules.Searches.Services namespace NadekoBot.Modules.Searches.Services
{ {
@ -43,9 +44,9 @@ namespace NadekoBot.Modules.Searches.Services
private readonly ConcurrentDictionary<ulong, SearchImageCacher> _imageCacher = new ConcurrentDictionary<ulong, SearchImageCacher>(); private readonly ConcurrentDictionary<ulong, SearchImageCacher> _imageCacher = new ConcurrentDictionary<ulong, SearchImageCacher>();
//todo clear when module unloaded //todo clear when module unloaded
public ConcurrentDictionary<ulong, Timer> _autoHentaiTimers { get; } = new ConcurrentDictionary<ulong, Timer>(); public ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> _autoBoobTimers { get; } = new ConcurrentDictionary<ulong, Timer>(); public ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> _autoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>(); public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>(); private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>();
@ -147,8 +148,7 @@ namespace NadekoBot.Modules.Searches.Services
if (blacklistedTags if (blacklistedTags
.Any(x => tag.ToLowerInvariant().Contains(x))) .Any(x => tag.ToLowerInvariant().Contains(x)))
{ {
//todo tag blacklisted throw new TagBlacklistedException();
throw new Exception();
} }
var cacher = _imageCacher.GetOrAdd(guild.Value, (key) => new SearchImageCacher()); var cacher = _imageCacher.GetOrAdd(guild.Value, (key) => new SearchImageCacher());
@ -231,12 +231,12 @@ namespace NadekoBot.Modules.Searches.Services
public Task Unload() public Task Unload()
{ {
_autoBoobTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoBoobTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
_autoBoobTimers.Clear(); AutoBoobTimers.Clear();
_autoButtTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoButtTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
_autoButtTimers.Clear(); AutoButtTimers.Clear();
_autoHentaiTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoHentaiTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
_autoHentaiTimers.Clear(); AutoHentaiTimers.Clear();
_imageCacher.Clear(); _imageCacher.Clear();
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00832" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -20,4 +20,12 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<None Include="NadekoBot.Modules.Searches.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -147,59 +147,57 @@ namespace NadekoBot.Modules.Utility
Format.Bold(rep.Repeater.Interval.Minutes.ToString()))).ConfigureAwait(false); Format.Bold(rep.Repeater.Interval.Minutes.ToString()))).ConfigureAwait(false);
} }
//todo guild date time [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(1)]
public async Task Repeat(GuildDateTime gt, [Remainder] string message)
{
if (!_service.RepeaterReady)
return;
//[NadekoCommand, Usage, Description, Aliases] if (string.IsNullOrWhiteSpace(message))
//[RequireContext(ContextType.Guild)] return;
//[RequireUserPermission(GuildPermission.ManageMessages)]
//[Priority(1)]
//public async Task Repeat(GuildDateTime gt, [Remainder] string message)
//{
// if (!_service.RepeaterReady)
// return;
// if (string.IsNullOrWhiteSpace(message)) var toAdd = new GuildRepeater()
// return; {
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id,
Interval = TimeSpan.FromHours(24),
StartTimeOfDay = gt.InputTimeUtc.TimeOfDay,
Message = message
};
// var toAdd = new GuildRepeater() using (var uow = _db.UnitOfWork)
// { {
// ChannelId = Context.Channel.Id, var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
// GuildId = Context.Guild.Id,
// Interval = TimeSpan.FromHours(24),
// StartTimeOfDay = gt.InputTimeUtc.TimeOfDay,
// Message = message
// };
// using (var uow = _db.UnitOfWork) if (gc.GuildRepeaters.Count >= 5)
// { return;
// var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters)); gc.GuildRepeaters.Add(toAdd);
// if (gc.GuildRepeaters.Count >= 5) await uow.CompleteAsync().ConfigureAwait(false);
// return; }
// gc.GuildRepeaters.Add(toAdd);
// await uow.CompleteAsync().ConfigureAwait(false); var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd);
// }
// var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd); _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] { rep }), (key, old) =>
{
old.Enqueue(rep);
return old;
});
// _service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] { rep }), (key, old) => var secondPart = GetText("repeater_initial",
// { Format.Bold(rep.InitialInterval.Hours.ToString()),
// old.Enqueue(rep); Format.Bold(rep.InitialInterval.Minutes.ToString()));
// return old;
// });
// var secondPart = GetText("repeater_initial", await Context.Channel.SendConfirmAsync(
// Format.Bold(rep.InitialInterval.Hours.ToString()), "🔁 " + GetText("repeater",
// Format.Bold(rep.InitialInterval.Minutes.ToString())); Format.Bold(((IGuildUser)Context.User).GuildPermissions.MentionEveryone ? rep.Repeater.Message : rep.Repeater.Message.SanitizeMentions()),
Format.Bold(rep.Repeater.Interval.Days.ToString()),
// await Context.Channel.SendConfirmAsync( Format.Bold(rep.Repeater.Interval.Hours.ToString()),
// "🔁 " + GetText("repeater", Format.Bold(rep.Repeater.Interval.Minutes.ToString())) + " " + secondPart).ConfigureAwait(false);
// Format.Bold(((IGuildUser)Context.User).GuildPermissions.MentionEveryone ? rep.Repeater.Message : rep.Repeater.Message.SanitizeMentions()), }
// Format.Bold(rep.Repeater.Interval.Days.ToString()),
// Format.Bold(rep.Repeater.Interval.Hours.ToString()),
// Format.Bold(rep.Repeater.Interval.Minutes.ToString())) + " " + secondPart).ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]

View File

@ -19,4 +19,8 @@
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
</Project> </Project>

View File

@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Xp", "Nad
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Utility", "NadekoBot.Modules.Utility\NadekoBot.Modules.Utility.csproj", "{07606931-CB55-4D20-8369-4E086B00EC52}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Utility", "NadekoBot.Modules.Utility\NadekoBot.Modules.Utility.csproj", "{07606931-CB55-4D20-8369-4E086B00EC52}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Searches", "NadekoBot.Module.Searches\NadekoBot.Modules.Searches.csproj", "{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Nsfw", "NadekoBot.Modules.Nsfw\NadekoBot.Modules.Nsfw.csproj", "{75ED72EC-7AB3-4B12-A2DA-3655C740B356}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Nsfw", "NadekoBot.Modules.Nsfw\NadekoBot.Modules.Nsfw.csproj", "{75ED72EC-7AB3-4B12-A2DA-3655C740B356}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Games", "NadekoBot.Modules.Games\NadekoBot.Modules.Games.csproj", "{FF6BDE61-24B4-4DC2-99EE-409EA1650180}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Games", "NadekoBot.Modules.Games\NadekoBot.Modules.Games.csproj", "{FF6BDE61-24B4-4DC2-99EE-409EA1650180}"
@ -31,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Pokemon",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Music", "NadekoBot.Modules.Music\NadekoBot.Modules.Music.csproj", "{674E28A6-30B1-413D-BBD3-E5F71614A00F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Music", "NadekoBot.Modules.Music\NadekoBot.Modules.Music.csproj", "{674E28A6-30B1-413D-BBD3-E5F71614A00F}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Searches", "NadekoBot.Modules.Searches\NadekoBot.Modules.Searches.csproj", "{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -62,12 +62,6 @@ Global
{07606931-CB55-4D20-8369-4E086B00EC52}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU {07606931-CB55-4D20-8369-4E086B00EC52}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{07606931-CB55-4D20-8369-4E086B00EC52}.Release|Any CPU.ActiveCfg = Release|Any CPU {07606931-CB55-4D20-8369-4E086B00EC52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07606931-CB55-4D20-8369-4E086B00EC52}.Release|Any CPU.Build.0 = Release|Any CPU {07606931-CB55-4D20-8369-4E086B00EC52}.Release|Any CPU.Build.0 = Release|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}.Release|Any CPU.Build.0 = Release|Any CPU
{75ED72EC-7AB3-4B12-A2DA-3655C740B356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {75ED72EC-7AB3-4B12-A2DA-3655C740B356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75ED72EC-7AB3-4B12-A2DA-3655C740B356}.Debug|Any CPU.Build.0 = Debug|Any CPU {75ED72EC-7AB3-4B12-A2DA-3655C740B356}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75ED72EC-7AB3-4B12-A2DA-3655C740B356}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU {75ED72EC-7AB3-4B12-A2DA-3655C740B356}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
@ -98,6 +92,12 @@ Global
{674E28A6-30B1-413D-BBD3-E5F71614A00F}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU {674E28A6-30B1-413D-BBD3-E5F71614A00F}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{674E28A6-30B1-413D-BBD3-E5F71614A00F}.Release|Any CPU.ActiveCfg = Release|Any CPU {674E28A6-30B1-413D-BBD3-E5F71614A00F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{674E28A6-30B1-413D-BBD3-E5F71614A00F}.Release|Any CPU.Build.0 = Release|Any CPU {674E28A6-30B1-413D-BBD3-E5F71614A00F}.Release|Any CPU.Build.0 = Release|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -107,12 +107,12 @@ Global
{A6CCEFBD-DCF2-482C-9643-47664683548F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {A6CCEFBD-DCF2-482C-9643-47664683548F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{07606931-CB55-4D20-8369-4E086B00EC52} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {07606931-CB55-4D20-8369-4E086B00EC52} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{8BEE9984-3EB3-45BE-A5CF-0DB912626B81} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{75ED72EC-7AB3-4B12-A2DA-3655C740B356} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {75ED72EC-7AB3-4B12-A2DA-3655C740B356} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{FF6BDE61-24B4-4DC2-99EE-409EA1650180} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {FF6BDE61-24B4-4DC2-99EE-409EA1650180} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{6436A700-694E-412C-92AE-B793FCD44E84} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {6436A700-694E-412C-92AE-B793FCD44E84} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{30463C26-555B-4760-9459-A16DFA015DFA} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {30463C26-555B-4760-9459-A16DFA015DFA} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{674E28A6-30B1-413D-BBD3-E5F71614A00F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {674E28A6-30B1-413D-BBD3-E5F71614A00F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{ED6DCB6E-66BE-45F4-A1D6-67FE01BCACD9} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}

20
watchbuildall.ps1 Normal file
View File

@ -0,0 +1,20 @@
$cur = (Get-Item -Path ".\" -Verbose).FullName
function WatchBuild([string] $path, [string] $args)
{
cd $cur
cd $path
cmd.exe /c "dotnet watch"
cd $cur
}
WatchBuild(".\NadekoBot.Modules.CustomReactions")
WatchBuild(".\NadekoBot.Modules.Gambling")
WatchBuild(".\NadekoBot.Modules.Games")
WatchBuild(".\NadekoBot.Modules.Music")
WatchBuild(".\NadekoBot.Modules.Nsfw")
WatchBuild(".\NadekoBot.Modules.Pokemon")
WatchBuild(".\NadekoBot.Modules.Searches")
WatchBuild(".\NadekoBot.Modules.Utility")
WatchBuild(".\NadekoBot.Modules.Xp")