diff --git a/lib/PBot/Core/Commands.pm b/lib/PBot/Core/Commands.pm index c8728399..3dc687ee 100644 --- a/lib/PBot/Core/Commands.pm +++ b/lib/PBot/Core/Commands.pm @@ -7,7 +7,7 @@ # SPDX-License-Identifier: MIT package PBot::Core::Commands; -use parent 'PBot::Core::Class', 'PBot::Core::Registerable'; +use parent 'PBot::Core::Class'; use PBot::Imports; @@ -16,19 +16,24 @@ use PBot::Utils::LoadModules qw/load_modules/; sub initialize { my ($self, %conf) = @_; - # PBot::Core::Commands can register subrefs - $self->PBot::Core::Registerable::initialize(%conf); + # registered commands hash table + $self->{commands} = {}; # command metadata stored as a HashObject - $self->{metadata} = PBot::Storage::HashObject->new(pbot => $self->{pbot}, name => 'Command metadata', filename => $conf{filename}); + $self->{metadata} = PBot::Storage::HashObject->new( + pbot => $self->{pbot}, + name => 'Command metadata', + filename => $conf{filename}, + ); + $self->{metadata}->load; } -sub register_commands { +sub load_commands { my ($self) = @_; - # register commands in Commands directory - $self->{pbot}->{logger}->log("Registering commands:\n"); + # load commands in Commands directory + $self->{pbot}->{logger}->log("Loading commands:\n"); load_modules($self, 'PBot::Core::Commands'); } @@ -39,15 +44,21 @@ sub register { Carp::croak("Missing parameters to Commands::register"); } - # register subref - my $command = $self->PBot::Core::Registerable::register($subref); + $name = lc $name; - # update internal metadata - $command->{name} = lc $name; - $command->{requires_cap} = $requires_cap // 0; + if (exists $self->{commands}->{$name}) { + $self->{pbot}->{logger}->log("Commands: warning: overwriting existing command $name\n"); + } + + # register command + $self->{commands}->{$name} = { + requires_cap => $requires_cap // 0, + subref => $subref, + }; # update command metadata if (not $self->{metadata}->exists($name)) { + # create new metadata $self->{metadata}->add($name, { requires_cap => $requires_cap, help => '' }, 1); } else { # metadata already exists, just update requires_cap unless it's already set. @@ -60,22 +71,18 @@ sub register { if ($requires_cap) { $self->{pbot}->{capabilities}->add("can-$name", undef, 1); } - - return $command; } sub unregister { my ($self, $name) = @_; Carp::croak("Missing name parameter to Commands::unregister") if not defined $name; $name = lc $name; - @{$self->{handlers}} = grep { $_->{name} ne $name } @{$self->{handlers}}; + delete $self->{commands}->{$name}; } sub exists { - my ($self, $keyword) = @_; - $keyword = lc $keyword; - foreach my $command (@{$self->{handlers}}) { return 1 if $command->{name} eq $keyword; } - return 0; + my ($self, $name) = @_; + return exists $self->{commands}->{lc $name}; } sub set_meta { @@ -107,6 +114,12 @@ sub interpreter { my $keyword = lc $context->{keyword}; my $from = $context->{from}; + # alias to the command + my $command = $self->{commands}->{$keyword}; + + # bail early if the command doesn't exist + return undef if not defined $command; + # set the channel the command is in reference to my ($cmd_channel) = $context->{arguments} =~ m/\B(#[^ ]+)/; # assume command is invoked in regards to first channel-like argument $cmd_channel = $from if not defined $cmd_channel; # otherwise command is invoked in regards to the channel the user is in @@ -124,84 +137,70 @@ sub interpreter { $cap_override = $context->{'cap-override'}; } - # go through all commands - # TODO: maybe use a hash lookup - foreach my $command (@{$self->{handlers}}) { + # does this command require capabilities + my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $command->{requires_cap}; - # is this the command - if ($command->{name} eq $keyword) { + # validate can-command capability + if ($requires_cap) { + if (defined $cap_override) { + if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) { + return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have."; + } + } else { + if (not defined $user) { + my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, $context->{hostmask}, 1); - # does this command require capabilities - my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $command->{requires_cap}; - - if ($requires_cap) { - if (defined $cap_override) { - if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) { - return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have."; - } + if (not defined $found_chan) { + return "/msg $context->{nick} You must have a user account to use $keyword. You may use the `my` command to create a personal user account. See `help my`."; } else { - if (not defined $user) { - my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, $context->{hostmask}, 1); - - if (not defined $found_chan) { - return "/msg $context->{nick} You must have a user account to use $keyword. You may use the `my` command to create a personal user account. See `help my`."; - } else { - return "/msg $context->{nick} You must have a user account in $cmd_channel to use $keyword. (You have an account in $found_chan.)"; - } - } elsif (not $user->{loggedin}) { - return "/msg $context->{nick} You must be logged into your user account to use $keyword."; - } - - if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) { - return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have."; - } + return "/msg $context->{nick} You must have a user account in $cmd_channel to use $keyword. (You have an account in $found_chan.)"; } + } elsif (not $user->{loggedin}) { + return "/msg $context->{nick} You must be logged into your user account to use $keyword."; } - if ($self->get_meta($keyword, 'preserve_whitespace')) { - $context->{preserve_whitespace} = 1; - } - - unless ($self->get_meta($keyword, 'dont-replace-pronouns')) { - $context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments}); - $context->{arglist} = $self->{pbot}->{interpreter}->make_args($context->{arguments}); - } - - # $self->{pbot}->{logger}->log("Disabling nickprefix\n"); - #$context->{nickprefix_disabled} = 1; - - if ($self->get_meta($keyword, 'background-process')) { - # execute this command as a backgrounded process - - # set timeout to command metadata value - my $timeout = $self->get_meta($keyword, 'process-timeout'); - - # otherwise set timeout to default value - $timeout //= $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout'); - - # execute command in background - $self->{pbot}->{process_manager}->execute_process( - $context, - sub { $context->{result} = $command->{subref}->($context) }, - $timeout, - ); - - # return no output since it will be handled by process manager - return ''; - } else { - # execute this command normally - my $result = $command->{subref}->($context); - - # disregard undesired command output if command is embedded - return undef if $context->{referenced} and $result =~ m/(?:usage:|no results)/i; - - # return command output - return $result; + if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) { + return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have."; } } } - return undef; + if ($self->get_meta($keyword, 'preserve_whitespace')) { + $context->{preserve_whitespace} = 1; + } + + unless ($self->get_meta($keyword, 'dont-replace-pronouns')) { + $context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments}); + $context->{arglist} = $self->{pbot}->{interpreter}->make_args($context->{arguments}); + } + + # execute this command as a backgrounded process? + if ($self->get_meta($keyword, 'background-process')) { + # set timeout to command metadata value + my $timeout = $self->get_meta($keyword, 'process-timeout'); + + # otherwise set timeout to default value + $timeout //= $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout'); + + # execute command in background + $self->{pbot}->{process_manager}->execute_process( + $context, + sub { $context->{result} = $command->{subref}->($context) }, + $timeout, + ); + + # return no output since it will be handled by process manager + return ''; + } else { + # execute this command normally + my $result = $command->{subref}->($context); + + # disregard undesired command output if command is embedded + return undef if $context->{embedded} and $result =~ m/(?:usage:|no results)/i; + + # return command output + return $result; + } } 1; diff --git a/lib/PBot/Core/Commands/Misc.pm b/lib/PBot/Core/Commands/Misc.pm index 18946c67..a4ae0b73 100644 --- a/lib/PBot/Core/Commands/Misc.pm +++ b/lib/PBot/Core/Commands/Misc.pm @@ -94,12 +94,13 @@ sub cmd_list { } if ($context->{arguments} =~ /^commands$/i) { + my $commands = $self->{pbot}->{commands}->{commands}; $text = 'Registered commands: '; - foreach my $command (sort { $a->{name} cmp $b->{name} } @{$self->{pbot}->{commands}->{handlers}}) { - if ($command->{requires_cap}) { - $text .= "+$command->{name} "; + foreach my $command (sort keys %$commands) { + if ($commands->{$command}->{requires_cap}) { + $text .= "+$command "; } else { - $text .= "$command->{name} "; + $text .= "$command "; } }