Context::getChannelIDOrDM()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Livia
4
 * Copyright 2017-2019 Charlotte Dunois, All Rights Reserved
5
 *
6
 * Website: https://charuru.moe
7
 * License: https://github.com/CharlotteDunois/Livia/blob/master/LICENSE
8
*/
9
10
namespace CharlotteDunois\Livia\Commands;
11
12
/**
13
 * Represents a command invocation context.
14
 *
15
 * @property \CharlotteDunois\Livia\Client                 $client          The client which initiated the instance.
16
 * @property \CharlotteDunois\Yasmin\Models\Message        $message         The message that triggers the command.
17
 * @property \CharlotteDunois\Livia\Commands\Command|null  $command         The command that got triggered, if any.
18
 *
19
 * @property string|null                                   $argString       Argument string for the command.
20
 * @property string[]|null                                 $patternMatches  Pattern matches (if from a pattern trigger).
21
 */
22
class Context extends \CharlotteDunois\Yasmin\Models\ClientBase {
23
    /**
24
     * The client which initiated the instance.
25
     * @var \CharlotteDunois\Livia\Client
26
     */
27
    protected $client;
28
    
29
    /**
30
     * The message that triggers the command.
31
     * @var \CharlotteDunois\Yasmin\Models\Message
32
     */
33
    protected $message;
34
    
35
    /**
36
     * The command that got triggered, if any.
37
     * @var \CharlotteDunois\Livia\Commands\Command|null
38
     */
39
    protected $command;
40
    
41
    /**
42
     * Argument string for the command.
43
     * @var string|null
44
     */
45
    protected $argString;
46
    
47
    /**
48
     * Pattern matches (if from a pattern trigger).
49
     * @var string[]|null
50
     */
51
    protected $patternMatches;
52
    
53
    /**
54
     * Command responses, as multidimensional array (channelID|dm => Message[]).
55
     * @var array
56
     */
57
    protected $responses = array();
58
    
59
    /**
60
     * Internal command for serialization.
61
     * @var string|null
62
     */
63
    protected $internalCommand;
64
    
65
    /**
66
     * @internal
67
     */
68
    function __construct(\CharlotteDunois\Livia\Client $client, \CharlotteDunois\Yasmin\Models\Message $message, \CharlotteDunois\Livia\Commands\Command $command = null, string $argString = null, array $patternMatches = null) {
69
        $this->client = $client;
70
        $this->message = $message;
71
        $this->command = $command;
72
        
73
        $this->argString = ($argString !== null ? \trim($argString) : null);
74
        $this->patternMatches = $patternMatches;
75
    }
76
    
77
    /**
78
     * @param string  $name
79
     * @return bool
80
     * @throws \Exception
81
     * @internal
82
     */
83
    function __isset($name) {
84
        try {
85
            return $this->$name !== null;
86
        } catch (\RuntimeException $e) {
87
            if($e->getTrace()[0]['function'] === '__get') {
88
                return false;
89
            }
90
            
91
            throw $e;
92
        }
93
    }
94
    
95
    /**
96
     * @param string  $name
97
     * @return mixed
98
     * @throws \RuntimeException
99
     * @internal
100
     */
101
    function __get($name) {
102
        if(\property_exists($this, $name)) {
103
            return $this->$name;
104
        }
105
        
106
        return $this->message->$name;
107
    }
108
    
109
    /**
110
     * @return mixed
111
     * @throws \RuntimeException
112
     * @internal
113
     */
114
    function __call($name, $args) {
115
        if(\method_exists($this->message, $name)) {
116
            return $this->message->$name(...$args);
117
        }
118
        
119
        throw new \RuntimeException('Unknown method '.\get_class($this).'::'.$name);
120
    }
121
    
122
    /**
123
     * @return string
124
     * @internal
125
     */
126
    function serialize() {
127
        $cmd = $this->command;
128
        $this->command = null;
129
        
130
        if($cmd !== null) {
131
            $this->internalCommand = $cmd->groupID.':'.$cmd->name;
132
        }
133
        
134
        $str = parent::serialize();
135
        $this->command = $cmd;
136
        
137
        return $str;
138
    }
139
    
140
    /**
141
     * @return void
142
     * @internal
143
     */
144
    function unserialize($data) {
145
        if(self::$serializeClient === null) {
146
            throw new \Exception('Unable to unserialize a class without ClientBase::$serializeClient being set');
147
        }
148
        
149
        parent::unserialize($data);
150
        
151
        /** @var \CharlotteDunois\Livia\Client  $this->client */
152
        $this->client = self::$serializeClient;
153
        
154
        if($this->internalCommand !== null) {
155
            $this->command = $this->client->registry->resolveCommand($this->internalCommand);
156
            $this->internalCommand = null;
157
        }
158
    }
159
    
160
    /**
161
     * Parses the argString into usable arguments, based on the argsType and argsCount of the command.
162
     * @return string|string[]
163
     * @throws \LogicException
164
     * @throws \RangeException
165
     */
166
    function parseCommandArgs() {
167
        if($this->command === null) {
168
            throw new \LogicException('This message has no command');
169
        }
170
        
171
        switch($this->command->argsType) {
172
            case 'single':
173
                $args = $this->argString;
174
                return \preg_replace(($this->command->argsSingleQuotes ? '/^("|\')(.*)\1$/u' : '/^(")(.*)"$/u'), '$2', $args);
175
            case 'multiple':
176
                return self::parseArgs($this->argString, $this->command->argsCount, $this->command->argsSingleQuotes);
177
            default:
178
                throw new \RangeException('Unknown argsType "'.$this->command->argsType.'".');
179
        }
180
    }
181
    
182
    /**
183
     * Runs the command. Resolves with an instance of Message or an array of Message instances.
184
     * @return \React\Promise\ExtendedPromiseInterface
185
     * @throws \LogicException
186
     */
187
    function run() {
188
        if($this->command === null) {
189
            throw new \LogicException('This message has no command');
190
        }
191
        
192
        return (new \React\Promise\Promise(function (callable $resolve, callable $reject) {
193
            $promises = array();
194
            
195
            // Obtain the member if we don't have it
196
            if($this->message->guild !== null && !$this->message->guild->members->has($this->message->author->id) && $this->message->webhookID === null) {
197
                $promises[] = $this->message->guild->fetchMember($this->message->author->id);
198
            }
199
            
200
            // Obtain the member for the client user if we don't have it
201
            if($this->message->guild !== null && $this->message->guild->me === null) {
202
                $promises[] = $this->message->guild->fetchMember($this->client->user->id);
203
            }
204
            
205
            if($this->command->guildOnly && $this->message->guild === null) {
206
                $this->client->emit('commandBlocked', $this, 'guildOnly');
207
                return $this->client->dispatcher->throttleNegativeResponseMessage($this, 'The `'.$this->command->name.'` command must be used in a server channel.', $resolve, $reject);
208
            }
209
            
210
            if($this->command->nsfw && !($this->message->channel->nsfw ?? true)) {
211
                $this->client->emit('commandBlocked', $this, 'nsfw');
212
                return $this->client->dispatcher->throttleNegativeResponseMessage($this, 'The `'.$this->command->name.'` command must be used in NSFW channels.', $resolve, $reject);
213
            }
214
            
215
            $perms = $this->command->hasPermission($this);
216
            if($perms === false || \is_string($perms)) {
217
                $this->client->emit('commandBlocked', $this, 'permission');
218
                
219
                if($this->patternMatches !== null && !((bool) $this->client->getOption('commandBlockedMessagePattern', true))) {
220
                    return $resolve();
221
                }
222
                
223
                if($perms === false) {
224
                    $perms = 'You do not have permission to use the `'.$this->command->name.'` command.';
225
                }
226
                
227
                return $this->client->dispatcher->throttleNegativeResponseMessage($this, $perms, $resolve, $reject);
228
            }
229
            
230
            // Ensure the client user has the required permissions
231
            if(
232
                $this->message->channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface &&
233
                $this->message->channel->guild !== null &&
234
                !empty($this->command->clientPermissions)
235
            ) {
236
                $perms = $this->message->channel->permissionsFor($this->message->guild->me);
237
                
238
                $missing = array();
239
                foreach($this->command->clientPermissions as $perm) {
240
                    if($perms->missing($perm)) {
241
                        $missing[] = $perm;
242
                    }
243
                }
244
                
245
                if(\count($missing) > 0) {
246
                    $this->client->emit('commandBlocked', $this, 'clientPermissions');
247
                    
248
                    if($this->patternMatches !== null && !((bool) $this->client->getOption('commandBlockedMessagePattern', true))) {
249
                        return $resolve();
250
                    }
251
                    
252
                    if(\count($missing) === 1) {
253
                        $msg = 'I need the permissions `'.$missing[0].'` permission for the `'.$this->command->name.'` command to work.';
254
                    } else {
255
                        $missing = \implode(', ', \array_map(function ($perm) {
256
                            return '`'.\CharlotteDunois\Yasmin\Models\Permissions::resolveToName($perm).'`';
257
                        }, $missing));
258
                        $msg = 'I need the following permissions for the `'.$this->command->name.'` command to work:'.\PHP_EOL.$missing;
259
                    }
260
                    
261
                    return $this->client->dispatcher->throttleNegativeResponseMessage($this, $msg, $resolve, $reject);
262
                }
263
            }
264
            
265
            // Throttle the command
266
            $throttle = $this->command->throttle($this->message->author->id);
267
            if($throttle && ($throttle['usages'] + 1) > ($this->command->throttling['usages'])) {
268
                $remaining = $throttle['start'] + $this->command->throttling['duration'] - \time();
269
                $this->client->emit('commandBlocked', $this, 'throttling');
270
                
271
                if($this->patternMatches !== null && !((bool) $this->client->getOption('commandThrottlingMessagePattern', true))) {
272
                    return $resolve();
273
                }
274
                
275
                return $this->client->dispatcher->throttleNegativeResponseMessage(
276
                    $this,
277
                    'You may not use the `'.$this->command->name.'` command again for another '.$remaining.' seconds.',
278
                    $resolve,
279
                    $reject
280
                );
281
            }
282
            
283
            // Figure out the command arguments
284
            $args = $this->patternMatches;
285
            $argmsgs = array();
286
            $countArgs = \count($this->command->args);
287
            
288
            if(!$args && $countArgs > 0) {
289
                $count = (!empty($this->command->args[($countArgs - 1)]['infinite']) ? \INF : $countArgs);
290
                $provided = self::parseArgs($this->argString, $count, $this->command->argsSingleQuotes);
291
                
292
                $promises[] = $this->command->argsCollector->obtain($this, $provided)->then(function ($result) use (&$args, &$argmsgs) {
293
                    if($result['cancelled']) {
294
                        if(\count($result['prompts']) === 0) {
295
                            throw new \CharlotteDunois\Livia\Exceptions\CommandFormatException($this);
296
                        }
297
                        
298
                        $argmsgs = $result['prompts'];
299
                        $this->client->emit('commandCancelled', $this, $result['cancelled']);
300
                        
301
                        throw new \CharlotteDunois\Livia\Exceptions\FriendlyException('Cancelled Command.');
302
                    }
303
                    
304
                    $args = $result['values'];
305
                    $argmsgs = $result['prompts'];
306
                    
307
                    if(!$args) {
308
                        $args = $this->parseCommandArgs();
309
                    }
310
                    
311
                    $args = new \ArrayObject(((array) $args), \ArrayObject::ARRAY_AS_PROPS);
312
                });
313
            } else {
314
                $args = new \ArrayObject(((array) $args), \ArrayObject::ARRAY_AS_PROPS);
315
            }
316
            
317
            // Run the command
318
            if($throttle) {
319
                $this->command->updateThrottle($this->message->author->id);
320
            }
321
            
322
            $typingCount = $this->message->channel->typingCount();
323
            
324
            \React\Promise\all($promises)->then(function () use (&$args, &$argmsgs) {
325
                $promise = $this->command->run($this, $args, ($this->patternMatches !== null));
326
                
327
                if(!($promise instanceof \React\Promise\PromiseInterface)) {
328
                    $promise = \React\Promise\resolve($promise);
329
                }
330
                
331
                $this->client->emit('commandRun', $this->command, $promise, $this, $args, ($this->patternMatches !== null));
332
                
333
                return $promise->then(function ($response) use (&$argmsgs) {
334
                    if(!(
335
                        $response instanceof \CharlotteDunois\Yasmin\Models\Message ||
336
                        $response instanceof \CharlotteDunois\Collect\Collection ||
337
                        \is_array($response) ||
338
                        $response === null
339
                    )) {
340
                        throw new \RuntimeException('Command '.$this->command->name.'\'s run() resolved with an unknown type ('.\gettype($response).'). Command run methods must return a Promise that resolve with a Message, an array of Messages, a Collection of Messages, or null.');
341
                    }
342
                    
343
                    if(!\is_array($response) && !($response instanceof \CharlotteDunois\Collect\Collection)) {
344
                        if($response instanceof \CharlotteDunois\Yasmin\Models\Message) {
345
                            $argmsgs[] = $response;
346
                        }
347
                        
348
                        return $argmsgs;
349
                    }
350
                    
351
                    foreach($response as &$val) {
352
                        if(!($val instanceof \React\Promise\PromiseInterface)) {
353
                            $val = \React\Promise\resolve($val);
354
                        }
355
                    }
356
                    
357
                    return \React\Promise\all($response)->then(function ($msgs) use (&$argmsgs) {
358
                        return \array_merge($argmsgs, $msgs);
359
                    });
360
                });
361
            })->then(null, function (\Throwable $error) use (&$args, $typingCount, &$argmsgs) {
362
                if($this->message->channel->typingCount() > $typingCount) {
363
                    $this->message->channel->stopTyping();
364
                }
365
                
366
                if($error instanceof \CharlotteDunois\Livia\Exceptions\FriendlyException) {
367
                    return $this->reply($error->getMessage())->then(function (\CharlotteDunois\Yasmin\Models\Message $msg) use (&$argmsgs) {
368
                        $argmsgs[] = $msg;
369
                        return $argmsgs;
370
                    });
371
                }
372
                
373
                $this->client->emit('commandError', $this->command, $error, $this, $args, ($this->patternMatches !== null));
374
                
375
                $owners = $this->client->owners;
376
                $ownersLength = \count($owners);
377
                
378
                if($ownersLength > 0) {
379
                    $index = 0;
380
                    $owners = \array_map(function ($user) use (&$index, $ownersLength) {
381
                        $or = ($ownersLength > 1 && $index === ($ownersLength - 1) ? 'or ' : '');
382
                        $index++;
383
                        
384
                        return $or.\CharlotteDunois\Yasmin\Utils\MessageHelpers::escapeMarkdown($user->tag);
385
                    }, $owners);
386
                    
387
                    $owners = \implode((\count($owners) > 2 ? ', ' : ' '), $owners);
388
                } else {
389
                    $owners = 'the bot owner';
390
                }
391
                
392
                return $this->reply('An error occurred while running the command: `'.\get_class($error).': '.\str_replace('`', '', $error->getMessage()).'`'.\PHP_EOL.
393
                        'You shouldn\'t ever receive an error like this.'.\PHP_EOL.
394
                        'Please contact '.$owners.($this->client->getOption('invite') ? ' in this server: '.$this->client->getOption('invite') : '.'))
395
                        ->then(function (\CharlotteDunois\Yasmin\Models\Message $msg) use (&$argmsgs) {
396
                            $argmsgs[] = $msg;
397
                            return $argmsgs;
398
                        });
399
            })->done($resolve, $reject);
400
        }));
401
    }
402
    
403
    /**
404
     * Responds to the command message
405
     * @param string  $type      One of plain, reply or direct.
406
     * @param string  $content
407
     * @param array   $options
408
     * @param bool    $fromEdit
409
     * @return \React\Promise\ExtendedPromiseInterface
410
     * @throws \RangeException
411
     * @throws \InvalidArgumentException
412
     */
413
    protected function respond(string $type, string $content, array $options = array(), bool $fromEdit = false) {
414
        if($type === 'reply' && $this->message->channel instanceof \CharlotteDunois\Yasmin\Interfaces\DMChannelInterface) {
415
            $type = 'plain';
416
        } elseif($type !== 'direct' && $this->message->guild !== null && !$this->message->channel->permissionsFor($this->client->user)->has('SEND_MESSAGES')) {
417
            $type = 'direct';
418
        }
419
        
420
        if(!empty($options['split']) && !\is_array($options['split'])) {
421
            $options['split'] = array();
422
        }
423
        
424
        $channelID = $this->getChannelIDOrDM($this->message->channel);
425
        $shouldEdit = (
426
            !empty($this->responses) &&
427
            (
428
                ($type === 'direct' && !empty($this->responses['dm'])) ||
429
                ($type !== 'direct' && !empty($this->responses[$channelID]))
430
            ) &&
431
            !$fromEdit &&
432
            empty($options['files'])
433
        );
434
        
435
        switch($type) {
436
            case 'plain':
437
                if($shouldEdit) {
438
                    return $this->editCurrentResponse($channelID, $type, $content, $options);
439
                } else {
440
                    return $this->message->channel->send($content, $options);
441
                }
442
            break;
443
            case 'reply':
444
                if($shouldEdit) {
445
                    return $this->editCurrentResponse($channelID, $type, $content, $options);
446
                } else {
447
                    if(!empty($options['split']) && empty($options['split']['prepend'])) {
448
                        $options['split']['prepend'] = $this->message->author->__toString().\CharlotteDunois\Yasmin\Models\Message::$replySeparator;
449
                    }
450
                    
451
                    return $this->message->reply($content, $options);
452
                }
453
            break;
454
            case 'direct':
455
                if($shouldEdit) {
456
                    return $this->editCurrentResponse($channelID, $type, $content, $options);
457
                } else {
458
                    return $this->message->author->createDM()->then(function ($channel) use ($content, $options) {
459
                        return $channel->send($content, $options);
460
                    });
461
                }
462
            break;
463
            default:
464
                throw new \RangeException('Unknown response type "'.$type.'"');
465
            break;
466
        }
467
    }
468
    
469
    /**
470
     * Edits a response to the command message. Resolves with an instance of Message or an array of Message instances.
471
     * @param \CharlotteDunois\Yasmin\Models\Message|\CharlotteDunois\Yasmin\Models\Message[]|null  $response
472
     * @param string                                                                                $type
473
     * @param string                                                                                $content
474
     * @param array                                                                                 $options
475
     * @return \React\Promise\ExtendedPromiseInterface
476
     */
477
    protected function editResponse($response, string $type, string $content, array $options = array()) {
478
        if(!$response) {
479
            return $this->respond($type, $content, $options);
480
        }
481
        
482
        if(!empty($options['split'])) {
483
            $content = \CharlotteDunois\Yasmin\Utils\MessageHelpers::splitMessage($content, (\is_array($options['split']) ? $options['split'] : array()));
484
            if(\count($content) === 1) {
485
                $content = $content[0];
486
            }
487
        }
488
        
489
        $prepend = '';
490
        if($type === 'reply') {
491
            $prepend = $this->message->author->__toString().\CharlotteDunois\Yasmin\Models\Message::$replySeparator;
492
        }
493
        
494
        if(\is_array($content)) {
495
            $promises = array();
496
            $clength = \count($content);
497
            
498
            if(\is_array($response)) {
499
                for($i = 0;  $i < $clength; $i++) {
500
                    if(!empty($response[$i])) {
501
                        $promises[] = $response[$i]->edit($prepend.$content[$i], $options);
502
                    } else {
503
                        $promises[] = $this->message->channel->send($prepend.$content[$i], $options);
504
                    }
505
                }
506
            } else {
507
                $promises[] = $response->edit($prepend.$content[0], $options);
508
                for($i = 1; $i < $clength; $i++) {
509
                    $promises[] = $this->message->channel->send($prepend.$content[$i], $options);
510
                }
511
            }
512
            
513
            return \React\Promise\all($promises);
514
        } else {
515
            if(\is_array($response)) {
516
                for($i = \count($response) - 1;  $i > 0; $i--) {
517
                    $response[$i]->delete()->done();
518
                }
519
                
520
                return $response[0]->edit($prepend.$content, $options);
521
            } else {
522
                return $response->edit($prepend.$content, $options);
523
            }
524
        }
525
    }
526
    
527
    /**
528
     * Edits the current response.
529
     * @param string  $id       The ID of the channel the response is in ("DM" for direct messages).
530
     * @param string  $type
531
     * @param string  $content
532
     * @param array   $options
533
     * @return \React\Promise\ExtendedPromiseInterface
534
     */
535
    protected function editCurrentResponse(string $id, string $type, string $content, array $options = array()) {
536
        if(empty($this->responses[$id])) {
537
            $this->responses[$id] = array();
538
        }
539
        
540
        if(!empty($this->responses[$id])) {
541
            $msg = \array_shift($this->responses[$id]);
542
        } else {
543
            $msg = null;
544
        }
545
        
546
        return $this->editResponse($msg, $type, $content, $options);
547
    }
548
    
549
    /**
550
     * Responds with a plain message. Resolves with an instance of Message or an array of Message instances.
551
     * @param string  $content
552
     * @param array   $options  Message Options.
553
     * @return \React\Promise\ExtendedPromiseInterface
554
     */
555
    function say(string $content, array $options = array()) {
556
        return $this->respond('plain', $content, $options);
557
    }
558
    
559
    /**
560
     * Responds with a reply message. Resolves with an instance of Message or an array of Message instances.
561
     * @param string  $content
562
     * @param array   $options  Message Options.
563
     * @return \React\Promise\ExtendedPromiseInterface
564
     */
565
    function reply(string $content, array $options = array()) {
566
        return $this->respond('reply', $content, $options);
567
    }
568
    
569
    /**
570
     * Responds with a direct message. Resolves with an instance of Message or an array of Message instances.
571
     * @param string  $content
572
     * @param array   $options  Message Options.
573
     * @return \React\Promise\ExtendedPromiseInterface
574
     */
575
    function direct(string $content, array $options = array()) {
576
        return $this->respond('direct', $content, $options);
577
    }
578
    
579
    /**
580
     * Shortcut to $this->message->edit.
581
     * @param string  $content
582
     * @param array   $options  Message Options.
583
     * @return \React\Promise\ExtendedPromiseInterface
584
     */
585
    function edit(string $content, array $options = array()) {
586
        return $this->message->edit($content, $options);
587
    }
588
    
589
    /**
590
     * Finalizes the command message by setting the responses and deleting any remaining prior ones.
591
     * @param \CharlotteDunois\Yasmin\Models\Message|\CharlotteDunois\Yasmin\Models\Message[]|null  $responses
592
     * @return void
593
     * @internal
594
     */
595
    function finalize($responses) {
596
        if(!empty($this->responses)) {
597
            $this->deleteRemainingResponses();
598
        }
599
        
600
        if(\is_array($responses)) {
601
            foreach($responses as $response) {
602
                /** @var \CharlotteDunois\Yasmin\Models\Message  $msg */
603
                $msg = (\is_array($response) ? $response[0] : $response);
604
                
605
                if(!($msg instanceof \CharlotteDunois\Yasmin\Models\Message)) {
606
                    continue;
607
                }
608
                
609
                $id = $this->getChannelIDOrDM($msg->channel);
610
                
611
                if(empty($this->responses[$id])) {
612
                    $this->responses[$id] = array();
613
                }
614
                
615
                $this->responses[$id][] = $response;
616
            }
617
        } elseif($responses !== null) {
618
            if(!($responses instanceof \CharlotteDunois\Yasmin\Models\Message)) {
619
                return;
620
            }
621
            
622
            $id = $this->getChannelIDOrDM($responses->channel);
623
            $this->responses[$id] = array($responses);
624
        }
625
    }
626
    
627
    /**
628
     * Deletes any prior responses that haven't been updated.
629
     * @return void
630
     * @internal
631
     */
632
    function deleteRemainingResponses() {
633
        foreach($this->responses as $id => $msgs) {
634
            foreach($msgs as $response) {
635
                if(\is_array($response)) {
636
                    foreach($response as $resp) {
637
                        $resp->delete()->done();
638
                    }
639
                } else {
640
                    $response->delete()->done();
641
                }
642
            }
643
            
644
            $this->responses[$id] = array();
645
        }
646
    }
647
    
648
    /**
649
     * @return string|int
650
     */
651
    protected function getChannelIDOrDM(\CharlotteDunois\Yasmin\Interfaces\TextChannelInterface $channel) {
652
        if(!($channel instanceof \CharlotteDunois\Yasmin\Interfaces\DMChannelInterface)) {
653
            return $channel->id;
654
        }
655
        
656
        return 'dm';
657
    }
658
    
659
    /**
660
     * Parses an argument string into an array of arguments.
661
     * @param string          $argString
662
     * @param int|float|null  $argCount           float = \INF
663
     * @param bool            $allowSingleQuotes
664
     * @return string[]
665
     */
666
    static function parseArgs(string $argString, $argCount = null, bool $allowSingleQuotes = true) {
667
        if(\mb_strlen($argString) === 0) {
668
            return array();
669
        }
670
        
671
        if($argCount === 1) {
672
            return array($argString);
673
        }
674
        
675
        $regex = '/(?:(['.($allowSingleQuotes ? "'" : '').'"])(.*?)(?<!\\\\)(?>\\\\\\\)*\1|([^\\s]+))/Su';
676
        $results = array();
677
        
678
        $argString = \trim($argString);
679
        
680
        if($argCount === null) {
681
            $argCount = \mb_strlen($argString); // Large enough to get all items
682
        }
683
        
684
        $content = $argString;
685
        \preg_match_all($regex, $argString, $matches);
686
        
687
        foreach($matches[0] as $key => $val) {
688
            $argCount--;
689
            if($argCount === 0) {
690
                break;
691
            }
692
            
693
            $val = \trim(($matches[3][$key] !== '' ? $matches[3][$key] : ($matches[2][$key] !== '' ? $matches[2][$key] : $matches[1][$key])));
694
            $results[] = $val;
695
            
696
            $content = \trim(\preg_replace('/'.\preg_quote($val, '/').'/u', '', $content, 1));
697
        }
698
        
699
        // If text remains, push it to the array as-is (except for wrapping quotes, which are removed)
700
        if(\mb_strlen($content) > 0) {
701
            $results[] = \preg_replace(($allowSingleQuotes ? '/^("|\')(.*)\1$/u' : '/^(")(.*)\1$/u'), '$2', $content);
702
        }
703
        
704
        if(\count($results) > 0) {
705
            $results = \array_filter($results, function ($val) {
706
                return (\mb_strlen(\trim($val)) > 0);
707
            });
708
        }
709
        
710
        return $results;
711
    }
712
    
713
    /**
714
     * @return void
715
     * @internal
716
     */
717
    function setResponses($responses) {
718
        $this->responses = $responses;
719
    }
720
}
721