Total Complexity | 74 |
Total Lines | 582 |
Duplicated Lines | 0 % |
Changes | 8 | ||
Bugs | 0 | Features | 0 |
Complex classes like Command often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Command, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
40 | abstract class Command { |
||
41 | /** |
||
42 | * The client which initiated the instance. |
||
43 | * @var \CharlotteDunois\Livia\Client |
||
44 | */ |
||
45 | protected $client; |
||
46 | |||
47 | /** |
||
48 | * The name of the command. |
||
49 | * @var string |
||
50 | */ |
||
51 | protected $name; |
||
52 | |||
53 | /** |
||
54 | * Aliases of the command. |
||
55 | * @var string[] |
||
56 | */ |
||
57 | protected $aliases = array(); |
||
58 | |||
59 | /** |
||
60 | * ID of the command group the command is part of. |
||
61 | * @var string |
||
62 | */ |
||
63 | protected $groupID; |
||
64 | |||
65 | /** |
||
66 | * A short description of the command. |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $description; |
||
70 | |||
71 | /** |
||
72 | * A longer description of the command. |
||
73 | * @var string|null |
||
74 | */ |
||
75 | protected $details; |
||
76 | |||
77 | /** |
||
78 | * Usage format string of the command. |
||
79 | * @var string |
||
80 | */ |
||
81 | protected $format = ''; |
||
82 | |||
83 | /** |
||
84 | * Examples of and for the command. |
||
85 | * @var string[] |
||
86 | */ |
||
87 | protected $examples = array(); |
||
88 | |||
89 | /** |
||
90 | * Whether the command can only be triggered in a guild channel. |
||
91 | * @var bool |
||
92 | */ |
||
93 | protected $guildOnly = false; |
||
94 | |||
95 | /** |
||
96 | * Whether the command can only be triggered by the bot owner (requires default hasPermission method). |
||
97 | * @var bool |
||
98 | */ |
||
99 | protected $ownerOnly = false; |
||
100 | |||
101 | /** |
||
102 | * The required permissions for the client user to make the command work. |
||
103 | * @var string[]|null |
||
104 | */ |
||
105 | protected $clientPermissions; |
||
106 | |||
107 | /** |
||
108 | * The required permissions for the user to use the command. |
||
109 | * @var string[]|null |
||
110 | */ |
||
111 | protected $userPermissions; |
||
112 | |||
113 | /** |
||
114 | * Whether the command can only be run in NSFW channels. |
||
115 | * @var bool |
||
116 | */ |
||
117 | protected $nsfw = false; |
||
118 | |||
119 | /** |
||
120 | * Options for throttling command usages. |
||
121 | * @var array |
||
122 | */ |
||
123 | protected $throttling = array(); |
||
124 | |||
125 | /** |
||
126 | * Whether the command gets handled normally. |
||
127 | * @var bool |
||
128 | */ |
||
129 | protected $defaultHandling = true; |
||
130 | |||
131 | /** |
||
132 | * An array containing the command arguments. |
||
133 | * @var array |
||
134 | */ |
||
135 | protected $args = array(); |
||
136 | |||
137 | /** |
||
138 | * How many times the user gets prompted for an argument. |
||
139 | * @var int|float |
||
140 | */ |
||
141 | protected $argsPromptLimit = \INF; |
||
142 | |||
143 | /** |
||
144 | * Whether single quotes are allowed to encapsulate an argument. |
||
145 | * @var bool |
||
146 | */ |
||
147 | protected $argsSingleQuotes = true; |
||
148 | |||
149 | /** |
||
150 | * How the arguments are split when passed to the command's run method. |
||
151 | * @var string |
||
152 | */ |
||
153 | protected $argsType = 'single'; |
||
154 | |||
155 | /** |
||
156 | * Maximum number of arguments that will be split. |
||
157 | * @var int |
||
158 | */ |
||
159 | protected $argsCount = 0; |
||
160 | |||
161 | /** |
||
162 | * Command patterns for pattern matches. |
||
163 | * @var string[] |
||
164 | */ |
||
165 | protected $patterns = array(); |
||
166 | |||
167 | /** |
||
168 | * Whether the command is guarded (can not be disabled). |
||
169 | * @var bool |
||
170 | */ |
||
171 | protected $guarded = false; |
||
172 | |||
173 | /** |
||
174 | * Whether the command is hidden in the `help` command overview. |
||
175 | * @var bool |
||
176 | */ |
||
177 | protected $hidden = false; |
||
178 | |||
179 | /** |
||
180 | * Whether the command is globally enabled. |
||
181 | * @var bool |
||
182 | */ |
||
183 | protected $globalEnabled = true; |
||
184 | |||
185 | /** |
||
186 | * Array of guild ID to bool, which indicates whether the command is enabled in that guild. |
||
187 | * @var bool[] |
||
188 | */ |
||
189 | protected $guildEnabled = array(); |
||
190 | |||
191 | /** |
||
192 | * The argument collector for the command. |
||
193 | * @var \CharlotteDunois\Livia\Arguments\ArgumentCollector|null |
||
194 | */ |
||
195 | protected $argsCollector; |
||
196 | |||
197 | /** |
||
198 | * A collection of throttle arrays. |
||
199 | * @var \CharlotteDunois\Collect\Collection |
||
200 | */ |
||
201 | protected $throttles; |
||
202 | |||
203 | /** |
||
204 | * Constructs a new Command. Info is an array as following: |
||
205 | * |
||
206 | * ``` |
||
207 | * array( |
||
208 | * 'name' => string, |
||
209 | * 'aliases' => string[], (optional) |
||
210 | * 'group' => string, (the ID of the command group) |
||
211 | * 'description => string, |
||
212 | * 'details' => string, (optional) |
||
213 | * 'format' => string, (optional) |
||
214 | * 'examples' => string[], (optional) |
||
215 | * 'guildOnly' => bool, (defaults to false) |
||
216 | * 'ownerOnly' => bool, (defaults to false) |
||
217 | * 'clientPermissions' => string[], (optional) |
||
218 | * 'userPermissions' => string[], (optional) |
||
219 | * 'nsfw' => bool, (defaults to false) |
||
220 | * 'throttling' => array, (associative array of array('usages' => int, 'duration' => int) - duration in seconds, optional) |
||
221 | * 'defaultHandling' => bool, (defaults to true) |
||
222 | * 'args' => array, ({@see \CharlotteDunois\Livia\Arguments\Argument} - key can be the index instead, optional) |
||
223 | * 'argsPromptLimit' => int|\INF, (optional) |
||
224 | * 'argsType' => string, (one of 'single' or 'multiple', defaults to 'single') |
||
225 | * 'argsCount' => int, (optional) |
||
226 | * 'argsSingleQuotes' => bool, (optional) |
||
227 | * 'patterns' => string[], (Regular Expression strings, pattern matches don't get parsed for arguments, optional) |
||
228 | * 'guarded' => bool, (defaults to false) |
||
229 | * 'hidden' => bool, (defaults to false) |
||
230 | * ) |
||
231 | * ``` |
||
232 | * |
||
233 | * @param \CharlotteDunois\Livia\Client $client |
||
234 | * @param array $info |
||
235 | * @throws \InvalidArgumentException |
||
236 | */ |
||
237 | function __construct(\CharlotteDunois\Livia\Client $client, array $info) { |
||
238 | $this->client = $client; |
||
239 | |||
240 | \CharlotteDunois\Validation\Validator::make($info, array( |
||
241 | 'name' => 'required|string|lowercase|nowhitespace', |
||
242 | 'group' => 'required|string', |
||
243 | 'description' => 'required|string', |
||
244 | 'aliases' => 'array:string', |
||
245 | 'autoAliases' => 'boolean', |
||
246 | 'details' => 'string', |
||
247 | 'format' => 'string', |
||
248 | 'examples' => 'array:string', |
||
249 | 'guildOnly' => 'boolean', |
||
250 | 'ownerOnly' => 'boolean', |
||
251 | 'clientPermissions' => 'array', |
||
252 | 'userPermissions' => 'array', |
||
253 | 'nsfw' => 'boolean', |
||
254 | 'throttling' => 'array', |
||
255 | 'defaultHandling' => 'boolean', |
||
256 | 'args' => 'array', |
||
257 | 'argsType' => 'string|in:single,multiple', |
||
258 | 'argsPromptLimit' => 'integer|float', |
||
259 | 'argsCount' => 'integer|min:2', |
||
260 | 'patterns' => 'array:string', |
||
261 | 'guarded' => 'boolean', |
||
262 | 'hidden' => 'boolean' |
||
263 | ))->throw(\InvalidArgumentException::class); |
||
264 | |||
265 | $this->name = $info['name']; |
||
266 | $this->groupID = $info['group']; |
||
267 | $this->description = $info['description']; |
||
268 | |||
269 | if(!empty($info['aliases'])) { |
||
270 | $this->aliases = $info['aliases']; |
||
271 | |||
272 | foreach($this->aliases as $alias) { |
||
273 | if(\mb_strtolower($alias) !== $alias) { |
||
274 | throw new \InvalidArgumentException('Command aliases must be lowercase'); |
||
275 | } |
||
276 | } |
||
277 | } |
||
278 | |||
279 | if(!empty($info['autoAliases'])) { |
||
280 | if(\mb_strpos($this->name, '-') !== false) { |
||
281 | $this->aliases[] = \str_replace('-', '', $this->name); |
||
282 | } |
||
283 | |||
284 | foreach($this->aliases as $alias) { |
||
285 | if(\mb_strpos($alias, '-') !== false) { |
||
286 | $this->aliases[] = \str_replace('-', '', $alias); |
||
287 | } |
||
288 | } |
||
289 | } |
||
290 | |||
291 | $this->details = $info['details'] ?? $this->details; |
||
292 | $this->format = $info['format'] ?? $this->format; |
||
293 | $this->examples = $info['examples'] ?? $this->examples; |
||
294 | |||
295 | $this->guildOnly = $info['guildOnly'] ?? $this->guildOnly; |
||
296 | $this->ownerOnly = $info['ownerOnly'] ?? $this->ownerOnly; |
||
297 | |||
298 | $this->clientPermissions = $info['clientPermissions'] ?? $this->clientPermissions; |
||
299 | $this->userPermissions = $info['userPermissions'] ?? $this->userPermissions; |
||
300 | |||
301 | $this->nsfw = $info['nsfw'] ?? $this->nsfw; |
||
302 | |||
303 | if(isset($info['throttling'])) { |
||
304 | if(empty($info['throttling']['usages']) || empty($info['throttling']['duration'])) { |
||
305 | throw new \InvalidArgumentException('Throttling array is missing elements or its elements are empty'); |
||
306 | } |
||
307 | |||
308 | if(!\is_int($info['throttling']['usages'])) { |
||
309 | throw new \InvalidArgumentException('Throttling usages must be an integer'); |
||
310 | } |
||
311 | |||
312 | if(!\is_int($info['throttling']['duration'])) { |
||
313 | throw new \InvalidArgumentException('Throttling duration must be an integer'); |
||
314 | } |
||
315 | |||
316 | $this->throttling = $info['throttling']; |
||
317 | } |
||
318 | |||
319 | $this->defaultHandling = $info['defaultHandling'] ?? $this->defaultHandling; |
||
320 | |||
321 | $this->args = $info['args'] ?? array(); |
||
322 | if(!empty($this->args)) { |
||
323 | $this->argsCollector = new \CharlotteDunois\Livia\Arguments\ArgumentCollector($this->client, $this->args, $this->argsPromptLimit); |
||
324 | |||
325 | if(empty($this->format)) { |
||
326 | $this->format = \array_reduce($this->argsCollector->args, function ($prev, $arg) { |
||
327 | $wrapL = ($arg->default !== null ? '[' : '<'); |
||
328 | $wrapR = ($arg->default !== null ? ']' : '>'); |
||
329 | |||
330 | return $prev.($prev ? ' ' : '').$wrapL.$arg->label.(!empty($arg->infinite) ? '...' : '').$wrapR; |
||
331 | }, null); |
||
332 | } |
||
333 | } |
||
334 | |||
335 | $this->argsSingleQuotes = (bool) ($info['argsSingleQuotes'] ?? $this->argsSingleQuotes); |
||
336 | $this->argsType = $info['argsType'] ?? $this->argsType; |
||
337 | $this->argsPromptLimit = $info['argsPromptLimit'] ?? $this->argsPromptLimit; |
||
338 | $this->argsCount = $info['argsCount'] ?? $this->argsCount; |
||
339 | |||
340 | $this->patterns = $info['patterns'] ?? $this->patterns; |
||
341 | $this->guarded = $info['guarded'] ?? $this->guarded; |
||
342 | $this->hidden = $info['hidden'] ?? $this->hidden; |
||
343 | |||
344 | $this->throttles = new \CharlotteDunois\Collect\Collection(); |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * @param string $name |
||
349 | * @return bool |
||
350 | * @throws \Exception |
||
351 | * @internal |
||
352 | */ |
||
353 | function __isset($name) { |
||
362 | } |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * @param string $name |
||
367 | * @return mixed |
||
368 | * @throws \RuntimeException |
||
369 | * @internal |
||
370 | */ |
||
371 | function __get($name) { |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Checks if the user has permission to use the command. |
||
387 | * @param \CharlotteDunois\Livia\Commands\Context $context |
||
388 | * @param bool $ownerOverride Whether the bot owner(s) will always have permission. |
||
389 | * @return bool|string Whether the user has permission, or an error message to respond with if they don't. |
||
390 | */ |
||
391 | function hasPermission(\CharlotteDunois\Livia\Commands\Context $context, bool $ownerOverride = true) { |
||
436 | } |
||
437 | |||
438 | /** |
||
439 | * Runs the command. The method must return null, an array of Message instances or an instance of Message, a Promise that resolves to an instance of Message, or an array of Message instances. The array can contain Promises which each resolves to an instance of Message. |
||
440 | * @param \CharlotteDunois\Livia\Commands\Context $context The context the command is being run for. |
||
441 | * @param \ArrayObject $args The arguments for the command, or the matches from a pattern. If args is specified on the command, this will be the argument values object. If argsType is single, then only one string will be passed. If multiple, an array of strings will be passed. When fromPattern is true, this is the matches array from the pattern match. |
||
442 | * @param bool $fromPattern Whether or not the command is being run from a pattern match. |
||
443 | * @return \React\Promise\ExtendedPromiseInterface|\React\Promise\ExtendedPromiseInterface[]|\CharlotteDunois\Yasmin\Models\Message|\CharlotteDunois\Yasmin\Models\Message[]|null|void |
||
444 | */ |
||
445 | abstract function run(\CharlotteDunois\Livia\Commands\Context $context, \ArrayObject $args, bool $fromPattern); |
||
446 | |||
447 | /** |
||
448 | * Reloads the command. |
||
449 | * @return void |
||
450 | * @throws \RuntimeException |
||
451 | */ |
||
452 | function reload() { |
||
453 | $this->client->registry->reregisterCommand($this->groupID.':'.$this->name, $this); |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Unloads the command. |
||
458 | * @return void |
||
459 | * @throws \RuntimeException |
||
460 | */ |
||
461 | function unload() { |
||
462 | $this->client->registry->unregisterCommand($this); |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Creates/obtains the throttle object for a user, if necessary (owners are excluded). |
||
467 | * @param string $userID |
||
468 | * @return array|null |
||
469 | * @internal |
||
470 | */ |
||
471 | final function throttle(string $userID) { |
||
472 | if(empty($this->throttling) || $this->client->isOwner($userID)) { |
||
473 | return null; |
||
474 | } |
||
475 | |||
476 | if(!$this->throttles->has($userID)) { |
||
477 | $this->throttles->set($userID, array( |
||
478 | 'start' => \time(), |
||
479 | 'usages' => 0, |
||
480 | 'timeout' => $this->client->addTimer($this->throttling['duration'], function () use ($userID) { |
||
481 | $this->throttles->delete($userID); |
||
482 | }) |
||
483 | )); |
||
484 | } |
||
485 | |||
486 | return $this->throttles->get($userID); |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Increments the usage of the throttle object for a user, if necessary (owners are excluded). |
||
491 | * @param string $userID |
||
492 | * @return void |
||
493 | * @internal |
||
494 | */ |
||
495 | final function updateThrottle(string $userID) { |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Enables or disables the command in a guild (or globally). |
||
520 | * @param string|int|\CharlotteDunois\Yasmin\Models\Guild|null $guild The guild instance or the guild ID. |
||
521 | * @param bool $enabled |
||
522 | * @return bool |
||
523 | * @throws \BadMethodCallException |
||
524 | * @throws \InvalidArgumentException |
||
525 | */ |
||
526 | function setEnabledIn($guild, bool $enabled) { |
||
527 | if($guild !== null) { |
||
528 | $guild = $this->client->guilds->resolve($guild); |
||
529 | } |
||
530 | |||
531 | if($this->guarded) { |
||
532 | throw new \BadMethodCallException('The group is guarded'); |
||
533 | } |
||
534 | |||
535 | if($guild !== null) { |
||
536 | $this->guildEnabled[$guild->id] = $enabled; |
||
537 | } else { |
||
538 | $this->globalEnabled = $enabled; |
||
539 | } |
||
540 | |||
541 | $this->client->emit('commandStatusChange', $guild, $this, $enabled); |
||
542 | return ($guild !== null ? $this->guildEnabled[$guild->id] : $this->globalEnabled); |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Checks if the command is enabled in a guild or globally. |
||
547 | * @param \CharlotteDunois\Yasmin\Models\Guild|string|int|null $guild The guild instance or the guild ID, null for global. |
||
548 | * @return bool |
||
549 | * @throws \InvalidArgumentException |
||
550 | */ |
||
551 | function isEnabledIn($guild) { |
||
552 | if($guild !== null) { |
||
553 | $guild = $this->client->guilds->resolve($guild); |
||
554 | return ($this->globalEnabled && (!\array_key_exists($guild->id, $this->guildEnabled) || $this->guildEnabled[$guild->id])); |
||
555 | } |
||
556 | |||
557 | return $this->globalEnabled; |
||
558 | } |
||
559 | |||
560 | /** |
||
561 | * Checks if the command is usable for a message. |
||
562 | * @param \CharlotteDunois\Livia\Commands\Context|null $context |
||
563 | * @return bool |
||
564 | */ |
||
565 | function isUsable(?\CharlotteDunois\Livia\Commands\Context $context = null) { |
||
566 | if($context === null) { |
||
567 | return $this->globalEnabled; |
||
568 | } |
||
569 | |||
570 | if($this->guildOnly && $context->message->guild === null) { |
||
571 | return false; |
||
572 | } |
||
573 | |||
574 | return ($this->isEnabledIn($context->message->guild) && $this->hasPermission($context) === true); |
||
575 | } |
||
576 | |||
577 | /** |
||
578 | * Creates a usage string for the command. |
||
579 | * @param string $argString A string of arguments for the command. |
||
580 | * @param string|null $prefix Prefix to use for the prefixed command format. |
||
581 | * @param \CharlotteDunois\Yasmin\Models\User $user User to use for the mention command format. Defaults to client user. |
||
582 | * @return string |
||
583 | */ |
||
584 | function usage(string $argString, string $prefix = null, \CharlotteDunois\Yasmin\Models\User $user = null) { |
||
594 | } |
||
595 | |||
596 | /** |
||
597 | * Creates a usage string for any command. |
||
598 | * @param string $command A command + arguments string. |
||
599 | * @param string|null $prefix Prefix to use for the prefixed command format. |
||
600 | * @param \CharlotteDunois\Yasmin\Models\User|null $user User to use for the mention command format. |
||
601 | * @return string |
||
602 | */ |
||
603 | static function anyUsage(string $command, string $prefix = null, \CharlotteDunois\Yasmin\Models\User $user = null) { |
||
622 | } |
||
623 | } |
||
624 |