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