405 lines
8.3 KiB
PHP
405 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace Slack;
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
use Slack\Blockkit\Blocks;
|
|
use Slack\Blockkit\Blocks\Context;
|
|
use Slack\Blockkit\Blocks\Elements\Text;
|
|
use Slack\Exceptions\{SlackException,SlackSyntaxException};
|
|
use Slack\Jobs\{DeleteChat,DeleteResponse};
|
|
use Slack\Message\Attachment;
|
|
use Slack\Models\{Channel,User};
|
|
use Slack\Response\Generic;
|
|
|
|
/**
|
|
* This class is used when composing a message to send to Slack.
|
|
*/
|
|
final class Message extends BlockKit
|
|
{
|
|
private const LOGKEY = 'SM-';
|
|
|
|
private const MAX_ATTACHMENTS = 20;
|
|
private const MAX_BLOCKS = 50;
|
|
|
|
private Model $o;
|
|
private ?Carbon $selfdestruct = NULL;
|
|
|
|
/**
|
|
* Message constructor.
|
|
*
|
|
* @param Model|null $o Who the message will be to - Channel or User
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct(Model $o=NULL)
|
|
{
|
|
parent::__construct();
|
|
|
|
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);
|
|
|
|
} else {
|
|
throw new \Exception('Model not handled: '.get_class($o));
|
|
}
|
|
|
|
$this->o = $o;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty the message
|
|
*
|
|
* @return Message
|
|
*/
|
|
public static function blank(): self
|
|
{
|
|
return new self;
|
|
}
|
|
|
|
/* HELPER METHODS */
|
|
|
|
/**
|
|
* Add a block to the message
|
|
*
|
|
* @param Attachment $attachment
|
|
* @return Message
|
|
* @throws SlackSyntaxException
|
|
*/
|
|
public function addAttachment(Attachment $attachment): self
|
|
{
|
|
if (! Arr::get($this->_data,'attachments'))
|
|
$this->attachments = collect();
|
|
|
|
$this->attachments->push($attachment);
|
|
|
|
if (count($this->attachments) > self::MAX_ATTACHMENTS)
|
|
throw new SlackSyntaxException(sprintf('Messages should not have more than %d attachments',self::MAX_ATTACHMENTS));
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addBlock(Blocks $block): self
|
|
{
|
|
if (! Arr::get($this->_data,'blocks'))
|
|
$this->blocks = collect();
|
|
|
|
$this->blocks->push($block);
|
|
|
|
if (count($this->blocks) > self::MAX_BLOCKS)
|
|
throw new SlackSyntaxException(sprintf('Messages should not have more than %d blocks',self::MAX_BLOCKS));
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function clearAttachments(): self
|
|
{
|
|
$this->attachments = '';
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* For messages that we interact with via a response_url
|
|
*
|
|
* @param bool $bool
|
|
* @return Message
|
|
*/
|
|
public function delete_original(bool $bool=TRUE): self
|
|
{
|
|
$this->delete_original = $bool;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function forgetTS(): self
|
|
{
|
|
$this->_data->forget('ts');
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return if this is an empty message
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isEmpty(): bool
|
|
{
|
|
return ! $this->jsonSerialize();
|
|
}
|
|
|
|
/**
|
|
* When we json_encode this object, this is the data that will be returned
|
|
*/
|
|
public function jsonSerialize()
|
|
{
|
|
// For interactive messages that generate a dialog, we need to return NULL
|
|
return count($this) ? parent::jsonSerialize() : NULL;
|
|
}
|
|
|
|
/**
|
|
* Post this message to slack
|
|
*
|
|
* @param Carbon|null $delete
|
|
* @return Generic
|
|
* @throws \Exception
|
|
*/
|
|
public function post(Carbon $delete=NULL): Generic
|
|
{
|
|
if (! $delete && $this->selfdestruct)
|
|
$delete = $this->selfdestruct;
|
|
|
|
if ($this->_data->get('blocks') && $this->_data->get('attachments'))
|
|
throw new SlackSyntaxException('Message cannot have blocks and attachments.');
|
|
|
|
if ((! isset($this->o)) || (! $this->o->team))
|
|
throw new SlackSyntaxException('Message needs to have a user or a channel to work out the team.');
|
|
|
|
$api = $this->o->team->slackAPI();
|
|
|
|
if ($this->ephemeral) {
|
|
$response = $api->postEphemeral($this);
|
|
|
|
} else {
|
|
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->postMessage($this);
|
|
}
|
|
|
|
if ($delete) {
|
|
Log::debug(sprintf('%s:Scheduling Delete of [%s:%s] on [%s]',static::LOGKEY,object_get($this->o,'channel_id',$this->o->id),$response->ts,$delete->format('Y-m-d')),['m'=>__METHOD__]);
|
|
|
|
// Queue the delete of the message if requested
|
|
dispatch((new DeleteChat($this->o,$response->ts))->onQueue('slack')->delay($delete));
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Post a message to slack using the respond_url
|
|
*
|
|
* @note This URL can only be used 5 times in 30 minutes
|
|
* @param string $url
|
|
* @param Carbon|null $delete
|
|
* @return object
|
|
* @throws SlackException
|
|
*/
|
|
public function respond(string $url,Carbon $delete=NULL): object
|
|
{
|
|
if (! $delete && $this->selfdestruct)
|
|
$delete = $this->selfdestruct;
|
|
|
|
$http = Http::acceptJson();
|
|
$http->withBody(json_encode($this),'application/json');
|
|
|
|
try {
|
|
$request = $http->post($url)->throw();
|
|
$response = $request->object();
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error(sprintf('%s:Got an error while posting to [%s] (%s)',static::LOGKEY,$url,$e->getMessage()),['m'=>__METHOD__]);
|
|
|
|
throw new \Exception($e->getMessage());
|
|
}
|
|
|
|
if (! $response->ok) {
|
|
Log::critical(sprintf('%s:Generic Error',static::LOGKEY),['m'=>__METHOD__,'r'=>$response]);
|
|
throw new SlackException(serialize($response),$request->status());
|
|
}
|
|
|
|
if ($delete) {
|
|
Log::debug(sprintf('%s:Scheduling Delete of [%s:%s] on [%s]',static::LOGKEY,object_get($this->o,'channel_id',$this->o->id),$url,$delete->format('Y-m-d')),['m'=>__METHOD__]);
|
|
|
|
// Queue the delete of the response if requested
|
|
dispatch((new DeleteResponse($url))->onQueue('slack')->delay($delete));
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Schedule a message
|
|
*
|
|
* @param Carbon $time
|
|
* @return Generic
|
|
*/
|
|
public function schedule(Carbon $time): Generic
|
|
{
|
|
$this->_data->put('post_at',$time->timestamp);
|
|
|
|
$api = $this->o->team->slackAPI();
|
|
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->scheduleMessage($this);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Make the message self destruct
|
|
*
|
|
* @param Carbon $time
|
|
* @return Message
|
|
*/
|
|
public function selfdestruct(Carbon $time): self
|
|
{
|
|
$this->addBlock(
|
|
Context::item(collect([
|
|
Text::item(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW])))
|
|
]))
|
|
);
|
|
|
|
$this->selfdestruct = $time;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set our channel
|
|
*
|
|
* @param Channel $o
|
|
* @return Message
|
|
*/
|
|
public function setChannel(Channel $o): self
|
|
{
|
|
$this->channel = $o->channel_id;
|
|
$this->o = $o;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set our channel
|
|
*
|
|
* @param User $o
|
|
* @return Message
|
|
*/
|
|
public function setUser(User $o): self
|
|
{
|
|
$this->channel = $o->user_id;
|
|
$this->o = $o;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/* CONFIGURATION METHODS */
|
|
|
|
/**
|
|
* @return Message
|
|
*/
|
|
public function ephemeral(): self
|
|
{
|
|
$this->ephemeral = TRUE;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the icon next to the message
|
|
*
|
|
* @param string $icon
|
|
* @return Message
|
|
* @deprecated
|
|
*/
|
|
public function icon_emoji(string $icon): self
|
|
{
|
|
$this->icon_emoji = $icon;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Option groups are used by the interactive Options controller and hold no other attributes
|
|
*
|
|
* @param Collection $collection
|
|
* @return Message
|
|
*/
|
|
public function option_groups(Collection $collection): self
|
|
{
|
|
$this->option_groups = $collection;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Replace the original message
|
|
*
|
|
* @param bool $bool
|
|
* @return Message
|
|
*/
|
|
public function replace_original(bool $bool=TRUE): self
|
|
{
|
|
$this->replace_original = $bool;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Message text
|
|
*
|
|
* @param string $string
|
|
* @return Message
|
|
*/
|
|
public function text(string $string): self
|
|
{
|
|
$this->text = $string;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the thread timestamp, used when adding a threaded response
|
|
*
|
|
* @param string $string
|
|
* @return Message
|
|
*/
|
|
public function thread_ts(string $string): self
|
|
{
|
|
$this->thread_ts = $string;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the timestamp, used when replacing messages
|
|
*
|
|
* @param string $string
|
|
* @return Message
|
|
*/
|
|
public function ts(string $string): self
|
|
{
|
|
$this->ts = $string;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* To slack from rendering URLs in the message
|
|
*
|
|
* @param bool $bool
|
|
* @return $this
|
|
*/
|
|
public function unfurl_links(bool $bool): self
|
|
{
|
|
$this->unfurl_links = $bool;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function username(string $user): self
|
|
{
|
|
$this->username = $user;
|
|
|
|
return $this;
|
|
}
|
|
} |