455 lines
14 KiB
PHP
455 lines
14 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Slack;
|
||
|
|
||
|
use Illuminate\Support\Arr;
|
||
|
use Illuminate\Support\Facades\App;
|
||
|
use Illuminate\Support\Facades\Log;
|
||
|
use Slack\Exceptions\{SlackAlreadyPinnedException,
|
||
|
SlackChannelNotFoundException,
|
||
|
SlackException,
|
||
|
SlackHashConflictException,
|
||
|
SlackMessageNotFoundException,
|
||
|
SlackNoAuthException,
|
||
|
SlackNoPinException,
|
||
|
SlackNotFoundException,
|
||
|
SlackNotInChannelException,
|
||
|
SlackThreadNotFoundException,
|
||
|
SlackTokenScopeException};
|
||
|
use Slack\Models\{Team,User};
|
||
|
use Slack\Response\ChannelList;
|
||
|
use Slack\Response\Generic;
|
||
|
use Slack\Response\User as ResponseUser;
|
||
|
use Slack\Response\Team as ResponseTeam;
|
||
|
use Slack\Response\Test;
|
||
|
|
||
|
class API
|
||
|
{
|
||
|
private const LOGKEY = 'API';
|
||
|
|
||
|
private const scopes = [
|
||
|
'auth.test'=>'', // 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',
|
||
|
'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->token_hidden),['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
|
||
|
* @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 $channel
|
||
|
* @param $thread_ts
|
||
|
* @return Generic
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public function getMessageHistory($channel,$thread_ts): Generic
|
||
|
{
|
||
|
Log::debug(sprintf('%s:Get Message Threads for Message [%s] on Channel [%s]',static::LOGKEY,$thread_ts,$channel),['m'=>__METHOD__]);
|
||
|
|
||
|
return new Generic($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)));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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,string $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,string $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,string $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,string $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;
|
||
|
}
|
||
|
}
|