Overview
  • Namespace
  • Class

Namespaces

  • Slack
    • Message

Classes

  • Slack\ApiClient
  • Slack\Bot
  • Slack\Channel
  • Slack\ClientObject
  • Slack\DataObject
  • Slack\DirectMessageChannel
  • Slack\Group
  • Slack\Message\Attachment
  • Slack\Message\AttachmentBuilder
  • Slack\Message\AttachmentField
  • Slack\Message\Message
  • Slack\Message\MessageBuilder
  • Slack\Payload
  • Slack\RealTimeClient
  • Slack\Team
  • Slack\User

Interfaces

  • Slack\ChannelInterface
  • Slack\Exception

Exceptions

  • Slack\ApiException
  • Slack\ConnectionException
  • Slack\UserNotFoundException
  1 <?php
  2 namespace Slack;
  3 
  4 use Devristo\Phpws\Client\WebSocket;
  5 use Devristo\Phpws\Messaging\WebSocketMessageInterface;
  6 use Evenement\EventEmitterTrait;
  7 use React\Promise;
  8 use Slack\Message\Message;
  9 
 10 /**
 11  * A client for the Slack real-time messaging API.
 12  */
 13 class RealTimeClient extends ApiClient
 14 {
 15     use EventEmitterTrait;
 16 
 17     /**
 18      * @var WebSocket A websocket connection to the Slack API.
 19      */
 20     protected $websocket;
 21 
 22     /**
 23      * @var int The ID of the last payload sent to Slack.
 24      */
 25     protected $lastMessageId = 0;
 26 
 27     /**
 28      * @var array An array of pending messages waiting for successful confirmation
 29      *            from Slack.
 30      */
 31     protected $pendingMessages = [];
 32 
 33     /**
 34      * @var bool Indicates if the client is connected.
 35      */
 36     protected $connected = false;
 37 
 38     /**
 39      * @var Team The team logged in to.
 40      */
 41     protected $team;
 42 
 43     /**
 44      * @var array A map of users.
 45      */
 46     protected $users = [];
 47 
 48     /**
 49      * @var array A map of channels.
 50      */
 51     protected $channels = [];
 52 
 53     /**
 54      * @var array A map of groups.
 55      */
 56     protected $groups = [];
 57 
 58     /**
 59      * @var array A map of direct message channels.
 60      */
 61     protected $dms = [];
 62 
 63     /**
 64      * @var array A map of bots.
 65      */
 66     protected $bots = [];
 67 
 68     /**
 69      * Connects to the real-time messaging server.
 70      *
 71      * @return \React\Promise\PromiseInterface
 72      */
 73     public function connect()
 74     {
 75         $deferred = new Promise\Deferred();
 76 
 77         // Request a real-time connection...
 78         $this->apiCall('rtm.start')
 79 
 80         // then connect to the socket...
 81         ->then(function (Payload $response) {
 82             $responseData = $response->getData();
 83             // get the team info
 84             $this->team = new Team($this, $responseData['team']);
 85 
 86             // Populate self user.
 87             $this->users[$responseData['self']['id']] = new User($this, $responseData['self']);
 88 
 89             // populate list of users
 90             foreach ($responseData['users'] as $data) {
 91                 $this->users[$data['id']] = new User($this, $data);
 92             }
 93 
 94             // populate list of channels
 95             foreach ($responseData['channels'] as $data) {
 96                 $this->channels[$data['id']] = new Channel($this, $data);
 97             }
 98 
 99             // populate list of groups
100             foreach ($responseData['groups'] as $data) {
101                 $this->groups[$data['id']] = new Group($this, $data);
102             }
103 
104             // populate list of dms
105             foreach ($responseData['ims'] as $data) {
106                 $this->dms[$data['id']] = new DirectMessageChannel($this, $data);
107             }
108 
109             // populate list of bots
110             foreach ($responseData['bots'] as $data) {
111                 $this->bots[$data['id']] = new Bot($this, $data);
112             }
113 
114             // Log PHPWS things to stderr
115             $logger = new \Zend\Log\Logger();
116             $logger->addWriter(new \Zend\Log\Writer\Stream('php://stderr'));
117 
118             // initiate the websocket connection
119             $this->websocket = new WebSocket($responseData['url'], $this->loop, $logger);
120             $this->websocket->on('message', function ($message) {
121                 $this->onMessage($message);
122             });
123 
124             return $this->websocket->open();
125         }, function($exception) use ($deferred) {
126             // if connection was not succesfull
127             $deferred->reject(new ConnectionException(
128                 'Could not connect to Slack API: '. $exception->getMessage(),
129                 $exception->getCode()
130             ));
131         })
132 
133         // then wait for the connection to be ready.
134         ->then(function () use ($deferred) {
135             $this->once('hello', function () use ($deferred) {
136                 $deferred->resolve();
137             });
138 
139             $this->once('error', function ($data) use ($deferred) {
140                 $deferred->reject(new ConnectionException(
141                     'Could not connect to WebSocket: '.$data['error']['msg'],
142                     $data['error']['code']));
143             });
144         });
145 
146         return $deferred->promise();
147     }
148 
149     /**
150      * Disconnects the client.
151      */
152     public function disconnect()
153     {
154         if (!$this->connected) {
155             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
156         }
157 
158         $this->websocket->close();
159         $this->connected = false;
160     }
161 
162     /**
163      * {@inheritDoc}
164      */
165     public function getTeam()
166     {
167         if (!$this->connected) {
168             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
169         }
170 
171         return Promise\resolve($this->team);
172     }
173 
174     /**
175      * {@inheritDoc}
176      */
177     public function getChannels()
178     {
179         if (!$this->connected) {
180             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
181         }
182 
183         return Promise\resolve(array_values($this->channels));
184     }
185 
186     /**
187      * {@inheritDoc}
188      */
189     public function getChannelById($id)
190     {
191         if (!$this->connected) {
192             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
193         }
194 
195         if (!isset($this->channels[$id])) {
196             return Promise\reject(new ApiException("No channel exists for ID '$id'."));
197         }
198 
199         return Promise\resolve($this->channels[$id]);
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     public function getGroups()
206     {
207         if (!$this->connected) {
208             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
209         }
210 
211         return Promise\resolve(array_values($this->groups));
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     public function getGroupById($id)
218     {
219         if (!$this->connected) {
220             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
221         }
222 
223         if (!isset($this->groups[$id])) {
224             return Promise\reject(new ApiException("No group exists for ID '$id'."));
225         }
226 
227         return Promise\resolve($this->groups[$id]);
228     }
229 
230     /**
231      * {@inheritDoc}
232      */
233     public function getDMs()
234     {
235         if (!$this->connected) {
236             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
237         }
238 
239         return Promise\resolve(array_values($this->dms));
240     }
241 
242     /**
243      * {@inheritDoc}
244      */
245     public function getDMById($id)
246     {
247         if (!$this->connected) {
248             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
249         }
250 
251         if (!isset($this->dms[$id])) {
252             return Promise\reject(new ApiException("No DM exists for ID '$id'."));
253         }
254 
255         return Promise\resolve($this->dms[$id]);
256     }
257 
258     /**
259      * {@inheritDoc}
260      */
261     public function getUsers()
262     {
263         if (!$this->connected) {
264             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
265         }
266 
267         return Promise\resolve(array_values($this->users));
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     public function getUserById($id)
274     {
275         if (!$this->connected) {
276             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
277         }
278 
279         if (!isset($this->users[$id])) {
280             return Promise\reject(new ApiException("No user exists for ID '$id'."));
281         }
282 
283         return Promise\resolve($this->users[$id]);
284     }
285 
286     /**
287      * Gets all bots in the Slack team.
288      *
289      * @return \React\Promise\PromiseInterface A promise for an array of bots.
290      */
291     public function getBots()
292     {
293         if (!$this->connected) {
294             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
295         }
296 
297         return Promise\resolve(array_values($this->bots));
298     }
299 
300     /**
301      * Gets a bot by its ID.
302      *
303      * @param string $id A bot ID.
304      *
305      * @return \React\Promise\PromiseInterface A promise for a bot object.
306      */
307     public function getBotById($id)
308     {
309         if (!$this->connected) {
310             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
311         }
312 
313         if (!isset($this->bots[$id])) {
314             return Promise\reject(new ApiException("No bot exists for ID '$id'."));
315         }
316 
317         return Promise\resolve($this->bots[$id]);
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     public function postMessage(Message $message)
324     {
325         if (!$this->connected) {
326             return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
327         }
328 
329         // We can't send attachments using the RTM API, so revert to the web API
330         // to send the message
331         if ($message->hasAttachments()) {
332             return parent::postMessage($message);
333         }
334 
335         $data = [
336             'id' => ++$this->lastMessageId,
337             'type' => 'message',
338             'channel' => $message->data['channel'],
339             'text' => $message->getText(),
340         ];
341         $this->websocket->send(json_encode($data));
342 
343         // Create a deferred object and add message to pending list so when a
344         // success message arrives, we can de-queue it and resolve the promise.
345         $deferred = new Promise\Deferred();
346         $this->pendingMessages[$this->lastMessageId] = $deferred;
347 
348         return $deferred->promise();
349     }
350 
351     /**
352      * Returns whether the client is connected.
353      *
354      * @return bool
355      */
356     public function isConnected()
357     {
358         return $this->connected;
359     }
360 
361     /**
362      * Handles incoming websocket messages, parses them, and emits them as remote events.
363      *
364      * @param WebSocketMessageInterface $messageRaw A websocket message.
365      */
366     private function onMessage(WebSocketMessageInterface $message)
367     {
368         // parse the message and get the event name
369         $payload = Payload::fromJson($message->getData());
370 
371         if (isset($payload['type'])) {
372             switch ($payload['type']) {
373                 case 'hello':
374                     $this->connected = true;
375                     break;
376 
377                 case 'team_rename':
378                     $this->team->data['name'] = $payload['name'];
379                     break;
380 
381                 case 'team_domain_change':
382                     $this->team->data['domain'] = $payload['domain'];
383                     break;
384 
385                 case 'channel_joined':
386                     $channel = new Channel($this, $payload['channel']);
387                     $this->channels[$channel->getId()] = $channel;
388                     break;
389 
390                 case 'channel_created':
391                     $this->getChannelById($payload['channel']['id'])->then(function (Channel $channel) {
392                         $this->channels[$channel->getId()] = $channel;
393                     });
394                     break;
395 
396                 case 'channel_deleted':
397                     unset($this->channels[$payload['channel']['id']]);
398                     break;
399 
400                 case 'channel_rename':
401                     $this->channels[$payload['channel']['id']]->data['name']
402                         = $payload['channel']['name'];
403                     break;
404 
405                 case 'channel_archive':
406                     $this->channels[$payload['channel']['id']]->data['is_archived'] = true;
407                     break;
408 
409                 case 'channel_unarchive':
410                     $this->channels[$payload['channel']['id']]->data['is_archived'] = false;
411                     break;
412 
413                 case 'group_joined':
414                     $group = new Group($this, $payload['channel']);
415                     $this->groups[$group->getId()] = $group;
416                     break;
417 
418                 case 'group_rename':
419                     $this->groups[$payload['group']['id']]->data['name']
420                         = $payload['channel']['name'];
421                     break;
422 
423                 case 'group_archive':
424                     $this->groups[$payload['group']['id']]->data['is_archived'] = true;
425                     break;
426 
427                 case 'group_unarchive':
428                     $this->groups[$payload['group']['id']]->data['is_archived'] = false;
429                     break;
430 
431                 case 'im_created':
432                     $dm = new DirectMessageChannel($this, $payload['channel']);
433                     $this->dms[$dm->getId()] = $dm;
434                     break;
435 
436                 case 'bot_added':
437                     $bot = new Bot($this, $payload['bot']);
438                     $this->bots[$bot->getId()] = $bot;
439                     break;
440 
441                 case 'bot_changed':
442                     $bot = new Bot($this, $payload['bot']);
443                     $this->bots[$bot->getId()] = $bot;
444                     break;
445             }
446 
447             // emit an event with the attached json
448             $this->emit($payload['type'], [$payload]);
449         } else {
450             // If reply_to is set, then it is a server confirmation for a previously
451             // sent message
452             if (isset($payload['reply_to'])) {
453                 if (isset($this->pendingMessages[$payload['reply_to']])) {
454                     $deferred = $this->pendingMessages[$payload['reply_to']];
455 
456                     // Resolve or reject the promise that was waiting for the reply.
457                     if (isset($payload['ok']) && $payload['ok'] === true) {
458                         $deferred->resolve();
459                     } else {
460                         $deferred->reject($payload['error']);
461                     }
462 
463                     unset($this->pendingMessages[$payload['reply_to']]);
464                 }
465             }
466         }
467     }
468 }
469 
API documentation generated by ApiGen