1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Botonomous; |
4
|
|
|
|
5
|
|
|
use Botonomous\listener\AbstractBaseListener; |
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
|
43 |
|
public function __construct(Config $config = null) |
26
|
|
|
{ |
27
|
43 |
|
if ($config !== null) { |
28
|
6 |
|
$this->setConfig($config); |
29
|
|
|
} |
30
|
|
|
|
31
|
43 |
|
$this->setTimezone(); |
32
|
43 |
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Set the timezone. |
36
|
|
|
* |
37
|
|
|
* @throws \Exception |
38
|
|
|
*/ |
39
|
43 |
|
private function setTimezone() |
40
|
|
|
{ |
41
|
|
|
// set timezone |
42
|
43 |
|
date_default_timezone_set($this->getConfig()->get('timezone')); |
43
|
43 |
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @param null|string $key |
47
|
|
|
* |
48
|
|
|
* @throws \Exception |
49
|
|
|
* |
50
|
|
|
* @return mixed |
51
|
|
|
*/ |
52
|
10 |
|
public function getRequest($key = null) |
53
|
|
|
{ |
54
|
10 |
|
return $this->getListener()->getRequest($key); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @throws \Exception |
59
|
|
|
* |
60
|
|
|
* @return AbstractBaseSlack|null|void |
61
|
|
|
*/ |
62
|
1 |
|
private function handleMessageActions() |
63
|
|
|
{ |
64
|
1 |
|
$post = $this->getRequestUtility()->getPost(); |
65
|
|
|
|
66
|
|
|
// ignore if payload is not set |
67
|
1 |
|
if (!isset($post['payload'])) { |
68
|
|
|
/* @noinspection PhpInconsistentReturnPointsInspection */ |
69
|
1 |
|
return; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// posted payload is in JSON |
73
|
1 |
|
$payload = json_decode($post['payload'], true); |
74
|
|
|
|
75
|
1 |
|
return (new MessageAction())->load($payload); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @throws \Exception |
80
|
|
|
*/ |
81
|
5 |
|
private function handleSendResponse() |
82
|
|
|
{ |
83
|
|
|
// 1. Start listening |
84
|
5 |
|
$this->getListener()->listen(); |
85
|
|
|
|
86
|
|
|
// 2. verify the request |
87
|
|
|
try { |
88
|
5 |
|
$verificationResult = $this->getListener()->verifyRequest(); |
89
|
|
|
|
90
|
5 |
|
if ($verificationResult[AbstractBaseListener::ORIGIN_VERIFICATION_SUCCESS_KEY] !== true) { |
91
|
2 |
|
throw new BotonomousException( |
92
|
5 |
|
$verificationResult[AbstractBaseListener::ORIGIN_VERIFICATION_MESSAGE_KEY] |
93
|
|
|
); |
94
|
|
|
} |
95
|
2 |
|
} catch (\Exception $e) { |
96
|
2 |
|
throw $e; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
// 3. pre process the request |
100
|
3 |
|
$this->preProcessRequest(); |
101
|
|
|
|
102
|
|
|
// 4. check access control |
103
|
3 |
|
if ($this->checkAccessControl() !== true) { |
104
|
2 |
|
return; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
// 5. set the current command |
108
|
1 |
|
$message = $this->getListener()->getMessage(); |
109
|
1 |
|
$this->setCurrentCommand($this->getMessageUtility()->extractCommandName($message)); |
110
|
|
|
|
111
|
|
|
// 6. log the message |
112
|
1 |
|
$this->getLoggerUtility()->logChat(__METHOD__, $message); |
113
|
|
|
|
114
|
|
|
// 7. send confirmation message if is enabled |
115
|
1 |
|
$this->getSender()->sendConfirmation(); |
116
|
|
|
|
117
|
|
|
// 8. And send the response to the channel, only if the response is not empty |
118
|
1 |
|
$response = $this->respond($message); |
119
|
|
|
|
120
|
1 |
|
if (!empty($response)) { |
121
|
1 |
|
$this->getSender()->send($response); |
122
|
|
|
} |
123
|
1 |
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* @throws \Exception |
127
|
|
|
* |
128
|
|
|
* @return bool |
129
|
|
|
*/ |
130
|
3 |
|
private function checkAccessControl(): bool |
131
|
|
|
{ |
132
|
|
|
// if accessControlEnabled is not set true ignore the check and return true |
133
|
3 |
|
if ($this->getConfig()->get('accessControlEnabled') !== true) { |
134
|
1 |
|
return true; |
135
|
|
|
} |
136
|
|
|
|
137
|
2 |
|
if ($this->getBlackList()->isBlackListed() !== false) { |
138
|
|
|
// found in blacklist |
139
|
1 |
|
$this->getSender()->send($this->getDictionary()->get('generic-messages')['blacklistedMessage']); |
140
|
|
|
|
141
|
1 |
|
return false; |
142
|
|
|
} |
143
|
|
|
|
144
|
1 |
|
if ($this->getWhiteList()->isWhiteListed() !== true) { |
145
|
|
|
// not found in whitelist |
146
|
1 |
|
$this->getSender()->send($this->getDictionary()->get('generic-messages')['whitelistedMessage']); |
147
|
|
|
|
148
|
1 |
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return true; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @throws \Exception |
156
|
|
|
*/ |
157
|
8 |
|
public function run() |
158
|
|
|
{ |
159
|
8 |
|
switch ($this->getListener()->determineAction()) { |
160
|
8 |
|
case 'oauth': |
161
|
1 |
|
return $this->handleOAuth(); |
162
|
7 |
|
case 'message_actions': |
163
|
1 |
|
return $this->handleMessageActions(); |
164
|
6 |
|
case 'url_verification': |
165
|
1 |
|
return $this->handleUrlVerification(); |
166
|
|
|
default: |
167
|
5 |
|
return $this->handleSendResponse(); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* handle OAuth. |
173
|
|
|
* |
174
|
|
|
* @throws \Exception |
175
|
|
|
*/ |
176
|
1 |
|
private function handleOAuth() |
177
|
|
|
{ |
178
|
1 |
|
return $this->getOauth()->doOauth(); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @throws \Exception |
183
|
|
|
*/ |
184
|
1 |
|
private function handleUrlVerification() |
185
|
|
|
{ |
186
|
1 |
|
$request = $this->getRequestUtility()->getPostedBody(); |
187
|
|
|
|
188
|
1 |
|
if (empty($request['challenge'])) { |
189
|
|
|
throw new BotonomousException('Challenge is missing for URL verification'); |
190
|
|
|
} |
191
|
|
|
|
192
|
1 |
|
echo $request['challenge']; |
193
|
1 |
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Pre-process the request. |
197
|
|
|
* |
198
|
|
|
* @throws \Exception |
199
|
|
|
*/ |
200
|
3 |
|
private function preProcessRequest() |
201
|
|
|
{ |
202
|
3 |
|
$request = $this->getListener()->getRequest(); |
203
|
|
|
|
204
|
|
|
// remove the trigger_word from beginning of the message |
205
|
3 |
|
if (!empty($request['trigger_word'])) { |
206
|
3 |
|
$request['text'] = $this->getMessageUtility()->removeTriggerWord( |
207
|
3 |
|
$request['text'], |
208
|
3 |
|
$request['trigger_word'] |
209
|
|
|
); |
210
|
|
|
|
211
|
3 |
|
$this->getListener()->setRequest($request); |
212
|
|
|
} |
213
|
3 |
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @param null $message |
217
|
|
|
* |
218
|
|
|
* @throws \Exception |
219
|
|
|
* |
220
|
|
|
* @return mixed |
221
|
|
|
*/ |
222
|
4 |
|
public function respond($message = null) |
223
|
|
|
{ |
224
|
|
|
try { |
225
|
|
|
// If message is not set, get it from the current request |
226
|
4 |
|
if ($message === null) { |
227
|
3 |
|
$message = $this->getListener()->getMessage(); |
228
|
|
|
} |
229
|
|
|
|
230
|
4 |
|
$commandExtractor = $this->getCommandExtractor(); |
231
|
4 |
|
$command = $commandExtractor->getCommandByMessage($message); |
232
|
|
|
|
233
|
4 |
|
if (!$command instanceof Command) { |
234
|
|
|
// something went wrong, error will tell us! |
235
|
2 |
|
return $commandExtractor->getError(); |
236
|
|
|
} |
237
|
|
|
|
238
|
3 |
|
$pluginClass = $this->getPluginClassByCommand($command); |
239
|
|
|
|
240
|
|
|
// check action exists |
241
|
3 |
|
$action = $command->getAction(); |
242
|
3 |
|
if (!method_exists($pluginClass, $action)) { |
243
|
1 |
|
$className = get_class($pluginClass); |
244
|
|
|
|
245
|
1 |
|
throw new BotonomousException("Action / function: '{$action}' does not exist in '{$className}'"); |
246
|
|
|
} |
247
|
|
|
|
248
|
2 |
|
return $pluginClass->$action(); |
249
|
1 |
|
} catch (\Exception $e) { |
250
|
1 |
|
throw $e; |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Get plugin class by command. |
256
|
|
|
* |
257
|
|
|
* @param Command $command |
258
|
|
|
* |
259
|
|
|
* @throws \Exception |
260
|
|
|
* |
261
|
|
|
* @return AbstractPlugin |
262
|
|
|
*/ |
263
|
3 |
|
private function getPluginClassByCommand(Command $command) |
264
|
|
|
{ |
265
|
|
|
// create the class |
266
|
3 |
|
$pluginClassFile = $command->getClass(); |
267
|
3 |
|
$pluginClass = new $pluginClassFile($this); |
268
|
|
|
|
269
|
|
|
// check class is valid |
270
|
3 |
|
if (!$pluginClass instanceof AbstractPlugin) { |
271
|
|
|
$className = get_class($pluginClass); |
272
|
|
|
|
273
|
|
|
throw new BotonomousException("Couldn't create class: '{$className}'"); |
274
|
|
|
} |
275
|
|
|
|
276
|
3 |
|
return $pluginClass; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @throws \Exception |
281
|
|
|
* |
282
|
|
|
* @return array |
283
|
|
|
*/ |
284
|
2 |
|
public function getCommands(): array |
285
|
|
|
{ |
286
|
2 |
|
if (!isset($this->commands)) { |
287
|
1 |
|
$this->setCommands($this->getCommandContainer()->getAllAsObject()); |
288
|
|
|
} |
289
|
|
|
|
290
|
2 |
|
return $this->commands; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* @param array $commands |
295
|
|
|
*/ |
296
|
2 |
|
public function setCommands(array $commands) |
297
|
|
|
{ |
298
|
2 |
|
$this->commands = $commands; |
299
|
2 |
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @return string |
303
|
|
|
*/ |
304
|
1 |
|
public function getLastError(): string |
305
|
|
|
{ |
306
|
1 |
|
return $this->lastError; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* @param string $lastError |
311
|
|
|
*/ |
312
|
1 |
|
public function setLastError(string $lastError) |
313
|
|
|
{ |
314
|
1 |
|
$this->lastError = $lastError; |
315
|
1 |
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Return the current command. |
319
|
|
|
* |
320
|
|
|
* @return string |
321
|
|
|
*/ |
322
|
1 |
|
public function getCurrentCommand(): string |
323
|
|
|
{ |
324
|
1 |
|
return $this->currentCommand; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* @param string $currentCommand |
329
|
|
|
*/ |
330
|
2 |
|
public function setCurrentCommand(string $currentCommand) |
331
|
|
|
{ |
332
|
2 |
|
$this->currentCommand = $currentCommand; |
333
|
2 |
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Determine if bot user id is mentioned in the message. |
337
|
|
|
* |
338
|
|
|
* @throws \Exception |
339
|
|
|
* |
340
|
|
|
* @return bool |
341
|
|
|
*/ |
342
|
1 |
|
public function youTalkingToMe(): bool |
343
|
|
|
{ |
344
|
1 |
|
$message = $this->getListener()->getMessage(); |
345
|
|
|
|
346
|
1 |
|
if (empty($message)) { |
347
|
1 |
|
return false; |
348
|
|
|
} |
349
|
|
|
|
350
|
1 |
|
if ($this->getMessageUtility()->isBotMentioned($message) === true) { |
351
|
1 |
|
return true; |
352
|
|
|
} |
353
|
|
|
|
354
|
1 |
|
$listener = $this->getListener(); |
355
|
|
|
// check direct messages |
356
|
1 |
|
return $listener instanceof EventListener && $listener->getEvent()->isDirectMessage() === true; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|