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
![]() |
|||||
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
![]() |
|||||
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:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are 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.php
However, as
OtherDir/Foo.php
does 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: