From 977c919ffe46aa241f1a397a16926d8eb1843705 Mon Sep 17 00:00:00 2001 From: Fabian Schmengler Date: Fri, 2 Sep 2022 12:46:56 +0200 Subject: [PATCH] Migrate legacy API from rtm.start to rtm.connect This makes the client mostly compatible with the September 2022 update --- src/Channel.php | 225 +-------------------------------- src/Conversation.php | 234 +++++++++++++++++++++++++++++++++++ src/DirectMessageChannel.php | 42 +------ src/Group.php | 143 +-------------------- src/RealTimeClient.php | 80 ++++++++---- 5 files changed, 291 insertions(+), 433 deletions(-) create mode 100644 src/Conversation.php diff --git a/src/Channel.php b/src/Channel.php index 9218d0a..ab420c0 100644 --- a/src/Channel.php +++ b/src/Channel.php @@ -6,229 +6,6 @@ /** * Represents a single Slack channel. */ -class Channel extends ClientObject implements ChannelInterface +class Channel extends Conversation { - /** - * {@inheritDoc} - */ - public function getId() - { - return $this->data['id']; - } - - /** - * Gets the channel name. - * - * @return string The name of the channel. - */ - public function getName() - { - return $this->data['name']; - } - - /** - * Gets the channel's purpose text. - * - * @return string The channel's purpose text. - */ - public function getPurpose() - { - return $this->data['purpose']['value']; - } - - /** - * Gets the channel topic text. - * - * @return string The channel's topic text. - */ - public function getTopic() - { - return $this->data['topic']['value']; - } - - /** - * Gets an iterator over all users in the channel. - * - * @return \React\Promise\PromiseInterface A promise for an array of user - * objects for each member in the channel. - */ - public function getMembers() - { - $memberPromises = []; - foreach ($this->data['members'] as $memberId) { - $memberPromises[] = $this->client->getUserById($memberId); - } - - return Promise\all($memberPromises); - } - - /** - * Gets the time the channel was created. - * - * @return \DateTime The time the channel was created. - */ - public function getTimeCreated() - { - $time = new \DateTime(); - $time->setTimestamp($this->data['created']); - return $time; - } - - /** - * Gets the creator of the channel. - * - * @return \React\Promise\PromiseInterface The user who created the channel. - */ - public function getCreator() - { - return $this->client->getUserById($this->data['creator']); - } - - /** - * Gets the number of message unread by the authenticated user. - * - * @return int The number of unread messages. - */ - public function getUnreadCount() - { - return $this->data['unread_count']; - } - - /** - * Checks if the channel has been archived. - * - * @return bool True if the channel has been archived, otherwise false. - */ - public function isArchived() - { - return $this->data['is_archived']; - } - - /** - * Renames the channel. - * - * @param string $name The name to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function rename($name) - { - return $this->client->apiCall('channels.rename', [ - 'channel' => $this->getId(), - 'name' => $name, - ])->then(function () use ($name) { - $this->data['name'] = $name; - return $name; - }); - } - - /** - * Sets the channel's purpose text. - * - * @param string $text The new purpose text to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function setPurpose($text) - { - return $this->client->apiCall('channels.setPurpose', [ - 'channel' => $this->getId(), - 'purpose' => $text, - ])->then(function () use ($text) { - $this->data['purpose']['value'] = $text; - return $text; - }); - } - - /** - * Sets the channel topic text. - * - * @param string $text The new topic text to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function setTopic($text) - { - return $this->client->apiCall('channels.setTopic', [ - 'channel' => $this->getId(), - 'topic' => $text, - ])->then(function () use ($text) { - $this->data['topic']['value'] = $text; - return $text; - }); - } - - /** - * Archives the channel. - * - * @return \React\Promise\PromiseInterface - */ - public function archive() - { - return $this->client->apiCall('channels.archive', [ - 'channel' => $this->getId(), - ])->then(function () { - $this->data['is_archived'] = true; - }); - } - - /** - * Un-archives the channel. - * - * @return \React\Promise\PromiseInterface - */ - public function unarchive() - { - return $this->client->apiCall('channels.unarchive', [ - 'channel' => $this->getId(), - ])->then(function () { - $this->data['is_archived'] = false; - }); - } - - /** - * Invites a user to the channel. - * - * @param User The user to invite. - * - * @return \React\Promise\PromiseInterface - */ - public function inviteUser(User $user) - { - return $this->client->apiCall('channels.invite', [ - 'channel' => $this->getId(), - 'user' => $user->getId(), - ])->then(function () use ($user) { - $this->data['members'][] = $user->getId(); - }); - } - - /** - * Kicks a user from the channel. - * - * @param User The user to kick. - * - * @return \React\Promise\PromiseInterface - */ - public function kickUser(User $user) - { - return $this->client->apiCall('channels.kick', [ - 'channel' => $this->getId(), - 'user' => $user->getId(), - ])->then(function () use ($user) { - unset($this->data['members'][$user->getId()]); - }); - } - - /** - * {@inheritDoc} - */ - public function close() - { - return $this->client->apiCall('channels.close', [ - 'channel' => $this->getId(), - ])->then(function ($response) { - return !isset($response['no_op']); - }); - } } diff --git a/src/Conversation.php b/src/Conversation.php new file mode 100644 index 0000000..fafcbcf --- /dev/null +++ b/src/Conversation.php @@ -0,0 +1,234 @@ +data['id']; + } + + /** + * Gets the channel name. + * + * @return string The name of the channel. + */ + public function getName() + { + return $this->data['name']; + } + + /** + * Gets the channel's purpose text. + * + * @return string The channel's purpose text. + */ + public function getPurpose() + { + return $this->data['purpose']['value']; + } + + /** + * Gets the channel topic text. + * + * @return string The channel's topic text. + */ + public function getTopic() + { + return $this->data['topic']['value']; + } + + /** + * Gets an iterator over all users in the channel. + * + * @return \React\Promise\PromiseInterface A promise for an array of user + * objects for each member in the channel. + */ + public function getMembers() + { + $memberPromises = []; + foreach ($this->data['members'] as $memberId) { + $memberPromises[] = $this->client->getUserById($memberId); + } + + return Promise\all($memberPromises); + } + + /** + * Gets the time the channel was created. + * + * @return \DateTime The time the channel was created. + */ + public function getTimeCreated() + { + $time = new \DateTime(); + $time->setTimestamp($this->data['created']); + return $time; + } + + /** + * Gets the creator of the channel. + * + * @return \React\Promise\PromiseInterface The user who created the channel. + */ + public function getCreator() + { + return $this->client->getUserById($this->data['creator']); + } + + /** + * Gets the number of message unread by the authenticated user. + * + * @return int The number of unread messages. + */ + public function getUnreadCount() + { + return $this->data['unread_count']; + } + + /** + * Checks if the channel has been archived. + * + * @return bool True if the channel has been archived, otherwise false. + */ + public function isArchived() + { + return $this->data['is_archived']; + } + + /** + * Renames the channel. + * + * @param string $name The name to set to. + * + * @return \React\Promise\PromiseInterface + */ + public function rename($name) + { + return $this->client->apiCall('conversations.rename', [ + 'channel' => $this->getId(), + 'name' => $name, + ])->then(function () use ($name) { + $this->data['name'] = $name; + return $name; + }); + } + + /** + * Sets the channel's purpose text. + * + * @param string $text The new purpose text to set to. + * + * @return \React\Promise\PromiseInterface + */ + public function setPurpose($text) + { + return $this->client->apiCall('conversations.setPurpose', [ + 'channel' => $this->getId(), + 'purpose' => $text, + ])->then(function () use ($text) { + $this->data['purpose']['value'] = $text; + return $text; + }); + } + + /** + * Sets the channel topic text. + * + * @param string $text The new topic text to set to. + * + * @return \React\Promise\PromiseInterface + */ + public function setTopic($text) + { + return $this->client->apiCall('conversations.setTopic', [ + 'channel' => $this->getId(), + 'topic' => $text, + ])->then(function () use ($text) { + $this->data['topic']['value'] = $text; + return $text; + }); + } + + /** + * Archives the channel. + * + * @return \React\Promise\PromiseInterface + */ + public function archive() + { + return $this->client->apiCall('conversations.archive', [ + 'channel' => $this->getId(), + ])->then(function () { + $this->data['is_archived'] = true; + }); + } + + /** + * Un-archives the channel. + * + * @return \React\Promise\PromiseInterface + */ + public function unarchive() + { + return $this->client->apiCall('conversations.unarchive', [ + 'channel' => $this->getId(), + ])->then(function () { + $this->data['is_archived'] = false; + }); + } + + /** + * Invites a user to the channel. + * + * @param User The user to invite. + * + * @return \React\Promise\PromiseInterface + */ + public function inviteUser(User $user) + { + return $this->client->apiCall('conversations.invite', [ + 'channel' => $this->getId(), + 'user' => $user->getId(), + ])->then(function () use ($user) { + $this->data['members'][] = $user->getId(); + }); + } + + /** + * Kicks a user from the channel. + * + * @param User The user to kick. + * + * @return \React\Promise\PromiseInterface + */ + public function kickUser(User $user) + { + return $this->client->apiCall('conversations.kick', [ + 'channel' => $this->getId(), + 'user' => $user->getId(), + ])->then(function () use ($user) { + unset($this->data['members'][$user->getId()]); + }); + } + + /** + * {@inheritDoc} + */ + public function close() + { + return $this->client->apiCall('conversations.close', [ + 'channel' => $this->getId(), + ])->then(function ($response) { + return !isset($response['no_op']); + }); + } +} diff --git a/src/DirectMessageChannel.php b/src/DirectMessageChannel.php index fe39f01..fd7a69c 100644 --- a/src/DirectMessageChannel.php +++ b/src/DirectMessageChannel.php @@ -4,47 +4,7 @@ /** * Contains information about a direct message channel. */ -class DirectMessageChannel extends ClientObject implements ChannelInterface +class DirectMessageChannel extends Conversation { - /** - * {@inheritDoc} - */ - public function getId() - { - return $this->data['id']; - } - /** - * Gets the time the channel was created. - * - * @return \DateTime The time the channel was created. - */ - public function getTimeCreated() - { - $time = new \DateTime(); - $time->setTimestamp($this->data['created']); - return $time; - } - - /** - * Gets the user the direct message channel is with. - * - * @return \React\Promise\PromiseInterface - */ - public function getUser() - { - return $this->client->getUserById($this->data['user']); - } - - /** - * {@inheritDoc} - */ - public function close() - { - return $this->client->apiCall('im.close', [ - 'channel' => $this->getId(), - ])->then(function ($response) { - return !isset($response['no_op']); - }); - } } diff --git a/src/Group.php b/src/Group.php index f09bd54..c355c64 100644 --- a/src/Group.php +++ b/src/Group.php @@ -4,147 +4,6 @@ /** * Contains information about a private group channel. */ -class Group extends Channel +class Group extends Conversation { - /** - * Renames the group. - * - * @param string $name The name to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function rename($name) - { - return $this->client->apiCall('groups.rename', [ - 'channel' => $this->getId(), - 'name' => $name, - ])->then(function () use ($name) { - $this->data['name'] = $name; - return $name; - }); - } - - /** - * Sets the group's purpose text. - * - * @param string $text The new purpose text to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function setPurpose($text) - { - return $this->client->apiCall('groups.setPurpose', [ - 'channel' => $this->getId(), - 'purpose' => $text, - ])->then(function () use ($text) { - $this->data['purpose']['value'] = $text; - return $text; - }); - } - - /** - * Sets the group topic text. - * - * @param string $text The new topic text to set to. - * - * @return \React\Promise\PromiseInterface - */ - public function setTopic($text) - { - return $this->client->apiCall('groups.setTopic', [ - 'channel' => $this->getId(), - 'topic' => $text, - ])->then(function () use ($text) { - $this->data['topic']['value'] = $text; - return $text; - }); - } - - /** - * Archives the group. - * - * @return \React\Promise\PromiseInterface - */ - public function archive() - { - return $this->client->apiCall('groups.archive', [ - 'channel' => $this->getId(), - ])->then(function () { - $this->data['is_archived'] = true; - }); - } - - /** - * Un-archives the group. - * - * @return \React\Promise\PromiseInterface - */ - public function unarchive() - { - return $this->client->apiCall('groups.unarchive', [ - 'channel' => $this->getId(), - ])->then(function () { - $this->data['is_archived'] = false; - }); - } - - /** - * Invites a user to the group. - * - * @param User $user The user to invite. - * - * @return \React\Promise\PromiseInterface - */ - public function inviteUser(User $user) - { - return $this->client->apiCall('groups.invite', [ - 'channel' => $this->getId(), - 'user' => $user->getId(), - ])->then(function () use ($user) { - $this->data['members'][] = $user->getId(); - }); - } - - /** - * Kicks a user from the group. - * - * @param User $user The user to kick. - * - * @return \React\Promise\PromiseInterface - */ - public function kickUser(User $user) - { - return $this->client->apiCall('groups.kick', [ - 'channel' => $this->getId(), - 'user' => $user->getId(), - ])->then(function () use ($user) { - unset($this->data['members'][$user->getId()]); - }); - } - - /** - * Opens the group. - * - * @return \React\Promise\PromiseInterface - */ - public function open() - { - return $this->client->apiCall('groups.open', [ - 'channel' => $this->getId(), - ])->then(function ($response) { - return !isset($response['no_op']); - }); - } - - /** - * {@inheritDoc} - */ - public function close() - { - return $this->client->apiCall('groups.close', [ - 'channel' => $this->getId(), - ])->then(function ($response) { - return !isset($response['no_op']); - }); - } } diff --git a/src/RealTimeClient.php b/src/RealTimeClient.php index c9a973a..f16aa28 100644 --- a/src/RealTimeClient.php +++ b/src/RealTimeClient.php @@ -48,11 +48,18 @@ class RealTimeClient extends ApiClient protected $users = []; /** + * @var array A map of conversations.. + */ + protected $conversations = []; + + /** + * @deprecated use $conversations instead (contains channels and groups). * @var array A map of channels. */ protected $channels = []; /** + * @deprecated use $conversations instead (contains channels and groups). * @var array A map of groups. */ protected $groups = []; @@ -63,6 +70,7 @@ class RealTimeClient extends ApiClient protected $dms = []; /** + * @deprecated Slack API does not support listing of bots anymore. * @var array A map of bots. */ protected $bots = []; @@ -93,7 +101,7 @@ public function connect() $deferred = new Promise\Deferred(); // Request a real-time connection... - $this->apiCall('rtm.start') + $this->apiCall('rtm.connect') // then connect to the socket... ->then(function (Payload $response) { @@ -104,31 +112,6 @@ public function connect() // Populate self user. $this->users[$responseData['self']['id']] = new User($this, $responseData['self']); - // populate list of users - foreach ($responseData['users'] as $data) { - $this->users[$data['id']] = new User($this, $data); - } - - // populate list of channels - foreach ($responseData['channels'] as $data) { - $this->channels[$data['id']] = new Channel($this, $data); - } - - // populate list of groups - foreach ($responseData['groups'] as $data) { - $this->groups[$data['id']] = new Group($this, $data); - } - - // populate list of dms - foreach ($responseData['ims'] as $data) { - $this->dms[$data['id']] = new DirectMessageChannel($this, $data); - } - - // populate list of bots - foreach ($responseData['bots'] as $data) { - $this->bots[$data['id']] = new Bot($this, $data); - } - // initiate the websocket connection // write PHPWS things to the existing logger $this->websocket = new WebSocket($responseData['url'], $this->loop, $this->logger); @@ -145,6 +128,49 @@ public function connect() )); }) + // then load additional data from API: users, ... + ->then(function() { + $this->apiCall('users.list')->then(function(Payload $response) { + $responseData = $response->getData(); + // populate list of users + foreach ($responseData['members'] as $data) { + $this->users[$data['id']] = new User($this, $data); + } + }, function($exception) use ($deferred) { + $deferred->reject(new ConnectionException( + 'Could not connect to Slack API: '. $exception->getMessage(), + $exception->getCode() + )); + }); + }) + + // and conversations. + ->then(function() { + $this->apiCall('conversations.list')->then(function(Payload $response) { + $responseData = $response->getData(); + // populate list of conversations + foreach ($responseData['channels'] as $data) { + $this->conversations[$data['id']] = new Conversation($this, $data); + // for backwards compatibility, distinguish channels, groups and direct messages: + if ($data['is_channel']) { + $this->channels[$data['id']] = new Channel($this, $data); + } + if ($data['is_group']) { + $this->groups[$data['id']] = new Group($this, $data); + } + if ($data['is_im']) { + $this->dms[$data['id']] = new DirectMessageChannel($this, $data); + } + } + + }, function($exception) use ($deferred) { + $deferred->reject(new ConnectionException( + 'Could not connect to Slack API: '. $exception->getMessage(), + $exception->getCode() + )); + }); + }) + // then wait for the connection to be ready. ->then(function () use ($deferred) { $this->once('hello', function () use ($deferred) { @@ -301,6 +327,7 @@ public function getUserById($id) /** * Gets all bots in the Slack team. * + * @deprecated Slack API does not support listing of bots anymore. * @return \React\Promise\PromiseInterface A promise for an array of bots. */ public function getBots() @@ -317,6 +344,7 @@ public function getBots() * * @param string $id A bot ID. * + * @todo use bots.info API to fetch Bot info on demand. * @return \React\Promise\PromiseInterface A promise for a bot object. */ public function getBotById($id)