Completed
Push — master ( 49f37f...c71174 )
by Ehsan
02:56
created

Slackbot::preProcessRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Botonomous;
4
5
use Botonomous\client\ApiClient;
6
use Botonomous\listener\EventListener;
7
use Botonomous\plugin\AbstractPlugin;
8
9
/**
10
 * Class Botonomous.
11
 */
12
class Slackbot extends AbstractBot
13
{
14
    private $commands;
15
    private $lastError;
16
    private $currentCommand;
17
18
    /**
19
     * Botonomous constructor.
20
     *
21
     * @param Config|null $config
22
     *
23
     * @throws \Exception
24
     */
25 35
    public function __construct(Config $config = null)
26
    {
27 35
        if ($config !== null) {
28 4
            $this->setConfig($config);
29
        }
30
31
        // set timezone
32 35
        date_default_timezone_set($this->getConfig()->get('timezone'));
33 35
    }
34
35
    /**
36
     * @param null|string $key
37
     *
38
     * @return mixed
39
     */
40 8
    public function getRequest($key = null)
41
    {
42 8
        return $this->getListener()->getRequest($key);
43
    }
44
45
    /**
46
     * @return string|null
47
     */
48 7
    private function determineAction()
49
    {
50 7
        $utility = $this->getRequestUtility();
51 7
        $getRequest = $utility->getGet();
52
53 7
        if (!empty($getRequest['action'])) {
54 2
            return strtolower($getRequest['action']);
55
        }
56
57 5
        $request = $utility->getPostedBody();
58
59 5
        if (isset($request['type']) && $request['type'] === 'url_verification') {
60 1
            return 'url_verification';
61
        }
62 4
    }
63
64
    /**
65
     * @return null|AbstractBaseSlack
66
     */
67 1
    private function handleMessageActions()
68
    {
69 1
        $post = $this->getRequestUtility()->getPost();
70
71
        // ignore if payload is not set
72 1
        if (!isset($post['payload'])) {
73
            /* @noinspection PhpInconsistentReturnPointsInspection */
74 1
            return;
75
        }
76
77
        // posted payload is in JSON
78 1
        $payload = json_decode($post['payload'], true);
79
80 1
        return (new MessageAction())->load($payload);
81
    }
82
83
    /**
84
     * @throws \Exception
85
     */
86 4
    private function handleSendResponse()
87
    {
88
        // 1. Start listening
89 4
        $this->getListener()->listen();
90
91
        // 2. verify the request
92
        try {
93 4
            $verificationResult = $this->verifyRequest();
94
95 4
            if ($verificationResult['success'] !== true) {
96 4
                throw new \Exception($verificationResult['message']);
97
            }
98 1
        } catch (\Exception $e) {
99 1
            throw $e;
100
        }
101
102
        // 3. pre process the request
103 3
        $this->preProcessRequest();
104
105
        // 4. check access control
106 3
        if ($this->checkAccessControl() !== true) {
107 2
            return;
108
        }
109
110
        // 5. set the current command
111 1
        $message = $this->getMessage();
112 1
        $this->setCurrentCommand($this->getMessageUtility()->extractCommandName($message));
113
114
        // 6. log the message
115 1
        if (empty($this->getRequest('debug'))) {
116 1
            $this->getLoggerUtility()->logRaw($this->getFormattingUtility()->newLine());
117 1
            $this->getLoggerUtility()->logChat(__METHOD__, $message);
118
        }
119
120 1
        $this->getLoggerUtility()->logRaw($this->getRequestUtility()->getContent());
121
122
        // 7. send confirmation message if is enabled
123 1
        $this->sendConfirmation();
124
125
        // 8. And send the response to the channel, only if the response is not empty
126 1
        $response = $this->respond($message);
127
128 1
        if (!empty($response)) {
129 1
            $this->getSender()->send($response);
130
        }
131 1
    }
132
133
    /**
134
     * @return bool
135
     */
136 3
    private function checkAccessControl()
137
    {
138
        // if accessControlEnabled is not set true ignore the check and return true
139 3
        if ($this->getConfig()->get('accessControlEnabled') !== true) {
140 1
            return true;
141
        }
142
143 2
        if ($this->getBlackList()->isBlackListed() !== false) {
144
            // found in blacklist
145 1
            $this->getSender()->send($this->getConfig()->get('blacklistedMessage'));
146
147 1
            return false;
148
        }
149
150 1
        if ($this->getWhiteList()->isWhiteListed() !== true) {
151
            // not found in whitelist
152 1
            $this->getSender()->send($this->getConfig()->get('whitelistedMessage'));
153
154 1
            return false;
155
        }
156
157
        return true;
158
    }
159
160
    /**
161
     * @throws \Exception
162
     */
163 7
    public function run()
164
    {
165 7
        switch ($this->determineAction()) {
166 7
            case 'oauth':
167 1
                return $this->handleOAuth();
168 6
            case 'message_actions':
169 1
                return $this->handleMessageActions();
170 5
            case 'url_verification':
171 1
                return $this->handleUrlVerification();
172
            default:
173 4
                return $this->handleSendResponse();
174
        }
175
    }
176
177
    /**
178
     * handle OAuth.
179
     */
180 1
    private function handleOAuth()
181
    {
182 1
        return $this->getOauth()->doOauth();
183
    }
184
185
    /**
186
     * @throws \Exception
187
     *
188
     * @return mixed
189
     */
190 1
    private function handleUrlVerification()
191
    {
192 1
        $request = $this->getRequestUtility()->getPostedBody();
193
194 1
        if (empty($request['challenge'])) {
195
            throw new \Exception('Challenge is missing for URL verification');
196
        }
197
198 1
        echo $request['challenge'];
199 1
    }
200
201
    /**
202
     * Send confirmation.
203
     */
204 1
    private function sendConfirmation()
205
    {
206 1
        $userId = $this->getRequest('user_id');
207
208 1
        $user = '';
209 1
        if (!empty($userId)) {
210 1
            $user = $this->getMessageUtility()->linkToUser($userId).' ';
211
        }
212
213 1
        $confirmMessage = $this->getConfig()->get('confirmReceivedMessage', ['user' => $user]);
214
215 1
        if (!empty($confirmMessage)) {
216 1
            $this->getSender()->send($confirmMessage);
217
        }
218 1
    }
219
220
    /**
221
     * Pre-process the request.
222
     */
223 3
    private function preProcessRequest()
224
    {
225 3
        $request = $this->getListener()->getRequest();
226
227
        // remove the trigger_word from beginning of the message
228 3
        if (!empty($request['trigger_word'])) {
229 3
            $request['text'] = $this->getMessageUtility()->removeTriggerWord(
230 3
                $request['text'],
231 3
                $request['trigger_word']
232
            );
233
234 3
            $this->getListener()->setRequest($request);
235
        }
236 3
    }
237
238
    /**
239
     * @param null $message
240
     *
241
     * @throws \Exception
242
     *
243
     * @return mixed
244
     */
245 4
    public function respond($message = null)
246
    {
247
        try {
248 4
            $command = $this->getCommandByMessage($message);
249
250 4
            if (!$command instanceof Command) {
251
                // something went wrong, error will tell us!
252 2
                return $this->getLastError();
253
            }
254
255
            // create the class
256 3
            $pluginClassFile = $command->getClass();
257 3
            $pluginClass = new $pluginClassFile($this);
258
259
            // check class is valid
260 3
            if (!$pluginClass instanceof AbstractPlugin) {
261
                throw new \Exception("Couldn't create class: '{$pluginClassFile}'");
262
            }
263
264
            // check action exists
265 3
            $action = $command->getAction();
266 3
            if (!method_exists($pluginClass, $action)) {
267 1
                throw new \Exception("Action / function: '{$action}' does not exist in '{$pluginClassFile}'");
268
            }
269
270 2
            return $pluginClass->$action();
271 1
        } catch (\Exception $e) {
272 1
            throw $e;
273
        }
274
    }
275
276
    /**
277
     * @param null $message
278
     *
279
     * @throws \Exception
280
     *
281
     * @return bool|Command
282
     */
283 7
    public function getCommandByMessage($message = null)
284
    {
285
        // If message is not set, get it from the current request
286 7
        if ($message === null) {
287 4
            $message = $this->getMessage();
288
        }
289
290 7
        if (empty($message)) {
291 1
            $this->setLastError('Message is empty');
292
293 1
            return false;
294
        }
295
296
        /**
297
         * Process the message.
298
         */
299 6
        $command = $this->getMessageUtility()->extractCommandName($message);
300
301 6
        $config = $this->getConfig();
302
303
        // check command name
304 6
        if (empty($command)) {
305
            // get the default command if no command is find in the message
306 2
            $command = $config->get('defaultCommand');
307
308 2
            if (empty($command)) {
309 2
                $this->setLastError($config->get('noCommandMessage'));
310
311 2
                return false;
312
            }
313
        }
314
315 4
        $commandObject = $this->getCommandContainer()->getAsObject($command);
316
317
        // check command details
318 4
        if (empty($commandObject)) {
319 1
            $this->setLastError($config->get('unknownCommandMessage', ['command' => $command]));
320
321 1
            return false;
322
        }
323
324 4
        if (!$commandObject instanceof Command) {
325
            throw new \Exception('Command is not an object');
326
        }
327
328
        // check the plugin for the command
329 4
        if (empty($commandObject->getPlugin())) {
330
            throw new \Exception('Plugin is not set for this command');
331
        }
332
333 4
        return $commandObject;
334
    }
335
336
    /**
337
     * @throws \Exception
338
     *
339
     * @return array<string,boolean|string>
340
     */
341 4
    private function verifyRequest()
342
    {
343 4
        $originCheck = $this->getListener()->verifyOrigin();
344
345 4
        if (!isset($originCheck['success'])) {
346
            throw new \Exception('Success must be provided in verifyOrigin response');
347
        }
348
349 4
        if ($originCheck['success'] !== true) {
350
            return [
351
                'success' => false,
352
                'message' => $originCheck['message'],
353
            ];
354
        }
355
356 4
        if ($this->getListener()->isThisBot() !== false) {
357
            return [
358 1
                'success' => false,
359
                'message' => 'Request comes from the bot',
360
            ];
361
        }
362
363
        return [
364 3
            'success' => true,
365
            'message' => 'Yay!',
366
        ];
367
    }
368
369
    /**
370
     * @return array
371
     */
372 2
    public function getCommands()
373
    {
374 2
        if (!isset($this->commands)) {
375 1
            $this->setCommands($this->getCommandContainer()->getAllAsObject());
376
        }
377
378 2
        return $this->commands;
379
    }
380
381
    /**
382
     * @param array $commands
383
     */
384 2
    public function setCommands(array $commands)
385
    {
386 2
        $this->commands = $commands;
387 2
    }
388
389
    /**
390
     * @return string
391
     */
392 4
    public function getLastError()
393
    {
394 4
        return $this->lastError;
395
    }
396
397
    /**
398
     * @param string $lastError
399
     */
400 4
    public function setLastError($lastError)
401
    {
402 4
        $this->lastError = $lastError;
403 4
    }
404
405
    /**
406
     * @return string
407
     */
408 1
    public function getCurrentCommand()
409
    {
410 1
        return $this->currentCommand;
411
    }
412
413
    /**
414
     * @param string $currentCommand
415
     */
416 2
    public function setCurrentCommand($currentCommand)
417
    {
418 2
        $this->currentCommand = $currentCommand;
419 2
    }
420
421
    /**
422
     * Return message based on the listener
423
     * If listener is event and event text is empty, fall back to request text.
424
     *
425
     * @return mixed|string
426
     */
427 11
    public function getMessage()
428
    {
429 11
        $listener = $this->getListener();
430 11
        if ($listener instanceof EventListener && $listener->getEvent() instanceof Event) {
431 1
            $message = $listener->getEvent()->getText();
432
433 1
            if (!empty($message)) {
434 1
                return $message;
435
            }
436
        }
437
438 10
        return $listener->getRequest('text');
439
    }
440
441
    /**
442
     * Determine if bot user id is mentioned in the message.
443
     *
444
     * @return bool
445
     */
446 1
    public function youTalkingToMe()
447
    {
448 1
        $message = $this->getMessage();
449
450 1
        if (empty($message)) {
451 1
            return false;
452
        }
453
454 1
        if ($this->getMessageUtility()->isBotMentioned($message) === true) {
455 1
            return true;
456
        }
457
458 1
        $listener = $this->getListener();
459 1
        if ($listener instanceof EventListener) {
460
            // check direct messages
461
            $list = (new ApiClient())->imList();
462
463
            if (!empty($list)) {
464
                foreach ($list as $ImObject) {
465
                    // ignore any direct conversation with the default slack bot
466
                    if ($ImObject['user'] === 'USLACKBOT') {
467
                        continue;
468
                    }
469
470
                    // if IM Object id equals the event channel id the conversation is with the bot
471
                    if ($ImObject['id'] === $listener->getEvent()->getChannel()) {
472
                        return true;
473
                    }
474
                }
475
            }
476
        }
477
478 1
        return false;
479
    }
480
}
481