2021-08-06 02:22:22 +00:00
< ? php
namespace Slack ;
use Illuminate\Support\Arr ;
use Illuminate\Support\Facades\App ;
use Illuminate\Support\Facades\Log ;
2022-08-23 07:48:09 +00:00
2022-02-22 06:47:32 +00:00
use Slack\Blockkit\Modal ;
2021-08-13 06:34:49 +00:00
use Slack\Response\Chat ;
2021-08-06 02:22:22 +00:00
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 ;
2022-01-18 03:34:57 +00:00
final class API
2021-08-06 02:22:22 +00:00
{
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' ,
2021-12-08 03:21:57 +00:00
'chat.scheduleMessage' => 'chat:write' ,
'chat.scheduledMessages.list' => '' ,
2021-08-06 02:22:22 +00:00
'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 ;
2021-08-10 03:48:59 +00:00
Log :: debug ( sprintf ( '%s:Slack API with token [%s]' , static :: LOGKEY , $this -> _token ? $this -> _token -> token_hidden : NULL ),[ 'm' => __METHOD__ ]);
2021-08-06 02:22:22 +00:00
}
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
*
2021-08-13 06:34:49 +00:00
* @ param $channel
* @ param $timestamp
* @ param int $limit
2021-08-06 02:22:22 +00:00
* @ 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
*
2021-08-13 06:34:49 +00:00
* @ param string $channel
* @ param string $thread_ts
* @ return Chat
2021-08-06 02:22:22 +00:00
* @ throws \Exception
*/
2021-08-13 06:34:49 +00:00
public function getMessageHistory ( string $channel , string $thread_ts ) : Chat
2021-08-06 02:22:22 +00:00
{
Log :: debug ( sprintf ( '%s:Get Message Threads for Message [%s] on Channel [%s]' , static :: LOGKEY , $thread_ts , $channel ),[ 'm' => __METHOD__ ]);
2021-08-13 06:34:49 +00:00
return new Chat ( $this -> execute ( 'conversations.replies' ,[ 'channel' => $channel , 'ts' => $thread_ts ]));
2021-08-06 02:22:22 +00:00
}
/**
* 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 )));
}
2021-12-08 03:21:57 +00:00
/**
* 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 ] : []));
}
2021-08-06 02:22:22 +00:00
/**
* 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 ()));
}
2022-02-22 06:47:32 +00:00
public function viewOpen ( string $trigger , Modal $view ) : Generic
2021-08-06 02:22:22 +00:00
{
Log :: debug ( sprintf ( '%s:Open a view' , static :: LOGKEY ),[ 'm' => __METHOD__ , 't' => $trigger ]);
2022-02-22 23:41:34 +00:00
return new Generic ( $this -> execute ( 'views.open' , json_encode ([ 'trigger_id' => $trigger , 'view' => $view ])));
2021-08-06 02:22:22 +00:00
}
/**
* 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 .
*/
2022-02-22 23:41:34 +00:00
public function viewPublish ( string $user , Modal $view , string $hash = '' ) : Generic
2021-08-06 02:22:22 +00:00
{
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 ])));
}
2022-02-22 23:41:34 +00:00
public function viewPush ( string $trigger , Modal $view ) : Generic
2021-08-06 02:22:22 +00:00
{
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 ])));
}
2022-02-22 23:41:34 +00:00
public function viewUpdate ( string $view_id , Modal $view ) : Generic
2021-08-06 02:22:22 +00:00
{
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 ;
}
2022-08-23 07:48:09 +00:00
}