2024-07-06 23:42:33 -04:00
using System.Text.RegularExpressions ;
2024-03-15 08:27:35 -04:00
using Microsoft.Extensions.DependencyInjection ;
2024-06-16 20:19:31 -04:00
using Serilog ;
2024-07-12 13:36:09 -04:00
using TwitchChatTTS.Chat.Groups ;
using TwitchChatTTS.Chat.Groups.Permissions ;
2024-03-15 08:27:35 -04:00
using TwitchLib.Client.Models ;
namespace TwitchChatTTS.Chat.Commands
{
public class ChatCommandManager
{
private IDictionary < string , ChatCommand > _commands ;
2024-06-24 18:11:36 -04:00
private readonly TwitchBotAuth _token ;
2024-07-06 23:42:33 -04:00
private readonly User _user ;
2024-07-12 13:36:09 -04:00
private readonly IGroupPermissionManager _permissionManager ;
private readonly IChatterGroupManager _chatterGroupManager ;
2024-06-24 18:11:36 -04:00
private readonly IServiceProvider _serviceProvider ;
private readonly ILogger _logger ;
2024-03-15 08:27:35 -04:00
private string CommandStartSign { get ; } = "!" ;
2024-07-12 13:36:09 -04:00
public ChatCommandManager (
TwitchBotAuth token ,
User user ,
IGroupPermissionManager permissionManager ,
IChatterGroupManager chatterGroupManager ,
IServiceProvider serviceProvider ,
ILogger logger
)
2024-06-16 20:19:31 -04:00
{
2024-03-15 08:27:35 -04:00
_token = token ;
2024-07-06 23:42:33 -04:00
_user = user ;
2024-07-12 13:36:09 -04:00
_permissionManager = permissionManager ;
_chatterGroupManager = chatterGroupManager ;
2024-03-15 08:27:35 -04:00
_serviceProvider = serviceProvider ;
_logger = logger ;
_commands = new Dictionary < string , ChatCommand > ( ) ;
GenerateCommands ( ) ;
}
2024-06-16 20:19:31 -04:00
private void Add ( ChatCommand command )
{
2024-03-15 08:27:35 -04:00
_commands . Add ( command . Name . ToLower ( ) , command ) ;
}
2024-06-16 20:19:31 -04:00
private void GenerateCommands ( )
{
2024-03-15 08:27:35 -04:00
var basetype = typeof ( ChatCommand ) ;
var assembly = GetType ( ) . Assembly ;
var types = assembly . GetTypes ( ) . Where ( t = > t . IsClass & & ! t . IsAbstract & & basetype . IsAssignableFrom ( t ) & & t . AssemblyQualifiedName ? . Contains ( ".Chat." ) = = true ) ;
2024-06-16 20:19:31 -04:00
foreach ( var type in types )
{
2024-03-15 08:27:35 -04:00
var key = "command-" + type . Name . Replace ( "Commands" , "Comm#ands" )
. Replace ( "Command" , "" )
. Replace ( "Comm#ands" , "Commands" )
. ToLower ( ) ;
2024-06-16 20:19:31 -04:00
2024-03-15 08:27:35 -04:00
var command = _serviceProvider . GetKeyedService < ChatCommand > ( key ) ;
2024-06-16 20:19:31 -04:00
if ( command = = null )
{
2024-06-24 18:11:36 -04:00
_logger . Error ( "Failed to add chat command: " + type . AssemblyQualifiedName ) ;
2024-03-15 08:27:35 -04:00
continue ;
}
2024-06-16 20:19:31 -04:00
2024-06-24 18:11:36 -04:00
_logger . Debug ( $"Added chat command {type.AssemblyQualifiedName}" ) ;
2024-03-15 08:27:35 -04:00
Add ( command ) ;
}
}
2024-07-12 13:36:09 -04:00
public async Task < ChatCommandResult > Execute ( string arg , ChatMessage message , IEnumerable < string > groups )
2024-06-16 20:19:31 -04:00
{
2024-03-15 08:27:35 -04:00
if ( _token . BroadcasterId = = null )
return ChatCommandResult . Unknown ;
if ( string . IsNullOrWhiteSpace ( arg ) )
return ChatCommandResult . Unknown ;
arg = arg . Trim ( ) ;
if ( ! arg . StartsWith ( CommandStartSign ) )
return ChatCommandResult . Unknown ;
2024-07-06 23:42:33 -04:00
string [ ] parts = Regex . Matches ( arg , "(?<match>[^\"\\n\\s]+|\"[^\"\\n]*\")" )
. Cast < Match > ( )
. Select ( m = > m . Groups [ "match" ] . Value )
. Select ( m = > m . StartsWith ( '"' ) & & m . EndsWith ( '"' ) ? m . Substring ( 1 , m . Length - 2 ) : m )
. ToArray ( ) ;
2024-03-15 08:27:35 -04:00
string com = parts . First ( ) . Substring ( CommandStartSign . Length ) . ToLower ( ) ;
string [ ] args = parts . Skip ( 1 ) . ToArray ( ) ;
long broadcasterId = long . Parse ( _token . BroadcasterId ) ;
2024-06-16 20:19:31 -04:00
if ( ! _commands . TryGetValue ( com , out ChatCommand ? command ) | | command = = null )
{
2024-06-24 18:11:36 -04:00
// Could be for another bot or just misspelled.
2024-07-12 13:36:09 -04:00
_logger . Debug ( $"Failed to find command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]" ) ;
2024-03-15 08:27:35 -04:00
return ChatCommandResult . Missing ;
}
2024-07-12 13:36:09 -04:00
// Check if command can be executed by this chatter.
long chatterId = long . Parse ( message . UserId ) ;
if ( chatterId ! = _user . OwnerId )
2024-06-16 20:19:31 -04:00
{
2024-07-12 13:36:09 -04:00
var executable = command . DefaultPermissionsOverwrite ? false : CanExecute ( chatterId , groups , com ) ;
if ( executable = = false )
{
_logger . Debug ( $"Denied permission to use command [chatter id: {chatterId}][command: {com}]" ) ;
return ChatCommandResult . Permission ;
}
else if ( executable = = null & & ! await command . CheckDefaultPermissions ( message , broadcasterId ) )
{
_logger . Debug ( $"Chatter is missing default permission to execute command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]" ) ;
return ChatCommandResult . Permission ;
}
2024-03-15 08:27:35 -04:00
}
2024-07-12 13:36:09 -04:00
// Check if the syntax is correct.
2024-06-16 20:19:31 -04:00
if ( command . Parameters . Count ( p = > ! p . Optional ) > args . Length )
{
2024-07-12 13:36:09 -04:00
_logger . Debug ( $"Command syntax issue when executing command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]" ) ;
2024-03-15 08:27:35 -04:00
return ChatCommandResult . Syntax ;
}
2024-06-16 20:19:31 -04:00
for ( int i = 0 ; i < Math . Min ( args . Length , command . Parameters . Count ) ; i + + )
{
if ( ! command . Parameters [ i ] . Validate ( args [ i ] ) )
{
_logger . Warning ( $"Commmand '{com}' failed because of the #{i + 1} argument. Invalid value: {args[i]}" ) ;
2024-03-15 08:27:35 -04:00
return ChatCommandResult . Syntax ;
}
}
2024-06-16 20:19:31 -04:00
try
{
2024-03-15 08:27:35 -04:00
await command . Execute ( args , message , broadcasterId ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception e )
{
2024-07-12 13:36:09 -04:00
_logger . Error ( e , $"Command '{arg}' failed [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]" ) ;
2024-03-15 08:27:35 -04:00
return ChatCommandResult . Fail ;
}
2024-07-12 13:36:09 -04:00
_logger . Information ( $"Executed the {com} command [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]" ) ;
2024-03-15 08:27:35 -04:00
return ChatCommandResult . Success ;
}
2024-07-12 13:36:09 -04:00
private bool? CanExecute ( long chatterId , IEnumerable < string > groups , string path )
{
_logger . Debug ( $"Checking for permission [chatter id: {chatterId}][group: {string.Join(" , ", groups)}][path: {path}]" ) ;
return _permissionManager . CheckIfAllowed ( groups , path ) ;
}
2024-03-15 08:27:35 -04:00
}
}