Enhancements

This commit is contained in:
Deon George 2021-08-10 13:48:59 +10:00
parent 9d66b5ed91
commit 4533b1d0ed
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
46 changed files with 1334 additions and 548 deletions

View File

@ -55,7 +55,7 @@ class API
{ {
$this->_token = $o->token; $this->_token = $o->token;
Log::debug(sprintf('%s:Slack API with token [%s]',static::LOGKEY,$this->_token->token_hidden),['m'=>__METHOD__]); Log::debug(sprintf('%s:Slack API with token [%s]',static::LOGKEY,$this->_token ? $this->_token->token_hidden : NULL),['m'=>__METHOD__]);
} }
public function authTest(): Test public function authTest(): Test

View File

@ -19,9 +19,9 @@ abstract class Base
public const signature_version = 'v0'; public const signature_version = 'v0';
public function __construct(Request $request) public function __construct(array $request)
{ {
$this->_data = json_decode(json_encode($request->all())); $this->_data = json_decode(json_encode($request));
if (get_class($this) == self::class) if (get_class($this) == self::class)
Log::debug(sprintf('SB-:Received from Slack [%s]',get_class($this)),['m'=>__METHOD__]); Log::debug(sprintf('SB-:Received from Slack [%s]',get_class($this)),['m'=>__METHOD__]);
@ -50,6 +50,7 @@ abstract class Base
if (! $o->exists and $create) { if (! $o->exists and $create) {
$o->team_id = $this->team()->id; $o->team_id = $this->team()->id;
$o->active = FALSE;
$o->save(); $o->save();
} }

View File

@ -18,87 +18,13 @@ class BlockKit implements \JsonSerializable
$this->_data = collect(); $this->_data = collect();
} }
public function count()
{
return $this->_data->count();
}
public function jsonSerialize() public function jsonSerialize()
{ {
return $this->_data; 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,
];
}
} }

View File

@ -1,256 +0,0 @@
<?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);
}
}

View File

@ -3,9 +3,11 @@
namespace Slack\Blockkit; namespace Slack\Blockkit;
use Slack\BlockKit; use Slack\BlockKit;
use Slack\Blockkit\Blocks\TextEmoji;
/** /**
* This class creates a slack actions used in BlockKit Actions * This class creates a slack actions used in BlockKit Actions
* @todo Still needed?
*/ */
class BlockAction extends BlockKit class BlockAction extends BlockKit
{ {
@ -18,15 +20,16 @@ class BlockAction extends BlockKit
* @param string $style * @param string $style
* @return BlockAction * @return BlockAction
* @throws \Exception * @throws \Exception
* @deprecated Move to Blocks/Button?
*/ */
public function addButton(string $text,string $action,string $value,string $style=''): self public function addButton(TextEmoji $text,string $action,string $value,string $style=''): self
{ {
if ($style AND ! in_array($style,['primary','danger'])) if ($style AND ! in_array($style,['primary','danger']))
abort('Invalid style: '.$style); abort('Invalid style: '.$style);
$this->_data->put('type','button'); $this->_data->put('type','button');
$this->_data->put('action_id',$action); $this->_data->put('action_id',$action);
$this->_data->put('text',$this->text($text,'plain_text')); $this->_data->put('text',$text);
$this->_data->put('value',$value); $this->_data->put('value',$value);
if ($style) if ($style)

341
src/Blockkit/Blocks.php Normal file
View File

@ -0,0 +1,341 @@
<?php
namespace Slack\Blockkit;
use Illuminate\Support\Collection;
use Slack\BlockKit;
use Slack\Exceptions\SlackException;
use Slack\Blockkit\Blocks\{Button,Text,TextEmoji};
/**
* Class Blockkit Block
* Represents a list of Blockit blocks
*
* @package Slack\Blockkit
*/
class Blocks extends BlockKit
{
/**
* Actions
*
* @param array $items
* @return Blocks
*/
private function actions(array $items): self
{
$this->_data->push(array_merge(['type'=>'actions'],$items));
return $this;
}
/**
* Add actions block
*
* @param Collection $elements
* @return Blocks
*/
public function addActionElements(Collection $elements): self
{
$this->actions(['elements'=>$elements]);
return $this;
}
/**
* Add context items
*
* @param Collection $items
* @return Blocks
*/
public function addContextElements(Collection $items): self
{
return $this->context(['elements'=>$items]);
}
/**
* Add a bock divider
*
* @returns Blocks
*/
public function addDivider(): self
{
$this->_data->push(['type'=>'divider']);
return $this;
}
/**
* Add a block header
*
* @param string $text
* @return Blocks
*/
public function addHeader(string $text): self
{
$this->_data->push(['type'=>'header','text'=>TextEmoji::item($text,TRUE)]);
return $this;
}
/**
* @param Collection $options
* @param string $action_id
* @return Collection
* @todo To Check
*/
public function addOverflow(Collection $options,string $action_id): Collection
{
return collect([
'type'=>'overflow',
'options'=>$options,
'action_id'=>$action_id,
]);
}
/**
* Add a section with accessories
*
* @param Text $text
* @param Button $accessory
* @return Blocks
*/
public function addSectionAccessoryButton(Text $text,Button $accessory): self
{
return $this->section([
'text'=>$text,
'accessory'=>$accessory,
]);
}
/**
* @param Text $label
* @param string $action
* @param Collection $options
* @param string|null $default
* @return Blocks
* @deprecated
* @todo Look at addSectionAccessory
*/
public function addSelect(Text $label,string $action,Collection $options,string $default=NULL): self
{
$this->_data->put('type','section');
$this->_data->put('text',$label);
// 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'=>TextEmoji::item($choice['name']),
'value'=>(string)$choice['value']
]);
}
}
$this->_data->put('accessory',$x);
return $this;
}
/**
* Add a section with fields
* @param Collection $items
* @return Blocks
*/
public function addSectionFields(Collection $items): self
{
return $this->section(['fields'=>$items]);
}
/**
* Generates a multiselect that queries back to the server for values
*
* @param Text $label
* @param string $action
* @return Blocks
* @todo To Change - and rename addSectionMultiSelectInput()
* @deprecated
*/
public function addMultiSelectInput(Text $label,string $action): self
{
$this->_data->put('type','section');
$this->_data->put('text',$label);
$this->_data->put('accessory',[
'action_id'=>$action,
'type'=>'multi_external_select',
]);
return $this;
}
/**
* Add a section with a multi select list
*
* @param Text $label
* @param string $action
* @param Collection $options
* @param Collection|null $selected
* @param int|null $maximum
* @return Blocks
* @throws \Exception
* @note Slack only allows 100 items
*/
public function addSectionMultiSelectStaticInput(Text $label,string $action,Collection $options,Collection $selected=NULL,int $maximum=NULL): self
{
if ($options->count() > 100)
throw new SlackException('Selection list cannot have more than 100 items.');
$x = collect();
$x->put('action_id',$action);
$x->put('type','multi_static_select');
$x->put('options',$options->transform(function ($item) {
return ['text'=>TextEmoji::item($item->name),'value'=>(string)$item->id];
}));
if ($selected and $selected->count())
$x->put('initial_options',$selected->transform(function ($item) {
return ['text'=>TextEmoji::item($item->name),'value'=>(string)$item->id];
}));
if ($maximum)
$x->put('max_selected_items',$maximum);
return $this->section([
'text' => $label,
'accessory' => $x
]);
}
/**
* Add a section with just text
*
* @param Text $text
* @return Blocks
*/
public function addSectionText(Text $text): self
{
return $this->section(['text'=>$text]);
}
/**
* A context block
*
* @param array $items
* @return Blocks
*/
private function context(array $items): self
{
$this->_data->push(array_merge(['type'=>'context'],$items));
return $this;
}
/**
* 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
* @deprecated - to optimize
*/
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;
}
/**
* A section block
*
* @param array $items
* @return Blocks
*/
private function section(array $items): self
{
$this->_data->push(array_merge(['type'=>'section'],$items));
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 Blocks
* @throws \Exception
* @deprecated - to optimize
*/
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 Blocks
* @throws \Exception
* @deprecated - to optimize
*/
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);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Slack\Blockkit\Blocks;
final class Button
{
public string $type;
public TextEmoji $text;
public string $value;
public string $url;
public string $action_id;
public string $style;
public function __construct(TextEmoji $text,string $value,string $action_id,string $url=NULL,string $style=NULL)
{
$this->type = 'button';
$this->text = $text;
$this->value = $value ?: '-';
$this->action_id = $action_id;
if ($url)
$this->url = $url;
if ($style)
$this->style = $style;
}
public static function item(TextEmoji $text,string $value,string $action_id,string $url=NULL,string $style=NULL): self
{
return new self($text,$value,$action_id,$url,$style);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Slack\Blockkit\Blocks;
use Slack\Blockkit\Input\Element;
final class Input
{
public string $type;
public Element $element;
public TextEmoji $label;
public function __construct(Element $element,TextEmoji $label)
{
$this->type = 'input';
$this->element = $element;
$this->label = $label;
}
public static function item(Element $element,TextEmoji $label): self
{
return new self($element,$label);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Slack\Blockkit\Blocks;
final class Text
{
public string $type;
public string $text;
public function __construct(string $text,string $type='mrkdwn')
{
$this->type = $type;
$this->text = $text;
}
public static function item(string $text,string $type='mrkdwn'): self
{
return new self($text,$type);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Slack\Blockkit\Blocks;
final class TextEmoji
{
public bool $emoji;
public string $text;
public string $type;
public function __construct(string $text,bool $emoji=TRUE)
{
$this->emoji = $emoji;
$this->text = $text;
$this->type = 'plain_text';
}
public static function item(string $text,bool $emoji=TRUE): self
{
return new self($text,$emoji);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Slack\Blockkit\Input;
final class Element
{
public string $type;
public string $action_id;
public bool $multiline;
public function __construct(string $type,string $action_id,bool $multiline=FALSE)
{
$this->type = $type;
$this->action_id = $action_id;
$this->multiline = $multiline;
}
public static function item(string $type,string $action_id,bool $multiline=FALSE): self
{
return new self($type,$action_id,$multiline);
}
}

View File

@ -2,40 +2,36 @@
namespace Slack\Blockkit; namespace Slack\Blockkit;
use Illuminate\Support\Str;
use Slack\BlockKit; use Slack\BlockKit;
use Slack\Blockkit\Blocks\TextEmoji;
/** /**
* This class creates a slack Modal Response * This class creates a slack Modal Response
*/ */
class Modal extends BlockKit class Modal extends BlockKit
{ {
protected $blocks;
private $action = NULL; private $action = NULL;
public function __construct(string $title) public function __construct(TextEmoji $title)
{ {
parent::__construct(); parent::__construct();
$this->blocks = collect();
$this->_data->put('type','modal'); $this->_data->put('type','modal');
$this->_data->put('title',$this->text(Str::limit($title,24),'plain_text')); $this->_data->put('title',$title);
} }
/*
public function action(string $action) public function action(string $action)
{ {
$this->action = $action; $this->action = $action;
} }
*/
/** /**
* The data that will be returned when converted to JSON. * The data that will be returned when converted to JSON.
*/ */
public function jsonSerialize() public function jsonSerialize()
{ {
if ($this->blocks->count())
$this->_data->put('blocks',$this->blocks);
switch ($this->action) { switch ($this->action) {
case 'clear': case 'clear':
return ['response_action'=>'clear']; return ['response_action'=>'clear'];
@ -51,23 +47,24 @@ class Modal extends BlockKit
/** /**
* Add a block to the modal * Add a block to the modal
* *
* @param Block $block * @param Blocks $blocks
* @return $this * @return $this
*/ */
public function addBlock(Block $block): self public function setBlocks(Blocks $blocks): self
{ {
$this->blocks->push($block); $this->_data->put('blocks',$blocks);
return $this; return $this;
} }
public function callback(string $id): self public function setCallback(string $id): self
{ {
$this->_data->put('callback_id',$id); $this->_data->put('callback_id',$id);
return $this; return $this;
} }
/*
public function close(string $text='Cancel'): self public function close(string $text='Cancel'): self
{ {
$this->_data->put('close', $this->_data->put('close',
@ -112,4 +109,5 @@ class Modal extends BlockKit
return $this; return $this;
} }
*/
} }

View File

@ -0,0 +1,20 @@
<?php
namespace Slack\Channels;
use Illuminate\Notifications\Notification;
class SlackBotChannel
{
public function send($notifiable,Notification $notification)
{
if (! $co = $notifiable->routeNotificationFor('slackapp',$notification)) {
return;
}
$o = $notification->toSlack($notifiable);
$o->setChannel($co);
return $o->post();
}
}

View File

@ -60,9 +60,9 @@ abstract class API
* @param array $args An associative array of arguments to pass to the * @param array $args An associative array of arguments to pass to the
* method call. * method call.
* @param bool $multipart Whether to send as a multipart request. Default to false * @param bool $multipart Whether to send as a multipart request. Default to false
* @param bool $callDeferred Wether to call the API asynchronous or not. * @param bool $callDeferred Whether to call the API asynchronous or not.
* *
* @return \React\Promise\PromiseInterface A promise for an API response. * @return PromiseInterface A promise for an API response.
*/ */
public function apiCall(string $method,array $args=[],bool $multipart=FALSE,bool $callDeferred=TRUE): PromiseInterface public function apiCall(string $method,array $args=[],bool $multipart=FALSE,bool $callDeferred=TRUE): PromiseInterface
{ {
@ -84,7 +84,6 @@ abstract class API
] ]
]); ]);
//dump(['m'=>__METHOD__,'l'=>__LINE__,'promise'=>$promise]);
// Add requests to the event loop to be handled at a later date. // Add requests to the event loop to be handled at a later date.
if ($callDeferred) { if ($callDeferred) {
$this->loop->futureTick(function () use ($promise) { $this->loop->futureTick(function () use ($promise) {

View File

@ -11,11 +11,15 @@ use Illuminate\Support\Facades\Log;
use React\EventLoop\LoopInterface; use React\EventLoop\LoopInterface;
use React\Promise\Deferred; use React\Promise\Deferred;
use React\Promise; use React\Promise;
use Slack\Command\Factory;
class SocketMode extends API class SocketMode extends API
{ {
use EventEmitterTrait; use EventEmitterTrait;
private const LOGKEY = 'ASM';
private bool $debug_reconnect = FALSE;
private bool $connected; private bool $connected;
private WebSocket $websocket; private WebSocket $websocket;
@ -37,7 +41,7 @@ class SocketMode extends API
* *
* @return \React\Promise\PromiseInterface * @return \React\Promise\PromiseInterface
*/ */
public function connect() public function connect(): Promise\PromiseInterface
{ {
$deferred = new Deferred; $deferred = new Deferred;
@ -81,9 +85,9 @@ class SocketMode extends API
// initiate the websocket connection // initiate the websocket connection
// write PHPWS things to the existing logger // write PHPWS things to the existing logger
$this->websocket = new WebSocket($response['url'].'&debug_reconnects=true', $this->loop, $this->logger); $this->websocket = new WebSocket($response['url'].($this->debug_reconnect ? '&debug_reconnects=true' : ''),$this->loop,$this->logger);
$this->websocket->on('message', function ($message) { $this->websocket->on('message', function ($message) {
Log::debug('Calling onMessage for',['m'=>serialize($message)]); Log::debug(sprintf('%s:- Calling onMessage ...',self::LOGKEY),['m'=>__METHOD__]);
$this->onMessage($message); $this->onMessage($message);
}); });
@ -124,6 +128,8 @@ class SocketMode extends API
$this->websocket->close(); $this->websocket->close();
$this->connected = FALSE; $this->connected = FALSE;
return TRUE;
} }
/** /**
@ -139,23 +145,37 @@ class SocketMode extends API
/** /**
* Handles incoming websocket messages, parses them, and emits them as remote events. * Handles incoming websocket messages, parses them, and emits them as remote events.
* *
* @param WebSocketMessageInterface $messageRaw A websocket message. * @param WebSocketMessageInterface $message
*/ */
private function onMessage(WebSocketMessageInterface $message) private function onMessage(WebSocketMessageInterface $message)
{ {
Log::debug('+ Start',['m'=>__METHOD__]); Log::debug(sprintf('%s:+ Start',self::LOGKEY),['m'=>__METHOD__]);
// parse the message and get the event name // parse the message and get the event name
$payload = Payload::fromJson($message->getData()); $payload = Payload::fromJson($message->getData());
$emitted = FALSE;
if (isset($payload['type'])) { if (isset($payload['type'])) {
$this->emit('_internal_message', [$payload['type'], $payload]); $this->emit('_internal_message', [$payload['type'], $payload]);
switch ($payload['type']) { switch ($payload['type']) {
case 'hello': case 'hello':
$this->connected = TRUE; $this->connected = TRUE;
break; break;
/* case 'disconnect':
Log::debug(sprintf('%s:- Disconnect Received, Re-Connecting...',self::LOGKEY),['m'=>__METHOD__]);
$this->disconnect();
$this->connect();
break;
// We got an event, we'll handle it later in SlackSocketClient::class (after ACKing it).
case 'events_api':
case 'interactive':
case 'slash_commands':
break;
/*
case 'team_rename': case 'team_rename':
$this->team->data['name'] = $payload['name']; $this->team->data['name'] = $payload['name'];
break; break;
@ -234,19 +254,47 @@ class SocketMode extends API
$user = new User($this, $payload['user']); $user = new User($this, $payload['user']);
$this->users[$user->getId()] = $user; $this->users[$user->getId()] = $user;
break; break;
*/ */
default:
Log::debug(sprintf('Unhandled type [%s]',$payload['type']),['m'=>__METHOD__,'p'=>$payload]);
}
// emit an event with the attached json default:
$this->emit($payload['type'], [$payload]); Log::debug(sprintf('%s:- Unhandled type [%s]',self::LOGKEY,$payload['type']),['m'=>__METHOD__,'p'=>$payload]);
}
} }
if (isset($payload['envelope_id'])) { if (isset($payload['envelope_id'])) {
// @acknowledge the event if (isset($payload['accepts_response_payload']) && $payload['accepts_response_payload']) {
$this->websocket->send(json_encode(['envelope_id'=>$payload['envelope_id']])); switch ($payload['type']) {
Log::debug(sprintf('Responded to event [%s] for (%s)',$payload['envelope_id'],$payload['type']),['m'=>__METHOD__]); case 'slash_commands':
$command = Factory::make($payload);
if (! method_exists($command,'respond')) {
Log::alert(sprintf('%s:! Cant respond to Command [%s], no respond method',static::LOGKEY,get_class($command)),['m'=>__METHOD__]);
abort(500,'No respond method() for '.get_class($command));
}
$response = ($x=$command->respond())->isEmpty() ? NULL : $x;
$this->websocket->send(json_encode(['envelope_id'=>$payload['envelope_id'],'payload'=>$response]));
$emitted = TRUE;
break;
default:
Log::debug(sprintf('%s:- Unhandled type [%s] for accepts_response_payload',self::LOGKEY,$payload['type']),['m'=>__METHOD__]);
$this->websocket->send(json_encode(['envelope_id'=>$payload['envelope_id']]));
}
} else {
// @acknowledge the event
$this->websocket->send(json_encode(['envelope_id'=>$payload['envelope_id']]));
}
Log::debug(sprintf('%s:- Responded to event [%s] for (%s)',self::LOGKEY,$payload['envelope_id'],$payload['type']),['m'=>__METHOD__]);
}
// If we havent already handled the event, we'll emit it to be handled upstream
if (isset($payload['type']) && ! $emitted) {
// emit an event with the attached json
$this->emit($payload['type'],[$payload]);
} }
if (! isset($payload['type']) || $payload['type'] == 'pong') { if (! isset($payload['type']) || $payload['type'] == 'pong') {
@ -269,6 +317,6 @@ class SocketMode extends API
} }
} }
Log::debug('= End',['m'=>__METHOD__]); Log::debug(sprintf('%s:= End',self::LOGKEY),['m'=>__METHOD__]);
} }
} }

View File

@ -2,16 +2,17 @@
namespace Slack\Command; namespace Slack\Command;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Base as SlackBase; use Slack\Base as SlackBase;
use Slack\Blockkit\Blocks;
use Slack\Message;
abstract class Base extends SlackBase abstract class Base extends SlackBase
{ {
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::info(sprintf('SCb:Slack SLASHCOMMAND Initialised [%s]',get_class($this)),['m'=>__METHOD__]); Log::info(sprintf('SCb:Slack SLASHCOMMAND Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
parent::_construct($request); parent::__construct($request);
} }
/** /**
@ -51,4 +52,20 @@ abstract class Base extends SlackBase
return object_get($this->_data,'trigger_id'); return object_get($this->_data,'trigger_id');
} }
} }
protected function bot_in_channel(): ?Message
{
$o = new Message;
if (! $this->channel() || ! $this->channel()->active) {
$blocks = new Blocks;
$blocks->addHeader(':robot_face: Bot not in this channel');
$blocks->addSectionText(Blocks\Text::item(sprintf('Please add %s to this channel and try this again.',$this->team()->bot->name ?: 'the BOT')));
$o->setAttachments((new Message\Attachments())->setBlocks($blocks)->setColor('#ff0000'));
}
return $o->isEmpty() ? NULL : $o;
}
} }

View File

@ -2,10 +2,10 @@
namespace Slack\Command; namespace Slack\Command;
use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Client\Payload;
class Factory { class Factory {
private const LOGKEY = 'SCf'; private const LOGKEY = 'SCf';
@ -14,39 +14,32 @@ class Factory {
* @var array event type to event class mapping * @var array event type to event class mapping
*/ */
public const map = [ public const map = [
'ask'=>Watson::class,
'ate'=>Ask::class,
'help'=>Help::class, 'help'=>Help::class,
'goto'=>Link::class,
'leaders'=>Leaders::class,
'products'=>Products::class,
'review'=>Review::class,
'wc'=>WatsonCollection::class,
]; ];
/** /**
* Returns new event instance * Returns new event instance
* *
* @param string $type * @param string $type
* @param Request $request * @param array $request
* @return Base * @return Base
*/ */
public static function create(string $type,Request $request) public static function create(string $type,array $request): Base
{ {
$class = Arr::get(self::map,$type,Unknown::class); $class = Arr::get(config('slack.commands',self::map),$type,Unknown::class);
Log::debug(sprintf('%s:Working out Slash Command Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]); Log::debug(sprintf('%s:Working out Slash Command Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
if (App::environment() == 'dev') if (App::environment() == 'local')
file_put_contents('/tmp/command.'.$type,print_r(json_decode(json_encode($request->all())),TRUE)); file_put_contents('/tmp/command.'.$type,print_r(json_decode(json_encode($request)),TRUE));
return new $class($request); return new $class($request);
} }
public static function make(Request $request): Base public static function make(Payload $request): Base
{ {
$data = json_decode(json_encode($request->all())); $data = json_decode(json_encode($request->getData()));
$command = preg_replace('/^([a-z]+)(\s?.*)/','$1',$data->text); $command = preg_replace('/^([a-z]+)(\s?.*)/','$1',$data->payload->text);
return self::create($command ?: 'help',$request); return self::create($command ?: 'help',Arr::get($request->getData(),'payload'));
} }
} }

View File

@ -3,7 +3,7 @@
namespace Slack\Command; namespace Slack\Command;
use Slack\Message; use Slack\Message;
use Slack\Message\Attachment; use Slack\Message\Attachments;
class Help extends Base class Help extends Base
{ {
@ -13,12 +13,12 @@ class Help extends Base
{ {
$o = new Message; $o = new Message;
$o->setText('Hi, I am the a *NEW* Bot'); $o->setText('Hi, I am a *NEW* Bot');
// Version // Version
$a = new Attachment; $a = new Attachments;
$a->addField('Version',config('app.version'),TRUE); $a->addField('Version',config('app.version','unknown'),TRUE);
$o->addAttachment($a); $o->setAttachments($a);
return $o; return $o;
} }

View File

@ -13,7 +13,7 @@ use Slack\Message;
*/ */
final class Unknown extends Base final class Unknown extends Base
{ {
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::notice(sprintf('SCU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]); Log::notice(sprintf('SCU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]);

View File

@ -6,6 +6,9 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use React\EventLoop\Loop; use React\EventLoop\Loop;
use Slack\Client\SocketMode; use Slack\Client\SocketMode;
use Slack\Command\Factory as SlackCommandFactory;
use Slack\Event\Factory as SlackEventFactory;
use Slack\Interactive\Factory as SlackInteractiveFactory;
class SlackSocketClient extends Command class SlackSocketClient extends Command
{ {
@ -47,12 +50,20 @@ class SlackSocketClient extends Command
$client = new SocketMode($loop); $client = new SocketMode($loop);
$client->setToken(config('slack.socket_token')); $client->setToken(config('slack.socket_token'));
$client->on('events_api', function ($data) use ($client) { $client->on('events_api',function ($data) {
dump(['data'=>$data]); event(SlackEventFactory::make($data));
});
$client->on('interactive',function ($data) {
event(SlackInteractiveFactory::make($data));
});
$client->on('slash_command',function ($data) {
event(SlackCommandFactory::make($data));
}); });
$client->connect()->then(function () { $client->connect()->then(function () {
Log::debug(sprintf('%s: Connected to slack.',self::LOGKEY)); Log::debug(sprintf('%s:- Connected to slack.',self::LOGKEY));
}); });
$loop->run(); $loop->run();

View File

@ -8,7 +8,7 @@ use Slack\Base as SlackBase;
abstract class Base extends SlackBase abstract class Base extends SlackBase
{ {
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::info(sprintf('SEb:Slack Event Initialised [%s]',get_class($this)),['m'=>__METHOD__]); Log::info(sprintf('SEb:Slack Event Initialised [%s]',get_class($this)),['m'=>__METHOD__]);

View File

@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Client\Payload;
class Factory { class Factory {
private const LOGKEY = 'SEf'; private const LOGKEY = 'SEf';
@ -27,22 +28,22 @@ class Factory {
/** /**
* Returns new event instance * Returns new event instance
* *
* @param string $type * @param string $type
* @param Request $request * @param array $request
* @return Base * @return Base
*/ */
public static function create(string $type,Request $request) public static function create(string $type,array $request): Base
{ {
$class = Arr::get(self::map,$type,Unknown::class); $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__]); Log::debug(sprintf('%s:Working out Event Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
if (App::environment() == 'dev') if (App::environment() == 'local')
file_put_contents('/tmp/event.'.$type,print_r($request->all(),TRUE)); file_put_contents('/tmp/event.'.$type,print_r($request,TRUE));
return new $class($request); return new $class($request);
} }
public static function make(Request $request): Base public static function make(Payload $request): Base
{ {
// During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller. // During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller.
static $o = NULL; static $o = NULL;
@ -50,7 +51,7 @@ class Factory {
if (! $o OR ($or != $request)) { if (! $o OR ($or != $request)) {
$or = $request; $or = $request;
$o = self::create($request->input('event.type'),$request); $o = self::create(Arr::get($request->getData(),'payload.event.type'),Arr::get($request->getData(),'payload'));
} }
return $o; return $o;

View File

@ -12,10 +12,10 @@ use Illuminate\Support\Facades\Log;
*/ */
class Unknown extends Base class Unknown extends Base
{ {
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::notice(sprintf('SEU:UNKNOWN Slack Event received [%s]',get_class($this)),['m'=>__METHOD__]); Log::notice(sprintf('SEU:UNKNOWN Slack Event received [%s]',get_class($this)),['m'=>__METHOD__]);
parent::__contruct($request); parent::__construct($request);
} }
} }

View File

@ -16,25 +16,6 @@ class SlackAppController extends Controller
{ {
private const LOGKEY = 'CSA'; 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_authorise_url = 'https://slack.com/oauth/v2/authorize';
private const slack_oauth_url = 'https://slack.com/api/oauth.v2.access'; 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'; private const slack_button = 'https://platform.slack-edge.com/img/add_to_slack.png';
@ -173,9 +154,15 @@ class SlackAppController extends Controller
$so->admin_id = $uo->id; $so->admin_id = $uo->id;
$so->save(); $so->save();
return sprintf('All set up! Head back to your slack instance <strong>%s</strong>."',$so->description); return sprintf('All set up! Head back to your slack instance <strong>%s</strong>.',$so->description);
} }
/**
* Define our parameters to install this Slack Integration
*
* @note The configuration file should include a list of scopes that this application needs
* @return array
*/
private function parameters(): array private function parameters(): array
{ {
return [ return [

View File

@ -2,7 +2,6 @@
namespace Slack\Interactive; namespace Slack\Interactive;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Base as SlackBase; use Slack\Base as SlackBase;
@ -14,12 +13,12 @@ abstract class Base extends SlackBase
// When retrieving multiple action values, this is the index we are retrieving. // When retrieving multiple action values, this is the index we are retrieving.
protected $index = 0; protected $index = 0;
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::info(sprintf('SIb:Slack INTERACTIVE MESSAGE Initialised [%s]',get_class($this)),['m'=>__METHOD__]); Log::info(sprintf('SIb:Slack INTERACTIVE MESSAGE Initialised [%s]',get_class($this)),['m'=>__METHOD__]);
// Our data is in a payload value // Our data is in a payload value
$this->_data = json_decode($request->input('payload')); parent::__construct($request);
} }
/** /**

View File

@ -60,25 +60,24 @@ class BlockActions extends Base
public function __get($key) public function __get($key)
{ {
switch ($key) { switch ($key) {
case 'callback_id': case 'actions':
return object_get($this->_data,'view.callback_id'); return object_get($this->_data,$key);
case 'action_id':
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),$key);
// An event can have more than 1 action, each action can have 1 value. // An event can have more than 1 action, each action can have 1 value.
case 'action_id': case 'action_key':
return $this->action('action'); return $this->action('action');
case 'action_value': case 'action_value':
return $this->action('value'); return $this->action('value');
case 'value': case 'callback_id':
switch (Arr::get(object_get($this->_data,'actions'),$this->index)->type) { return object_get($this->_data,'view.callback_id');
case 'external_select':
case 'overflow': case 'keys':
case 'static_select': return collect(object_get($this->_data,'view.blocks'))->pluck('accessory.action_id');
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 // For Block Actions that are messages
case 'message_ts': case 'message_ts':
@ -87,16 +86,41 @@ class BlockActions extends Base
case 'channel_id': case 'channel_id':
return object_get($this->_data,'channel.id') ?: Channel::findOrFail($this->action('value'))->channel_id; return object_get($this->_data,'channel.id') ?: Channel::findOrFail($this->action('value'))->channel_id;
case 'team_id': // view.team_id represent workspace publishing view
return object_get($this->_data,'user.team_id');
case 'view_id': case 'view_id':
return object_get($this->_data,'view.id'); return object_get($this->_data,'view.id');
case 'actions': case 'value':
return object_get($this->_data,$key); 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');
case 'multi_static_select':
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'selected_options.value');
default:
return object_get(Arr::get(object_get($this->_data,'actions'),$this->index),$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 case 'values':
// @todo Currently working with Slack to understand this behaviour switch (Arr::get(object_get($this->_data,'actions'),$this->index)->type) {
case 'team_id': // view.team_id represent workspace publishing view // @todo To Check
return object_get($this->_data,'user.team_id'); case 'external_select':
// @todo To Check
case 'overflow':
// @todo To Check
case 'static_select':
return count(object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'selected_option'));
case 'multi_static_select':
return collect(object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'selected_options'))->pluck('value');
default:
return count(object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'value'));
}
case 'value_count':
return count($this->values);
default: default:
return parent::__get($key); return parent::__get($key);
@ -116,16 +140,16 @@ class BlockActions extends Base
$value = NULL; $value = NULL;
// We only take the action up to the pipe symbol // 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'); $action_key = object_get(Arr::get(object_get($this->_data,'actions'),$this->index),'action_id');
if (preg_match($regex,$action_id)) { if (preg_match($regex,$action_key)) {
$action = preg_replace($regex,'$1',$action_id); $action = preg_replace($regex,'$1',$action_key);
$value = preg_replace($regex,'$2',$action_id); $value = preg_replace($regex,'$2',$action_key);
} }
switch ($key) { switch ($key) {
case 'action': case 'action':
return $action ?: $action_id; return $action ?: $action_key;
case 'value': case 'value':
return $value; return $value;
} }
@ -142,20 +166,4 @@ class BlockActions extends Base
{ {
return object_get($this->_data,'message') ? TRUE : FALSE; 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;
}
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Client\Payload;
class Factory { class Factory {
private const LOGKEY = 'SIF'; private const LOGKEY = 'SIF';
@ -24,31 +25,30 @@ class Factory {
/** /**
* Returns new event instance * Returns new event instance
* *
* @param string $type * @param string $type
* @param Request $request * @param array $request
* @return Base * @return Base
*/ */
public static function create(string $type,Request $request) public static function create(string $type,array $request): Base
{ {
$class = Arr::get(self::map,$type,Unknown::class); $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__]); Log::debug(sprintf('%s:Working out Interactive Message Event Class for [%s] as [%s]',static::LOGKEY,$type,$class),['m'=>__METHOD__]);
if (App::environment() == 'dev') if (App::environment() == 'local')
file_put_contents('/tmp/interactive.'.$type,print_r(json_decode($request->input('payload')),TRUE)); file_put_contents('/tmp/interactive.'.$type,print_r($request,TRUE));
return new $class($request); return new $class($request);
} }
public static function make(Request $request): Base public static function make(Payload $request): Base
{ {
// During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller. // During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller.
static $o = NULL; static $o = NULL;
static $or = NULL; static $or = NULL;
if (! $o OR ($or != $request)) { if (! $o OR ($or != $request)) {
$data = json_decode($request->input('payload'));
$or = $request; $or = $request;
$o = self::create($data->type,$request); $o = self::create(Arr::get($request->getData(),'payload.type'),Arr::get($request->getData(),'payload'));
} }
return $o; return $o;

View File

@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Log;
*/ */
class Unknown extends Base class Unknown extends Base
{ {
public function __construct(Request $request) public function __construct(array $request)
{ {
Log::notice(sprintf('SIU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]); Log::notice(sprintf('SIU:UNKNOWN Slack Interaction Option received [%s]',get_class($this)),['m'=>__METHOD__]);

View File

@ -0,0 +1,30 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Jobs\SlackHomeTabUpdate;
use Slack\Event\AppHomeOpened;
class AppHomeOpenedListener implements ShouldQueue
{
private const LOGKEY = 'LAH';
public $queue = 'high';
/**
* Handle the event.
*
* @param AppHomeOpened $event
* @return void
*/
public function handle(AppHomeOpened $event)
{
// Do some magic with event data
Log::info(sprintf('%s:App Home Page open for [%s] in team [%s]',self::LOGKEY,$event->user_id,$event->team_id),['m'=>__METHOD__]);
dispatch((new SlackHomeTabUpdate($event))->onQueue('high'));
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Slack\Jobs\DeleteChat;
use Slack\Interactive\BlockActions;
class BlockActionListener
{
protected const LOGKEY = 'LBA';
// Block actions arent queued, since slack expects a response to the request
//public $queue = 'high';
/**
* Handle the event.
*
* @param BlockActions $event
* @return void
* @throws \Exception
*/
public function handle(BlockActions $event): void
{
// Do some magic with event data
Log::info(sprintf('%s:Block Action for Callback [%s] User [%s] in [%s]',self::LOGKEY,$event->callback_id,$event->user_id,$event->team_id),['m'=>__METHOD__]);
switch ($event->callback_id) {
// Messages that generate a block action dont have a callback ID
case NULL:
Log::debug(sprintf('%s:Callback NULL [%s] (%s)',self::LOGKEY,$event->isMessage(),$event->action_id),['m'=>__METHOD__]);
$this->messageEvent($event);
break;
default:
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',self::LOGKEY,$event->callback_id),['m'=>__METHOD__]);
}
}
/**
* Events from messages
*
* @param BlockActions $event
* @throws \Exception
*/
protected function messageEvent(BlockActions $event): void
{
Log::info(sprintf('%s:[%s] has [%d] actions',self::LOGKEY,$event->callback_id,count($event->actions)),['m'=>__METHOD__]);
foreach ($event->actions as $id => $action) {
$event->index = $id;
Log::debug(sprintf('%s:Action [%s]',self::LOGKEY,$event->action_id),['m'=>__METHOD__]);
switch ($event->action_id) {
case 'self_destruct':
// Queue the delete of the message
dispatch((new DeleteChat($event->user(),$event->message_ts))->onQueue('low'));
// @todo If this message is on integrations messages channel, which is not the user_id() - need to use the user's integration direct channel ID
break;
default:
Log::notice(sprintf('%s:Unhandled ACTION [%s]',self::LOGKEY,$event->action_id),['m'=>__METHOD__]);
}
}
}
/**
* Store data coming in from a block action dialog
*
* @param BlockActions $event
*/
protected function store(BlockActions $event): void
{
foreach ($event->actions as $id => $action) {
$event->index = $id;
switch ($event->action_id) {
default:
Log::notice(sprintf('%s:Unhandled ACTION [%s]',static::LOGKEY,$event->action_id),['m'=>__METHOD__]);
}
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\MemberJoinedChannel;
class ChannelJoinListener implements ShouldQueue
{
private const LOGKEY = 'LCJ';
public $queue = 'high';
/**
* Handle the event.
*
* @param MemberJoinedChannel $event
* @return void
*/
public function handle(MemberJoinedChannel $event)
{
// Do some magic with event data
Log::info(sprintf('%s:User [%s] joined Channel [%s]',self::LOGKEY,$event->invited,$event->channel_id),['m'=>__METHOD__]);
if ($event->team()->bot->user_id == $event->invited) {
$o = $event->channel(TRUE);
$o->active = TRUE;
$o->save();
Log::debug(sprintf('%s:BOT [%s] Joined Channel [%s]',self::LOGKEY,$event->invited,$event->channel_id),['m'=>__METHOD__]);
} else {
Log::debug(sprintf('%s:Wasnt the BOT who joined Channel [%s]',self::LOGKEY,$event->channel_id),['m'=>__METHOD__]);
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\{Base,ChannelLeft,GroupLeft};
class ChannelLeftListener implements ShouldQueue
{
private const LOGKEY = 'LCL';
public $queue = 'high';
/**
* Handle the event.
*
* @param Base $event
* @return void
*/
public function handle(Base $event)
{
if (! $event instanceof ChannelLeft AND ! $event instanceof GroupLeft)
abort(500,'Wrong class calling this listener? '.get_class($event));
// Do some magic with event data
Log::info(sprintf('%s:Bot Left Channel [%s]',self::LOGKEY,$event->channel_id),['m'=>__METHOD__,'c'=>$event->channel_id]);
$o = $event->channel(TRUE);
$o->active = FALSE;
$o->save();
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Interactive\InteractiveMessage;
class InteractiveMessageListener implements ShouldQueue
{
private const LOGKEY = 'LIM';
public $queue = 'high';
/**
* Handle the event.
*
* @param InteractiveMessage $event
* @return void
*/
public function handle(InteractiveMessage $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Interactive Message for Callback [%s] User [%s] in [%s]',self::LOGKEY,$event->callback_id,$event->user_id,$event->team_id),['m'=>__METHOD__]);
switch ($event->callback_id) {
default:
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',self::LOGKEY,$event->callback_id),['m'=>__METHOD__]);
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\Message;
class MessageListener implements ShouldQueue
{
private const LOGKEY = 'LM-';
public $queue = 'high';
/**
* Handle the event.
*
* @param Message $event
* @return void
*/
public function handle(Message $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Message event [%s] - subtype [%s]',self::LOGKEY,$event->ts,$event->type),['m'=>__METHOD__]);
switch ($event->type) {
case 'channel_join': // Handled by another event (member_joined_channel - needs to be defined in Event Subscriptions)
case 'group_join': // Handled by another event (member_joined_channel - needs to be defined in Event Subscriptions)
case 'message_changed':
Log::debug(sprintf('%s:Ignoring message subtype [%s]',self::LOGKEY,$event->type),['m'=>__METHOD__]);
break;
default:
Log::notice(sprintf('%s:Unhandled TYPE [%s]',self::LOGKEY,$event->type),['m'=>__METHOD__]);
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\PinAdded;
class PinAddedListener implements ShouldQueue
{
private const LOGKEY = 'LPA';
public $queue = 'high';
/**
* Handle the event.
*
* @param PinAdded $event
* @return void
*/
public function handle(PinAdded $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Pin Added to message [%s] in [%s]',self::LOGKEY,$event->ts,$event->channel_id),['m'=>__METHOD__]);
Log::notice(sprintf('%s:Ignoring Pin Add on [%s]',static::LOGKEY,$event->ts),['m'=>__METHOD__]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\PinRemoved;
class PinRemovedListener implements ShouldQueue
{
private const LOGKEY = 'LPR';
public $queue = 'high';
/**
* Handle the event.
*
* @param PinRemoved $event
* @return void
*/
public function handle(PinRemoved $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Pin Removed from message [%s] in [%s]',self::LOGKEY,$event->ts,$event->channel_id),['m'=>__METHOD__]);
Log::debug(sprintf('%s:Ignoring Pin Remove on [%s]',static::LOGKEY,$event->ts),['m'=>__METHOD__]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Event\ReactionAdded;
class ReactionAddedListener implements ShouldQueue
{
private const LOGKEY = 'LRA';
public $queue = 'high';
/**
* Handle the event.
*
* @param ReactionAdded $event
* @return void
*/
public function handle(ReactionAdded $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Reaction [%s] added to message in [%s]',self::LOGKEY,$event->reaction,$event->team_id),['m'=>__METHOD__]);
Log::debug(sprintf('%s:Ignoring Reaction Add [%s] on [%s]',static::LOGKEY,$event->reaction,$event->ts),['m'=>__METHOD__]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Modal;
use Slack\Interactive\Shortcut;
use Slack\Message;
class ShortcutListener //implements ShouldQueue
{
private const LOGKEY = 'LSC';
// Block actions arent queued, since slack expects a response to the request
//public $queue = 'high';
/**
* Handle the event.
*
* @param Shortcut $event
* @return void
* @todo To Test
*/
public function handle(Shortcut $event): Message
{
if (! $event->channel() || ! $event->channel()->active) {
$modal = new Modal(Blocks\TextEmoji::item(config('app.name')));
$blocks = new Blocks;
$blocks->addHeader(':robot_face: Bot not in this channel');
$blocks->addSectionText(Blocks\Text::item('Please add the BOT to this channel and try this again.'));
$modal->setBlocks($blocks);
try {
$event->team()->slackAPI()->viewOpen($event->trigger_id,json_encode($modal));
} catch (\Exception $e) {
Log::error(sprintf('%s:Got an error posting view to slack: %s',static::LOGKEY,$e->getMessage()),['m'=>__METHOD__]);
}
return (new Message)->blank();
}
// Do some magic with event data
Log::info(sprintf('%s:Shortcut [%s] triggered for: [%s]',self::LOGKEY,$event->callback_id,$event->team_id),['m'=>__METHOD__]);
switch ($event->callback_id) {
default:
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',self::LOGKEY,$event->callback_id),['m'=>__METHOD__]);
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Interactive\ViewClosed;
class ViewClosedListener implements ShouldQueue
{
private const LOGKEY = 'LVC';
public $queue = 'high';
/**
* Handle the event.
*
* @param ViewClosed $event
* @return void
*/
public function handle(ViewClosed $event)
{
// Do some magic with event data
Log::info(sprintf('%s:Block Action for Callback [%s] User [%s] in [%s]',self::LOGKEY,$event->callback_id,$event->user_id,$event->team_id),['m'=>__METHOD__]);
switch ($event->callback_id) {
default:
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',self::LOGKEY,$event->callback_id),['m'=>__METHOD__]);
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Slack\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Slack\Interactive\ViewSubmission;
class ViewSubmissionListener implements ShouldQueue
{
private const LOGKEY = 'LVC';
public $queue = 'high';
/**
* Handle the event.
*
* @param ViewSubmission $event
* @return void
*/
public function handle(ViewSubmission $event)
{
// Do some magic with event data
Log::info(sprintf('%s:View Submission for Callback [%s] User [%s] in [%s]',self::LOGKEY,$event->callback_id,$event->user_id,$event->team_id),['m'=>__METHOD__]);
switch ($event->callback_id) {
default:
Log::notice(sprintf('%s:Unhandled CALLBACK [%s]',self::LOGKEY,$event->callback_id),['m'=>__METHOD__]);
}
}
}

View File

@ -6,11 +6,11 @@ use Carbon\Carbon;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Slack\Jobs\DeleteChat; use Slack\Blockkit\Blocks;
use Slack\Models\{Channel,User};
use Slack\Blockkit\Block;
use Slack\Exceptions\SlackException; use Slack\Exceptions\SlackException;
use Slack\Message\Attachment; use Slack\Jobs\DeleteChat;
use Slack\Message\Attachments;
use Slack\Models\{Channel,User};
use Slack\Response\Generic; use Slack\Response\Generic;
/** /**
@ -20,55 +20,48 @@ class Message implements \JsonSerializable
{ {
protected const LOGKEY = 'SM-'; protected const LOGKEY = 'SM-';
private $o; private Model $o;
private $attachments; private Blocks $blocks;
private $blocks;
/** /**
* Message constructor. * Message constructor.
* *
* @param Model|null $o Who the message will be to - Channel or User * @param Model|null $o Who the message will be to - Channel or User
* @throws SlackException
*/ */
public function __construct(Model $o=NULL) public function __construct(Model $o=NULL)
{ {
$this->_data = collect(); $this->_data = collect();
// Message is to a channel if ($o) {
if ($o instanceof Channel) { // Message is to a channel
$this->setChannel($o); if ($o instanceof Channel) {
$this->setChannel($o);
// Message is to a user // Message is to a user
} elseif ($o instanceof User) { } elseif ($o instanceof User) {
$this->setUser($o); $this->setUser($o);
} else {
throw new SlackException('Model not handled: '.get_class($o));
}
$this->o = $o;
} }
$this->o = $o; $this->blocks = new Blocks;
$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 * Add a block to the message
* *
* @param BlockKit $block * @param Blocks $blocks
* @return $this * @return Message
* @todo to test
*/ */
public function addBlock(BlockKit $block): self public function addBlock(Blocks $blocks): self
{ {
$this->blocks->push($block); $this->blocks = $blocks;
return $this; return $this;
} }
@ -76,11 +69,12 @@ class Message implements \JsonSerializable
/** /**
* Empty the message * Empty the message
* *
* @return $this * @return Message
*/ */
public function blank(): self public function blank(): self
{ {
$this->_data = collect(); $this->_data = collect();
$this->blocks = new Blocks;
return $this; return $this;
} }
@ -117,15 +111,8 @@ class Message implements \JsonSerializable
*/ */
public function jsonSerialize() public function jsonSerialize()
{ {
if ($this->blocks->count()) { if ($this->blocks->count())
if ($this->_data->has('text'))
throw new \Exception('Messages cannot have text and blocks!');
$this->_data->put('blocks',$this->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 // For interactive messages that generate a dialog, we need to return NULL
return $this->_data->count() ? $this->_data : NULL; return $this->_data->count() ? $this->_data : NULL;
@ -143,6 +130,9 @@ class Message implements \JsonSerializable
if ($this->_data->has('ephemeral')) if ($this->_data->has('ephemeral'))
abort('500','Cannot post ephemeral messages.'); abort('500','Cannot post ephemeral messages.');
if ($this->blocks->count() && $this->_data->get('attachments'))
throw new SlackException('Message cannot have blocks and attachments.');
$api = $this->o->team->slackAPI(); $api = $this->o->team->slackAPI();
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->postMessage($this); $response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->postMessage($this);
@ -156,18 +146,33 @@ class Message implements \JsonSerializable
return $response; return $response;
} }
public function replace(bool $replace=TRUE): self public function setReplace(bool $replace=TRUE): self
{ {
$this->_data['replace_original'] = $replace ? 'true' : 'false'; $this->_data->put('replace_original',$replace ? 'true' : 'false');
return $this;
}
/**
* To slack from rendering URLs in the message
*
* @param bool $unfurl
* @return $this
*/
public function setUnfurlLinks(bool $unfurl): self
{
$this->_data->put('unfurl_links',$unfurl ? 'true' : 'false');
return $this; return $this;
} }
/** /**
* Post a message to slack using the respond_url * Post a message to slack using the respond_url
* @note This URL can only be used 5 times in 30 minutes
* *
* @note This URL can only be used 5 times in 30 minutes
* @param string $url * @param string $url
* @return string
* @throws SlackException
*/ */
public function respond(string $url) public function respond(string $url)
{ {
@ -212,23 +217,53 @@ class Message implements \JsonSerializable
*/ */
public function selfdestruct(Carbon $time): Generic public function selfdestruct(Carbon $time): Generic
{ {
$this->addBlock( $this->blocks->addContextElements(
(new Block)->addContext(
collect() collect()
->push((new BlockKit)->text(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW])))))); ->push(Blocks\Text::item(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW])))));
return $this->post($time); return $this->post($time);
} }
/**
* Add an attachment to a message
*
* @param Attachment $attachments
* @return Message
*/
public function setAttachments(Attachments $attachments): self
{
$this->_data->put('attachments',[$attachments]);
return $this;
}
/**
* Add blocks to the message
*
* @param Blocks $blocks
* @return Message
* @throws \Exception
*/
public function setBlocks(Blocks $blocks): self
{
if ($this->blocks->count())
throw new \Exception('Blocks already defined');
$this->blocks = $blocks;
return $this;
}
/** /**
* Set our channel * Set our channel
* *
* @param Channel $o * @param Channel $o
* @return Message * @return Message
*/ */
public function setChannel(Channel $o) public function setChannel(Channel $o): self
{ {
$this->_data['channel'] = $o->channel_id; $this->_data->put('channel',$o->channel_id);
$this->o = $o;
return $this; return $this;
} }
@ -237,7 +272,7 @@ class Message implements \JsonSerializable
* Set the icon next to the message * Set the icon next to the message
* *
* @param string $icon * @param string $icon
* @return $this * @return Message
* @deprecated * @deprecated
*/ */
public function setIcon(string $icon): self public function setIcon(string $icon): self
@ -255,7 +290,7 @@ class Message implements \JsonSerializable
*/ */
public function setOptionGroup(array $array): void public function setOptionGroup(array $array): void
{ {
$this->_data = collect(); $this->_data = collect(); // @todo Why are clearing our data?
$this->_data->put('option_groups',$array); $this->_data->put('option_groups',$array);
} }
@ -263,7 +298,7 @@ class Message implements \JsonSerializable
* Message text * Message text
* *
* @param string $string * @param string $string
* @return $this * @return Message
*/ */
public function setText(string $string): self public function setText(string $string): self
{ {
@ -272,6 +307,12 @@ class Message implements \JsonSerializable
return $this; return $this;
} }
/**
* Set the timestamp, used when replacing messages
*
* @param string $string
* @return Message
*/
public function setTS(string $string): self public function setTS(string $string): self
{ {
$this->_data->put('ts',$string); $this->_data->put('ts',$string);
@ -279,6 +320,12 @@ class Message implements \JsonSerializable
return $this; return $this;
} }
/**
* Set the thread timestamp, used when adding a threaded response
*
* @param string $string
* @return Message
*/
public function setThreadTS(string $string): self public function setThreadTS(string $string): self
{ {
$this->_data->put('thread_ts',$string); $this->_data->put('thread_ts',$string);
@ -292,16 +339,17 @@ class Message implements \JsonSerializable
* @param User $o * @param User $o
* @return Message * @return Message
*/ */
public function setUser(User $o) public function setUser(User $o): self
{ {
$this->_data['channel'] = $o->user_id; $this->_data->put('channel',$o->user_id);
$this->o = $o;
return $this; return $this;
} }
public function setUserName(string $user) public function setUserName(string $user): self
{ {
$this->_data['username'] = $user; $this->_data->put('username',$user);
return $this; return $this;
} }

View File

@ -4,6 +4,7 @@ namespace Slack\Message;
use Slack\BlockKit; use Slack\BlockKit;
use Slack\Blockkit\BlockAction; use Slack\Blockkit\BlockAction;
use Slack\Blockkit\Blocks;
/** /**
* Class MessageAttachment - Slack Message Attachments * Class MessageAttachment - Slack Message Attachments
@ -11,23 +12,27 @@ use Slack\Blockkit\BlockAction;
* *
* @package Slack\Message * @package Slack\Message
*/ */
class Attachment implements \JsonSerializable class Attachments implements \JsonSerializable
{ {
private $_data; private $_data;
private $actions; private $actions;
private $blocks; //private $blocks;
private $blockactions; private $blockactions;
// @todo To rework
public function __construct() public function __construct()
{ {
$this->actions = collect(); $this->actions = collect();
$this->blocks = collect(); //$this->blocks = collect();
$this->blockactions = collect(); $this->blockactions = collect();
$this->_data = collect(); $this->_data = collect();
} }
// @todo To rework
public function jsonSerialize() public function jsonSerialize()
{ {
/*
if ($this->actions->count() AND ! $this->_data->has('callback_id')) if ($this->actions->count() AND ! $this->_data->has('callback_id'))
abort(500,'Actions without a callback ID'); abort(500,'Actions without a callback ID');
@ -47,15 +52,16 @@ class Attachment implements \JsonSerializable
if ($this->blocks->count()) if ($this->blocks->count())
$this->_data->put('blocks',$this->blocks); $this->_data->put('blocks',$this->blocks);
*/
return $this->_data; return $this->_data;
} }
/** /**
* Add an attachment to a message * Add an attachment to a message
* *
* @param AttachmentAction $action * @param AttachmentAction $action
* @return Attachment * @return Attachment
* @todo To rework
*/ */
public function addAction(AttachmentAction $action): self public function addAction(AttachmentAction $action): self
{ {
@ -69,6 +75,7 @@ class Attachment implements \JsonSerializable
* *
* @param BlockKit $block * @param BlockKit $block
* @return Attachment * @return Attachment
* @deprecated
*/ */
public function addBlock(BlockKit $block): self public function addBlock(BlockKit $block): self
{ {
@ -82,6 +89,7 @@ class Attachment implements \JsonSerializable
* *
* @param BlockAction $action * @param BlockAction $action
* @return $this * @return $this
* @todo To rework
*/ */
public function addBlockAction(BlockAction $action): self public function addBlockAction(BlockAction $action): self
{ {
@ -90,6 +98,8 @@ class Attachment implements \JsonSerializable
return $this; return $this;
} }
//* @todo To rework
public function addField(string $title,string $value,bool $short): self public function addField(string $title,string $value,bool $short): self
{ {
if (! $this->_data->has('fields')) if (! $this->_data->has('fields'))
@ -103,12 +113,12 @@ class Attachment implements \JsonSerializable
return $this; return $this;
} }
/** /**
* Set where markdown should be parsed by slack * Set where markdown should be parsed by slack
* *
* @param array $array * @param array $array
* @return $this * @return $this
* @todo To rework
*/ */
public function markdownIn(array $array): self public function markdownIn(array $array): self
{ {
@ -123,6 +133,7 @@ class Attachment implements \JsonSerializable
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setCallbackID(string $string): self public function setCallbackID(string $string): self
{ {
@ -131,11 +142,25 @@ class Attachment implements \JsonSerializable
return $this; return $this;
} }
/**
* Add a blocks to the message attachment
*
* @param Blocks $blocks
* @return self
*/
public function setBlocks(Blocks $blocks): self
{
$this->_data->put('blocks',$blocks);
return $this;
}
/** /**
* Configure the attachment color (on the left of the attachment) * Configure the attachment color (on the left of the attachment)
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setColor(string $string): self public function setColor(string $string): self
{ {
@ -149,6 +174,7 @@ class Attachment implements \JsonSerializable
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setFooter(string $string): self public function setFooter(string $string): self
{ {
@ -162,6 +188,7 @@ class Attachment implements \JsonSerializable
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setPretext(string $string): self public function setPretext(string $string): self
{ {
@ -175,6 +202,7 @@ class Attachment implements \JsonSerializable
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setText(string $string): self public function setText(string $string): self
{ {
@ -188,6 +216,7 @@ class Attachment implements \JsonSerializable
* *
* @param string $string * @param string $string
* @return $this * @return $this
* @todo To rework
*/ */
public function setTitle(string $string): self public function setTitle(string $string): self
{ {

View File

@ -53,4 +53,18 @@ class Channel extends Model
{ {
return preg_match('/^D/',$this->channel_id) OR $this->name == 'directmessage'; return preg_match('/^D/',$this->channel_id) OR $this->name == 'directmessage';
} }
/**
* Return a slack URL to the timestamp
*
* @param string $ts
* @return string
*/
public function url(string $ts): string
{
if (! $this->team->url)
return '';
return sprintf('https://%s/archives/%s/p%s',$this->team->url,$this->channel_id,str_replace('.','',$ts));
}
} }

View File

@ -64,6 +64,15 @@ class Team extends Model
return implode('-',$attrs); return implode('-',$attrs);
} }
/**
* Return the team's slack URL
* @return string
*/
public function getUrlAttribute(): string
{
return $this->team_name ? sprintf('%s.slack.com',$this->team_name) : '';
}
/* METHODS */ /* METHODS */
/** /**

View File

@ -2,7 +2,11 @@
namespace Slack\Providers; namespace Slack\Providers;
use Illuminate\Notifications\ChannelManager;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Slack\API;
use Slack\Channels\SlackBotChannel;
use Slack\Console\Commands\SlackSocketClient; use Slack\Console\Commands\SlackSocketClient;
class SlackServiceProvider extends ServiceProvider class SlackServiceProvider extends ServiceProvider
@ -35,5 +39,11 @@ class SlackServiceProvider extends ServiceProvider
$this->mergeConfigFrom(__DIR__.'/../config/slack.php','slack'); $this->mergeConfigFrom(__DIR__.'/../config/slack.php','slack');
$this->loadRoutesFrom(realpath(__DIR__ .'/../routes.php')); $this->loadRoutesFrom(realpath(__DIR__ .'/../routes.php'));
Notification::resolved(function (ChannelManager $service) {
$service->extend('slackapp', function ($app) {
return new SlackBotChannel($app->make(API::class));
});
});
} }
} }

View File

@ -40,7 +40,7 @@ class SlackIntegration extends Migration
$table->timestamps(); $table->timestamps();
$table->string('team_id', 45)->unique(); $table->string('team_id', 45)->unique();
$table->string('name')->nullable(); $table->string('team_name')->nullable();
$table->string('description')->nullable(); $table->string('description')->nullable();
$table->boolean('active'); $table->boolean('active');
@ -62,8 +62,8 @@ class SlackIntegration extends Migration
$table->string('name')->nullable(); $table->string('name')->nullable();
$table->boolean('active'); $table->boolean('active');
$table->integer('enterprise_id')->nullable()->unsigned(); $table->integer('team_id')->nullable()->unsigned();
$table->foreign('enterprise_id')->references('id')->on('slack_enterprises'); $table->foreign('team_id')->references('id')->on('slack_teams');
}); });
Schema::table('slack_users', function (Blueprint $table) { Schema::table('slack_users', function (Blueprint $table) {