2016-08-29 22:47:04 +00:00
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 ;
2017-05-19 08:02:29 +00:00
using System.Collections.Immutable ;
2017-05-25 02:24:43 +00:00
using NadekoBot.DataStructures.ModuleBehaviors ;
2016-08-29 22:47:04 +00:00
namespace NadekoBot.Services
{
2017-02-26 21:05:17 +00:00
public class GuildUserComparer : IEqualityComparer < IGuildUser >
2016-10-10 08:06:06 +00:00
{
2016-12-17 04:09:04 +00:00
public bool Equals ( IGuildUser x , IGuildUser y ) = > x . Id = = y . Id ;
2016-10-10 08:06:06 +00:00
2016-12-17 04:09:04 +00:00
public int GetHashCode ( IGuildUser obj ) = > obj . Id . GetHashCode ( ) ;
2016-10-10 08:06:06 +00:00
}
2016-08-29 22:47:04 +00:00
public class CommandHandler
{
2017-01-18 20:52:42 +00:00
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 ;
2017-01-14 16:37:11 +00:00
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 ;
2016-08-29 22:47:04 +00:00
2017-05-19 08:02:29 +00:00
private ImmutableArray < AsyncLazy < IDMChannel > > ownerChannels { get ; set ; } = new ImmutableArray < AsyncLazy < IDMChannel > > ( ) ;
2016-10-10 08:06:06 +00:00
2017-03-31 12:07:18 +00:00
public event Func < IUserMessage , CommandInfo , Task > CommandExecuted = delegate { return Task . CompletedTask ; } ;
2016-08-30 00:17:21 +00:00
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 > ( ) ;
2017-02-26 21:05:17 +00:00
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 )
2016-08-29 22:47:04 +00:00
{
_client = client ;
_commandService = commandService ;
2017-05-22 23:59:31 +00:00
_creds = credentials ;
_bot = bot ;
2016-08-29 22:47:04 +00:00
_log = LogManager . GetCurrentClassLogger ( ) ;
2017-01-06 23:46:07 +00:00
2017-02-26 21:05:17 +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 ) ;
2016-10-10 08:06:06 +00:00
}
2017-05-02 18:16:34 +00:00
2017-05-22 23:59:31 +00:00
public void AddServices ( INServiceProvider services )
{
_services = services ;
}
2017-05-02 18:16:34 +00:00
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-02 18:16:34 +00:00
{
2017-05-03 07:49:35 +00:00
_log . Warn ( "Channel for external execution not found." ) ;
return ;
}
2017-05-02 18:16:34 +00:00
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-02 18:16:34 +00:00
}
2017-05-03 07:49:35 +00:00
catch { }
}
2017-05-02 18:16:34 +00:00
}
2017-02-17 11:05:05 +00:00
public Task StartHandling ( )
2016-10-10 08:06:06 +00:00
{
2016-08-29 22:47:04 +00:00
_client . MessageReceived + = MessageReceivedHandler ;
2017-04-15 00:54:19 +00:00
_client . MessageUpdated + = ( oldmsg , newMsg , channel ) = >
2017-02-17 14:20:28 +00:00
{
2017-05-22 23:59:31 +00:00
var ignore = Task . Run ( ( ) = >
2017-02-17 14:20:28 +00:00
{
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);
2017-02-17 14:20:28 +00:00
}
catch ( Exception ex )
{
_log . Warn ( ex ) ;
}
return Task . CompletedTask ;
} ) ;
return Task . CompletedTask ;
} ;
2017-02-17 11:05:05 +00:00
return Task . CompletedTask ;
2016-08-29 22:47:04 +00:00
}
2017-02-26 21:05:17 +00:00
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" +
2017-01-18 20:52:42 +00:00
"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-18 20:52:42 +00:00
) ;
2017-01-07 21:31:42 +00:00
}
2017-05-29 04:13:22 +00:00
private async Task MessageReceivedHandler ( SocketMessage msg )
2017-01-07 21:31:42 +00:00
{
2017-05-29 04:13:22 +00:00
await Task . Yield ( ) ;
try
2017-01-07 21:31:42 +00:00
{
2017-05-29 04:13:22 +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
2017-05-29 04:13:22 +00:00
if ( ! ( msg is SocketUserMessage usrMsg ) )
return ;
2017-01-22 20:06:10 +00:00
#if ! GLOBAL_NADEKO
2017-05-29 04:13:22 +00:00
// track how many messagges each user is sending
UserMessagesSent . AddOrUpdate ( usrMsg . Author . Id , 1 , ( key , old ) = > + + old ) ;
2017-01-22 20:06:10 +00:00
#endif
2017-01-07 21:31:42 +00:00
2017-05-29 04:13:22 +00:00
var channel = msg . Channel as ISocketMessageChannel ;
var guild = ( msg . Channel as SocketTextChannel ) ? . Guild ;
2017-01-07 21:31:42 +00:00
2017-05-29 04:13:22 +00:00
await TryRunCommand ( guild , channel , usrMsg ) ;
}
catch ( Exception ex )
{
_log . Warn ( "Error in CommandHandler" ) ;
_log . Warn ( ex ) ;
if ( ex . InnerException ! = null )
2017-03-31 12:07:18 +00:00
{
2017-05-29 04:13:22 +00:00
_log . Warn ( "Inner Exception of the error in CommandHandler" ) ;
_log . Warn ( ex . InnerException ) ;
2017-03-31 12:07:18 +00:00
}
2017-05-29 04:13:22 +00:00
}
2017-03-31 12:07:18 +00:00
}
2017-02-02 13:16:28 +00:00
2017-05-29 04:13:22 +00:00
public async Task TryRunCommand ( SocketGuild guild , ISocketMessageChannel channel , IUserMessage usrMsg )
2017-03-31 12:07:18 +00:00
{
var execTime = Environment . TickCount ;
2016-11-05 19:03:50 +00:00
2017-05-25 02:24:43 +00:00
foreach ( var svc in _services )
{
2017-05-29 04:13:22 +00:00
if ( svc is IEarlyBlocker blocker & &
await blocker . TryBlockEarly ( _client , guild , usrMsg ) . ConfigureAwait ( false ) )
2017-05-25 02:24:43 +00:00
{
2017-05-29 04:13:22 +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 ;
}
}
2017-03-31 12:07:18 +00:00
var exec2 = Environment . TickCount - execTime ;
2017-05-29 04:13:22 +00:00
2017-03-31 12:07:18 +00:00
2017-05-29 04:13:22 +00:00
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
2017-03-31 12:07:18 +00:00
var exec3 = Environment . TickCount - execTime ;
2017-02-05 18:15:05 +00:00
2017-03-31 12:07:18 +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-11-05 19:03:50 +00:00
2016-12-21 11:52:01 +00:00
2017-03-31 12:07:18 +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-03-31 12:07:18 +00:00
{
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 ;
2017-05-29 04:13:22 +00:00
////todo commandHandler
if ( exec )
{
2017-05-25 02:24:43 +00:00
// await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false);
2017-05-29 04:13:22 +00:00
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
2017-05-29 04:13:22 +00:00
if ( exec )
return ;
2017-03-31 12:07:18 +00:00
2017-05-29 04:13:22 +00:00
}
2017-03-31 12:07:18 +00:00
2017-05-29 04:13:22 +00:00
foreach ( var svc in _services )
{
if ( svc is ILateExecutor exec )
{
await exec . LateExecute ( _client , guild , usrMsg ) . ConfigureAwait ( false ) ;
}
2017-03-31 12:07:18 +00:00
}
2017-05-29 04:13:22 +00:00
2016-08-29 22:47:04 +00:00
}
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
2016-12-21 08:33:47 +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 ;
}
}
2016-10-14 22:03:52 +00:00
2016-12-31 12:21:18 +00:00
var cmd = commands [ i ] . Command ;
2017-03-08 23:58:58 +00:00
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
{
2016-09-30 02:20:09 +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));
//}
2016-09-30 02:20:09 +00:00
2017-01-14 16:37:11 +00:00
2017-05-22 23:59:31 +00:00
//if (module.Name == typeof(Permissions).Name)
2017-04-09 20:28:42 +00:00
//{
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-04-09 20:28:42 +00:00
// {
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."));
2017-04-09 20:28:42 +00:00
// }
//}
2017-05-29 04:13:22 +00:00
//////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."));
//}
2017-01-18 20:52:42 +00:00
// 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 ;
2017-05-29 04:13:22 +00:00
//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 ;
}
}
2016-10-07 22:23:41 +00:00
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."));
2016-12-21 08:33:47 +00:00
}
2016-08-29 22:47:04 +00:00
}
2016-12-31 12:21:18 +00:00
}