diff --git a/src/API.php b/src/API.php
index 9f20357..30cc00a 100644
--- a/src/API.php
+++ b/src/API.php
@@ -55,7 +55,7 @@ class API
{
$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
diff --git a/src/Base.php b/src/Base.php
index 33a538c..16e6347 100644
--- a/src/Base.php
+++ b/src/Base.php
@@ -19,9 +19,9 @@ abstract class Base
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)
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) {
$o->team_id = $this->team()->id;
+ $o->active = FALSE;
$o->save();
}
diff --git a/src/BlockKit.php b/src/BlockKit.php
index c26d4e9..6344e00 100644
--- a/src/BlockKit.php
+++ b/src/BlockKit.php
@@ -18,87 +18,13 @@ class BlockKit implements \JsonSerializable
$this->_data = collect();
}
+ public function count()
+ {
+ return $this->_data->count();
+ }
+
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,
- ];
- }
}
diff --git a/src/Blockkit/Block.php b/src/Blockkit/Block.php
deleted file mode 100644
index 8035938..0000000
--- a/src/Blockkit/Block.php
+++ /dev/null
@@ -1,256 +0,0 @@
-_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);
- }
-
-}
diff --git a/src/Blockkit/BlockAction.php b/src/Blockkit/BlockAction.php
index c6995a7..1c8dbcf 100644
--- a/src/Blockkit/BlockAction.php
+++ b/src/Blockkit/BlockAction.php
@@ -3,9 +3,11 @@
namespace Slack\Blockkit;
use Slack\BlockKit;
+use Slack\Blockkit\Blocks\TextEmoji;
/**
* This class creates a slack actions used in BlockKit Actions
+ * @todo Still needed?
*/
class BlockAction extends BlockKit
{
@@ -18,15 +20,16 @@ class BlockAction extends BlockKit
* @param string $style
* @return BlockAction
* @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']))
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('text',$text);
$this->_data->put('value',$value);
if ($style)
diff --git a/src/Blockkit/Blocks.php b/src/Blockkit/Blocks.php
new file mode 100644
index 0000000..41fc630
--- /dev/null
+++ b/src/Blockkit/Blocks.php
@@ -0,0 +1,341 @@
+_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);
+ }
+}
diff --git a/src/Blockkit/Blocks/Button.php b/src/Blockkit/Blocks/Button.php
new file mode 100644
index 0000000..35756cb
--- /dev/null
+++ b/src/Blockkit/Blocks/Button.php
@@ -0,0 +1,30 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/src/Blockkit/Blocks/Input.php b/src/Blockkit/Blocks/Input.php
new file mode 100644
index 0000000..eb04389
--- /dev/null
+++ b/src/Blockkit/Blocks/Input.php
@@ -0,0 +1,24 @@
+type = 'input';
+ $this->element = $element;
+ $this->label = $label;
+ }
+
+ public static function item(Element $element,TextEmoji $label): self
+ {
+ return new self($element,$label);
+ }
+}
\ No newline at end of file
diff --git a/src/Blockkit/Blocks/Text.php b/src/Blockkit/Blocks/Text.php
new file mode 100644
index 0000000..7200e10
--- /dev/null
+++ b/src/Blockkit/Blocks/Text.php
@@ -0,0 +1,20 @@
+type = $type;
+ $this->text = $text;
+ }
+
+ public static function item(string $text,string $type='mrkdwn'): self
+ {
+ return new self($text,$type);
+ }
+}
\ No newline at end of file
diff --git a/src/Blockkit/Blocks/TextEmoji.php b/src/Blockkit/Blocks/TextEmoji.php
new file mode 100644
index 0000000..830ab6f
--- /dev/null
+++ b/src/Blockkit/Blocks/TextEmoji.php
@@ -0,0 +1,22 @@
+emoji = $emoji;
+ $this->text = $text;
+ $this->type = 'plain_text';
+ }
+
+ public static function item(string $text,bool $emoji=TRUE): self
+ {
+ return new self($text,$emoji);
+ }
+}
\ No newline at end of file
diff --git a/src/Blockkit/Input/Element.php b/src/Blockkit/Input/Element.php
new file mode 100644
index 0000000..f02c7a6
--- /dev/null
+++ b/src/Blockkit/Input/Element.php
@@ -0,0 +1,22 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/src/Blockkit/Modal.php b/src/Blockkit/Modal.php
index 7db40a2..48c5893 100644
--- a/src/Blockkit/Modal.php
+++ b/src/Blockkit/Modal.php
@@ -2,40 +2,36 @@
namespace Slack\Blockkit;
-use Illuminate\Support\Str;
use Slack\BlockKit;
+use Slack\Blockkit\Blocks\TextEmoji;
/**
* This class creates a slack Modal Response
*/
class Modal extends BlockKit
{
- protected $blocks;
private $action = NULL;
- public function __construct(string $title)
+ public function __construct(TextEmoji $title)
{
parent::__construct();
- $this->blocks = collect();
-
$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)
{
$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'];
@@ -51,23 +47,24 @@ class Modal extends BlockKit
/**
* Add a block to the modal
*
- * @param Block $block
+ * @param Blocks $blocks
* @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;
}
- public function callback(string $id): self
+ public function setCallback(string $id): self
{
$this->_data->put('callback_id',$id);
return $this;
}
+ /*
public function close(string $text='Cancel'): self
{
$this->_data->put('close',
@@ -112,4 +109,5 @@ class Modal extends BlockKit
return $this;
}
+ */
}
diff --git a/src/Channels/SlackBotChannel.php b/src/Channels/SlackBotChannel.php
new file mode 100644
index 0000000..0851398
--- /dev/null
+++ b/src/Channels/SlackBotChannel.php
@@ -0,0 +1,20 @@
+routeNotificationFor('slackapp',$notification)) {
+ return;
+ }
+
+ $o = $notification->toSlack($notifiable);
+ $o->setChannel($co);
+
+ return $o->post();
+ }
+}
\ No newline at end of file
diff --git a/src/Client/API.php b/src/Client/API.php
index 84875d0..64479d4 100644
--- a/src/Client/API.php
+++ b/src/Client/API.php
@@ -60,9 +60,9 @@ abstract class API
* @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.
+ * @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
{
@@ -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.
if ($callDeferred) {
$this->loop->futureTick(function () use ($promise) {
diff --git a/src/Client/SocketMode.php b/src/Client/SocketMode.php
index e398967..5282b0b 100644
--- a/src/Client/SocketMode.php
+++ b/src/Client/SocketMode.php
@@ -11,11 +11,15 @@ use Illuminate\Support\Facades\Log;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise;
+use Slack\Command\Factory;
class SocketMode extends API
{
use EventEmitterTrait;
+ private const LOGKEY = 'ASM';
+
+ private bool $debug_reconnect = FALSE;
private bool $connected;
private WebSocket $websocket;
@@ -37,7 +41,7 @@ class SocketMode extends API
*
* @return \React\Promise\PromiseInterface
*/
- public function connect()
+ public function connect(): Promise\PromiseInterface
{
$deferred = new Deferred;
@@ -81,9 +85,9 @@ class SocketMode extends API
// 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 = new WebSocket($response['url'].($this->debug_reconnect ? '&debug_reconnects=true' : ''),$this->loop,$this->logger);
$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);
});
@@ -124,6 +128,8 @@ class SocketMode extends API
$this->websocket->close();
$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.
*
- * @param WebSocketMessageInterface $messageRaw A websocket message.
+ * @param 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
$payload = Payload::fromJson($message->getData());
+ $emitted = FALSE;
if (isset($payload['type'])) {
$this->emit('_internal_message', [$payload['type'], $payload]);
+
switch ($payload['type']) {
case 'hello':
$this->connected = TRUE;
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':
$this->team->data['name'] = $payload['name'];
break;
@@ -234,19 +254,47 @@ class SocketMode extends API
$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]);
+ default:
+ Log::debug(sprintf('%s:- Unhandled type [%s]',self::LOGKEY,$payload['type']),['m'=>__METHOD__,'p'=>$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['accepts_response_payload']) && $payload['accepts_response_payload']) {
+ switch ($payload['type']) {
+ 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') {
@@ -269,6 +317,6 @@ class SocketMode extends API
}
}
- Log::debug('= End',['m'=>__METHOD__]);
+ Log::debug(sprintf('%s:= End',self::LOGKEY),['m'=>__METHOD__]);
}
}
diff --git a/src/Command/Base.php b/src/Command/Base.php
index d520a83..d73b23c 100644
--- a/src/Command/Base.php
+++ b/src/Command/Base.php
@@ -2,16 +2,17 @@
namespace Slack\Command;
-use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Slack\Base as SlackBase;
+use Slack\Blockkit\Blocks;
+use Slack\Message;
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__]);
- parent::_construct($request);
+ parent::__construct($request);
}
/**
@@ -51,4 +52,20 @@ abstract class Base extends SlackBase
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;
+ }
}
diff --git a/src/Command/Factory.php b/src/Command/Factory.php
index 4a13d16..f5512ee 100644
--- a/src/Command/Factory.php
+++ b/src/Command/Factory.php
@@ -2,10 +2,10 @@
namespace Slack\Command;
-use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
+use Slack\Client\Payload;
class Factory {
private const LOGKEY = 'SCf';
@@ -14,39 +14,32 @@ class Factory {
* @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
+ * @param string $type
+ * @param array $request
* @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__]);
- if (App::environment() == 'dev')
- file_put_contents('/tmp/command.'.$type,print_r(json_decode(json_encode($request->all())),TRUE));
+ if (App::environment() == 'local')
+ file_put_contents('/tmp/command.'.$type,print_r(json_decode(json_encode($request)),TRUE));
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()));
- $command = preg_replace('/^([a-z]+)(\s?.*)/','$1',$data->text);
+ $data = json_decode(json_encode($request->getData()));
+ $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'));
}
}
diff --git a/src/Command/Help.php b/src/Command/Help.php
index b4c2a47..1868fcb 100644
--- a/src/Command/Help.php
+++ b/src/Command/Help.php
@@ -3,7 +3,7 @@
namespace Slack\Command;
use Slack\Message;
-use Slack\Message\Attachment;
+use Slack\Message\Attachments;
class Help extends Base
{
@@ -13,12 +13,12 @@ class Help extends Base
{
$o = new Message;
- $o->setText('Hi, I am the a *NEW* Bot');
+ $o->setText('Hi, I am a *NEW* Bot');
// Version
- $a = new Attachment;
- $a->addField('Version',config('app.version'),TRUE);
- $o->addAttachment($a);
+ $a = new Attachments;
+ $a->addField('Version',config('app.version','unknown'),TRUE);
+ $o->setAttachments($a);
return $o;
}
diff --git a/src/Command/Unknown.php b/src/Command/Unknown.php
index 1b69a6e..cf03919 100644
--- a/src/Command/Unknown.php
+++ b/src/Command/Unknown.php
@@ -13,7 +13,7 @@ use Slack\Message;
*/
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__]);
diff --git a/src/Console/Commands/SlackSocketClient.php b/src/Console/Commands/SlackSocketClient.php
index 0babf79..f111f53 100644
--- a/src/Console/Commands/SlackSocketClient.php
+++ b/src/Console/Commands/SlackSocketClient.php
@@ -6,6 +6,9 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use React\EventLoop\Loop;
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
{
@@ -47,12 +50,20 @@ class SlackSocketClient extends Command
$client = new SocketMode($loop);
$client->setToken(config('slack.socket_token'));
- $client->on('events_api', function ($data) use ($client) {
- dump(['data'=>$data]);
+ $client->on('events_api',function ($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 () {
- Log::debug(sprintf('%s: Connected to slack.',self::LOGKEY));
+ Log::debug(sprintf('%s:- Connected to slack.',self::LOGKEY));
});
$loop->run();
diff --git a/src/Event/Base.php b/src/Event/Base.php
index a8c4aad..3c7bd6a 100644
--- a/src/Event/Base.php
+++ b/src/Event/Base.php
@@ -8,7 +8,7 @@ use Slack\Base as 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__]);
diff --git a/src/Event/Factory.php b/src/Event/Factory.php
index 039c1aa..a2817c0 100644
--- a/src/Event/Factory.php
+++ b/src/Event/Factory.php
@@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
+use Slack\Client\Payload;
class Factory {
private const LOGKEY = 'SEf';
@@ -27,22 +28,22 @@ class Factory {
/**
* Returns new event instance
*
- * @param string $type
- * @param Request $request
+ * @param string $type
+ * @param array $request
* @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);
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));
+ if (App::environment() == 'local')
+ file_put_contents('/tmp/event.'.$type,print_r($request,TRUE));
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.
static $o = NULL;
@@ -50,7 +51,7 @@ class Factory {
if (! $o OR ($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;
diff --git a/src/Event/Unknown.php b/src/Event/Unknown.php
index c0db70a..86a8486 100644
--- a/src/Event/Unknown.php
+++ b/src/Event/Unknown.php
@@ -12,10 +12,10 @@ use Illuminate\Support\Facades\Log;
*/
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__]);
- parent::__contruct($request);
+ parent::__construct($request);
}
}
diff --git a/src/Http/Controllers/SlackAppController.php b/src/Http/Controllers/SlackAppController.php
index aa08d69..35c24c6 100644
--- a/src/Http/Controllers/SlackAppController.php
+++ b/src/Http/Controllers/SlackAppController.php
@@ -16,25 +16,6 @@ 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';
@@ -173,9 +154,15 @@ class SlackAppController extends Controller
$so->admin_id = $uo->id;
$so->save();
- return sprintf('All set up! Head back to your slack instance %s."',$so->description);
+ return sprintf('All set up! Head back to your slack instance %s.',$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
{
return [
diff --git a/src/Interactive/Base.php b/src/Interactive/Base.php
index 4f9ceaf..e5a31bd 100644
--- a/src/Interactive/Base.php
+++ b/src/Interactive/Base.php
@@ -2,7 +2,6 @@
namespace Slack\Interactive;
-use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
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.
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__]);
// Our data is in a payload value
- $this->_data = json_decode($request->input('payload'));
+ parent::__construct($request);
}
/**
diff --git a/src/Interactive/BlockActions.php b/src/Interactive/BlockActions.php
index 13c624b..1d513ef 100644
--- a/src/Interactive/BlockActions.php
+++ b/src/Interactive/BlockActions.php
@@ -60,25 +60,24 @@ class BlockActions extends Base
public function __get($key)
{
switch ($key) {
- case 'callback_id':
- return object_get($this->_data,'view.callback_id');
+ case 'actions':
+ 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.
- case 'action_id':
+ case 'action_key':
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');
- }
+ case 'callback_id':
+ return object_get($this->_data,'view.callback_id');
+
+ case 'keys':
+ return collect(object_get($this->_data,'view.blocks'))->pluck('accessory.action_id');
// For Block Actions that are messages
case 'message_ts':
@@ -87,16 +86,41 @@ class BlockActions extends Base
case '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':
return object_get($this->_data,'view.id');
- case 'actions':
- return object_get($this->_data,$key);
+ 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');
+ 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
- // @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');
+ case 'values':
+ switch (Arr::get(object_get($this->_data,'actions'),$this->index)->type) {
+ // @todo To Check
+ 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:
return parent::__get($key);
@@ -116,16 +140,16 @@ class BlockActions extends Base
$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');
+ $action_key = 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);
+ if (preg_match($regex,$action_key)) {
+ $action = preg_replace($regex,'$1',$action_key);
+ $value = preg_replace($regex,'$2',$action_key);
}
switch ($key) {
case 'action':
- return $action ?: $action_id;
+ return $action ?: $action_key;
case 'value':
return $value;
}
@@ -142,20 +166,4 @@ class BlockActions extends Base
{
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;
- }
}
diff --git a/src/Interactive/Factory.php b/src/Interactive/Factory.php
index 6c0b99b..ad7afdd 100644
--- a/src/Interactive/Factory.php
+++ b/src/Interactive/Factory.php
@@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
+use Slack\Client\Payload;
class Factory {
private const LOGKEY = 'SIF';
@@ -24,31 +25,30 @@ class Factory {
/**
* Returns new event instance
*
- * @param string $type
- * @param Request $request
+ * @param string $type
+ * @param array $request
* @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);
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));
+ if (App::environment() == 'local')
+ file_put_contents('/tmp/interactive.'.$type,print_r($request,TRUE));
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.
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);
+ $o = self::create(Arr::get($request->getData(),'payload.type'),Arr::get($request->getData(),'payload'));
}
return $o;
diff --git a/src/Interactive/Unknown.php b/src/Interactive/Unknown.php
index cc043c6..6a4bf89 100644
--- a/src/Interactive/Unknown.php
+++ b/src/Interactive/Unknown.php
@@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Log;
*/
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__]);
diff --git a/src/Listeners/AppHomeOpenedListener.php b/src/Listeners/AppHomeOpenedListener.php
new file mode 100644
index 0000000..aca56b7
--- /dev/null
+++ b/src/Listeners/AppHomeOpenedListener.php
@@ -0,0 +1,30 @@
+user_id,$event->team_id),['m'=>__METHOD__]);
+
+ dispatch((new SlackHomeTabUpdate($event))->onQueue('high'));
+ }
+}
diff --git a/src/Listeners/BlockActionListener.php b/src/Listeners/BlockActionListener.php
new file mode 100644
index 0000000..1c7f9b0
--- /dev/null
+++ b/src/Listeners/BlockActionListener.php
@@ -0,0 +1,85 @@
+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__]);
+ }
+ }
+ }
+}
diff --git a/src/Listeners/ChannelJoinListener.php b/src/Listeners/ChannelJoinListener.php
new file mode 100644
index 0000000..b35c88d
--- /dev/null
+++ b/src/Listeners/ChannelJoinListener.php
@@ -0,0 +1,38 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Listeners/ChannelLeftListener.php b/src/Listeners/ChannelLeftListener.php
new file mode 100644
index 0000000..fc1a086
--- /dev/null
+++ b/src/Listeners/ChannelLeftListener.php
@@ -0,0 +1,34 @@
+channel_id),['m'=>__METHOD__,'c'=>$event->channel_id]);
+
+ $o = $event->channel(TRUE);
+ $o->active = FALSE;
+ $o->save();
+ }
+}
diff --git a/src/Listeners/InteractiveMessageListener.php b/src/Listeners/InteractiveMessageListener.php
new file mode 100644
index 0000000..24e23b5
--- /dev/null
+++ b/src/Listeners/InteractiveMessageListener.php
@@ -0,0 +1,32 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Listeners/MessageListener.php b/src/Listeners/MessageListener.php
new file mode 100644
index 0000000..d9afced
--- /dev/null
+++ b/src/Listeners/MessageListener.php
@@ -0,0 +1,38 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Listeners/PinAddedListener.php b/src/Listeners/PinAddedListener.php
new file mode 100644
index 0000000..1926b8a
--- /dev/null
+++ b/src/Listeners/PinAddedListener.php
@@ -0,0 +1,29 @@
+ts,$event->channel_id),['m'=>__METHOD__]);
+
+ Log::notice(sprintf('%s:Ignoring Pin Add on [%s]',static::LOGKEY,$event->ts),['m'=>__METHOD__]);
+ }
+}
diff --git a/src/Listeners/PinRemovedListener.php b/src/Listeners/PinRemovedListener.php
new file mode 100644
index 0000000..c6c27f8
--- /dev/null
+++ b/src/Listeners/PinRemovedListener.php
@@ -0,0 +1,29 @@
+ts,$event->channel_id),['m'=>__METHOD__]);
+
+ Log::debug(sprintf('%s:Ignoring Pin Remove on [%s]',static::LOGKEY,$event->ts),['m'=>__METHOD__]);
+ }
+}
diff --git a/src/Listeners/ReactionAddedListener.php b/src/Listeners/ReactionAddedListener.php
new file mode 100644
index 0000000..48e1770
--- /dev/null
+++ b/src/Listeners/ReactionAddedListener.php
@@ -0,0 +1,29 @@
+reaction,$event->team_id),['m'=>__METHOD__]);
+
+ Log::debug(sprintf('%s:Ignoring Reaction Add [%s] on [%s]',static::LOGKEY,$event->reaction,$event->ts),['m'=>__METHOD__]);
+ }
+}
diff --git a/src/Listeners/ShortcutListener.php b/src/Listeners/ShortcutListener.php
new file mode 100644
index 0000000..7d4d6c8
--- /dev/null
+++ b/src/Listeners/ShortcutListener.php
@@ -0,0 +1,54 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Listeners/ViewClosedListener.php b/src/Listeners/ViewClosedListener.php
new file mode 100644
index 0000000..b708f5f
--- /dev/null
+++ b/src/Listeners/ViewClosedListener.php
@@ -0,0 +1,32 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Listeners/ViewSubmissionListener.php b/src/Listeners/ViewSubmissionListener.php
new file mode 100644
index 0000000..fdb8ec0
--- /dev/null
+++ b/src/Listeners/ViewSubmissionListener.php
@@ -0,0 +1,32 @@
+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__]);
+ }
+ }
+}
diff --git a/src/Message.php b/src/Message.php
index 15a472c..f11f0db 100644
--- a/src/Message.php
+++ b/src/Message.php
@@ -6,11 +6,11 @@ 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\Blockkit\Blocks;
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;
/**
@@ -20,55 +20,48 @@ class Message implements \JsonSerializable
{
protected const LOGKEY = 'SM-';
- private $o;
- private $attachments;
- private $blocks;
+ private Model $o;
+ private Blocks $blocks;
/**
* Message constructor.
*
* @param Model|null $o Who the message will be to - Channel or User
+ * @throws SlackException
*/
public function __construct(Model $o=NULL)
{
$this->_data = collect();
- // Message is to a channel
- if ($o instanceof Channel) {
- $this->setChannel($o);
+ if ($o) {
+ // Message is to a channel
+ if ($o instanceof Channel) {
+ $this->setChannel($o);
- // Message is to a user
- } elseif ($o instanceof User) {
- $this->setUser($o);
+ // Message is to a user
+ } elseif ($o instanceof User) {
+ $this->setUser($o);
+
+ } else {
+ throw new SlackException('Model not handled: '.get_class($o));
+ }
+
+ $this->o = $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;
+ $this->blocks = new Blocks;
}
/**
* Add a block to the message
*
- * @param BlockKit $block
- * @return $this
+ * @param Blocks $blocks
+ * @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;
}
@@ -76,11 +69,12 @@ class Message implements \JsonSerializable
/**
* Empty the message
*
- * @return $this
+ * @return Message
*/
public function blank(): self
{
$this->_data = collect();
+ $this->blocks = new Blocks;
return $this;
}
@@ -117,15 +111,8 @@ class Message implements \JsonSerializable
*/
public function jsonSerialize()
{
- if ($this->blocks->count()) {
- if ($this->_data->has('text'))
- throw new \Exception('Messages cannot have text and blocks!');
-
+ if ($this->blocks->count())
$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;
@@ -143,6 +130,9 @@ class Message implements \JsonSerializable
if ($this->_data->has('ephemeral'))
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();
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->postMessage($this);
@@ -156,18 +146,33 @@ class Message implements \JsonSerializable
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;
}
/**
* 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
+ * @return string
+ * @throws SlackException
*/
public function respond(string $url)
{
@@ -212,23 +217,53 @@ class Message implements \JsonSerializable
*/
public function selfdestruct(Carbon $time): Generic
{
- $this->addBlock(
- (new Block)->addContext(
+ $this->blocks->addContextElements(
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);
}
+ /**
+ * 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
*
* @param Channel $o
* @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;
}
@@ -237,7 +272,7 @@ class Message implements \JsonSerializable
* Set the icon next to the message
*
* @param string $icon
- * @return $this
+ * @return Message
* @deprecated
*/
public function setIcon(string $icon): self
@@ -255,7 +290,7 @@ class Message implements \JsonSerializable
*/
public function setOptionGroup(array $array): void
{
- $this->_data = collect();
+ $this->_data = collect(); // @todo Why are clearing our data?
$this->_data->put('option_groups',$array);
}
@@ -263,7 +298,7 @@ class Message implements \JsonSerializable
* Message text
*
* @param string $string
- * @return $this
+ * @return Message
*/
public function setText(string $string): self
{
@@ -272,6 +307,12 @@ class Message implements \JsonSerializable
return $this;
}
+ /**
+ * Set the timestamp, used when replacing messages
+ *
+ * @param string $string
+ * @return Message
+ */
public function setTS(string $string): self
{
$this->_data->put('ts',$string);
@@ -279,6 +320,12 @@ class Message implements \JsonSerializable
return $this;
}
+ /**
+ * Set the thread timestamp, used when adding a threaded response
+ *
+ * @param string $string
+ * @return Message
+ */
public function setThreadTS(string $string): self
{
$this->_data->put('thread_ts',$string);
@@ -292,16 +339,17 @@ class Message implements \JsonSerializable
* @param User $o
* @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;
}
- public function setUserName(string $user)
+ public function setUserName(string $user): self
{
- $this->_data['username'] = $user;
+ $this->_data->put('username',$user);
return $this;
}
diff --git a/src/Message/Attachment.php b/src/Message/Attachments.php
similarity index 85%
rename from src/Message/Attachment.php
rename to src/Message/Attachments.php
index bc6015a..5d94df3 100644
--- a/src/Message/Attachment.php
+++ b/src/Message/Attachments.php
@@ -4,6 +4,7 @@ namespace Slack\Message;
use Slack\BlockKit;
use Slack\Blockkit\BlockAction;
+use Slack\Blockkit\Blocks;
/**
* Class MessageAttachment - Slack Message Attachments
@@ -11,23 +12,27 @@ use Slack\Blockkit\BlockAction;
*
* @package Slack\Message
*/
-class Attachment implements \JsonSerializable
+class Attachments implements \JsonSerializable
{
private $_data;
private $actions;
- private $blocks;
+ //private $blocks;
private $blockactions;
+ // @todo To rework
public function __construct()
{
$this->actions = collect();
- $this->blocks = collect();
+ //$this->blocks = collect();
$this->blockactions = collect();
$this->_data = collect();
}
+ // @todo To rework
+
public function jsonSerialize()
{
+ /*
if ($this->actions->count() AND ! $this->_data->has('callback_id'))
abort(500,'Actions without a callback ID');
@@ -47,15 +52,16 @@ class Attachment implements \JsonSerializable
if ($this->blocks->count())
$this->_data->put('blocks',$this->blocks);
+ */
return $this->_data;
}
-
/**
* Add an attachment to a message
*
* @param AttachmentAction $action
* @return Attachment
+ * @todo To rework
*/
public function addAction(AttachmentAction $action): self
{
@@ -69,6 +75,7 @@ class Attachment implements \JsonSerializable
*
* @param BlockKit $block
* @return Attachment
+ * @deprecated
*/
public function addBlock(BlockKit $block): self
{
@@ -82,6 +89,7 @@ class Attachment implements \JsonSerializable
*
* @param BlockAction $action
* @return $this
+ * @todo To rework
*/
public function addBlockAction(BlockAction $action): self
{
@@ -90,6 +98,8 @@ class Attachment implements \JsonSerializable
return $this;
}
+ //* @todo To rework
+
public function addField(string $title,string $value,bool $short): self
{
if (! $this->_data->has('fields'))
@@ -103,12 +113,12 @@ class Attachment implements \JsonSerializable
return $this;
}
-
/**
* Set where markdown should be parsed by slack
*
* @param array $array
* @return $this
+ * @todo To rework
*/
public function markdownIn(array $array): self
{
@@ -123,6 +133,7 @@ class Attachment implements \JsonSerializable
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setCallbackID(string $string): self
{
@@ -131,11 +142,25 @@ class Attachment implements \JsonSerializable
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)
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setColor(string $string): self
{
@@ -149,6 +174,7 @@ class Attachment implements \JsonSerializable
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setFooter(string $string): self
{
@@ -162,6 +188,7 @@ class Attachment implements \JsonSerializable
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setPretext(string $string): self
{
@@ -175,6 +202,7 @@ class Attachment implements \JsonSerializable
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setText(string $string): self
{
@@ -188,6 +216,7 @@ class Attachment implements \JsonSerializable
*
* @param string $string
* @return $this
+ * @todo To rework
*/
public function setTitle(string $string): self
{
diff --git a/src/Models/Channel.php b/src/Models/Channel.php
index 2710776..55db525 100644
--- a/src/Models/Channel.php
+++ b/src/Models/Channel.php
@@ -53,4 +53,18 @@ class Channel extends Model
{
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));
+ }
}
diff --git a/src/Models/Team.php b/src/Models/Team.php
index c8e1ea2..4eb5a7f 100644
--- a/src/Models/Team.php
+++ b/src/Models/Team.php
@@ -64,6 +64,15 @@ class Team extends Model
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 */
/**
diff --git a/src/Providers/SlackServiceProvider.php b/src/Providers/SlackServiceProvider.php
index 6e3f519..6b23745 100644
--- a/src/Providers/SlackServiceProvider.php
+++ b/src/Providers/SlackServiceProvider.php
@@ -2,7 +2,11 @@
namespace Slack\Providers;
+use Illuminate\Notifications\ChannelManager;
+use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ServiceProvider;
+use Slack\API;
+use Slack\Channels\SlackBotChannel;
use Slack\Console\Commands\SlackSocketClient;
class SlackServiceProvider extends ServiceProvider
@@ -35,5 +39,11 @@ class SlackServiceProvider extends ServiceProvider
$this->mergeConfigFrom(__DIR__.'/../config/slack.php','slack');
$this->loadRoutesFrom(realpath(__DIR__ .'/../routes.php'));
+
+ Notification::resolved(function (ChannelManager $service) {
+ $service->extend('slackapp', function ($app) {
+ return new SlackBotChannel($app->make(API::class));
+ });
+ });
}
}
\ No newline at end of file
diff --git a/src/database/migrations/2021_08_06_002815_slack_integration.php b/src/database/migrations/2021_08_06_002815_slack_integration.php
index 3b23431..ffa6110 100644
--- a/src/database/migrations/2021_08_06_002815_slack_integration.php
+++ b/src/database/migrations/2021_08_06_002815_slack_integration.php
@@ -40,7 +40,7 @@ class SlackIntegration extends Migration
$table->timestamps();
$table->string('team_id', 45)->unique();
- $table->string('name')->nullable();
+ $table->string('team_name')->nullable();
$table->string('description')->nullable();
$table->boolean('active');
@@ -62,8 +62,8 @@ class SlackIntegration extends Migration
$table->string('name')->nullable();
$table->boolean('active');
- $table->integer('enterprise_id')->nullable()->unsigned();
- $table->foreign('enterprise_id')->references('id')->on('slack_enterprises');
+ $table->integer('team_id')->nullable()->unsigned();
+ $table->foreign('team_id')->references('id')->on('slack_teams');
});
Schema::table('slack_users', function (Blueprint $table) {