'', // No scope required 'chat.delete'=>'chat:write', 'chat.postMessage'=>'chat:write', 'chat.update'=>'chat:write', 'conversations.history'=>'channels:history', // Also need groups:history for Private Channels and im:history for messages to the bot. 'conversations.info'=>'channels:history', // (channels:read) Also need groups:read for Private Channels 'conversations.list'=>'channels:read', 'conversations.replies'=>'channels:history', // Also need groups:history for Private Channels 'dialog.open'=>'', // No scope required 'pins.add'=>'pins:write', 'pins.remove'=>'pins:write', 'chat.scheduleMessage'=>'chat:write', 'chat.scheduledMessages.list'=>'', 'team.info'=>'team:read', 'views.open'=>'', // No scope required 'views.publish'=>'', // No scope required 'views.push'=>'', // No scope required 'views.update'=>'', // No scope required 'users.conversations'=>'channels:read', 'users.info'=>'users:read', ]; // Our slack token to use private $_token; public function __construct(Team $o) { $this->_token = $o->token; Log::debug(sprintf('%s:Slack API with token [%s]',static::LOGKEY,$this->_token ? $this->_token->token_hidden : NULL),['m'=>__METHOD__]); } public function authTest(): Test { Log::debug(sprintf('%s:Auth Test',static::LOGKEY),['m'=>__METHOD__]); return new Test($this->execute('auth.test',[])); } /** * Delete a message in a channel * * @param $channel * @param $timestamp * @return Generic * @throws \Exception */ public function deleteChat($channel,$timestamp): Generic { Log::debug(sprintf('%s:Delete Message [%s] in [%s]',static::LOGKEY,$timestamp,$channel),['m'=>__METHOD__]); return new Generic($this->execute('chat.delete',['channel'=>$channel,'ts'=>$timestamp])); } /** * Get Messages on a channel from a specific timestamp * * @param $channel * @param $timestamp * @param int $limit * @return Generic * @throws \Exception */ public function getChannelHistory($channel,$timestamp,$limit=20): Generic { Log::debug(sprintf('%s:Message History for Channel [%s] from Timestamp [%s]',static::LOGKEY,$channel,$timestamp),['m'=>__METHOD__]); return new Generic($this->execute('conversations.history',['channel'=>$channel,'oldest'=>$timestamp,'limit'=>$limit])); } /** * Get information on a channel. * * @param $channel * @return Generic * @throws \Exception */ public function getChannelInfo($channel): Generic { Log::debug(sprintf('%s:Channel Information [%s]',static::LOGKEY,$channel),['m'=>__METHOD__]); return new Generic($this->execute('conversations.info',['channel'=>$channel])); } /** * Get a list of channels. * * @param int $limit * @return Generic * @throws \Exception */ public function getChannelList(int $limit=100): Generic { Log::debug(sprintf('%s:Channel List',static::LOGKEY),['m'=>__METHOD__]); return new Generic($this->execute('conversations.list',['limit'=>$limit])); } /** * Get all messages from a thread * * @param string $channel * @param string $thread_ts * @return Chat * @throws \Exception */ public function getMessageHistory(string $channel,string $thread_ts): Chat { Log::debug(sprintf('%s:Get Message Threads for Message [%s] on Channel [%s]',static::LOGKEY,$thread_ts,$channel),['m'=>__METHOD__]); return new Chat($this->execute('conversations.replies',['channel'=>$channel,'ts'=>$thread_ts])); } /** * Get information on a user * * @param string $team_id * @return ResponseTeam * @throws \Exception */ public function getTeam(string $team_id): ResponseTeam { Log::debug(sprintf('%s:Team Info [%s]',static::LOGKEY,$team_id),['m'=>__METHOD__]); return new ResponseTeam($this->execute('team.info',['team'=>$team_id])); } /** * Get information on a user * * @param $user_id * @return ResponseUser * @throws \Exception */ public function getUser($user_id): ResponseUser { Log::debug(sprintf('%s:User Info [%s]',static::LOGKEY,$user_id),['m'=>__METHOD__]); return new ResponseUser($this->execute('users.info',['user'=>$user_id])); } /** * Open a dialogue with the user * * @param string $trigger * @param string $dialog * @return Generic * @throws \Exception */ public function dialogOpen(string $trigger,string $dialog): Generic { Log::debug(sprintf('%s:Open a Dialog',static::LOGKEY),['m'=>__METHOD__,'d'=>$dialog,'t'=>$trigger]); return new Generic($this->execute('dialog.open',json_encode(['dialog'=>$dialog,'trigger_id'=>$trigger]))); } /** * Migrate users to Enterprise IDs * * @param array $users * @return Generic * @throws \Exception */ public function migrationExchange(array $users): Generic { Log::debug(sprintf('%s:Migrate Exchange [%s] users',static::LOGKEY,count($users)),['m'=>__METHOD__]); return new Generic($this->execute('migration.exchange',['users'=>join(',',$users)])); } /** * Pin a message in a channel * * @param $channel * @param $timestamp * @return Generic * @throws \Exception */ public function pinMessage(string $channel,string $timestamp): Generic { Log::debug(sprintf('%s:Pin Message [%s|%s]',static::LOGKEY,$channel,$timestamp),['m'=>__METHOD__]); return new Generic($this->execute('pins.add',json_encode(['channel'=>$channel,'timestamp'=>$timestamp]))); } /** * Post a Slack Message * * @param Message $request * @return Generic * @throws \Exception */ public function postMessage(Message $request): Generic { Log::debug(sprintf('%s:Post a Slack Message',static::LOGKEY),['m'=>__METHOD__,'r'=>$request]); return new Generic($this->execute('chat.postMessage',json_encode($request))); } /** * Schedule a slack message * * @param Message $request * @return Generic * @throws \Exception */ public function scheduleMessage(Message $request): Generic { Log::debug(sprintf('%s:Scheduling a Slack Message',static::LOGKEY),['m'=>__METHOD__,'r'=>$request]); return new Generic($this->execute('chat.scheduleMessage',json_encode($request))); } /** * Get the scheduled messages * * @param Message $request * @return Generic * @throws \Exception */ public function scheduleMessagesList(string $request=NULL): Generic { Log::debug(sprintf('%s:Get the Scheduled Messages in Slack',static::LOGKEY),['m'=>__METHOD__,'r'=>$request]); return new Generic($this->execute('chat.scheduledMessages.list',$request ? ['channel'=>$request] : [])); } /** * Remove a Pin from a message * * @param $channel * @param $timestamp * @return Generic * @throws \Exception */ public function unpinMessage($channel,$timestamp): Generic { Log::debug(sprintf('%s:Remove Pin from Message [%s|%s]',static::LOGKEY,$channel,$timestamp),['m'=>__METHOD__]); return new Generic($this->execute('pins.remove',json_encode(['channel'=>$channel,'timestamp'=>$timestamp]))); } /** * Update a Slack Message * * @param Message $request * @return Generic * @throws \Exception */ public function updateMessage(Message $request): Generic { Log::debug(sprintf('%s:Update a Slack Message',static::LOGKEY),['m'=>__METHOD__,'r'=>$request]); return new Generic($this->execute('chat.update',json_encode($request))); } /** * Get the list of channels for a user (the bot normally) * * @param User $uo * @param int $limit * @param string|null $cursor * @return ChannelList * @throws \Exception */ public function getUserChannels(User $uo,int $limit=100,string $cursor=NULL): ChannelList { Log::debug(sprintf('%s:Channel List for [%s] (%s:%s)',static::LOGKEY,$uo->user_id,$limit,$cursor),['m'=>__METHOD__]); $args = collect([ 'limit'=>$limit, 'exclude_archived'=>false, 'types'=>'public_channel,private_channel', 'user'=>$uo->user_id, ]); if ($cursor) $args->put('cursor',$cursor); return new ChannelList($this->execute('users.conversations',$args->toArray())); } public function viewOpen(string $trigger,Modal $view): Generic { Log::debug(sprintf('%s:Open a view',static::LOGKEY),['m'=>__METHOD__,'t'=>$trigger]); return new Generic($this->execute('views.open',json_encode(['trigger_id'=>$trigger,'view'=>$view]))); } /** * Publish a view * * @param string $user * @param string $view * @param string $hash * @return Generic * @throws \Exception * @todo Add some smarts to detect if the new view is the same as the current view, and thus no need to post. */ public function viewPublish(string $user,Modal $view,string $hash=''): Generic { Log::debug(sprintf('%s:Publish a view',static::LOGKEY),['m'=>__METHOD__,'u'=>$user,'h'=>$hash]); return new Generic($this->execute('views.publish',json_encode($hash ? ['user_id'=>$user,'view'=>$view,'hash'=>$hash] : ['user_id'=>$user,'view'=>$view]))); } public function viewPush(string $trigger,Modal $view): Generic { Log::debug(sprintf('%s:Push a view',static::LOGKEY),['m'=>__METHOD__,'t'=>$trigger]); return new Generic($this->execute('views.push',json_encode(['trigger_id'=>$trigger,'view'=>$view]))); } public function viewUpdate(string $view_id,Modal $view): Generic { Log::debug(sprintf('%s:Update a view',static::LOGKEY),['m'=>__METHOD__,'id'=>$view_id]); return new Generic($this->execute('views.update',json_encode(['view_id'=>$view_id,'view'=>$view]))); } /** * Call the Slack API * * @param string $method * @param null $parameters * @return object * @throws \Exception */ private function execute(string $method,$parameters = NULL): object { switch (config('app.env')) { case 'dev': $url = 'http://steno:3000'; break; case 'testing': $url = 'http://localhost:3000'; break; case 'testing-l': $url = 'http://steno_replay:3000'; break; default: $url = 'https://slack.com'; } $url .= '/api/'.$method; // If we dont have a scope definition, or if the scope definition is not in the token if (is_null($x=Arr::get(self::scopes,$method)) OR (($x !== '') AND ! $this->_token->hasScope($x))) { throw new SlackTokenScopeException(sprintf('Token [%d:%s] doesnt have the required scope: [%s] for [%s]',$this->_token->id,$this->_token->token_hidden,serialize($x),$method)); } // If we are passed an array, we'll do a normal post. if (is_array($parameters)) { $parameters['token'] = $this->_token->token; $request = $this->prepareRequest( $url, $parameters ); // If we are json, then we'll do an application/json post } elseif (is_json($parameters)) { $request = $this->prepareRequest( $url, $parameters, [ 'Content-Type: application/json; charset=utf-8', 'Content-Length: '.strlen($parameters), 'Authorization: Bearer '.$this->_token->token, ] ); } else { throw new \Exception('Parameters unknown'); } try { $response = curl_exec($request); if (! $response) throw new \Exception('CURL exec returned an empty response: '.serialize(curl_getinfo($request))); $result = json_decode($response); } catch (\Exception $e) { Log::error(sprintf('%s:Got an error while posting to [%s] (%s)',static::LOGKEY,$url,$e->getMessage()),['m'=>__METHOD__]); throw new \Exception($e->getMessage()); } if (! $result) { Log::error(sprintf('%s:Our result shouldnt be empty',static::LOGKEY),['m'=>__METHOD__,'r'=>$request,'R'=>$response]); throw new SlackException('Slack Result is Empty'); } if (! $result->ok) { switch ($result->error) { case 'already_pinned': throw new SlackAlreadyPinnedException('Already Pinned',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'not_authed': throw new SlackNoAuthException('No Auth Token',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'channel_not_found': throw new SlackChannelNotFoundException('Channel Not Found',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'hash_conflict': if (App::environment() == 'dev') file_put_contents('/tmp/hash_conflict.'.$method,print_r(json_decode(json_decode($parameters)->view),TRUE)); throw new SlackHashConflictException('Hash Conflict',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'message_not_found': throw new SlackMessageNotFoundException('Message Not Found',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'no_pin': throw new SlackNoPinException('No Pin',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'not_in_channel': throw new SlackNotInChannelException('Not In Channel',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'not_found': file_put_contents('/tmp/method.'.$method,print_r(['request'=>is_json($parameters) ? json_decode($parameters,TRUE) : $parameters,'response'=>$result],TRUE)); throw new SlackNotFoundException('Not Found',curl_getinfo($request,CURLINFO_HTTP_CODE)); case 'thread_not_found': throw new SlackThreadNotFoundException('Thread Not Found',curl_getinfo($request,CURLINFO_HTTP_CODE)); default: Log::error(sprintf('%s:Generic Error',static::LOGKEY),['m'=>__METHOD__,'t'=>$this->_token->team_id,'r'=>$result]); throw new SlackException($result->error,curl_getinfo($request,CURLINFO_HTTP_CODE)); } } curl_close($request); return $result; } /** * Setup the API call * * @param $url * @param string $parameters * @param array $headers * @return resource */ private function prepareRequest($url,$parameters='',$headers = []) { $request = curl_init(); curl_setopt($request,CURLOPT_URL,$url); curl_setopt($request,CURLOPT_RETURNTRANSFER,TRUE); curl_setopt($request,CURLOPT_HTTPHEADER,$headers); curl_setopt($request,CURLINFO_HEADER_OUT,TRUE); curl_setopt($request,CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($request,CURLOPT_POSTFIELDS,$parameters); return $request; } }