Initial commit - based from qabot but converted into a composer module
This commit is contained in:
commit
9d66b5ed91
30
composer.json
Normal file
30
composer.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "leenooks/slack",
|
||||||
|
"description": "Leenooks Slack Interaction.",
|
||||||
|
"keywords": ["laravel", "leenooks"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Deon George",
|
||||||
|
"email": "deon@leenooks.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"mpociot/phpws": "^2.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Slack\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Slack\\Providers\\SlackServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev"
|
||||||
|
}
|
454
src/API.php
Normal file
454
src/API.php
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
111
src/Base.php
Normal file
111
src/Base.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Models\{Channel,Enterprise,Team,User};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Base - is a Base to all incoming Slack POST requests
|
||||||
|
*
|
||||||
|
* @package Slack
|
||||||
|
*/
|
||||||
|
abstract class Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'SB-';
|
||||||
|
|
||||||
|
protected $_data = [];
|
||||||
|
|
||||||
|
public const signature_version = 'v0';
|
||||||
|
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
$this->_data = json_decode(json_encode($request->all()));
|
||||||
|
|
||||||
|
if (get_class($this) == self::class)
|
||||||
|
Log::debug(sprintf('SB-:Received from Slack [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests to the object should pull values from $_data
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
abstract public function __get(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Channel object that a Response is related to
|
||||||
|
*
|
||||||
|
* @param bool $create
|
||||||
|
* @return Channel|null
|
||||||
|
*/
|
||||||
|
final public function channel(bool $create=FALSE): ?Channel
|
||||||
|
{
|
||||||
|
$o = Channel::firstOrNew(
|
||||||
|
[
|
||||||
|
'channel_id'=>$this->channel_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $o->exists and $create) {
|
||||||
|
$o->team_id = $this->team()->id;
|
||||||
|
$o->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o->exists ? $o : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function enterprise(): Enterprise
|
||||||
|
{
|
||||||
|
return Enterprise::firstOrNew(
|
||||||
|
[
|
||||||
|
'enterprise_id'=>$this->enterprise_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the SlackTeam object that a Response is related to
|
||||||
|
*
|
||||||
|
* @param bool $any
|
||||||
|
* @return Team|null
|
||||||
|
*/
|
||||||
|
final public function team(bool $any=FALSE): ?Team
|
||||||
|
{
|
||||||
|
$o = Team::firstOrNew(
|
||||||
|
[
|
||||||
|
'team_id'=>$this->team_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $o->exists and $any) {
|
||||||
|
$o = $this->enterprise()->teams->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o->exists ? $o : NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the User Object
|
||||||
|
* The user object may not exist, especially if the event was triggered by a different user
|
||||||
|
*
|
||||||
|
* @note Users with both team_id and enterprise_id set to NULL should never be created
|
||||||
|
*/
|
||||||
|
final public function user(): User
|
||||||
|
{
|
||||||
|
$o = User::firstOrNew(
|
||||||
|
[
|
||||||
|
'user_id'=>$this->user_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $o->exists) {
|
||||||
|
$o->team_id = $this->enterprise_id ? NULL : $this->team()->id;
|
||||||
|
$o->enterprise_id = ($x=$this->enterprise())->exists ? $x->id : NULL;
|
||||||
|
$o->save();
|
||||||
|
|
||||||
|
Log::debug(sprintf('%s: User Created in DB [%s] (%s)',self::LOGKEY,$this->user_id,$o->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
104
src/BlockKit.php
Normal file
104
src/BlockKit.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BlockKit - Slack Blockit Objects
|
||||||
|
*
|
||||||
|
* @package Slack
|
||||||
|
*/
|
||||||
|
class BlockKit implements \JsonSerializable
|
||||||
|
{
|
||||||
|
protected Collection $_data;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->_data = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a BlockKit Button
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param string $value
|
||||||
|
* @param string|null $action_id
|
||||||
|
* @return \Illuminate\Support\Collection
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function button(string $label,string $value,string $action_id=NULL): Collection
|
||||||
|
{
|
||||||
|
$x = collect();
|
||||||
|
$x->put('type','button');
|
||||||
|
$x->put('text',$this->text($label,'plain_text'));
|
||||||
|
$x->put('value',$value);
|
||||||
|
|
||||||
|
if ($action_id)
|
||||||
|
$x->put('action_id',$action_id);
|
||||||
|
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the input dialog
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @param int $minlength
|
||||||
|
* @param string $placeholder
|
||||||
|
* @param bool $multiline
|
||||||
|
* @param string $hint
|
||||||
|
* @param string $initial
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function input(string $label,string $action,int $minlength,string $placeholder='',bool $multiline=FALSE,string $hint='',string $initial='')
|
||||||
|
{
|
||||||
|
$this->_data->put('type','input');
|
||||||
|
$this->_data->put('element',[
|
||||||
|
'type'=>'plain_text_input',
|
||||||
|
'action_id'=>$action,
|
||||||
|
'placeholder'=>$this->text($placeholder ?: ' ','plain_text'),
|
||||||
|
'multiline'=>$multiline,
|
||||||
|
'min_length'=>$minlength,
|
||||||
|
'initial_value'=>$initial,
|
||||||
|
]);
|
||||||
|
$this->_data->put('label',[
|
||||||
|
'type'=>'plain_text',
|
||||||
|
'text'=>$label,
|
||||||
|
'emoji'=>true,
|
||||||
|
]);
|
||||||
|
$this->_data->put('optional',$minlength ? FALSE : TRUE);
|
||||||
|
|
||||||
|
if ($hint)
|
||||||
|
$this->_data->put('hint',$this->text($hint,'plain_text'));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BlockKit Text item
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @param string $type
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function text(string $text,string $type='mrkdwn'): array
|
||||||
|
{
|
||||||
|
// Quick Validation
|
||||||
|
if (! in_array($type,['mrkdwn','plain_text']))
|
||||||
|
throw new \Exception('Invalid text type: '.$type);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type'=>$type,
|
||||||
|
'text'=>$text,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
256
src/Blockkit/Block.php
Normal file
256
src/Blockkit/Block.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Blockkit;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Slack\BlockKit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Blockkit Block
|
||||||
|
* Represents a Block used in Blockkit
|
||||||
|
*
|
||||||
|
* @package Slack\Blockkit
|
||||||
|
*/
|
||||||
|
class Block extends BlockKit
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add Actions Block
|
||||||
|
*
|
||||||
|
* @param Collection $elements
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addAction(Collection $elements): self
|
||||||
|
{
|
||||||
|
// Initialise
|
||||||
|
$this->_data = collect();
|
||||||
|
|
||||||
|
$this->_data->put('type','actions');
|
||||||
|
$this->_data->put('elements',$elements);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context block
|
||||||
|
*
|
||||||
|
* @param Collection $elements
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addContext(Collection $elements): self
|
||||||
|
{
|
||||||
|
// Initialise
|
||||||
|
$this->_data = collect();
|
||||||
|
|
||||||
|
$this->_data->put('type','context');
|
||||||
|
$this->_data->put('elements',$elements);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a bock divider
|
||||||
|
*/
|
||||||
|
public function addDivider(): self
|
||||||
|
{
|
||||||
|
$this->_data->put('type','divider');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a block header
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @param string $type
|
||||||
|
* @return Block
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addHeader(string $text,string $type='plain_text'): self
|
||||||
|
{
|
||||||
|
$this->_data->put('type','header');
|
||||||
|
$this->_data->put('text',$this->text($text,$type));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a multiselect that queries back to the server for values
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addMultiSelectInput(string $label,string $action): self
|
||||||
|
{
|
||||||
|
$this->_data->put('type','section');
|
||||||
|
$this->_data->put('text',$this->text('mrkdwn',$label));
|
||||||
|
$this->_data->put('accessory',[
|
||||||
|
'action_id'=>$action,
|
||||||
|
'type'=>'multi_external_select',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @param Collection $options
|
||||||
|
* @param Collection|null $selected
|
||||||
|
* @param int|null $maximum
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addMultiSelectStaticInput(string $label,string $action,Collection $options,Collection $selected=NULL,int $maximum=NULL): self
|
||||||
|
{
|
||||||
|
$this->_data->put('type','section');
|
||||||
|
$this->_data->put('text',$this->text($label,'mrkdwn'));
|
||||||
|
|
||||||
|
$x = collect();
|
||||||
|
$x->put('action_id',$action);
|
||||||
|
$x->put('type','multi_static_select');
|
||||||
|
$x->put('options',$options->transform(function ($item) {
|
||||||
|
return ['text'=>$this->text($item->name,'plain_text'),'value'=>(string)$item->id];
|
||||||
|
}));
|
||||||
|
|
||||||
|
if ($selected and $selected->count())
|
||||||
|
$x->put('initial_options',$selected->transform(function ($item) {
|
||||||
|
return ['text'=>$this->text($item->name,'plain_text'),'value'=>(string)$item->id];
|
||||||
|
}));
|
||||||
|
|
||||||
|
if ($maximum)
|
||||||
|
$x->put('max_selected_items',$maximum);
|
||||||
|
|
||||||
|
$this->_data->put('accessory',$x);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $options
|
||||||
|
* @param string $action
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function addOverflow(Collection $options,string $action): Collection
|
||||||
|
{
|
||||||
|
return collect([
|
||||||
|
'type'=>'overflow',
|
||||||
|
'options'=>$options,
|
||||||
|
'action_id'=>$action,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A section block
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @param string $type
|
||||||
|
* @param Collection|null $accessories
|
||||||
|
* @param string|null $block_id
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addSection(string $text,string $type='mrkdwn',Collection $accessories=NULL,string $block_id=NULL): self
|
||||||
|
{
|
||||||
|
// Initialise
|
||||||
|
$this->_data = collect();
|
||||||
|
|
||||||
|
$this->_data->put('type','section');
|
||||||
|
$this->_data->put('text',$this->text($text,$type));
|
||||||
|
|
||||||
|
if ($block_id)
|
||||||
|
$this->_data->put('block_id',$block_id);
|
||||||
|
|
||||||
|
if ($accessories AND $accessories->count())
|
||||||
|
$this->_data->put('accessory',$accessories);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @param Collection $options
|
||||||
|
* @param string|null $default
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addSelect(string $label,string $action,Collection $options,string $default=NULL): self
|
||||||
|
{
|
||||||
|
$this->_data->put('type','section');
|
||||||
|
$this->_data->put('text',$this->text($label,'mrkdwn'));
|
||||||
|
|
||||||
|
// Accessories
|
||||||
|
$x = collect();
|
||||||
|
$x->put('action_id',$action);
|
||||||
|
$x->put('type','static_select');
|
||||||
|
$x->put('options',$options->map(function ($item) {
|
||||||
|
if (is_array($item))
|
||||||
|
$item = (object)$item;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'text'=>[
|
||||||
|
'type'=>'plain_text',
|
||||||
|
'text'=>(string)$item->name,
|
||||||
|
],
|
||||||
|
'value'=>(string)($item->value ?: $item->id)
|
||||||
|
];
|
||||||
|
}));
|
||||||
|
|
||||||
|
if ($default) {
|
||||||
|
$choice = $options->filter(function($item) use ($default) {
|
||||||
|
if (is_array($item))
|
||||||
|
$item = (object)$item;
|
||||||
|
|
||||||
|
return ($item->value == $default) ? $item : NULL;
|
||||||
|
})->filter()->pop();
|
||||||
|
|
||||||
|
if ($choice) {
|
||||||
|
$x->put('initial_option',[
|
||||||
|
'text'=>$this->text($choice['name'],'plain_text'),
|
||||||
|
'value'=>(string)$choice['value']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_data->put('accessory',$x);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a single-line input dialog
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @param string $placeholder
|
||||||
|
* @param int $minlength
|
||||||
|
* @param string $hint
|
||||||
|
* @param string $initial
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addSingleLineInput(string $label,string $action,string $placeholder='',int $minlength=5,string $hint='',string $initial=''): self
|
||||||
|
{
|
||||||
|
return $this->input($label,$action,$minlength,$placeholder,FALSE,$hint,$initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a multi-line input dialog
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param string $action
|
||||||
|
* @param string $placeholder
|
||||||
|
* @param int $minlength
|
||||||
|
* @param string $hint
|
||||||
|
* @param string $initial
|
||||||
|
* @return $this
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addMultiLineInput(string $label,string $action,string $placeholder='',int $minlength=20,string $hint='',string $initial=''): self
|
||||||
|
{
|
||||||
|
return $this->input($label,$action,$minlength,$placeholder,TRUE,$hint,$initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/Blockkit/BlockAction.php
Normal file
37
src/Blockkit/BlockAction.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Blockkit;
|
||||||
|
|
||||||
|
use Slack\BlockKit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class creates a slack actions used in BlockKit Actions
|
||||||
|
*/
|
||||||
|
class BlockAction extends BlockKit
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add a block button
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @param string $action
|
||||||
|
* @param string $value
|
||||||
|
* @param string $style
|
||||||
|
* @return BlockAction
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addButton(string $text,string $action,string $value,string $style=''): self
|
||||||
|
{
|
||||||
|
if ($style AND ! in_array($style,['primary','danger']))
|
||||||
|
abort('Invalid style: '.$style);
|
||||||
|
|
||||||
|
$this->_data->put('type','button');
|
||||||
|
$this->_data->put('action_id',$action);
|
||||||
|
$this->_data->put('text',$this->text($text,'plain_text'));
|
||||||
|
$this->_data->put('value',$value);
|
||||||
|
|
||||||
|
if ($style)
|
||||||
|
$this->_data->put('style',$style);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
115
src/Blockkit/Modal.php
Normal file
115
src/Blockkit/Modal.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Blockkit;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Slack\BlockKit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class creates a slack Modal Response
|
||||||
|
*/
|
||||||
|
class Modal extends BlockKit
|
||||||
|
{
|
||||||
|
protected $blocks;
|
||||||
|
private $action = NULL;
|
||||||
|
|
||||||
|
public function __construct(string $title)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->blocks = collect();
|
||||||
|
|
||||||
|
$this->_data->put('type','modal');
|
||||||
|
$this->_data->put('title',$this->text(Str::limit($title,24),'plain_text'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action(string $action)
|
||||||
|
{
|
||||||
|
$this->action = $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data that will be returned when converted to JSON.
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
if ($this->blocks->count())
|
||||||
|
$this->_data->put('blocks',$this->blocks);
|
||||||
|
|
||||||
|
switch ($this->action) {
|
||||||
|
case 'clear':
|
||||||
|
return ['response_action'=>'clear'];
|
||||||
|
|
||||||
|
case 'update':
|
||||||
|
return ['response_action'=>'update','view'=>$this->_data];
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $this->_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a block to the modal
|
||||||
|
*
|
||||||
|
* @param Block $block
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addBlock(Block $block): self
|
||||||
|
{
|
||||||
|
$this->blocks->push($block);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callback(string $id): self
|
||||||
|
{
|
||||||
|
$this->_data->put('callback_id',$id);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(string $text='Cancel'): self
|
||||||
|
{
|
||||||
|
$this->_data->put('close',
|
||||||
|
[
|
||||||
|
'type'=>'plain_text',
|
||||||
|
'text'=>$text,
|
||||||
|
'emoji'=>true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function meta(string $id): self
|
||||||
|
{
|
||||||
|
$this->_data->put('private_metadata',$id);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notifyClose(): self
|
||||||
|
{
|
||||||
|
$this->_data->put('notify_on_close',TRUE);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function private(array $data): self
|
||||||
|
{
|
||||||
|
$this->_data->put('private_metadata',json_encode($data));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit(string $text='Submit'): self
|
||||||
|
{
|
||||||
|
$this->_data->put('submit',
|
||||||
|
[
|
||||||
|
'type'=>'plain_text',
|
||||||
|
'text'=>$text,
|
||||||
|
'emoji'=>true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
134
src/Client/API.php
Normal file
134
src/Client/API.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Client;
|
||||||
|
|
||||||
|
use GuzzleHttp;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use React\Promise\Deferred;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
abstract class API
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The base URL for API requests.
|
||||||
|
*/
|
||||||
|
const BASE_URL = 'https://slack.com/api/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The Slack API token string.
|
||||||
|
*/
|
||||||
|
protected $token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var GuzzleHttp\ClientInterface A Guzzle HTTP client.
|
||||||
|
*/
|
||||||
|
protected $httpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoopInterface An event loop instance.
|
||||||
|
*/
|
||||||
|
protected $loop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new API client instance.
|
||||||
|
*
|
||||||
|
* @param LoopInterface $loop
|
||||||
|
* @param GuzzleHttp\ClientInterface|null $httpClient A Guzzle client instance to
|
||||||
|
* send requests with.
|
||||||
|
*/
|
||||||
|
public function __construct(LoopInterface $loop,GuzzleHttp\ClientInterface $httpClient = null)
|
||||||
|
{
|
||||||
|
$this->loop = $loop;
|
||||||
|
$this->httpClient = $httpClient ?: new GuzzleHttp\Client();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Slack API token to be used during method calls.
|
||||||
|
*
|
||||||
|
* @param string $token The API token string.
|
||||||
|
*/
|
||||||
|
public function setToken($token)
|
||||||
|
{
|
||||||
|
$this->token = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an API request.
|
||||||
|
*
|
||||||
|
* @param string $method The API method to call.
|
||||||
|
* @param array $args An associative array of arguments to pass to the
|
||||||
|
* method call.
|
||||||
|
* @param bool $multipart Whether to send as a multipart request. Default to false
|
||||||
|
* @param bool $callDeferred Wether to call the API asynchronous or not.
|
||||||
|
*
|
||||||
|
* @return \React\Promise\PromiseInterface A promise for an API response.
|
||||||
|
*/
|
||||||
|
public function apiCall(string $method,array $args=[],bool $multipart=FALSE,bool $callDeferred=TRUE): PromiseInterface
|
||||||
|
{
|
||||||
|
// create the request url
|
||||||
|
$requestUrl = self::BASE_URL.$method;
|
||||||
|
|
||||||
|
// set the api token
|
||||||
|
$args['token'] = $this->token;
|
||||||
|
|
||||||
|
// send a post request with all arguments
|
||||||
|
$requestType = $multipart ? 'multipart' : 'form_params';
|
||||||
|
$requestData = $multipart ? $this->convertToMultipartArray($args) : $args;
|
||||||
|
|
||||||
|
$promise = $this->httpClient->postAsync($requestUrl,[
|
||||||
|
//$requestType => $requestData,
|
||||||
|
'headers'=>[
|
||||||
|
'Content-Type'=>'application/json',
|
||||||
|
'Authorization'=>'Bearer '.$args['token'],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
//dump(['m'=>__METHOD__,'l'=>__LINE__,'promise'=>$promise]);
|
||||||
|
// Add requests to the event loop to be handled at a later date.
|
||||||
|
if ($callDeferred) {
|
||||||
|
$this->loop->futureTick(function () use ($promise) {
|
||||||
|
$promise->wait();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$promise->wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the response has arrived, parse it and resolve. Note that our
|
||||||
|
// promises aren't pretty; Guzzle promises are not compatible with React
|
||||||
|
// promises, so the only Guzzle promises ever used die in here and it is
|
||||||
|
// React from here on out.
|
||||||
|
$deferred = new Deferred();
|
||||||
|
$promise->then(function (ResponseInterface $response) use ($deferred) {
|
||||||
|
// get the response as a json object
|
||||||
|
$payload = Payload::fromJson((string) $response->getBody());
|
||||||
|
|
||||||
|
// check if there was an error
|
||||||
|
if (isset($payload['ok']) && $payload['ok'] === TRUE) {
|
||||||
|
$deferred->resolve($payload);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// make a nice-looking error message and throw an exception
|
||||||
|
$niceMessage = ucfirst(str_replace('_', ' ', $payload['error']));
|
||||||
|
$deferred->reject(new ApiException($niceMessage));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $deferred->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertToMultipartArray(array $options): array
|
||||||
|
{
|
||||||
|
$convertedOptions = [];
|
||||||
|
|
||||||
|
foreach ($options as $key => $value) {
|
||||||
|
$convertedOptions[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'contents' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $convertedOptions;
|
||||||
|
}
|
||||||
|
}
|
7
src/Client/ApiException.php
Normal file
7
src/Client/ApiException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Client;
|
||||||
|
|
||||||
|
class ApiException extends \RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
7
src/Client/Exception.php
Normal file
7
src/Client/Exception.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Client;
|
||||||
|
|
||||||
|
interface Exception
|
||||||
|
{
|
||||||
|
}
|
117
src/Client/Payload.php
Normal file
117
src/Client/Payload.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores incoming or outgoing message data for a Slack API call.
|
||||||
|
*/
|
||||||
|
class Payload implements \ArrayAccess, \JsonSerializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array The response data.
|
||||||
|
*/
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new payload object.
|
||||||
|
*
|
||||||
|
* @param array $data The payload data.
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a response object from a JSON message.
|
||||||
|
*
|
||||||
|
* @param string $json A JSON string.
|
||||||
|
* @return Payload The parsed response.
|
||||||
|
*/
|
||||||
|
public static function fromJson($json): self
|
||||||
|
{
|
||||||
|
$data = json_decode((string)$json,true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE || (! is_array($data))) {
|
||||||
|
throw new \UnexpectedValueException('Invalid JSON message.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the payload data.
|
||||||
|
*
|
||||||
|
* @return array The payload data.
|
||||||
|
*/
|
||||||
|
public function getData()
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the payload to a JSON message.
|
||||||
|
*
|
||||||
|
* @return string A JSON message.
|
||||||
|
*/
|
||||||
|
public function toJson(): string
|
||||||
|
{
|
||||||
|
return json_encode($this->data,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
if (is_null($offset)) {
|
||||||
|
$this->data[] = $value;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->data[$offset] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return isset($this->data[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
*/
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
unset($this->data[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
return $this->data[$offset] ?? NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->toJson();
|
||||||
|
}
|
||||||
|
}
|
274
src/Client/SocketMode.php
Normal file
274
src/Client/SocketMode.php
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Client;
|
||||||
|
|
||||||
|
use Devristo\Phpws\Client\WebSocket;
|
||||||
|
use Devristo\Phpws\Messaging\WebSocketMessageInterface;
|
||||||
|
use Evenement\EventEmitterTrait;
|
||||||
|
use GuzzleHttp\ClientInterface;
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use React\Promise\Deferred;
|
||||||
|
use React\Promise;
|
||||||
|
|
||||||
|
class SocketMode extends API
|
||||||
|
{
|
||||||
|
use EventEmitterTrait;
|
||||||
|
|
||||||
|
private bool $connected;
|
||||||
|
private WebSocket $websocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Zend\Log\Logger Logger for this client
|
||||||
|
*/
|
||||||
|
protected $logger = null;
|
||||||
|
private array $pendingMessages;
|
||||||
|
|
||||||
|
public function __construct(LoopInterface $loop, ClientInterface $httpClient = null) {
|
||||||
|
parent::__construct($loop, $httpClient);
|
||||||
|
|
||||||
|
$this->logger = new \Zend\Log\Logger();
|
||||||
|
$this->logger->addWriter(new \Zend\Log\Writer\Stream('php://stderr'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the real-time messaging server.
|
||||||
|
*
|
||||||
|
* @return \React\Promise\PromiseInterface
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
$deferred = new Deferred;
|
||||||
|
|
||||||
|
// Request a real-time connection...
|
||||||
|
$this->apiCall('apps.connections.open')
|
||||||
|
// then connect to the socket...
|
||||||
|
->then(function (Payload $response) {
|
||||||
|
/*
|
||||||
|
$responseData = $response->getData();
|
||||||
|
// get the team info
|
||||||
|
$this->team = new Team($this, $responseData['team']);
|
||||||
|
|
||||||
|
// 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($response['url'].'&debug_reconnects=true', $this->loop, $this->logger);
|
||||||
|
$this->websocket->on('message', function ($message) {
|
||||||
|
Log::debug('Calling onMessage for',['m'=>serialize($message)]);
|
||||||
|
$this->onMessage($message);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->websocket->open();
|
||||||
|
|
||||||
|
}, function($exception) use ($deferred) {
|
||||||
|
// if connection was not successful
|
||||||
|
$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) {
|
||||||
|
$deferred->resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->once('error', function ($data) use ($deferred) {
|
||||||
|
$deferred->reject(new ConnectionException(
|
||||||
|
'Could not connect to WebSocket: '.$data['error']['msg'],
|
||||||
|
$data['error']['code']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return $deferred->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the client.
|
||||||
|
*/
|
||||||
|
public function disconnect()
|
||||||
|
{
|
||||||
|
if (! $this->connected) {
|
||||||
|
return Promise\reject(new ConnectionException('Client not connected. Did you forget to call `connect()`?'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->websocket->close();
|
||||||
|
$this->connected = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the client is connected.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConnected()
|
||||||
|
{
|
||||||
|
return $this->connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming websocket messages, parses them, and emits them as remote events.
|
||||||
|
*
|
||||||
|
* @param WebSocketMessageInterface $messageRaw A websocket message.
|
||||||
|
*/
|
||||||
|
private function onMessage(WebSocketMessageInterface $message)
|
||||||
|
{
|
||||||
|
Log::debug('+ Start',['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// parse the message and get the event name
|
||||||
|
$payload = Payload::fromJson($message->getData());
|
||||||
|
|
||||||
|
if (isset($payload['type'])) {
|
||||||
|
$this->emit('_internal_message', [$payload['type'], $payload]);
|
||||||
|
switch ($payload['type']) {
|
||||||
|
case 'hello':
|
||||||
|
$this->connected = TRUE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
case 'team_rename':
|
||||||
|
$this->team->data['name'] = $payload['name'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'team_domain_change':
|
||||||
|
$this->team->data['domain'] = $payload['domain'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_joined':
|
||||||
|
$channel = new Channel($this, $payload['channel']);
|
||||||
|
$this->channels[$channel->getId()] = $channel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_created':
|
||||||
|
$this->getChannelById($payload['channel']['id'])->then(function (Channel $channel) {
|
||||||
|
$this->channels[$channel->getId()] = $channel;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_deleted':
|
||||||
|
unset($this->channels[$payload['channel']]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_rename':
|
||||||
|
$this->channels[$payload['channel']['id']]->data['name']
|
||||||
|
= $payload['channel']['name'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_archive':
|
||||||
|
$this->channels[$payload['channel']]->data['is_archived'] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_unarchive':
|
||||||
|
$this->channels[$payload['channel']]->data['is_archived'] = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group_joined':
|
||||||
|
$group = new Group($this, $payload['channel']);
|
||||||
|
$this->groups[$group->getId()] = $group;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group_rename':
|
||||||
|
$this->groups[$payload['group']['id']]->data['name']
|
||||||
|
= $payload['channel']['name'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group_archive':
|
||||||
|
$this->groups[$payload['group']['id']]->data['is_archived'] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group_unarchive':
|
||||||
|
$this->groups[$payload['group']['id']]->data['is_archived'] = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'im_created':
|
||||||
|
$dm = new DirectMessageChannel($this, $payload['channel']);
|
||||||
|
$this->dms[$dm->getId()] = $dm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bot_added':
|
||||||
|
$bot = new Bot($this, $payload['bot']);
|
||||||
|
$this->bots[$bot->getId()] = $bot;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bot_changed':
|
||||||
|
$bot = new Bot($this, $payload['bot']);
|
||||||
|
$this->bots[$bot->getId()] = $bot;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'team_join':
|
||||||
|
$user = new User($this, $payload['user']);
|
||||||
|
$this->users[$user->getId()] = $user;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user_change':
|
||||||
|
$user = new User($this, $payload['user']);
|
||||||
|
$this->users[$user->getId()] = $user;
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
default:
|
||||||
|
Log::debug(sprintf('Unhandled type [%s]',$payload['type']),['m'=>__METHOD__,'p'=>$payload]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit an event with the attached json
|
||||||
|
$this->emit($payload['type'], [$payload]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($payload['envelope_id'])) {
|
||||||
|
// @acknowledge the event
|
||||||
|
$this->websocket->send(json_encode(['envelope_id'=>$payload['envelope_id']]));
|
||||||
|
Log::debug(sprintf('Responded to event [%s] for (%s)',$payload['envelope_id'],$payload['type']),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($payload['type']) || $payload['type'] == 'pong') {
|
||||||
|
// If reply_to is set, then it is a server confirmation for a previously
|
||||||
|
// sent message
|
||||||
|
if (isset($payload['reply_to'])) {
|
||||||
|
if (isset($this->pendingMessages[$payload['reply_to']])) {
|
||||||
|
$deferred = $this->pendingMessages[$payload['reply_to']];
|
||||||
|
|
||||||
|
// Resolve or reject the promise that was waiting for the reply.
|
||||||
|
if (isset($payload['ok']) && $payload['ok'] === true || $payload['type'] == 'pong') {
|
||||||
|
$deferred->resolve();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$deferred->reject($payload['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->pendingMessages[$payload['reply_to']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug('= End',['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
}
|
54
src/Command/Base.php
Normal file
54
src/Command/Base.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Command;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base as SlackBase;
|
||||||
|
|
||||||
|
abstract class Base extends SlackBase
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('SCb:Slack SLASHCOMMAND Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
parent::_construct($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
* @note: Classes should return:
|
||||||
|
* + channel_id,
|
||||||
|
* + team_id,
|
||||||
|
* + ts,
|
||||||
|
* + user_id
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|object
|
||||||
|
*/
|
||||||
|
public function __get(string $key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'command':
|
||||||
|
$command = preg_replace('/^([a-z]+)(\s?.*)/','$1',$this->_data->text);
|
||||||
|
|
||||||
|
return $command ?: 'help';
|
||||||
|
|
||||||
|
case 'slashcommand':
|
||||||
|
return object_get($this->_data,'command');
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
case 'response_url':
|
||||||
|
case 'enterprise_id':
|
||||||
|
case 'team_id':
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
case 'text':
|
||||||
|
return preg_replace("/^{$this->command}\s*/",'',object_get($this->_data,$key));
|
||||||
|
|
||||||
|
case 'trigger':
|
||||||
|
return object_get($this->_data,'trigger_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/Command/Factory.php
Normal file
52
src/Command/Factory.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Command;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
private const LOGKEY = 'SCf';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array event type to event class mapping
|
||||||
|
*/
|
||||||
|
public const map = [
|
||||||
|
'ask'=>Watson::class,
|
||||||
|
'ate'=>Ask::class,
|
||||||
|
'help'=>Help::class,
|
||||||
|
'goto'=>Link::class,
|
||||||
|
'leaders'=>Leaders::class,
|
||||||
|
'products'=>Products::class,
|
||||||
|
'review'=>Review::class,
|
||||||
|
'wc'=>WatsonCollection::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new event instance
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param Request $request
|
||||||
|
* @return Base
|
||||||
|
*/
|
||||||
|
public static function create(string $type,Request $request)
|
||||||
|
{
|
||||||
|
$class = Arr::get(self::map,$type,Unknown::class);
|
||||||
|
Log::debug(sprintf('%s:Working out Slash Command Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (App::environment() == 'dev')
|
||||||
|
file_put_contents('/tmp/command.'.$type,print_r(json_decode(json_encode($request->all())),TRUE));
|
||||||
|
|
||||||
|
return new $class($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function make(Request $request): Base
|
||||||
|
{
|
||||||
|
$data = json_decode(json_encode($request->all()));
|
||||||
|
$command = preg_replace('/^([a-z]+)(\s?.*)/','$1',$data->text);
|
||||||
|
|
||||||
|
return self::create($command ?: 'help',$request);
|
||||||
|
}
|
||||||
|
}
|
25
src/Command/Help.php
Normal file
25
src/Command/Help.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Command;
|
||||||
|
|
||||||
|
use Slack\Message;
|
||||||
|
use Slack\Message\Attachment;
|
||||||
|
|
||||||
|
class Help extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'SH-';
|
||||||
|
|
||||||
|
public function respond(): Message
|
||||||
|
{
|
||||||
|
$o = new Message;
|
||||||
|
|
||||||
|
$o->setText('Hi, I am the a *NEW* Bot');
|
||||||
|
|
||||||
|
// Version
|
||||||
|
$a = new Attachment;
|
||||||
|
$a->addField('Version',config('app.version'),TRUE);
|
||||||
|
$o->addAttachment($a);
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
31
src/Command/Unknown.php
Normal file
31
src/Command/Unknown.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Command;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all unknown slack commands that we havent specifically programmed for.
|
||||||
|
*
|
||||||
|
* @package Slack\Command
|
||||||
|
*/
|
||||||
|
final class Unknown extends Base
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::notice(sprintf('SCU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function respond(): Message
|
||||||
|
{
|
||||||
|
$o = new Message;
|
||||||
|
|
||||||
|
$o->setText(sprintf('I didnt understand your command "%s". You might like to try `%s help` instead.',$this->command,$this->slashcommand));
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
60
src/Console/Commands/SlackSocketClient.php
Normal file
60
src/Console/Commands/SlackSocketClient.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use React\EventLoop\Loop;
|
||||||
|
use Slack\Client\SocketMode;
|
||||||
|
|
||||||
|
class SlackSocketClient extends Command
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'SSC';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'slack:socketmode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Start SocketMode Client';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Make sure our socket_token is defined
|
||||||
|
if (! config('slack.socket_token'))
|
||||||
|
throw new \Exception('SocketMode Client Token not defined.');
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$loop = Loop::get();
|
||||||
|
|
||||||
|
$client = new SocketMode($loop);
|
||||||
|
$client->setToken(config('slack.socket_token'));
|
||||||
|
|
||||||
|
$client->on('events_api', function ($data) use ($client) {
|
||||||
|
dump(['data'=>$data]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$client->connect()->then(function () {
|
||||||
|
Log::debug(sprintf('%s: Connected to slack.',self::LOGKEY));
|
||||||
|
});
|
||||||
|
|
||||||
|
$loop->run();
|
||||||
|
}
|
||||||
|
}
|
44
src/Event/AppHomeOpened.php
Normal file
44
src/Event/AppHomeOpened.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles when the user opens the app home page
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => app_home_opened
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [tab] => messages
|
||||||
|
* [event_ts] => 1599626320.358395
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01APNQ0T4Z
|
||||||
|
* [event_time] => 1599626320
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class AppHomeOpened extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
case 'tab':
|
||||||
|
return object_get($this->_data,'event.tab');
|
||||||
|
case 'view':
|
||||||
|
return object_get($this->_data,'event.view',new \stdClass);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/Event/Base.php
Normal file
44
src/Event/Base.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base as SlackBase;
|
||||||
|
|
||||||
|
abstract class Base extends SlackBase
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('SEb:Slack Event Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
* @note: Classes should return:
|
||||||
|
* + channel_id,
|
||||||
|
* + team_id,
|
||||||
|
* + ts,
|
||||||
|
* + user_id
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|object
|
||||||
|
*/
|
||||||
|
public function __get(string $key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
// For interactive post responses, the channel ID is "channel"
|
||||||
|
return object_get($this->_data,$key) ?: object_get($this->_data,'channel');
|
||||||
|
|
||||||
|
case 'enterprise_id':
|
||||||
|
case 'team_id':
|
||||||
|
case 'ts':
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
src/Event/ChannelLeft.php
Normal file
39
src/Event/ChannelLeft.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles Pin Added responses received from Slack
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => channel_left
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [actor_id] => {SLACKUSER}
|
||||||
|
* [event_ts] => 1601602161.000100
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01CHTCNMFA
|
||||||
|
* [event_time] => 1601602161
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ChannelLeft extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.channel');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
src/Event/Factory.php
Normal file
58
src/Event/Factory.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
private const LOGKEY = 'SEf';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array event type to event class mapping
|
||||||
|
*/
|
||||||
|
public const map = [
|
||||||
|
'app_home_opened'=>AppHomeOpened::class,
|
||||||
|
'member_joined_channel'=>MemberJoinedChannel::class,
|
||||||
|
'channel_left'=>ChannelLeft::class,
|
||||||
|
'group_left'=>GroupLeft::class,
|
||||||
|
'message'=>Message::class,
|
||||||
|
'reaction_added'=>ReactionAdded::class,
|
||||||
|
'pin_added'=>PinAdded::class,
|
||||||
|
'pin_removed'=>PinRemoved::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new event instance
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param Request $request
|
||||||
|
* @return Base
|
||||||
|
*/
|
||||||
|
public static function create(string $type,Request $request)
|
||||||
|
{
|
||||||
|
$class = Arr::get(self::map,$type,Unknown::class);
|
||||||
|
Log::debug(sprintf('%s:Working out Event Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (App::environment() == 'dev')
|
||||||
|
file_put_contents('/tmp/event.'.$type,print_r($request->all(),TRUE));
|
||||||
|
|
||||||
|
return new $class($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function make(Request $request): Base
|
||||||
|
{
|
||||||
|
// During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller.
|
||||||
|
static $o = NULL;
|
||||||
|
static $or = NULL;
|
||||||
|
|
||||||
|
if (! $o OR ($or != $request)) {
|
||||||
|
$or = $request;
|
||||||
|
$o = self::create($request->input('event.type'),$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
39
src/Event/GroupLeft.php
Normal file
39
src/Event/GroupLeft.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles Pin Added responses received from Slack
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => group_left
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [actor_id] => {SLACKUSER}
|
||||||
|
* [event_ts] => 1601602161.000100
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01CHTCNMFA
|
||||||
|
* [event_time] => 1601602161
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class GroupLeft extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.channel');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/Event/MemberJoinedChannel.php
Normal file
45
src/Event/MemberJoinedChannel.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles when a user is invited to a channel
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => Oow8S2EFvrZoS9z8N4nwf9Jo
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => member_joined_channel
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [channel_type] => G
|
||||||
|
* [team] => {SLACKTEAM}
|
||||||
|
* [inviter] => {SLACKUSER}
|
||||||
|
* [event_ts] => 1605160285.000800
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01EKN4AYRZ
|
||||||
|
* [event_time] => 1605160285
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* [event_context] => 1-member_joined_channel-T159T77TM-G4D3PH40L
|
||||||
|
*/
|
||||||
|
class MemberJoinedChannel extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.channel');
|
||||||
|
case 'invited':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/Event/Message.php
Normal file
65
src/Event/Message.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles message responses received from Slack
|
||||||
|
* These events come as messages on a thread, or new messages posted in the main stream.
|
||||||
|
* There are also subtype events, representing deletion and broadcasting
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [client_msg_id] => f49c853e-8958-4f8d-b8ed-fe2c302755de
|
||||||
|
* [type] => message
|
||||||
|
* [subtype] => message_deleted
|
||||||
|
* [text] => foo
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [ts] => 1599718357.012700
|
||||||
|
* [team] => {SLACKTEAM}
|
||||||
|
* [blocks] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* [thread_ts] => 1598854309.005800
|
||||||
|
* [parent_user_id] => {SLACKUSER}
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [event_ts] => 1599718357.012700
|
||||||
|
* [channel_type] => group
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01AE1G2402
|
||||||
|
* [event_time] => 1599718357
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class Message extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.channel');
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
case 'type':
|
||||||
|
return object_get($this->_data,'event.subtype');
|
||||||
|
|
||||||
|
case 'thread_ts':
|
||||||
|
return object_get($this->_data,'event.'.($this->type == 'message_deleted' ? 'previous_message.' : '').$key);
|
||||||
|
|
||||||
|
case 'deleted_ts':
|
||||||
|
case 'text':
|
||||||
|
case 'ts':
|
||||||
|
return object_get($this->_data,'event.'.$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
src/Event/PinAdded.php
Normal file
78
src/Event/PinAdded.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles Pin Added responses received from Slack
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => pin_added
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [channel_id] => {SLACKCHANNEL}
|
||||||
|
* [item] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => message
|
||||||
|
* [created] => 1599637814
|
||||||
|
* [created_by] => {SLACKUSER}
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [message] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [bot_id] => B4TC0EYKU
|
||||||
|
* [type] => message
|
||||||
|
* [text] =>
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [ts] => 1599617180.008300
|
||||||
|
* [team] => {SLACKTEAM}
|
||||||
|
* [bot_profile] => stdClass Object
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* [pinned_to] => Array
|
||||||
|
* (
|
||||||
|
* [0] => G4D0B9B7V
|
||||||
|
* )
|
||||||
|
* [permalink] => https://leenooks.slack.com/archives/G4D0B9B7V/p1599617180008300
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* [item_user] => {SLACKUSER}
|
||||||
|
* [pin_count] => 25
|
||||||
|
* [pinned_info] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [pinned_by] => {SLACKUSER}
|
||||||
|
* [pinned_ts] => 1599637814
|
||||||
|
* )
|
||||||
|
* [event_ts] => 1599637814.008900
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01AHHE5TS8
|
||||||
|
* [event_time] => 1599637814
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* [0] => {SLACKUSER}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class PinAdded extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
|
||||||
|
case 'ts':
|
||||||
|
return object_get($this->_data,'event.item.message.ts');
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.'.$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/Event/PinRemoved.php
Normal file
75
src/Event/PinRemoved.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles Pin Removed responses received from Slack
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => pin_removed
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [channel_id] => {SLACKCHANNEL}
|
||||||
|
* [item] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => message
|
||||||
|
* [created] => 1599550210
|
||||||
|
* [created_by] => {SLACKUSER}
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [message] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [bot_id] => {SLACKUSER}
|
||||||
|
* [type] => message
|
||||||
|
* [text] =>
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [ts] => 1599550210.007600
|
||||||
|
* [team] => {SLACKTEAM}
|
||||||
|
* [bot_profile] => stdClass Object
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* [permalink] => https://leenooks.slack.com/archives/G4D0B9B7V/p1599550210007600
|
||||||
|
* )
|
||||||
|
* [item_user] => {SLACKUSER}
|
||||||
|
* [pin_count] => 24
|
||||||
|
* [pinned_info] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [pinned_by] => {SLACKUSER}
|
||||||
|
* [pinned_ts] => 1599550210
|
||||||
|
* )
|
||||||
|
* [has_pins] => 1
|
||||||
|
* [event_ts] => 1599636527.008800
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01A887TPRT
|
||||||
|
* [event_time] => 1599636527
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class PinRemoved extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
|
||||||
|
case 'ts':
|
||||||
|
return object_get($this->_data,'event.item.message.ts');
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.'.$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/Event/ReactionAdded.php
Normal file
55
src/Event/ReactionAdded.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles when the user adds a reaction to a message
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [event] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => reaction_added
|
||||||
|
* [user] => {SLACKUSER}
|
||||||
|
* [item] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => message
|
||||||
|
* [channel] => {SLACKCHANNEL}
|
||||||
|
* [ts] => 1598854309.005800
|
||||||
|
* )
|
||||||
|
* [reaction] => question
|
||||||
|
* [item_user] => {SLACKUSER}
|
||||||
|
* [event_ts] => 1599709789.010500
|
||||||
|
* )
|
||||||
|
* [type] => event_callback
|
||||||
|
* [event_id] => Ev01ADSDSE74
|
||||||
|
* [event_time] => 1599709789
|
||||||
|
* [authed_users] => Array
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ReactionAdded extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
|
||||||
|
case 'reaction':
|
||||||
|
return object_get($this->_data,'event.'.$key);
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'event.item.channel');
|
||||||
|
|
||||||
|
case 'ts':
|
||||||
|
return object_get($this->_data,'event.item.ts');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Event/Unknown.php
Normal file
21
src/Event/Unknown.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Event;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all unknown slack event that we havent specifically programmed for.
|
||||||
|
*
|
||||||
|
* @package Slack\Event
|
||||||
|
*/
|
||||||
|
class Unknown extends Base
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::notice(sprintf('SEU:UNKNOWN Slack Event received [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
parent::__contruct($request);
|
||||||
|
}
|
||||||
|
}
|
7
src/Exceptions/SlackAlreadyPinnedException.php
Normal file
7
src/Exceptions/SlackAlreadyPinnedException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackAlreadyPinnedException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackChannelNotFoundException.php
Normal file
7
src/Exceptions/SlackChannelNotFoundException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackChannelNotFoundException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackException.php
Normal file
7
src/Exceptions/SlackException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackHashConflictException.php
Normal file
7
src/Exceptions/SlackHashConflictException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackHashConflictException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackMessageNotFoundException.php
Normal file
7
src/Exceptions/SlackMessageNotFoundException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackMessageNotFoundException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackNoAuthException.php
Normal file
7
src/Exceptions/SlackNoAuthException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackNoAuthException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackNoPinException.php
Normal file
7
src/Exceptions/SlackNoPinException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackNoPinException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackNotFoundException.php
Normal file
7
src/Exceptions/SlackNotFoundException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackNotFoundException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackNotInChannelException.php
Normal file
7
src/Exceptions/SlackNotInChannelException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackNotInChannelException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackThreadNotFoundException.php
Normal file
7
src/Exceptions/SlackThreadNotFoundException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackThreadNotFoundException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
7
src/Exceptions/SlackTokenScopeException.php
Normal file
7
src/Exceptions/SlackTokenScopeException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Exceptions;
|
||||||
|
|
||||||
|
class SlackTokenScopeException extends SlackException
|
||||||
|
{
|
||||||
|
}
|
187
src/Http/Controllers/SlackAppController.php
Normal file
187
src/Http/Controllers/SlackAppController.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
|
||||||
|
use Slack\Jobs\TeamUpdate;
|
||||||
|
use Slack\Models\{Enterprise,Team,Token,User};
|
||||||
|
|
||||||
|
class SlackAppController extends Controller
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'CSA';
|
||||||
|
|
||||||
|
protected static $scopes = [
|
||||||
|
/*
|
||||||
|
'channels:history',
|
||||||
|
'channels:read',
|
||||||
|
'chat:write',
|
||||||
|
'chat:write.customize',
|
||||||
|
'groups:history',
|
||||||
|
'groups:read',
|
||||||
|
'im:history',
|
||||||
|
'im:read',
|
||||||
|
'im:write',
|
||||||
|
'team:read',
|
||||||
|
*/
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $user_scopes = [
|
||||||
|
//'pins.write',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const slack_authorise_url = 'https://slack.com/oauth/v2/authorize';
|
||||||
|
private const slack_oauth_url = 'https://slack.com/api/oauth.v2.access';
|
||||||
|
private const slack_button = 'https://platform.slack-edge.com/img/add_to_slack.png';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install this app - Slack Button
|
||||||
|
*/
|
||||||
|
public function button()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s?%s"><img alt="Add to Slack" height="40" width="139" src="%s" srcSet="%s 1x, %s@2x.png 2x" /></a>',
|
||||||
|
self::slack_authorise_url,
|
||||||
|
http_build_query($this->parameters()),
|
||||||
|
self::slack_button,self::slack_button,self::slack_button
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function home()
|
||||||
|
{
|
||||||
|
return sprintf('Hi, for instructions on how to install me, please reach out to <strong>@deon.</strong>');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setup()
|
||||||
|
{
|
||||||
|
return Redirect::to(self::slack_authorise_url.'?'.http_build_query($this->parameters()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install this Slack Application.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return string
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*/
|
||||||
|
public function install(Request $request)
|
||||||
|
{
|
||||||
|
if (! config('slack.client_id') OR ! config('slack.client_secret'))
|
||||||
|
abort(403,'Slack ClientID or Secret not set');
|
||||||
|
|
||||||
|
$client = new Client;
|
||||||
|
|
||||||
|
$response = $client->request('POST',self::slack_oauth_url,[
|
||||||
|
'auth'=>[config('slack.client_id'),config('slack.client_secret')],
|
||||||
|
'form_params'=>[
|
||||||
|
'code'=>$request->get('code'),
|
||||||
|
'redirect_url'=>$request->url(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->getStatusCode() != 200)
|
||||||
|
abort(403,'Something didnt work, status code not 200');
|
||||||
|
|
||||||
|
$output = json_decode($response->getBody());
|
||||||
|
|
||||||
|
if (App::environment() == 'local')
|
||||||
|
file_put_contents('/tmp/install',print_r($output,TRUE));
|
||||||
|
|
||||||
|
if (! $output->ok)
|
||||||
|
abort(403,'Something didnt work, status not OK ['.(string)$response->getBody().']');
|
||||||
|
|
||||||
|
// Are we an enterprise?
|
||||||
|
$eo = NULL;
|
||||||
|
|
||||||
|
if ($output->enterprise) {
|
||||||
|
$eo = Enterprise::firstOrNew(
|
||||||
|
[
|
||||||
|
'enterprise_id'=>$output->enterprise->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$eo->name = $output->enterprise->name;
|
||||||
|
$eo->active = TRUE;
|
||||||
|
$eo->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store our team details
|
||||||
|
$so = Team::firstOrNew(
|
||||||
|
[
|
||||||
|
'team_id'=>$output->team->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We just installed, so we'll make it active, even if it already exists.
|
||||||
|
$so->description = $output->team->name;
|
||||||
|
$so->active = 1;
|
||||||
|
$so->enterprise_id = $eo ? $eo->id : NULL;
|
||||||
|
$so->save();
|
||||||
|
|
||||||
|
dispatch((new TeamUpdate($so))->onQueue('slack'));
|
||||||
|
|
||||||
|
// Store our app token
|
||||||
|
$to = $so->token;
|
||||||
|
|
||||||
|
if (! $to) {
|
||||||
|
$to = new Token;
|
||||||
|
$to->description = 'App: Oauth';
|
||||||
|
}
|
||||||
|
|
||||||
|
$to->active = 1;
|
||||||
|
$to->token = $output->access_token;
|
||||||
|
$to->scope = $output->scope;
|
||||||
|
$so->token()->save($to);
|
||||||
|
|
||||||
|
Log::debug(sprintf('%s:TOKEN Created [%s]',self::LOGKEY,$to->id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Create the bot user
|
||||||
|
// Store the user who install, and make them admin
|
||||||
|
$bo = User::firstOrNew(
|
||||||
|
[
|
||||||
|
'user_id'=>$output->bot_user_id,
|
||||||
|
]);
|
||||||
|
$bo->enterprise_id = $eo ? $eo->id : NULL;
|
||||||
|
$bo->team_id = $so->id;
|
||||||
|
$bo->active = 0;
|
||||||
|
$bo->admin = 0;
|
||||||
|
$bo->save();
|
||||||
|
|
||||||
|
$so->bot_id = $bo->id;
|
||||||
|
$so->save();
|
||||||
|
|
||||||
|
Log::debug(sprintf('%s:BOT Created [%s]',self::LOGKEY,$bo->id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Store the user who install, and make them admin
|
||||||
|
$uo = User::firstOrNew(
|
||||||
|
[
|
||||||
|
'user_id'=>$output->authed_user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$uo->enterprise_id = $eo ? $eo->id : NULL;
|
||||||
|
$uo->team_id = $eo ? NULL : $so->id;
|
||||||
|
$uo->active = 1;
|
||||||
|
$uo->admin = 1;
|
||||||
|
$uo->save();
|
||||||
|
|
||||||
|
Log::debug(sprintf('%s:ADMIN Created [%s]',self::LOGKEY,$uo->id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Update Slack Object with admin_id
|
||||||
|
$so->admin_id = $uo->id;
|
||||||
|
$so->save();
|
||||||
|
|
||||||
|
return sprintf('All set up! Head back to your slack instance <strong>%s</strong>."',$so->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parameters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'client_id' => config('slack.client_id'),
|
||||||
|
'scope' => join(',',config('slack.bot_scopes')),
|
||||||
|
'user_scope' => join(',',config('slack.user_scopes')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
65
src/Interactive/Base.php
Normal file
65
src/Interactive/Base.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base as SlackBase;
|
||||||
|
|
||||||
|
abstract class Base extends SlackBase
|
||||||
|
{
|
||||||
|
// Does the event respond with a reply to the HTTP request, or via a post with a trigger
|
||||||
|
public $respondNow = FALSE;
|
||||||
|
|
||||||
|
// When retrieving multiple action values, this is the index we are retrieving.
|
||||||
|
protected $index = 0;
|
||||||
|
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('SIb:Slack INTERACTIVE MESSAGE Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Our data is in a payload value
|
||||||
|
$this->_data = json_decode($request->input('payload'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
* @note: Classes should return:
|
||||||
|
* + channel_id,
|
||||||
|
* + team_id,
|
||||||
|
* + ts,
|
||||||
|
* + user_id
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|object
|
||||||
|
*/
|
||||||
|
public function __get(string $key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'enterprise_id':
|
||||||
|
return object_get($this->_data,'team.enterprise_id');
|
||||||
|
case 'team_id':
|
||||||
|
return object_get($this->_data,'team.id');
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'user.id');
|
||||||
|
|
||||||
|
case 'callback_id':
|
||||||
|
case 'trigger_id':
|
||||||
|
case 'type':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable updating the index to actions with $event->index = <id>
|
||||||
|
*
|
||||||
|
* @param $key
|
||||||
|
* @param $value
|
||||||
|
*/
|
||||||
|
public function __set($key,$value)
|
||||||
|
{
|
||||||
|
if ($key == 'index')
|
||||||
|
$this->{$key} = $value;
|
||||||
|
}
|
||||||
|
}
|
161
src/Interactive/BlockActions.php
Normal file
161
src/Interactive/BlockActions.php
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Slack\Models\Channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles when the users responds with a Block Action event
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [type] => block_actions
|
||||||
|
* [user] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKUSER}
|
||||||
|
* [username] => {SLACKUSER}
|
||||||
|
* [name] => {SLACKUSER}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* )
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [container] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => view
|
||||||
|
* [view_id] => V018HRRS38R
|
||||||
|
* )
|
||||||
|
* [trigger_id] => 1346041864311.39333245939.7c6adb7bca538143098386f07effa532
|
||||||
|
* [team] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKTEAM}
|
||||||
|
* [domain] => {SLACKDOMAIN}
|
||||||
|
* )
|
||||||
|
* [view] => stdClass Object
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* [actions] => Array
|
||||||
|
* (
|
||||||
|
* [0] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [action_id] => faq_product
|
||||||
|
* [block_id] => IRFcN
|
||||||
|
* [text] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => plain_text
|
||||||
|
* [text] => ASK QUESTION
|
||||||
|
* [emoji] => 1
|
||||||
|
* )
|
||||||
|
* [value] => question_new
|
||||||
|
* [type] => button
|
||||||
|
* [action_ts] => 1600065294.860855
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class BlockActions extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'IBA';
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'callback_id':
|
||||||
|
return object_get($this->_data,'view.callback_id');
|
||||||
|
|
||||||
|
// An event can have more than 1 action, each action can have 1 value.
|
||||||
|
case 'action_id':
|
||||||
|
return $this->action('action');
|
||||||
|
|
||||||
|
case 'action_value':
|
||||||
|
return $this->action('value');
|
||||||
|
|
||||||
|
case 'value':
|
||||||
|
switch (Arr::get(object_get($this->_data,'actions'),$this->index)->type) {
|
||||||
|
case 'external_select':
|
||||||
|
case 'overflow':
|
||||||
|
case 'static_select':
|
||||||
|
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'selected_option.value');
|
||||||
|
default:
|
||||||
|
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'value');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Block Actions that are messages
|
||||||
|
case 'message_ts':
|
||||||
|
return object_get($this->_data,'message.ts');
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'channel.id') ?: Channel::findOrFail($this->action('value'))->channel_id;
|
||||||
|
|
||||||
|
case 'view_id':
|
||||||
|
return object_get($this->_data,'view.id');
|
||||||
|
|
||||||
|
case 'actions':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
// For some reason this object is not making sense, and while we should be getting team.id or even view.team_id, the actual team appears to be in user.team_id
|
||||||
|
// @todo Currently working with Slack to understand this behaviour
|
||||||
|
case 'team_id': // view.team_id represent workspace publishing view
|
||||||
|
return object_get($this->_data,'user.team_id');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separate out an action command to the id that the command relates to
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function action(string $key): ?string
|
||||||
|
{
|
||||||
|
$regex = '/^([a-z_]+)\|([0-9]+)$/';
|
||||||
|
$action = NULL;
|
||||||
|
$value = NULL;
|
||||||
|
|
||||||
|
// We only take the action up to the pipe symbol
|
||||||
|
$action_id = object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'action_id');
|
||||||
|
|
||||||
|
if (preg_match($regex,$action_id)) {
|
||||||
|
$action = preg_replace($regex,'$1',$action_id);
|
||||||
|
$value = preg_replace($regex,'$2',$action_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($key) {
|
||||||
|
case 'action':
|
||||||
|
return $action ?: $action_id;
|
||||||
|
case 'value':
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some block actions are triggered by messages, and thus dont have a callback_id
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isMessage(): bool
|
||||||
|
{
|
||||||
|
return object_get($this->_data,'message') ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the selected options from a block action actions array
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function selected_options(): Collection
|
||||||
|
{
|
||||||
|
$result = collect();
|
||||||
|
|
||||||
|
foreach (Arr::get(object_get($this->_data,'actions'),'0')->selected_options as $option) {
|
||||||
|
$result->push($option->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
56
src/Interactive/Factory.php
Normal file
56
src/Interactive/Factory.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
private const LOGKEY = 'SIF';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array event type to event class mapping
|
||||||
|
*/
|
||||||
|
public const map = [
|
||||||
|
'block_actions'=>BlockActions::class,
|
||||||
|
'interactive_message'=>InteractiveMessage::class,
|
||||||
|
'shortcut'=>Shortcut::class,
|
||||||
|
'view_closed'=>ViewClosed::class,
|
||||||
|
'view_submission'=>ViewSubmission::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new event instance
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param Request $request
|
||||||
|
* @return Base
|
||||||
|
*/
|
||||||
|
public static function create(string $type,Request $request)
|
||||||
|
{
|
||||||
|
$class = Arr::get(self::map,$type,Unknown::class);
|
||||||
|
Log::debug(sprintf('%s:Working out Interactive Message Event Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (App::environment() == 'dev')
|
||||||
|
file_put_contents('/tmp/interactive.'.$type,print_r(json_decode($request->input('payload')),TRUE));
|
||||||
|
|
||||||
|
return new $class($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function make(Request $request): Base
|
||||||
|
{
|
||||||
|
// During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller.
|
||||||
|
static $o = NULL;
|
||||||
|
static $or = NULL;
|
||||||
|
|
||||||
|
if (! $o OR ($or != $request)) {
|
||||||
|
$data = json_decode($request->input('payload'));
|
||||||
|
$or = $request;
|
||||||
|
$o = self::create($data->type,$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
118
src/Interactive/InteractiveMessage.php
Normal file
118
src/Interactive/InteractiveMessage.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InteractiveMessage
|
||||||
|
*
|
||||||
|
* @package Slack\Interactive
|
||||||
|
*
|
||||||
|
* [type] => interactive_message
|
||||||
|
* [actions] => Array
|
||||||
|
* (
|
||||||
|
* [0] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [name] => type
|
||||||
|
* [type] => select
|
||||||
|
* [selected_options] => Array
|
||||||
|
* (
|
||||||
|
* [0] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [value] => ID|1
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* [callback_id] => classify|438
|
||||||
|
* [team] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKTEAM}
|
||||||
|
* [domain] => {SLACKDOMAIN}
|
||||||
|
* )
|
||||||
|
* [channel] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKCHANNEL}
|
||||||
|
* [name] => directmessage
|
||||||
|
* )
|
||||||
|
* [user] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKUSER}
|
||||||
|
* [name] => {SLACKUSER}
|
||||||
|
* )
|
||||||
|
* [action_ts] => 1603777165.467584
|
||||||
|
* [message_ts] => 1603768794.012800
|
||||||
|
* [attachment_id] => 3
|
||||||
|
* [token] => Oow8S2EFvrZoS9z8N4nwf9Jo
|
||||||
|
* [is_app_unfurl] =>
|
||||||
|
* [original_message] => stdClass Object
|
||||||
|
* (
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* [response_url] => {SLACKRESPONSEURL}
|
||||||
|
* [trigger_id] => 1452241456197.39333245939.7f8618e13013ae0a0ae7d86be2258021
|
||||||
|
*/
|
||||||
|
class InteractiveMessage extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'IIM';
|
||||||
|
|
||||||
|
// Does the event respond with a reply to the HTTP request, or via a post with a trigger
|
||||||
|
public $respondNow = TRUE;
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
// An event can have more than 1 action, each action can have 1 value.
|
||||||
|
case 'action_id':
|
||||||
|
case 'name':
|
||||||
|
case 'type':
|
||||||
|
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),$key);
|
||||||
|
|
||||||
|
case 'value':
|
||||||
|
switch ($this->type) {
|
||||||
|
case 'button':
|
||||||
|
return Arr::get(object_get($this->_data,'actions'),$this->index)->value;
|
||||||
|
case 'select':
|
||||||
|
return Arr::get(object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'selected_options'),0)->value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'channel.id');
|
||||||
|
case 'message_ts':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function respond(): Message
|
||||||
|
{
|
||||||
|
Log::info(sprintf('%s:Interactive Message - Callback [%s] Name [%s] Type [%s]',static::LOGKEY,$this->callback_id,$this->name,$this->type),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
$action = NULL;
|
||||||
|
$id = NULL;
|
||||||
|
|
||||||
|
if (preg_match('/^(.*)\|([0-9]+)/',$this->callback_id)) {
|
||||||
|
[$action,$id] = explode('|',$this->callback_id,2);
|
||||||
|
|
||||||
|
} elseif (preg_match('/^[a-z_]+$/',$this->callback_id)) {
|
||||||
|
$id = $this->name;
|
||||||
|
$action = $this->callback_id;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If we get here, its an action that we dont know about.
|
||||||
|
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',static::LOGKEY,$this->callback_id),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
default:
|
||||||
|
Log::notice(sprintf('%s:Unhandled ACTION [%s]',static::LOGKEY,$action),['m'=>__METHOD__]);
|
||||||
|
return (new Message)->setText('That didnt work, I didnt know what to do with your button - you might like to tell '.$this->team()->owner->slack_user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/Interactive/Shortcut.php
Normal file
38
src/Interactive/Shortcut.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles when the user opens the app via a shortcut configured in Interactivity & Shortcuts
|
||||||
|
*
|
||||||
|
* EG:
|
||||||
|
* [type] => shortcut
|
||||||
|
* [token] => {SLACKTOKEN}
|
||||||
|
* [action_ts] => 1600393871.567037
|
||||||
|
* [team] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKTEAM}
|
||||||
|
* [domain] => {SLACKDOMAIN}
|
||||||
|
* )
|
||||||
|
* [user] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKUSER}
|
||||||
|
* [username] => {SLACKUSER}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* )
|
||||||
|
* [callback_id] => sc_question_ask
|
||||||
|
* [trigger_id] => 1357077877831.39333245939.79f59e011ce5e5a1865d0ae2ac94b3be
|
||||||
|
*/
|
||||||
|
class Shortcut extends Base
|
||||||
|
{
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'event.user');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Interactive/Unknown.php
Normal file
21
src/Interactive/Unknown.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all unknown slack event that we havent specifically programmed for.
|
||||||
|
*
|
||||||
|
* @package Slack\Interactive
|
||||||
|
*/
|
||||||
|
class Unknown extends Base
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::notice(sprintf('SIU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
}
|
87
src/Interactive/ViewClosed.php
Normal file
87
src/Interactive/ViewClosed.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ViewClosed
|
||||||
|
* [type] => view_closed
|
||||||
|
* [team] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKTEAM}
|
||||||
|
* [domain] => {SLACKDOMAIN}
|
||||||
|
* )
|
||||||
|
* [user] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKUSER}
|
||||||
|
* [username] => {SLACKUSER}
|
||||||
|
* [name] => {SLACKUSER}
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* )
|
||||||
|
* [api_app_id] => A4TCZ007N
|
||||||
|
* [token] => Oow8S2EFvrZoS9z8N4nwf9Jo
|
||||||
|
* [view] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => V01DRFF9SKT
|
||||||
|
* [team_id] => {SLACKTEAM}
|
||||||
|
* [type] => modal
|
||||||
|
* [blocks] => Array
|
||||||
|
* (
|
||||||
|
* )
|
||||||
|
* [private_metadata] =>
|
||||||
|
* [callback_id] => askme-products
|
||||||
|
* [state] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [values] => stdClass Object
|
||||||
|
* (
|
||||||
|
* )
|
||||||
|
* [hash] => 1603754939.JuTA8UTb
|
||||||
|
* [title] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => plain_text
|
||||||
|
* [text] => AskMe Products
|
||||||
|
* [emoji] => 1
|
||||||
|
* )
|
||||||
|
* [clear_on_close] =>
|
||||||
|
* [notify_on_close] => 1
|
||||||
|
* [close] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [type] => plain_text
|
||||||
|
* [text] => Close
|
||||||
|
* [emoji] => 1
|
||||||
|
* )
|
||||||
|
* [submit] =>
|
||||||
|
* [previous_view_id] =>
|
||||||
|
* [root_view_id] => V01DRFF9SKT
|
||||||
|
* [app_id] => A4TCZ007N
|
||||||
|
* [external_id] =>
|
||||||
|
* [app_installed_team_id] => T159T77TM
|
||||||
|
* [bot_id] => B4TC0EYKU
|
||||||
|
* )
|
||||||
|
* [is_cleared] =>
|
||||||
|
*
|
||||||
|
* @package Slack\Interactive
|
||||||
|
*/
|
||||||
|
class ViewClosed extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'IVC';
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'callback_id':
|
||||||
|
return object_get($this->_data,'view.callback_id');
|
||||||
|
|
||||||
|
// An event can have more than 1 action, each action can have 1 value.
|
||||||
|
case 'action_id':
|
||||||
|
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),$key);
|
||||||
|
|
||||||
|
case 'view':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
src/Interactive/ViewSubmission.php
Normal file
93
src/Interactive/ViewSubmission.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Interactive;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Models\Team;
|
||||||
|
use Slack\Blockkit\Modal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming modal view submission event.
|
||||||
|
*
|
||||||
|
* @package Slack\Interactive
|
||||||
|
*/
|
||||||
|
class ViewSubmission extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'IVS';
|
||||||
|
|
||||||
|
// View Submissions must respond with via a trigger or inline
|
||||||
|
public $respondNow = TRUE;
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'callback_id':
|
||||||
|
return object_get($this->_data,'view.'.$key);
|
||||||
|
|
||||||
|
case 'meta':
|
||||||
|
return object_get($this->_data,'view.private_metadata');
|
||||||
|
|
||||||
|
case 'view_id':
|
||||||
|
return object_get($this->_data,'view.id');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function blocks(): Collection
|
||||||
|
{
|
||||||
|
$result = collect();
|
||||||
|
|
||||||
|
foreach (object_get($this->_data,'view.blocks',[]) as $id=>$block) {
|
||||||
|
switch (object_get($block,'type')) {
|
||||||
|
case 'input':
|
||||||
|
$result->put($block->element->action_id,$block->block_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'section':
|
||||||
|
$result->put($block->block_id,$id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value(string $block_id): ?string
|
||||||
|
{
|
||||||
|
$key = Arr::get($this->blocks(),$block_id);
|
||||||
|
|
||||||
|
// Return the state value, or the original block value
|
||||||
|
return object_get($this->_data,'view.state.values.'.$key.'.'.$block_id.'.value') ?: object_get(Arr::get(object_get($this->_data,'view.blocks'),$key),'text.text','');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function respond(): Modal
|
||||||
|
{
|
||||||
|
// Do some magic with event data
|
||||||
|
Log::info(sprintf('%s:View Submission for Callback [%s] User [%s] in [%s]',self::LOGKEY,$this->callback_id,$this->user_id,$this->team_id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
$action = NULL;
|
||||||
|
$id = NULL;
|
||||||
|
|
||||||
|
if (preg_match('/^(.*)\|([0-9]+)/',$this->callback_id)) {
|
||||||
|
[$action,$cid] = explode('|',$this->callback_id,2);
|
||||||
|
|
||||||
|
} elseif (preg_match('/^[a-z_]+$/',$this->callback_id)) {
|
||||||
|
$action = $this->callback_id;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If we get here, its an action that we dont know about.
|
||||||
|
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',static::LOGKEY,$this->callback_id),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
default:
|
||||||
|
Log::notice(sprintf('%s:Unhandled ACTION [%s]',self::LOGKEY,$action),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Modal(new Team);
|
||||||
|
}
|
||||||
|
}
|
55
src/Jobs/DeleteChat.php
Normal file
55
src/Jobs/DeleteChat.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Models\{Channel,User};
|
||||||
|
use Slack\Exceptions\SlackException;
|
||||||
|
|
||||||
|
class DeleteChat extends Job
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'JDC';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param Model $o
|
||||||
|
* @param string $ts
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __construct(Model $o,string $ts)
|
||||||
|
{
|
||||||
|
if ($o instanceof Channel) {
|
||||||
|
$this->_data['cid'] = $o->channel_id;
|
||||||
|
|
||||||
|
} elseif ($o instanceof User) {
|
||||||
|
$this->_data['cid'] = $o->user_id;
|
||||||
|
|
||||||
|
} else
|
||||||
|
throw new \Exception('Invalid Model: '.get_class($o));
|
||||||
|
|
||||||
|
$this->_data['to'] = $o->team;
|
||||||
|
$this->_data['ts'] = $ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
Log::info(sprintf('%s:Start - Delete Chat in Channel [%s] with TS [%s]',static::LOGKEY,$this->cid,$this->ts),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->to->slackAPI()->deleteChat($this->cid,$this->ts);
|
||||||
|
|
||||||
|
Log::debug(sprintf('%s:Deleted Slack Message: %s',static::LOGKEY,$this->ts),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
} catch (SlackException $e) {
|
||||||
|
Log::error(sprintf('%s:Failed to delete slack message [%s] [%s]',static::LOGKEY,$this->ts,$e->getMessage()),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/Jobs/Job.php
Normal file
32
src/Jobs/Job.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
abstract class Job implements ShouldQueue
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Queueable Jobs
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This job base class provides a central location to place any logic that
|
||||||
|
| is shared across all of your jobs. The trait included with the class
|
||||||
|
| provides access to the "queueOn" and "delay" queue helper methods.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $_data = [];
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
return Arr::get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
47
src/Jobs/TeamUpdate.php
Normal file
47
src/Jobs/TeamUpdate.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Exceptions\SlackTokenScopeException;
|
||||||
|
use Slack\Models\Team;
|
||||||
|
|
||||||
|
class TeamUpdate extends Job
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'JTU';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param Team $to
|
||||||
|
*/
|
||||||
|
public function __construct(Team $to)
|
||||||
|
{
|
||||||
|
$this->_data['to'] = $to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = $this->to->slackAPI()->getTeam($this->to->team_id);
|
||||||
|
|
||||||
|
} catch (SlackTokenScopeException $e) {
|
||||||
|
Log::error(sprintf('%s:%s',self::LOGKEY,$e->getMessage()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to refresh the team, in case their status has changed since the job was scheduled.
|
||||||
|
$this->to->refresh();
|
||||||
|
|
||||||
|
$this->to->team_name = $response->domain;
|
||||||
|
$this->to->description = $response->name;
|
||||||
|
|
||||||
|
if ($this->to->isDirty())
|
||||||
|
Log::debug(sprintf('%s:Updated [%s] (%s)',self::LOGKEY,$this->to->id,$this->to->team_id),['m'=>__METHOD__,'changed'=>$this->to->getDirty()]);
|
||||||
|
else
|
||||||
|
Log::debug(sprintf('%s:No Update for [%s] (%s)',self::LOGKEY,$this->to->id,$this->to->user_id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
$this->to->save();
|
||||||
|
}
|
||||||
|
}
|
308
src/Message.php
Normal file
308
src/Message.php
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonInterface;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Jobs\DeleteChat;
|
||||||
|
use Slack\Models\{Channel,User};
|
||||||
|
use Slack\Blockkit\Block;
|
||||||
|
use Slack\Exceptions\SlackException;
|
||||||
|
use Slack\Message\Attachment;
|
||||||
|
use Slack\Response\Generic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used when composing a message to send to Slack.
|
||||||
|
*/
|
||||||
|
class Message implements \JsonSerializable
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'SM-';
|
||||||
|
|
||||||
|
private $o;
|
||||||
|
private $attachments;
|
||||||
|
private $blocks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message constructor.
|
||||||
|
*
|
||||||
|
* @param Model|null $o Who the message will be to - Channel or User
|
||||||
|
*/
|
||||||
|
public function __construct(Model $o=NULL)
|
||||||
|
{
|
||||||
|
$this->_data = collect();
|
||||||
|
|
||||||
|
// Message is to a channel
|
||||||
|
if ($o instanceof Channel) {
|
||||||
|
$this->setChannel($o);
|
||||||
|
|
||||||
|
// Message is to a user
|
||||||
|
} elseif ($o instanceof User) {
|
||||||
|
$this->setUser($o);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->o = $o;
|
||||||
|
$this->attachments = collect();
|
||||||
|
$this->blocks = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an attachment to a message
|
||||||
|
*
|
||||||
|
* @param Attachment $attachment
|
||||||
|
* @return Message
|
||||||
|
*/
|
||||||
|
public function addAttachment(Attachment $attachment): self
|
||||||
|
{
|
||||||
|
$this->attachments->push($attachment);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a block to the message
|
||||||
|
*
|
||||||
|
* @param BlockKit $block
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addBlock(BlockKit $block): self
|
||||||
|
{
|
||||||
|
$this->blocks->push($block);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty the message
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function blank(): self
|
||||||
|
{
|
||||||
|
$this->_data = collect();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @todo This doesnt appear to work
|
||||||
|
public function ephemeral(): self
|
||||||
|
{
|
||||||
|
$this->_data->put('ephemeral',TRUE);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function forgetTS(): self
|
||||||
|
{
|
||||||
|
$this->_data->forget('ts');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if this is an empty message
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return $this->jsonSerialize() ? FALSE : TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we json_encode this object, this is the data that will be returned
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
if ($this->blocks->count()) {
|
||||||
|
if ($this->_data->has('text'))
|
||||||
|
throw new \Exception('Messages cannot have text and blocks!');
|
||||||
|
|
||||||
|
$this->_data->put('blocks',$this->blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->attachments->count())
|
||||||
|
$this->_data->put('attachments',$this->attachments);
|
||||||
|
|
||||||
|
// For interactive messages that generate a dialog, we need to return NULL
|
||||||
|
return $this->_data->count() ? $this->_data : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post this message to slack
|
||||||
|
*
|
||||||
|
* @param Carbon|null $delete
|
||||||
|
* @return Generic
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function post(Carbon $delete=NULL): Generic
|
||||||
|
{
|
||||||
|
if ($this->_data->has('ephemeral'))
|
||||||
|
abort('500','Cannot post ephemeral messages.');
|
||||||
|
|
||||||
|
$api = $this->o->team->slackAPI();
|
||||||
|
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->postMessage($this);
|
||||||
|
|
||||||
|
if ($delete) {
|
||||||
|
Log::debug(sprintf('%s:Scheduling Delete of [%s:%s] on [%s]',static::LOGKEY,object_get($this->o,'channel_id',$this->o->id),$response->ts,$delete->format('Y-m-d')),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Queue the delete of the message if requested
|
||||||
|
dispatch((new DeleteChat($this->o,$response->ts))->onQueue('low')->delay($delete));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function replace(bool $replace=TRUE): self
|
||||||
|
{
|
||||||
|
$this->_data['replace_original'] = $replace ? 'true' : 'false';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post a message to slack using the respond_url
|
||||||
|
* @note This URL can only be used 5 times in 30 minutes
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
public function respond(string $url)
|
||||||
|
{
|
||||||
|
$request = curl_init();
|
||||||
|
|
||||||
|
curl_setopt($request,CURLOPT_URL,$url);
|
||||||
|
curl_setopt($request,CURLOPT_RETURNTRANSFER,TRUE);
|
||||||
|
curl_setopt($request,CURLINFO_HEADER_OUT,TRUE);
|
||||||
|
curl_setopt($request,CURLOPT_HTTPHEADER,['Content-Type: application/json; charset=utf-8']);
|
||||||
|
curl_setopt($request,CURLOPT_SSL_VERIFYPEER,FALSE);
|
||||||
|
curl_setopt($request,CURLOPT_POSTFIELDS,json_encode($this));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = curl_exec($request);
|
||||||
|
if (! $result)
|
||||||
|
throw new \Exception('CURL exec returned an empty response: '.serialize(curl_getinfo($request)));
|
||||||
|
|
||||||
|
} 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 !== 'ok') {
|
||||||
|
switch ($result) {
|
||||||
|
default:
|
||||||
|
Log::critical(sprintf('%s:Generic Error',static::LOGKEY),['m'=>__METHOD__,'r'=>$result]);
|
||||||
|
throw new SlackException($result,curl_getinfo($request,CURLINFO_HTTP_CODE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($request);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the message self destruct
|
||||||
|
*
|
||||||
|
* @param Carbon $time
|
||||||
|
* @return Generic
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function selfdestruct(Carbon $time): Generic
|
||||||
|
{
|
||||||
|
$this->addBlock(
|
||||||
|
(new Block)->addContext(
|
||||||
|
collect()
|
||||||
|
->push((new BlockKit)->text(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW]))))));
|
||||||
|
|
||||||
|
return $this->post($time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set our channel
|
||||||
|
*
|
||||||
|
* @param Channel $o
|
||||||
|
* @return Message
|
||||||
|
*/
|
||||||
|
public function setChannel(Channel $o)
|
||||||
|
{
|
||||||
|
$this->_data['channel'] = $o->channel_id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the icon next to the message
|
||||||
|
*
|
||||||
|
* @param string $icon
|
||||||
|
* @return $this
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public function setIcon(string $icon): self
|
||||||
|
{
|
||||||
|
$this->_data->put('icon_emoji',$icon);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option groups are used by the interactive Options controller and hold no other attributes
|
||||||
|
*
|
||||||
|
* @param array $array
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setOptionGroup(array $array): void
|
||||||
|
{
|
||||||
|
$this->_data = collect();
|
||||||
|
$this->_data->put('option_groups',$array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message text
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setText(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('text',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTS(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('ts',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setThreadTS(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('thread_ts',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set our channel
|
||||||
|
*
|
||||||
|
* @param User $o
|
||||||
|
* @return Message
|
||||||
|
*/
|
||||||
|
public function setUser(User $o)
|
||||||
|
{
|
||||||
|
$this->_data['channel'] = $o->user_id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserName(string $user)
|
||||||
|
{
|
||||||
|
$this->_data['username'] = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
198
src/Message/Attachment.php
Normal file
198
src/Message/Attachment.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Message;
|
||||||
|
|
||||||
|
use Slack\BlockKit;
|
||||||
|
use Slack\Blockkit\BlockAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MessageAttachment - Slack Message Attachments
|
||||||
|
* Represents an Single Attachment that can be added to a Message
|
||||||
|
*
|
||||||
|
* @package Slack\Message
|
||||||
|
*/
|
||||||
|
class Attachment implements \JsonSerializable
|
||||||
|
{
|
||||||
|
private $_data;
|
||||||
|
private $actions;
|
||||||
|
private $blocks;
|
||||||
|
private $blockactions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->actions = collect();
|
||||||
|
$this->blocks = collect();
|
||||||
|
$this->blockactions = collect();
|
||||||
|
$this->_data = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
if ($this->actions->count() AND ! $this->_data->has('callback_id'))
|
||||||
|
abort(500,'Actions without a callback ID');
|
||||||
|
|
||||||
|
if ($this->blockactions->count()) {
|
||||||
|
$x = collect();
|
||||||
|
$x->put('type','actions');
|
||||||
|
$x->put('elements',$this->blockactions);
|
||||||
|
|
||||||
|
$this->blocks->push($x);
|
||||||
|
|
||||||
|
// Empty out our blockactions, incase we are converted to json a second time.
|
||||||
|
$this->blockactions = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->actions->count())
|
||||||
|
$this->_data->put('actions',$this->actions);
|
||||||
|
|
||||||
|
if ($this->blocks->count())
|
||||||
|
$this->_data->put('blocks',$this->blocks);
|
||||||
|
|
||||||
|
return $this->_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an attachment to a message
|
||||||
|
*
|
||||||
|
* @param AttachmentAction $action
|
||||||
|
* @return Attachment
|
||||||
|
*/
|
||||||
|
public function addAction(AttachmentAction $action): self
|
||||||
|
{
|
||||||
|
$this->actions->push($action);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a block to message
|
||||||
|
*
|
||||||
|
* @param BlockKit $block
|
||||||
|
* @return Attachment
|
||||||
|
*/
|
||||||
|
public function addBlock(BlockKit $block): self
|
||||||
|
{
|
||||||
|
$this->blocks->push($block);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a BlockAction to a Block
|
||||||
|
*
|
||||||
|
* @param BlockAction $action
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addBlockAction(BlockAction $action): self
|
||||||
|
{
|
||||||
|
$this->blockactions->push($action);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addField(string $title,string $value,bool $short): self
|
||||||
|
{
|
||||||
|
if (! $this->_data->has('fields'))
|
||||||
|
$this->_data->put('fields',collect());
|
||||||
|
|
||||||
|
$this->_data->get('fields')->push([
|
||||||
|
'title'=>$title,
|
||||||
|
'value'=>$value,
|
||||||
|
'short'=>$short
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set where markdown should be parsed by slack
|
||||||
|
*
|
||||||
|
* @param array $array
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function markdownIn(array $array): self
|
||||||
|
{
|
||||||
|
// @todo Add array check to make sure it has valid items
|
||||||
|
$this->_data->put('mrkdown_in',$array);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the attachment color (on the left of the attachment)
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setCallbackID(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('callback_id',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the attachment color (on the left of the attachment)
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setColor(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('color',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text used in the attachments footer
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFooter(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('footer',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the pre-text, displayed after the title.
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setPretext(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('pretext',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text used in the attachment
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setText(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('text',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Title used in the attachment
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTitle(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('title',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
133
src/Message/AttachmentAction.php
Normal file
133
src/Message/AttachmentAction.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MessageAttachmentAction - Slack Message Attachments Actions
|
||||||
|
* Represents an Single Action for a Slack Message Attachment
|
||||||
|
*
|
||||||
|
* @package Slack\Message
|
||||||
|
*/
|
||||||
|
class AttachmentAction implements \JsonSerializable
|
||||||
|
{
|
||||||
|
private $_data;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->_data = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function minSize(int $int): self
|
||||||
|
{
|
||||||
|
$this->_data->put('min_query_length',$int);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a confirmation diaglog when this action is selected
|
||||||
|
*
|
||||||
|
* @param string $title
|
||||||
|
* @param string $text
|
||||||
|
* @param string $ok_text
|
||||||
|
* @param string $dismiss_text
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setConfirm(string $title,string $text,string $ok_text,string $dismiss_text): self
|
||||||
|
{
|
||||||
|
$this->_data->put('confirm',[
|
||||||
|
'title'=>$title,
|
||||||
|
'text'=>$text,
|
||||||
|
'ok_text'=>$ok_text,
|
||||||
|
'dismiss_text'=>$dismiss_text
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of the action
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setName(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('name',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text displayed in the action
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setStyle(string $style): self
|
||||||
|
{
|
||||||
|
if (! in_array($style,['danger','primary']))
|
||||||
|
abort(500,'Style not supported: '.$style);
|
||||||
|
|
||||||
|
$this->_data->put('style',$style);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text displayed in the action
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setText(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('text',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text displayed in the action
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setType(string $type): self
|
||||||
|
{
|
||||||
|
if (! in_array($type,['button','select']))
|
||||||
|
abort(500,'Type not supported: '.$type);
|
||||||
|
|
||||||
|
$this->_data->put('type',$type);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value for the action
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setValue(string $string): self
|
||||||
|
{
|
||||||
|
$this->_data->put('value',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function source(string $string): self
|
||||||
|
{
|
||||||
|
if (! in_array($string,['external']))
|
||||||
|
abort(500,'Dont know how to handle: '.$string);
|
||||||
|
|
||||||
|
$this->_data->put('data_source',$string);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
56
src/Models/Channel.php
Normal file
56
src/Models/Channel.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
|
class Channel extends Model
|
||||||
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
|
protected $fillable = ['team_id','channel_id','name','active'];
|
||||||
|
protected $table = 'slack_channels';
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function team()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Team::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the user is allowed to use this bot
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getIsAllowedAttribute(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the channel name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNameAttribute(): string
|
||||||
|
{
|
||||||
|
return Arr::get($this->attributes,'name') ?: $this->channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this channel a direct message channel?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isDirect(): bool
|
||||||
|
{
|
||||||
|
return preg_match('/^D/',$this->channel_id) OR $this->name == 'directmessage';
|
||||||
|
}
|
||||||
|
}
|
21
src/Models/Enterprise.php
Normal file
21
src/Models/Enterprise.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
|
class Enterprise extends Model
|
||||||
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
|
protected $fillable = ['enterprise_id'];
|
||||||
|
protected $table = 'slack_enterprises';
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function teams()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Team::class);
|
||||||
|
}
|
||||||
|
}
|
87
src/Models/Team.php
Normal file
87
src/Models/Team.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
use Slack\API;
|
||||||
|
|
||||||
|
class Team extends Model
|
||||||
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
|
protected $fillable = ['team_id'];
|
||||||
|
protected $table = 'slack_teams';
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function admins()
|
||||||
|
{
|
||||||
|
return $this->hasMany(User::class,'team_id','id')->where('admin','=',TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bot()
|
||||||
|
{
|
||||||
|
return $this->hasOne(User::class,'id','bot_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function channels()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Channel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function owner()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class,'admin_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens applicable to this team
|
||||||
|
// @todo team_id can now be null, so we need to get it from the enterprise_id.
|
||||||
|
public function token()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Token::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function users()
|
||||||
|
{
|
||||||
|
return $this->hasMany(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an obfuscated token.
|
||||||
|
*
|
||||||
|
* @note Some tokens have 3 fields (separated by a dash), some have 4
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAppTokenObfuscateAttribute(): string
|
||||||
|
{
|
||||||
|
$attrs = explode('-',$this->getAppTokenAttribute()->token);
|
||||||
|
$items = count($attrs)-1;
|
||||||
|
$attrs[$items] = '...'.substr($attrs[$items],-5);
|
||||||
|
|
||||||
|
return implode('-',$attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the owner and the admins together.
|
||||||
|
* @deprecated ?
|
||||||
|
*/
|
||||||
|
public function admin_users()
|
||||||
|
{
|
||||||
|
return $this->admins->merge($this->owner->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance of the API ready to interact with Slack
|
||||||
|
*
|
||||||
|
* @return API
|
||||||
|
*/
|
||||||
|
public function slackAPI(): API
|
||||||
|
{
|
||||||
|
return new API($this);
|
||||||
|
}
|
||||||
|
}
|
46
src/Models/Token.php
Normal file
46
src/Models/Token.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
|
class Token extends Model
|
||||||
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
|
protected $table = 'slack_tokens';
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function team()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Team::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
public function getScopesAttribute(): Collection
|
||||||
|
{
|
||||||
|
return collect(explode(',',$this->scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTokenHiddenAttribute(): string
|
||||||
|
{
|
||||||
|
return '...'.substr($this->token,-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this token include a specific scope
|
||||||
|
*
|
||||||
|
* @param string|null $scope
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasScope(?string $scope): bool
|
||||||
|
{
|
||||||
|
return ($scope AND ($this->getScopesAttribute()->search($scope) !== FALSE));
|
||||||
|
}
|
||||||
|
}
|
49
src/Models/User.php
Normal file
49
src/Models/User.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
|
class User extends Model
|
||||||
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
|
private const LOGKEY = '-MU';
|
||||||
|
|
||||||
|
protected $fillable = ['user_id'];
|
||||||
|
protected $table = 'slack_users';
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function enterprise()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Enterprise::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the user in slack response format
|
||||||
|
*/
|
||||||
|
public function getSlackUserAttribute(): string
|
||||||
|
{
|
||||||
|
return sprintf('<@%s>',$this->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the team that this user is in - normally required to get the team token
|
||||||
|
* For enterprise users, any team token will do.
|
||||||
|
*
|
||||||
|
* If the integration is not installed in any channels, team will be blank
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getTeamAttribute(): ?Team
|
||||||
|
{
|
||||||
|
Log::debug(sprintf('%s:User [%s]',self::LOGKEY,$this->id),['team'=>$this->team_id,'enterprise'=>$this->enterprise_id,'eo'=>$this->enterprise]);
|
||||||
|
|
||||||
|
return $this->team_id ? Team::find($this->team_id) : (($x=$this->enterprise->teams) ? $x->first() : NULL);
|
||||||
|
}
|
||||||
|
}
|
51
src/Options/Base.php
Normal file
51
src/Options/Base.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Options;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base as SlackBase;
|
||||||
|
|
||||||
|
abstract class Base extends SlackBase
|
||||||
|
{
|
||||||
|
// Does the event respond with a reply to the HTTP request, or via a post with a trigger
|
||||||
|
public $respondNow = TRUE;
|
||||||
|
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('SOb:Slack INTERACTIVE MESSAGE Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Our data is in a payload value
|
||||||
|
$this->_data = json_decode($request->input('payload'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
* @note: Classes should return:
|
||||||
|
* + channel_id,
|
||||||
|
* + team_id,
|
||||||
|
* + ts,
|
||||||
|
* + user_id
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|object
|
||||||
|
*/
|
||||||
|
public function __get(string $key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'team_id':
|
||||||
|
return object_get($this->_data,'team.id');
|
||||||
|
case 'channel_id':
|
||||||
|
return object_get($this->_data,'channel.id');
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'user.id');
|
||||||
|
|
||||||
|
case 'callback_id':
|
||||||
|
//case 'action_ts':
|
||||||
|
//case 'message_ts':
|
||||||
|
case 'type':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/Options/Factory.php
Normal file
52
src/Options/Factory.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Options;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
private const LOGKEY = 'SOf';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array event type to event class mapping
|
||||||
|
*/
|
||||||
|
public const map = [
|
||||||
|
'interactive_message'=>InteractiveMessage::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new event instance
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param Request $request
|
||||||
|
* @return Base
|
||||||
|
*/
|
||||||
|
public static function create(string $type,Request $request)
|
||||||
|
{
|
||||||
|
$class = Arr::get(self::map,$type,Unknown::class);
|
||||||
|
Log::debug(sprintf('%s:Working out Interactive Options Event Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (App::environment() == 'dev')
|
||||||
|
file_put_contents('/tmp/option.'.$type,print_r(json_decode($request->input('payload')),TRUE));
|
||||||
|
|
||||||
|
return new $class($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function make(Request $request): Base
|
||||||
|
{
|
||||||
|
// During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller.
|
||||||
|
static $o = NULL;
|
||||||
|
static $or = NULL;
|
||||||
|
|
||||||
|
if (! $o OR ($or != $request)) {
|
||||||
|
$data = json_decode($request->input('payload'));
|
||||||
|
$or = $request;
|
||||||
|
$o = self::create($data->type,$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
73
src/Options/InteractiveMessage.php
Normal file
73
src/Options/InteractiveMessage.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Options;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InteractiveMessage
|
||||||
|
*
|
||||||
|
* [name] => source
|
||||||
|
* [value] =>
|
||||||
|
* [callback_id] => classify|46
|
||||||
|
* [type] => interactive_message
|
||||||
|
* [team] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKTEAM}
|
||||||
|
* [domain] => {SLACKDOMAIN}
|
||||||
|
* )
|
||||||
|
* [channel] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKCHANNEL}
|
||||||
|
* [name] => directmessage
|
||||||
|
* )
|
||||||
|
* [user] => stdClass Object
|
||||||
|
* (
|
||||||
|
* [id] => {SLACKUSER}
|
||||||
|
* [name] => {SLACKUSER}
|
||||||
|
* )
|
||||||
|
* [action_ts] => 1603780652.484943
|
||||||
|
* [message_ts] => 1601349865.001500
|
||||||
|
* [attachment_id] => 3
|
||||||
|
* [token] => Oow8S2EFvrZoS9z8N4nwf9Jo
|
||||||
|
*/
|
||||||
|
class InteractiveMessage extends Base
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'OIM';
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'name':
|
||||||
|
case 'value':
|
||||||
|
case 'message_ts':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive messages can return their output in the incoming HTTP post
|
||||||
|
*
|
||||||
|
* @return Message
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function respond(): Message
|
||||||
|
{
|
||||||
|
Log::info(sprintf('%s:Interactive Option - Callback [%s] Name [%s] Value [%s]',static::LOGKEY,$this->callback_id,$this->name,$this->value),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (preg_match('/^(.*)\|([0-9]+)/',$this->callback_id)) {
|
||||||
|
[$action,$id] = explode('|',$this->callback_id,2);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If we get here, its an action that we dont know about.
|
||||||
|
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',static::LOGKEY,$this->callback_id),['m'=>__METHOD__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new Message)->blank();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/Options/Unknown.php
Normal file
21
src/Options/Unknown.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Options;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all unknown slack event that we havent specifically programmed for.
|
||||||
|
*
|
||||||
|
* @package Slack\Options
|
||||||
|
*/
|
||||||
|
class Unknown extends Base
|
||||||
|
{
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
Log::notice(sprintf('SOU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
}
|
39
src/Providers/SlackServiceProvider.php
Normal file
39
src/Providers/SlackServiceProvider.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Slack\Console\Commands\SlackSocketClient;
|
||||||
|
|
||||||
|
class SlackServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap the application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
if (config('slack.run_migrations')) {
|
||||||
|
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->commands([
|
||||||
|
SlackSocketClient::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->mergeConfigFrom(__DIR__.'/../config/slack.php','slack');
|
||||||
|
|
||||||
|
$this->loadRoutesFrom(realpath(__DIR__ .'/../routes.php'));
|
||||||
|
}
|
||||||
|
}
|
69
src/Response/Base.php
Normal file
69
src/Response/Base.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base as SlackBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This parent class handles responses received from Slack
|
||||||
|
*
|
||||||
|
* @note: This class is used for events not specifically created.
|
||||||
|
*/
|
||||||
|
class Base extends SlackBase implements \JsonSerializable
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RB-';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Constructor Setup
|
||||||
|
*
|
||||||
|
* @param object $response
|
||||||
|
*/
|
||||||
|
public function __construct(object $response)
|
||||||
|
{
|
||||||
|
$this->_data = $response;
|
||||||
|
|
||||||
|
// This is only for child classes
|
||||||
|
if (get_class($this) == Base::class) {
|
||||||
|
Log::debug(sprintf('%s:Slack RESPONSE Initialised [%s]',static::LOGKEY,get_class($this)),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
if (App::environment() == 'dev')
|
||||||
|
file_put_contents('/tmp/response',print_r($this,TRUE),FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
* @note: Classes should return:
|
||||||
|
* + channel_id,
|
||||||
|
* + team_id,
|
||||||
|
* + ts,
|
||||||
|
* + user_id
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel_id':
|
||||||
|
// For interactive post responses, the channel ID is "channel"
|
||||||
|
return object_get($this->_data,$key) ?: object_get($this->_data,'channel');
|
||||||
|
|
||||||
|
case 'team_id':
|
||||||
|
case 'ts':
|
||||||
|
case 'user_id':
|
||||||
|
case 'messages': // Used by getMessageHistory()
|
||||||
|
case 'type': // Needed by URL verification
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we json_encode this object, this is the data that will be returned
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->_data ? $this->_data : new \stdClass;
|
||||||
|
}
|
||||||
|
}
|
23
src/Response/ChannelList.php
Normal file
23
src/Response/ChannelList.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
final class ChannelList extends Base
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RCL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'havemore':
|
||||||
|
return object_get($this->_data,'response_metadata.next_cursor');
|
||||||
|
case 'channels':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/Response/Generic.php
Normal file
27
src/Response/Generic.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a Generic Slack Response to API calls
|
||||||
|
*/
|
||||||
|
class Generic extends Base
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RGE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'view_id':
|
||||||
|
return object_get($this->_data,'view.id');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/Response/Team.php
Normal file
30
src/Response/Team.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing the response to a Slack User Query
|
||||||
|
*
|
||||||
|
* @package Slack\Response
|
||||||
|
*/
|
||||||
|
final class Team extends Base
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RT_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'id':
|
||||||
|
case 'name':
|
||||||
|
case 'domain':
|
||||||
|
case 'enterprise_id':
|
||||||
|
case 'enterprise_name':
|
||||||
|
return object_get($this->_data,'team.'.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/Response/Test.php
Normal file
31
src/Response/Test.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the Slack Response to an Auth Test API call
|
||||||
|
*/
|
||||||
|
class Test extends Base
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RTE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'bot_id':
|
||||||
|
case 'is_enterprise_install':
|
||||||
|
case 'team':
|
||||||
|
case 'url':
|
||||||
|
case 'user':
|
||||||
|
return object_get($this->_data,$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/Response/User.php
Normal file
35
src/Response/User.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing the response to a Slack User Query
|
||||||
|
*
|
||||||
|
* @package Slack\Response
|
||||||
|
*/
|
||||||
|
final class User extends Base
|
||||||
|
{
|
||||||
|
protected const LOGKEY = 'RU_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @note: This method is limited to certain values to ensure integrity reasons
|
||||||
|
*/
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'deleted':
|
||||||
|
case 'is_admin':
|
||||||
|
case 'is_bot':
|
||||||
|
case 'is_restricted':
|
||||||
|
case 'is_ultra_restricted':
|
||||||
|
case 'is_owner':
|
||||||
|
case 'enterprise_user':
|
||||||
|
return object_get($this->_data,'user.'.$key);
|
||||||
|
|
||||||
|
case 'user_id':
|
||||||
|
return object_get($this->_data,'user.id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/config/slack.php
Normal file
8
src/config/slack.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'socket_token' => env('SLACK_SOCKET_TOKEN',NULL),
|
||||||
|
'client_id' => env('SLACK_CLIENT_ID',NULL),
|
||||||
|
'client_secret' => env('SLACK_CLIENT_SECRET',NULL),
|
||||||
|
'signing_secret' => env('SLACK_SIGNING_SECRET',NULL),
|
||||||
|
];
|
106
src/database/migrations/2021_08_06_002815_slack_integration.php
Normal file
106
src/database/migrations/2021_08_06_002815_slack_integration.php
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class SlackIntegration extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('slack_enterprises', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->string('enterprise_id', 45)->unique();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->boolean('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('slack_users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->string('user_id', 45)->unique();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->boolean('active');
|
||||||
|
$table->boolean('admin');
|
||||||
|
|
||||||
|
$table->integer('enterprise_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('enterprise_id')->references('id')->on('slack_enterprises');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('slack_teams', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->string('team_id', 45)->unique();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
$table->boolean('active');
|
||||||
|
|
||||||
|
$table->integer('bot_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('bot_id')->references('id')->on('slack_users');
|
||||||
|
|
||||||
|
$table->integer('admin_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('admin_id')->references('id')->on('slack_users');
|
||||||
|
|
||||||
|
$table->integer('enterprise_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('enterprise_id')->references('id')->on('slack_enterprises');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('slack_channels', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->string('channel_id', 45)->unique();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->boolean('active');
|
||||||
|
|
||||||
|
$table->integer('enterprise_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('enterprise_id')->references('id')->on('slack_enterprises');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('slack_users', function (Blueprint $table) {
|
||||||
|
$table->integer('team_id')->nullable()->unsigned();
|
||||||
|
$table->foreign('team_id')->references('id')->on('slack_teams');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('slack_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->string('token');
|
||||||
|
$table->string('scope')->nullable();
|
||||||
|
$table->boolean('active');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
|
||||||
|
$table->integer('team_id')->unique()->unsigned();
|
||||||
|
$table->foreign('team_id')->references('id')->on('slack_teams');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('slack_users', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['team_id']);
|
||||||
|
$table->dropColumn(['team_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('slack_tokens');
|
||||||
|
Schema::dropIfExists('slack_channels');
|
||||||
|
Schema::dropIfExists('slack_teams');
|
||||||
|
Schema::dropIfExists('slack_users');
|
||||||
|
Schema::dropIfExists('slack_enterprises');
|
||||||
|
}
|
||||||
|
}
|
17
src/routes.php
Normal file
17
src/routes.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$routeConfig = [
|
||||||
|
'namespace' => 'Slack\Http\Controllers',
|
||||||
|
];
|
||||||
|
|
||||||
|
app('router')->group($routeConfig, function ($router) {
|
||||||
|
$router->get('slack-install-button', [
|
||||||
|
'uses' => 'SlackAppController@button',
|
||||||
|
'as' => 'slack-install-button',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router->get('slack-install', [
|
||||||
|
'uses' => 'SlackAppController@install',
|
||||||
|
'as' => 'slack-install',
|
||||||
|
]);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user