Verified Commit 0ac8246c authored by FabioWidmer's avatar FabioWidmer
Browse files

Add more message types

parent bacf307e
# Changelog
## v1.1.0 - 2022-03-17
### Added
- Support image, location, audio, video and file messages
- Throw exception on unsuccessful result
### Changed
- Variable assignment in messages: Change `$message->text` to `$message->setText()`
## v1.0.1 - 2022-03-05
### Fixed
......
......@@ -69,7 +69,7 @@ ## Formatting Notifications
}
```
_Right now only text messages are supported. More message types are coming soon._
_Right now only 1:1 chat messages (text, image, location, audio, video, file) are supported. Group message types are coming soon._
## Routing Notifications
......@@ -85,3 +85,27 @@ ## Routing Notifications
```
By using `Receiver::TYPE_EMAIL` or `Receiver::TYPE_PHONE` you can make use of an automatic ID lookup.
## Exceptions
The package only throws the following exceptions:
### ThreemaChannelException
Is thrown in case of fatal errors. For example, if certain types are not supported or the configuration is incorrect.
### ThreemaChannelResultException
Since Laravel does not return a result when sending notifications, this exception is thrown instead.
For example, sending a message to an unknown Threema ID would lead into this exception.
It should be caught and the result it contains should be processed:
```php
use Illuminate\Notifications\Exceptions\ThreemaChannelResultException;
try {
$notifiable->notify(new TwoFactorVerificationCode());
} catch(ThreemaChannelResultException $exception) {
$exception->getResult();
}
```
......@@ -7,6 +7,7 @@
"php": "^8.0",
"illuminate/notifications": "^9.0",
"illuminate/support": "^9.0",
"php-ffmpeg/php-ffmpeg": "^1.0",
"threema/msgapi-sdk": "^1.5"
},
"require-dev": {
......
This diff is collapsed.
......@@ -3,7 +3,13 @@
namespace Illuminate\Notifications\Channels;
use Illuminate\Notifications\Exceptions\ThreemaChannelException;
use Illuminate\Notifications\Exceptions\ThreemaChannelResultException;
use Illuminate\Notifications\Messages\ThreemaAudioMessage;
use Illuminate\Notifications\Messages\ThreemaFileMessage;
use Illuminate\Notifications\Messages\ThreemaImageMessage;
use Illuminate\Notifications\Messages\ThreemaLocationMessage;
use Illuminate\Notifications\Messages\ThreemaTextMessage;
use Illuminate\Notifications\Messages\ThreemaVideoMessage;
use Illuminate\Notifications\Notification;
use Threema\MsgApi\Commands\Results\Result;
use Threema\MsgApi\Connection;
......@@ -37,19 +43,20 @@ public function __construct(Connection $connection, ?string $privateKey = null)
/**
* @throws ThreemaChannelException
* @throws ThreemaChannelResultException
*/
public function send(mixed $notifiable, Notification $notification): ?Result
public function send(mixed $notifiable, Notification $notification): Result
{
$message = $notification->toThreema($notifiable);
$connection = $message->channel ? $message->channel->connection : $this->connection;
$privateKey = $message->channel ? $message->channel->privateKey : $this->privateKey;
$connection = $message->getChannel() ? $message->getChannel()->connection : $this->connection;
$privateKey = $message->getChannel() ? $message->getChannel()->privateKey : $this->privateKey;
if (!$receiver = $notifiable->routeNotificationFor('threema', $notification)) {
throw new ThreemaChannelException('Notifiable is missing "routeNotificationForThreema" function.');
}
if ($privateKey === null && $message instanceof ThreemaTextMessage) {
$result = $connection->sendSimple($receiver, $message->text);
$result = $connection->sendSimple($receiver, $message->getText());
} else {
$e2eHelper = new E2EHelper($privateKey, $connection);
......@@ -67,7 +74,7 @@ public function send(mixed $notifiable, Notification $notification): ?Result
if ($lookup->isSuccess()) {
$threemaId = $lookup->getId();
} else {
return $lookup;
throw new ThreemaChannelResultException($lookup);
}
} else {
throw new ThreemaChannelException('This lookup type is not supported by Laravel Threema.');
......@@ -76,7 +83,36 @@ public function send(mixed $notifiable, Notification $notification): ?Result
try {
if ($message instanceof ThreemaTextMessage) {
$result = $e2eHelper->sendTextMessage($threemaId, $message->text);
$result = $e2eHelper->sendTextMessage($threemaId, $message->getText());
} else if ($message instanceof ThreemaImageMessage) {
$result = $e2eHelper->sendImageMessage($threemaId, $message->getPath());
} else if ($message instanceof ThreemaLocationMessage) {
$result = $e2eHelper->sendLocationMessage(
$threemaId,
$message->getLatitude(),
$message->getLongitude(),
$message->getAccuracy(),
$message->getPoiName(),
$message->getPoiAddress()
);
} else if ($message instanceof ThreemaAudioMessage) {
$result = $e2eHelper->sendAudioMessage($threemaId, $message->getDuration(), $message->getPath());
} else if ($message instanceof ThreemaVideoMessage) {
$result = $e2eHelper->sendVideoMessage(
$threemaId,
$message->getDuration(),
$message->getPath(),
$message->getThumbnailPath(),
);
} else if ($message instanceof ThreemaFileMessage) {
$result = $e2eHelper->sendFileMessage(
$threemaId,
$message->getPath(),
$message->getThumbnailPath(),
$message->getCaption(),
$message->getName(),
$message->getType(),
);
} else {
throw new ThreemaChannelException('This message type is not supported by Laravel Threema.');
}
......@@ -85,6 +121,10 @@ public function send(mixed $notifiable, Notification $notification): ?Result
}
}
if(!$result->isSuccess()) {
throw new ThreemaChannelResultException($result);
}
return $result;
}
}
<?php
namespace Illuminate\Notifications\Exceptions;
use Exception;
use Threema\MsgApi\Commands\Results\Result;
use Throwable;
class ThreemaChannelResultException extends Exception
{
protected Result $result;
public function __construct(Result $result, ?Throwable $previous = null)
{
$this->result = $result;
parent::__construct($result->getErrorMessage(), $result->getErrorCode(), $previous);
}
public function getResult(): Result
{
return $this->result;
}
}
<?php
namespace Illuminate\Notifications\Messages;
use Exception;
use FFMpeg\FFProbe;
use Illuminate\Notifications\Channels\ThreemaChannel;
use Illuminate\Notifications\Exceptions\ThreemaChannelException;
class ThreemaAudioMessage extends ThreemaMessage
{
protected string $path;
private int $duration;
/**
* @throws ThreemaChannelException
*/
public function __construct(string $path, ?ThreemaChannel $channel = null)
{
parent::__construct($channel);
$this->path = $path;
$this->duration = $this->calculateDuration();
}
public function getPath(): string
{
return $this->path;
}
/**
* @throws ThreemaChannelException
*/
public function setPath(string $path): self
{
$this->path = $path;
$this->duration = $this->calculateDuration();
return $this;
}
public function getDuration(): int
{
return $this->duration;
}
/**
* @throws ThreemaChannelException
*/
private function calculateDuration(): int
{
try {
$ffmpeg = FFProbe::create();
$audio = $ffmpeg->format($this->path);
return $audio->get('duration');
} catch(Exception $exception) {
throw new ThreemaChannelException('The underlying FFMpeg has thrown an exception.', 0, $exception);
}
}
}
<?php
namespace Illuminate\Notifications\Messages;
use Illuminate\Notifications\Channels\ThreemaChannel;
class ThreemaFileMessage extends ThreemaMessage
{
protected string $path;
protected ?string $name;
protected ?string $caption;
protected ?int $type;
protected ?string $thumbnailPath;
public function __construct(
string $path,
?string $name = null,
?string $caption = null,
?int $type = null,
?string $thumbnailPath = null,
?ThreemaChannel $channel = null
) {
parent::__construct($channel);
$this->path = $path;
$this->name = $name;
$this->caption = $caption;
$this->type = $type;
$this->thumbnailPath = $thumbnailPath;
}
public function getPath(): string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getCaption(): ?string
{
return $this->caption;
}
public function setCaption(?string $caption): self
{
$this->caption = $caption;
return $this;
}
public function getType(): ?int
{
return $this->type;
}
public function setType(?int $type): self
{
$this->type = $type;
return $this;
}
public function getThumbnailPath(): ?string
{
return $this->thumbnailPath;
}
public function setThumbnailPath(?string $thumbnailPath): self
{
$this->thumbnailPath = $thumbnailPath;
return $this;
}
}
<?php
namespace Illuminate\Notifications\Messages;
use Illuminate\Notifications\Channels\ThreemaChannel;
class ThreemaImageMessage extends ThreemaMessage
{
protected string $path;
public function __construct(string $path, ?ThreemaChannel $channel = null)
{
parent::__construct($channel);
$this->path = $path;
}
public function getPath(): string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
}
<?php
namespace Illuminate\Notifications\Messages;
use Illuminate\Notifications\Channels\ThreemaChannel;
class ThreemaLocationMessage extends ThreemaMessage
{
protected float $latitude;
protected float $longitude;
protected ?float $accuracy;
protected ?string $poiName;
protected ?string $poiAddress;
public function __construct(float $latitude, float $longitude, ?float $accuracy = null, ?string $poiName = null, ?string $poiAddress = null, ?ThreemaChannel $channel = null)
{
parent::__construct($channel);
$this->latitude = $latitude;
$this->longitude = $longitude;
$this->accuracy = $accuracy;
$this->poiName = $poiName;
$this->poiAddress = $poiAddress;
}
public function getLatitude(): float
{
return $this->latitude;
}
public function setLatitude(float $latitude): self
{
$this->latitude = $latitude;
return $this;
}
public function getLongitude(): float
{
return $this->longitude;
}
public function setLongitude(float $longitude): self
{
$this->longitude = $longitude;
return $this;
}
public function getAccuracy(): ?float
{
return $this->accuracy;
}
public function setAccuracy(?float $accuracy): self
{
$this->accuracy = $accuracy;
return $this;
}
public function getPoiName(): ?string
{
return $this->poiName;
}
public function setPoiName(?string $poiName): self
{
$this->poiName = $poiName;
return $this;
}
public function getPoiAddress(): ?string
{
return $this->poiAddress;
}
public function setPoiAddress(?string $poiAddress): self
{
$this->poiAddress = $poiAddress;
return $this;
}
}
......@@ -6,10 +6,22 @@
abstract class ThreemaMessage
{
public ?ThreemaChannel $channel;
protected ?ThreemaChannel $channel;
public function __construct(?ThreemaChannel $channel = null)
{
$this->channel = $channel;
}
public function getChannel(): ?ThreemaChannel
{
return $this->channel;
}
public function setChannel(?ThreemaChannel $channel): self
{
$this->channel = $channel;
return $this;
}
}
......@@ -6,7 +6,7 @@
class ThreemaTextMessage extends ThreemaMessage
{
public string $text;
protected string $text;
public function __construct(string $text, ?ThreemaChannel $channel = null)
{
......@@ -14,4 +14,16 @@ public function __construct(string $text, ?ThreemaChannel $channel = null)
$this->text = $text;
}
public function getText(): string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
}
<?php
namespace Illuminate\Notifications\Messages;
use Exception;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe;
use Illuminate\Notifications\Channels\ThreemaChannel;
use Illuminate\Notifications\Exceptions\ThreemaChannelException;
class ThreemaVideoMessage extends ThreemaMessage
{
protected string $path;
protected string $thumbnailPath;
private int $duration;
/**
* @throws ThreemaChannelException
*/
public function __construct(string $path, ?string $thumbnailPath = null, ?ThreemaChannel $channel = null)
{
parent::__construct($channel);
$this->path = $path;
if($thumbnailPath === null) {
$thumbnailPath = $this->generateThumbnailPath();
}
$this->thumbnailPath = $thumbnailPath;
$this->duration = $this->calculateDuration();
}
public function getPath(): string
{
return $this->path;
}
/**
* @throws ThreemaChannelException
*/
public function setPath(string $path): self
{
$this->path = $path;
$this->duration = $this->calculateDuration();
return $this;
}
public function getThumbnailPath(): string
{
return $this->thumbnailPath;
}
public function setThumbnailPath(string $thumbnailPath): self
{
$this->thumbnailPath = $thumbnailPath;
return $this;
}
public function getDuration(): int
{
return $this->duration;
}
/**
* @throws ThreemaChannelException
*/
private function generateThumbnailPath(): string
{
try {
$file = tempnam(sys_get_temp_dir(), 'frame.jpg');
$ffmpeg = FFMpeg::create();
$video = $ffmpeg->open($this->path);
$video
->frame(TimeCode::fromSeconds(0))
->save($file);
return $file;
} catch(Exception $exception) {
throw new ThreemaChannelException('The underlying FFMpeg has thrown an exception.', 0, $exception);
}
}
/**
* @throws ThreemaChannelException
*/
private function calculateDuration(): int
{
try {
$ffmpeg = FFProbe::create();
$audio = $ffmpeg->format($this->path);
return $audio->get('duration');
} catch(Exception $exception) {
throw new ThreemaChannelException('The underlying FFMpeg has thrown an exception.', 0, $exception);
}
}
}
......@@ -4,15 +4,23 @@
use Illuminate\Notifications\Channels\ThreemaChannel;
use Illuminate\Notifications\Exceptions\ThreemaChannelException;
use Illuminate\Notifications\Exceptions\ThreemaChannelResultException;
use Illuminate\Notifications\Messages\ThreemaAudioMessage;
use Illuminate\Notifications\Messages\ThreemaFileMessage;
use Illuminate\Notifications\Messages\ThreemaImageMessage;
use Illuminate\Notifications\Messages\ThreemaLocationMessage;
<