NadekoBot/src/NadekoBot/Services/CommandHandler.cs

400 lines
18 KiB
C#
Raw Normal View History

using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using NLog;
using Discord.Commands;
2016-10-03 17:21:05 +00:00
using NadekoBot.Extensions;
2016-12-31 16:34:21 +00:00
using System.Collections.Concurrent;
2017-01-06 23:46:07 +00:00
using System.Threading;
2017-01-07 21:31:42 +00:00
using NadekoBot.DataStructures;
using System.Collections.Immutable;
2017-05-25 02:24:43 +00:00
using NadekoBot.DataStructures.ModuleBehaviors;
namespace NadekoBot.Services
{
public class GuildUserComparer : IEqualityComparer<IGuildUser>
{
2016-12-17 04:09:04 +00:00
public bool Equals(IGuildUser x, IGuildUser y) => x.Id == y.Id;
2016-12-17 04:09:04 +00:00
public int GetHashCode(IGuildUser obj) => obj.Id.GetHashCode();
}
public class CommandHandler
{
public const int GlobalCommandsCooldown = 750;
2017-01-07 21:31:42 +00:00
2017-01-15 01:08:14 +00:00
private readonly DiscordShardedClient _client;
private readonly CommandService _commandService;
private readonly Logger _log;
2017-05-22 23:59:31 +00:00
private readonly IBotCredentials _creds;
private readonly NadekoBot _bot;
private INServiceProvider _services;
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>();
public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; };
2016-12-31 16:34:21 +00:00
//userid/msg count
public ConcurrentDictionary<ulong, uint> UserMessagesSent { get; } = new ConcurrentDictionary<ulong, uint>();
2017-01-06 23:46:07 +00:00
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private readonly Timer _clearUsersOnShortCooldown;
2017-01-06 23:46:07 +00:00
2017-05-22 23:59:31 +00:00
public CommandHandler(DiscordShardedClient client, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
{
_client = client;
_commandService = commandService;
2017-05-22 23:59:31 +00:00
_creds = credentials;
_bot = bot;
_log = LogManager.GetCurrentClassLogger();
2017-01-06 23:46:07 +00:00
_clearUsersOnShortCooldown = new Timer(_ =>
2017-01-06 23:46:07 +00:00
{
UsersOnShortCooldown.Clear();
2017-01-07 21:31:42 +00:00
}, null, GlobalCommandsCooldown, GlobalCommandsCooldown);
}
2017-05-22 23:59:31 +00:00
public void AddServices(INServiceProvider services)
{
_services = services;
}
public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText)
{
2017-05-03 07:49:35 +00:00
if (guildId != null)
{
2017-05-22 23:59:31 +00:00
var guild = _client.GetGuild(guildId.Value);
2017-05-03 07:49:35 +00:00
var channel = guild?.GetChannel(channelId) as SocketTextChannel;
if (channel == null)
{
2017-05-03 07:49:35 +00:00
_log.Warn("Channel for external execution not found.");
return;
}
2017-05-03 07:49:35 +00:00
try
{
IUserMessage msg = await channel.SendMessageAsync(commandText).ConfigureAwait(false);
msg = (IUserMessage)await channel.GetMessageAsync(msg.Id).ConfigureAwait(false);
await TryRunCommand(guild, channel, msg).ConfigureAwait(false);
//msg.DeleteAfter(5);
}
2017-05-03 07:49:35 +00:00
catch { }
}
}
public Task StartHandling()
{
_client.MessageReceived += MessageReceivedHandler;
2017-04-15 00:54:19 +00:00
_client.MessageUpdated += (oldmsg, newMsg, channel) =>
{
2017-05-22 23:59:31 +00:00
var ignore = Task.Run(() =>
{
try
{
var usrMsg = newMsg as SocketUserMessage;
var guild = (usrMsg?.Channel as ITextChannel)?.Guild;
2017-05-22 23:59:31 +00:00
////todo invite filtering
//if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
// await WordFiltered(guild, usrMsg).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
return Task.CompletedTask;
});
return Task.CompletedTask;
};
return Task.CompletedTask;
}
private const float _oneThousandth = 1.0f / 1000;
2017-05-22 23:59:31 +00:00
2017-05-25 02:24:43 +00:00
private Task LogSuccessfulExecution(IUserMessage usrMsg, bool exec, ITextChannel channel, params int[] execPoints)
2017-01-07 21:31:42 +00:00
{
2017-05-22 23:59:31 +00:00
_log.Info("Command Executed after " + string.Join("/", execPoints.Select(x => x * _oneThousandth)) + "s\n\t" +
2017-01-07 21:31:42 +00:00
"User: {0}\n\t" +
"Server: {1}\n\t" +
"Channel: {2}\n\t" +
"Message: {3}",
usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
2017-05-22 23:59:31 +00:00
usrMsg.Content // {3}
2017-02-05 18:15:05 +00:00
);
2017-02-02 13:16:28 +00:00
return Task.CompletedTask;
2017-01-07 21:31:42 +00:00
}
2016-10-03 02:19:14 +00:00
2017-05-25 02:24:43 +00:00
private void LogErroredExecution(IUserMessage usrMsg, bool exec, ITextChannel channel, params int[] execPoints)
2017-01-07 21:31:42 +00:00
{
2017-05-22 23:59:31 +00:00
_log.Warn("Command Errored after " + string.Join("/", execPoints.Select(x => x * _oneThousandth)) + "s\n\t" +
"User: {0}\n\t" +
"Server: {1}\n\t" +
"Channel: {2}\n\t" +
"Message: {3}\n\t" +
"Error: {4}",
usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content,// {3}
2017-05-25 02:24:43 +00:00
exec
//exec.Result.ErrorReason // {4}
);
2017-01-07 21:31:42 +00:00
}
private async Task MessageReceivedHandler(SocketMessage msg)
2017-01-07 21:31:42 +00:00
{
await Task.Yield();
try
2017-01-07 21:31:42 +00:00
{
if (msg.Author.IsBot || !_bot.Ready) //no bots, wait until bot connected and initialized
return;
2017-01-06 20:56:47 +00:00
if (!(msg is SocketUserMessage usrMsg))
return;
#if !GLOBAL_NADEKO
// track how many messagges each user is sending
UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old);
#endif
2017-01-07 21:31:42 +00:00
var channel = msg.Channel as ISocketMessageChannel;
var guild = (msg.Channel as SocketTextChannel)?.Guild;
2017-01-07 21:31:42 +00:00
await TryRunCommand(guild, channel, usrMsg);
}
catch (Exception ex)
{
_log.Warn("Error in CommandHandler");
_log.Warn(ex);
if (ex.InnerException != null)
{
_log.Warn("Inner Exception of the error in CommandHandler");
_log.Warn(ex.InnerException);
}
}
}
2017-02-02 13:16:28 +00:00
public async Task TryRunCommand(SocketGuild guild, ISocketMessageChannel channel, IUserMessage usrMsg)
{
var execTime = Environment.TickCount;
2017-05-25 02:24:43 +00:00
foreach (var svc in _services)
{
if (svc is IEarlyBlocker blocker &&
await blocker.TryBlockEarly(_client, guild, usrMsg).ConfigureAwait(false))
2017-05-25 02:24:43 +00:00
{
_log.Info("Blocked User: [{0}] Message: [{1}] Service: [{2}]", usrMsg.Author, usrMsg.Content, svc.GetType().Name);
2017-05-25 02:24:43 +00:00
return;
}
}
var exec2 = Environment.TickCount - execTime;
foreach (var svc in _services)
{
if (svc is IEarlyBlockingExecutor exec &&
await exec.TryExecuteEarly(_client, guild, usrMsg).ConfigureAwait(false))
{
_log.Info("User [{0}] executed [{1}] in [{2}]", usrMsg.Author, usrMsg.Content, svc.GetType().Name);
return;
}
}
2017-01-07 21:31:42 +00:00
var exec3 = Environment.TickCount - execTime;
2017-02-05 18:15:05 +00:00
string messageContent = usrMsg.Content;
2017-05-22 23:59:31 +00:00
////todo alias mapping
// if (guild != null)
// {
// if (Modules.Utility.Utility.CommandMapCommands.AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps))
// {
// var keys = maps.Keys
// .OrderByDescending(x => x.Length);
// var lowerMessageContent = messageContent.ToLowerInvariant();
// foreach (var k in keys)
// {
// string newMessageContent;
// if (lowerMessageContent.StartsWith(k + " "))
// newMessageContent = maps[k] + messageContent.Substring(k.Length, messageContent.Length - k.Length);
// else if (lowerMessageContent == k)
// newMessageContent = maps[k];
// else
// continue;
// _log.Info(@"--Mapping Command--
//GuildId: {0}
//Trigger: {1}
//Mapping: {2}", guild.Id, messageContent, newMessageContent);
// var oldMessageContent = messageContent;
// messageContent = newMessageContent;
// try { await usrMsg.Channel.SendConfirmAsync($"{oldMessageContent} => {newMessageContent}").ConfigureAwait(false); } catch { }
// break;
// }
// }
// }
2016-12-21 11:52:01 +00:00
// execute the command and measure the time it took
2017-05-22 23:59:31 +00:00
if (messageContent.StartsWith(NadekoBot.Prefix))
{
2017-05-22 23:59:31 +00:00
var exec = await Task.Run(() => ExecuteCommandAsync(new CommandContext(_client, usrMsg), NadekoBot.Prefix.Length, _services, MultiMatchHandling.Best)).ConfigureAwait(false);
execTime = Environment.TickCount - execTime;
////todo commandHandler
if (exec)
{
2017-05-25 02:24:43 +00:00
// await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
await LogSuccessfulExecution(usrMsg, exec, channel as ITextChannel, exec2, exec3, execTime).ConfigureAwait(false);
return;
}
2017-05-25 02:24:43 +00:00
//else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand)
//{
// LogErroredExecution(usrMsg, exec, channel, exec2, exec3, execTime);
// if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception)
// {
// if (exec.PermissionCache != null && exec.PermissionCache.Verbose)
// try { await usrMsg.Channel.SendMessageAsync("⚠️ " + exec.Result.ErrorReason).ConfigureAwait(false); } catch { }
// }
// return;
//}
2017-05-22 23:59:31 +00:00
if (exec)
return;
}
foreach (var svc in _services)
{
if (svc is ILateExecutor exec)
{
await exec.LateExecute(_client, guild, usrMsg).ConfigureAwait(false);
}
}
}
2017-01-07 21:31:42 +00:00
2017-05-25 02:24:43 +00:00
public Task<bool> ExecuteCommandAsync(CommandContext context, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
2017-05-22 23:59:31 +00:00
=> ExecuteCommand(context, context.Message.Content.Substring(argPos), serviceProvider, multiMatchHandling);
2016-09-22 18:53:49 +00:00
2017-05-25 02:24:43 +00:00
public async Task<bool> ExecuteCommand(CommandContext context, string input, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
2016-12-18 00:52:53 +00:00
{
var searchResult = _commandService.Search(context, input);
2016-09-22 18:53:49 +00:00
if (!searchResult.IsSuccess)
2017-05-25 02:24:43 +00:00
return false;
2016-09-22 18:53:49 +00:00
var commands = searchResult.Commands;
for (int i = commands.Count - 1; i >= 0; i--)
{
2017-05-24 20:28:16 +00:00
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, serviceProvider).ConfigureAwait(false);
2016-09-22 18:53:49 +00:00
if (!preconditionResult.IsSuccess)
{
if (commands.Count == 1)
2017-05-25 02:24:43 +00:00
return false;
2016-09-22 18:53:49 +00:00
else
continue;
}
2016-12-18 00:52:53 +00:00
var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false);
2016-09-22 18:53:49 +00:00
if (!parseResult.IsSuccess)
{
if (parseResult.Error == CommandError.MultipleMatches)
{
TypeReaderValue[] argList, paramList;
switch (multiMatchHandling)
{
case MultiMatchHandling.Best:
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray();
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray();
parseResult = ParseResult.FromSuccess(argList, paramList);
break;
}
}
if (!parseResult.IsSuccess)
{
if (commands.Count == 1)
2017-05-25 02:24:43 +00:00
return false;
2016-09-22 18:53:49 +00:00
else
continue;
}
}
var cmd = commands[i].Command;
var resetCommand = cmd.Name == "resetperms";
2017-01-02 07:24:42 +00:00
var module = cmd.Module.GetTopLevelModule();
2016-12-18 00:52:53 +00:00
if (context.Guild != null)
2016-09-22 18:53:49 +00:00
{
2017-05-22 23:59:31 +00:00
////todo perms
//PermissionCache pc = Permissions.GetCache(context.Guild.Id);
//if (!resetCommand && !pc.Permissions.CheckPermissions(context.Message, cmd.Aliases.First(), module.Name, out int index))
//{
// var 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));
//}
2017-05-22 23:59:31 +00:00
//if (module.Name == typeof(Permissions).Name)
//{
2017-05-22 23:59:31 +00:00
// var guildUser = (IGuildUser)context.User;
// if (!guildUser.GetRoles().Any(r => r.Name.Trim().ToLowerInvariant() == pc.PermRole.Trim().ToLowerInvariant()) && guildUser.Id != guildUser.Guild.OwnerId)
// {
2017-05-22 23:59:31 +00:00
// 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 _cs.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."));
//// }
////}
2016-09-22 18:53:49 +00:00
}
2017-05-22 23:59:31 +00:00
////todo perms
//if (cmd.Name != "resetglobalperms" &&
// (GlobalPermissionCommands.BlockedCommands.Contains(cmd.Aliases.First().ToLowerInvariant()) ||
// GlobalPermissionCommands.BlockedModules.Contains(module.Name.ToLowerInvariant())))
//{
// return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"Command or module is blocked globally by the bot owner."));
//}
// Bot will ignore commands which are ran more often than what specified by
// GlobalCommandsCooldown constant (miliseconds)
if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
2017-05-25 02:24:43 +00:00
return false;
//return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.");
var commandName = cmd.Aliases.First();
foreach (var svc in _services)
{
if (svc is ILateBlocker exec &&
await exec.TryBlockLate(_client, context.Message, context.Guild, context.Channel, context.User, cmd.Module.GetTopLevelModule().Name, commandName).ConfigureAwait(false))
{
_log.Info("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName, svc.GetType().Name);
return false;
}
}
2017-05-25 02:24:43 +00:00
await commands[i].ExecuteAsync(context, parseResult, serviceProvider);
return true;
2016-09-22 18:53:49 +00:00
}
2017-05-25 02:24:43 +00:00
return false;
//return new ExecuteCommandResult(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."));
}
}
}