mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-10-08 04:47:44 +00:00
Added PhotoAttachment
This commit is contained in:
+73
@@ -6,18 +6,23 @@ namespace BushlanovDev\MaxMessengerBot;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\ClientApiException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\NetworkException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The main entry point for interacting with the Max Bot API.
|
||||
@@ -37,6 +42,7 @@ class Api
|
||||
private const string ACTION_ME = '/me';
|
||||
private const string ACTION_SUBSCRIPTIONS = '/subscriptions';
|
||||
private const string ACTION_MESSAGES = '/messages';
|
||||
private const string ACTION_UPLOADS = '/uploads';
|
||||
|
||||
private readonly ClientApiInterface $client;
|
||||
|
||||
@@ -204,4 +210,71 @@ class Api
|
||||
|
||||
return $this->modelFactory->createMessage($response['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for the subsequent file upload.
|
||||
*
|
||||
* @param UploadType $type Uploaded file type.
|
||||
*
|
||||
* @return UploadEndpoint Endpoint you should upload to your binaries.
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getUploadUrl(UploadType $type): UploadEndpoint
|
||||
{
|
||||
return $this->modelFactory->createUploadEndpoint(
|
||||
$this->client->request(
|
||||
self::METHOD_POST,
|
||||
self::ACTION_UPLOADS,
|
||||
['type' => $type->value],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified method for uploading a file and getting the resulting attachment object.
|
||||
*
|
||||
* @param UploadType $type Uploaded file type.
|
||||
* @param string $filePath Path to the file on the local disk.
|
||||
*
|
||||
* @return AbstractAttachmentRequest
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws NetworkException
|
||||
* @throws ClientApiException
|
||||
*/
|
||||
public function uploadAttachment(UploadType $type, string $filePath): AbstractAttachmentRequest
|
||||
{
|
||||
if (!file_exists($filePath) || !is_readable($filePath)) {
|
||||
throw new InvalidArgumentException("File not found or not readable: $filePath");
|
||||
}
|
||||
|
||||
$fileHandle = fopen($filePath, 'r');
|
||||
if ($fileHandle === false) {
|
||||
throw new RuntimeException("Could not open file for reading: $filePath");
|
||||
}
|
||||
|
||||
$uploadEndpoint = $this->getUploadUrl($type);
|
||||
|
||||
$uploadResult = $this->client->upload(
|
||||
$uploadEndpoint->url,
|
||||
$fileHandle,
|
||||
basename($filePath),
|
||||
);
|
||||
|
||||
fclose($fileHandle);
|
||||
|
||||
if (!isset($uploadResult['token'])) {
|
||||
throw new SerializationException('Could not find "token" in upload server response.');
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
UploadType::Image => PhotoAttachmentRequest::fromToken($uploadResult['token']),
|
||||
default => throw new LogicException(
|
||||
"Attachment creation for type '$type->value' is not yet implemented."
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,48 @@ final class Client implements ClientApiInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function upload(string $uri, mixed $fileContents, string $fileName): array
|
||||
{
|
||||
$boundary = '--------------------------' . microtime(true);
|
||||
$bodyStream = $this->streamFactory->createStream();
|
||||
|
||||
$bodyStream->write("--$boundary\r\n");
|
||||
$bodyStream->write("Content-Disposition: form-data; name=\"data\"; filename=\"{$fileName}\"\r\n");
|
||||
$bodyStream->write("Content-Type: application/octet-stream\r\n\r\n");
|
||||
|
||||
if (is_resource($fileContents)) {
|
||||
$bodyStream->write((string)stream_get_contents($fileContents));
|
||||
} else {
|
||||
$bodyStream->write((string)$fileContents);
|
||||
}
|
||||
$bodyStream->write("\r\n");
|
||||
$bodyStream->write("--$boundary--\r\n");
|
||||
|
||||
$request = $this->requestFactory
|
||||
->createRequest('POST', $uri)
|
||||
->withHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary)
|
||||
->withBody($bodyStream);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
throw new NetworkException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->handleErrorResponse($response);
|
||||
|
||||
$responseBody = (string)$response->getBody();
|
||||
|
||||
try {
|
||||
return json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw new SerializationException('Failed to decode upload server response JSON.', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the response for an error status code and throws a corresponding typed exception.
|
||||
*
|
||||
|
||||
@@ -24,4 +24,18 @@ interface ClientApiInterface
|
||||
* @throws SerializationException for JSON encoding/decoding failures.
|
||||
*/
|
||||
public function request(string $method, string $uri, array $queryParams = [], array $body = []): array;
|
||||
|
||||
/**
|
||||
* Performs a file download at the specified URL.
|
||||
*
|
||||
* @param string $uri URL received from the download API.
|
||||
* @param resource|string $fileContents File content (stream resource or string).
|
||||
* @param string $fileName The name of the file that will be sent to the server.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function upload(string $uri, mixed $fileContents, string $fileName): array;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum UploadType: string
|
||||
{
|
||||
case Image = 'image';
|
||||
case Video = 'video';
|
||||
case Audio = 'audio';
|
||||
case File = 'file';
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
@@ -81,4 +82,17 @@ class ModelFactory
|
||||
{
|
||||
return Message::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint you should upload to your binaries.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return UploadEndpoint
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createUploadEndpoint(array $data): UploadEndpoint
|
||||
{
|
||||
return UploadEndpoint::fromArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Request to attach image. All fields are mutually exclusive.
|
||||
*/
|
||||
final readonly class PhotoAttachmentPayload extends AbstractAttachmentPayload
|
||||
{
|
||||
/**
|
||||
* @param string|null $url Any external image URL you want to attach.
|
||||
* @param string|null $token Token of any existing attachment.
|
||||
* @param PhotoToken[]|null $photos Tokens were obtained after uploading images.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $url = null,
|
||||
public ?string $token = null,
|
||||
#[ArrayOf(PhotoToken::class)]
|
||||
public ?array $photos = null,
|
||||
) {
|
||||
if (count(array_filter([$this->url, $this->token, $this->photos])) !== 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Provide exactly one of "url", "token", or "photos" for PhotoAttachmentPayload.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Encoded information of uploaded image
|
||||
*/
|
||||
final readonly class PhotoToken extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $token Encoded information of uploaded image.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
|
||||
/**
|
||||
* Request to attach some data to message.
|
||||
*/
|
||||
final readonly class PhotoAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* Creates a request to attach an image by URL.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(url: $url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request to attach an image using the token received after uploading.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromToken(string $token): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(token: $token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request to attach an image using the tokens received after uploading.
|
||||
*
|
||||
* @param PhotoToken[] $photos
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromPhotos(array $photos): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(photos: $photos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhotoAttachmentPayload $payload Request to attach image.
|
||||
*/
|
||||
private function __construct(PhotoAttachmentPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Image, $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* Endpoint you should upload to your binaries
|
||||
*/
|
||||
final readonly class UploadEndpoint extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url URL to upload.
|
||||
* @param string|null $token Video or audio token for send message.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public ?string $token = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user