PortlandLabs /
slackbot
| 1 | <?php |
||||
| 2 | namespace PortlandLabs\Slackbot\Command; |
||||
| 3 | |||||
| 4 | use Illuminate\Support\Str; |
||||
| 5 | use League\CLImate\Argument\Argument; |
||||
| 6 | use League\CLImate\Argument\Filter; |
||||
| 7 | use League\CLImate\Argument\Summary; |
||||
| 8 | use PortlandLabs\Slackbot\Bot; |
||||
| 9 | use PortlandLabs\Slackbot\Command\Argument\Manager; |
||||
|
0 ignored issues
–
show
|
|||||
| 10 | use PortlandLabs\Slackbot\Permission\Checker; |
||||
| 11 | use PortlandLabs\Slackbot\Permission\User; |
||||
| 12 | use PortlandLabs\Slackbot\Slack\Rtm\Event\Message; |
||||
| 13 | |||||
| 14 | abstract class SimpleCommand implements ConsoleStyleCommand |
||||
| 15 | { |
||||
| 16 | |||||
| 17 | /** @var string */ |
||||
| 18 | protected $signature = ''; |
||||
| 19 | |||||
| 20 | /** @var string */ |
||||
| 21 | protected $description = ''; |
||||
| 22 | |||||
| 23 | /** @var Bot */ |
||||
| 24 | protected $bot; |
||||
| 25 | |||||
| 26 | /** @var Manager */ |
||||
| 27 | protected $argumentManager; |
||||
| 28 | |||||
| 29 | /** @var Checker */ |
||||
| 30 | protected $checker; |
||||
| 31 | |||||
| 32 | /** @var string The Role class required */ |
||||
| 33 | protected $role = User::class; |
||||
| 34 | |||||
| 35 | public function __construct(Bot $bot, Manager $argumentManager, Checker $checker) |
||||
| 36 | { |
||||
| 37 | $this->bot = $bot; |
||||
| 38 | $this->argumentManager = $argumentManager; |
||||
| 39 | $this->checker = $checker; |
||||
| 40 | } |
||||
| 41 | |||||
| 42 | /** |
||||
| 43 | * Add arguments to the argument manager |
||||
| 44 | * This method gets called after we match the command to a request |
||||
| 45 | * |
||||
| 46 | * @param Manager $manager |
||||
| 47 | * |
||||
| 48 | * @return Manager |
||||
| 49 | * @throws \Exception |
||||
| 50 | */ |
||||
| 51 | public function configure(Manager $manager): Manager |
||||
| 52 | { |
||||
| 53 | $manager = SignatureParser::parse($this->signature, $manager); |
||||
| 54 | $manager->setRole($this->role); |
||||
| 55 | |||||
| 56 | return $manager; |
||||
| 57 | } |
||||
| 58 | |||||
| 59 | /** |
||||
| 60 | * Determine whether we should handle this message |
||||
| 61 | * |
||||
| 62 | * @param Message $message |
||||
| 63 | * |
||||
| 64 | * @return bool |
||||
| 65 | * @throws \Exception |
||||
| 66 | */ |
||||
| 67 | public function shouldHandle(Message $message): bool |
||||
| 68 | { |
||||
| 69 | $text = $message->getText(); |
||||
| 70 | $userId = $this->bot->rtm()->getUserId(); |
||||
| 71 | |||||
| 72 | // Early return if the text doesn't start with "@slackbot " |
||||
| 73 | if (!Str::startsWith($text, "<@$userId>")) { |
||||
| 74 | return false; |
||||
| 75 | } |
||||
| 76 | |||||
| 77 | // Determine our signature |
||||
| 78 | $command = $this->argumentManager->getCommand(); |
||||
| 79 | if (!$command) { |
||||
| 80 | $this->argumentManager = $this->configure($this->argumentManager); |
||||
| 81 | $command = $this->argumentManager->getCommand(); |
||||
| 82 | } |
||||
| 83 | |||||
| 84 | // If we still don't have a command, let's just return false |
||||
| 85 | if (!$command) { |
||||
| 86 | return false; |
||||
| 87 | } |
||||
| 88 | |||||
| 89 | $shouldRun = Str::startsWith($text, "<@$userId> $command ") || $text === "<@$userId> $command"; |
||||
| 90 | if (!$shouldRun) { |
||||
| 91 | return false; |
||||
| 92 | } |
||||
| 93 | |||||
| 94 | // Make sure the user have the right role |
||||
| 95 | $role = $this->checker->getRole($message); |
||||
| 96 | return ($role instanceof $this->role); |
||||
| 97 | } |
||||
| 98 | |||||
| 99 | /** |
||||
| 100 | * Handle a simple command message |
||||
| 101 | * |
||||
| 102 | * @param Message $message |
||||
| 103 | * @throws \Exception |
||||
| 104 | */ |
||||
| 105 | public function handle(Message $message) |
||||
| 106 | { |
||||
| 107 | $argumentManager = $this->argumentManager; |
||||
| 108 | if (!$argumentManager->getCommand()) { |
||||
| 109 | $argumentManager = $this->configure($argumentManager); |
||||
| 110 | } |
||||
| 111 | |||||
| 112 | if (!$argumentManager->exists('help')) { |
||||
| 113 | $argumentManager->add([ |
||||
| 114 | 'help' => [ |
||||
| 115 | 'prefix' => 'h', |
||||
| 116 | 'longPrefix' => 'help', |
||||
| 117 | 'description' => 'Output helpful information about a command', |
||||
| 118 | 'noValue' => true |
||||
| 119 | ] |
||||
| 120 | ]); |
||||
| 121 | } |
||||
| 122 | |||||
| 123 | // Parse the actual message text |
||||
| 124 | $text = Str::replaceFirst( |
||||
| 125 | sprintf('<@%s> ', $this->bot->rtm()->getUserId()), |
||||
| 126 | '', |
||||
| 127 | $message->getText()); |
||||
| 128 | |||||
| 129 | // Pass in the actual split string |
||||
| 130 | $parsedManager = clone $argumentManager; |
||||
| 131 | |||||
| 132 | // GT / LT signs are encoded in slack messages so we can safely use them here as a stand-in. |
||||
| 133 | $argv = \Clue\Arguments\split(str_replace("\n", "<>\n", $text)); |
||||
| 134 | |||||
| 135 | // Restore newlines |
||||
| 136 | $argv = array_map(function($arg) { |
||||
| 137 | return str_replace('<>', "\n", $arg); |
||||
| 138 | }, $argv); |
||||
| 139 | |||||
| 140 | try { |
||||
| 141 | $parsedManager->parse($argv); |
||||
| 142 | } catch (\Exception $e) { |
||||
| 143 | if (!$parsedManager->get('help')) { |
||||
| 144 | $this->bot->feignTyping($message->getChannel(), "*Error:*\n> " . $e->getMessage()); |
||||
|
0 ignored issues
–
show
It seems like
$message->getChannel() can also be of type null; however, parameter $channel of PortlandLabs\Slackbot\Bot::feignTyping() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 145 | return; |
||||
| 146 | } |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | if ($parsedManager->get('help')) { |
||||
| 150 | // Output usage info |
||||
| 151 | $this->outputUsage($message, $parsedManager, $this); |
||||
| 152 | } else { |
||||
| 153 | // Run the command |
||||
| 154 | $this->run($message, $parsedManager); |
||||
| 155 | } |
||||
| 156 | } |
||||
| 157 | |||||
| 158 | /** |
||||
| 159 | * Output the usage statement for this command |
||||
| 160 | * |
||||
| 161 | * @param Message $message |
||||
| 162 | * @param Manager $manager |
||||
| 163 | */ |
||||
| 164 | protected function outputUsage(Message $message, Manager $manager, SimpleCommand $command) |
||||
| 165 | { |
||||
| 166 | $output = []; |
||||
| 167 | $channel = $message->getChannel(); |
||||
| 168 | |||||
| 169 | $filter = new Filter(); |
||||
| 170 | $filter->setArguments($manager->all()); |
||||
| 171 | |||||
| 172 | $summary = new Summary(); |
||||
| 173 | |||||
| 174 | // Print the description if it's defined. |
||||
| 175 | if ($command->getDescription()) { |
||||
| 176 | $output[] = '*' . ucfirst($manager->getCommand()) . '*: ' . $command->getDescription(); |
||||
| 177 | $output[] = '>>>'; |
||||
| 178 | } |
||||
| 179 | |||||
| 180 | // Output the simple usage statement |
||||
| 181 | $orderedArguments = array_merge($filter->withPrefix(), $filter->withoutPrefix()); |
||||
| 182 | $output[] = "*Usage:* `{$manager->getCommand()} " . $summary->short($orderedArguments) . '`'; |
||||
| 183 | |||||
| 184 | // Output the detailed arguments |
||||
| 185 | foreach (['required', 'optional'] as $type) { |
||||
| 186 | /** @var Argument[] $filteredArguments */ |
||||
| 187 | $filteredArguments = $filter->{$type}(); |
||||
| 188 | |||||
| 189 | if (count($filteredArguments) == 0) { |
||||
| 190 | continue; |
||||
| 191 | } |
||||
| 192 | |||||
| 193 | $output[] = ''; |
||||
| 194 | $output[] = '*' . mb_convert_case($type, MB_CASE_TITLE) . ' Arguments:*'; |
||||
| 195 | |||||
| 196 | foreach ($filteredArguments as $argument) { |
||||
| 197 | $argumentString = '`' .$summary->argument($argument) . '`'; |
||||
| 198 | |||||
| 199 | if ($description = $argument->description()) { |
||||
| 200 | $argumentString .= " _{$description}_"; |
||||
| 201 | } |
||||
| 202 | |||||
| 203 | $output[] = $argumentString; |
||||
| 204 | } |
||||
| 205 | } |
||||
| 206 | |||||
| 207 | $this->bot->feignTyping($channel, implode(PHP_EOL, $output)); |
||||
|
0 ignored issues
–
show
It seems like
$channel can also be of type null; however, parameter $channel of PortlandLabs\Slackbot\Bot::feignTyping() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 208 | } |
||||
| 209 | |||||
| 210 | /** |
||||
| 211 | * Get the description associated with this command |
||||
| 212 | * |
||||
| 213 | * @return string |
||||
| 214 | */ |
||||
| 215 | public function getDescription(): string |
||||
| 216 | { |
||||
| 217 | return $this->description; |
||||
| 218 | } |
||||
| 219 | } |
Let?s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let?s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: