Skip to content

Helper for sending InputMedia objects #934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Exclamation symbols (:exclamation:) note something of importance e.g. breaking c

## [Unreleased]
### Added
- Helper for sending `InputMedia` objects using `Request::sendMediaGroup()` and `Request::editMediaMessage()` methods.
- Allow passing absolute file path for InputFile fields, instead of `Request::encodeFile($path)`.
### Changed
- All Message field types dynamically search for an existing Command class that can handle them.
### Deprecated
Expand All @@ -13,6 +15,7 @@ Exclamation symbols (:exclamation:) note something of importance e.g. breaking c
### Removed
### Fixed
- Constraint errors in `/cleanup` command.
- Return correct objects for requests.
### Security

## [0.55.1] - 2019-01-06
Expand Down
4 changes: 2 additions & 2 deletions src/Entities/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use Exception;
use Longman\TelegramBot\Entities\InlineQuery\InlineEntity;
use Longman\TelegramBot\TelegramLog;
use Longman\TelegramBot\Entities\InputMedia\InputMedia;

/**
* Class Entity
Expand Down Expand Up @@ -149,7 +149,7 @@ public function __call($method, $args)
}
} elseif ($action === 'set') {
// Limit setters to specific classes.
if ($this instanceof InlineEntity || $this instanceof Keyboard || $this instanceof KeyboardButton) {
if ($this instanceof InlineEntity || $this instanceof InputMedia || $this instanceof Keyboard || $this instanceof KeyboardButton) {
$this->$property_name = $args[0];

return $this;
Expand Down
64 changes: 30 additions & 34 deletions src/Entities/ServerResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

namespace Longman\TelegramBot\Entities;

use Longman\TelegramBot\Entities\Games\GameHighScore;
use Longman\TelegramBot\Request;

/**
* Class ServerResponse
*
Expand Down Expand Up @@ -103,32 +106,27 @@ public function printError($return = false)
* @param string $bot_username
*
* @return \Longman\TelegramBot\Entities\Chat|\Longman\TelegramBot\Entities\ChatMember|\Longman\TelegramBot\Entities\File|\Longman\TelegramBot\Entities\Message|\Longman\TelegramBot\Entities\User|\Longman\TelegramBot\Entities\UserProfilePhotos|\Longman\TelegramBot\Entities\WebhookInfo
* @throws \Longman\TelegramBot\Exception\TelegramException
*/
private function createResultObject($result, $bot_username)
private function createResultObject(array $result, $bot_username)
{
$action = Request::getCurrentAction();

// We don't need to save the raw_data of the response object!
$result['raw_data'] = null;

$result_object_types = [
'total_count' => 'UserProfilePhotos', //Response from getUserProfilePhotos
'stickers' => 'StickerSet', //Response from getStickerSet
'file_id' => 'File', //Response from getFile
'title' => 'Chat', //Response from getChat
'username' => 'User', //Response from getMe
'user' => 'ChatMember', //Response from getChatMember
'url' => 'WebhookInfo', //Response from getWebhookInfo
'getChat' => Chat::class,
'getChatMember' => ChatMember::class,
'getFile' => File::class,
'getMe' => User::class,
'getStickerSet' => StickerSet::class,
'getUserProfilePhotos' => UserProfilePhotos::class,
'getWebhookInfo' => WebhookInfo::class,
];
foreach ($result_object_types as $type => $object_class) {
if (isset($result[$type])) {
$object_class = __NAMESPACE__ . '\\' . $object_class;

return new $object_class($result);
}
}
$object_class = array_key_exists($action, $result_object_types) ? $result_object_types[$action] : Message::class;

//Response from sendMessage
return new Message($result, $bot_username);
return new $object_class($result, $bot_username);
}

/**
Expand All @@ -137,28 +135,26 @@ private function createResultObject($result, $bot_username)
* @param array $result
* @param string $bot_username
*
* @return null|\Longman\TelegramBot\Entities\ChatMember[]|\Longman\TelegramBot\Entities\Update[]
* @throws \Longman\TelegramBot\Exception\TelegramException
* @return \Longman\TelegramBot\Entities\ChatMember[]|\Longman\TelegramBot\Entities\Games\GameHighScore[]|\Longman\TelegramBot\Entities\Message[]|\Longman\TelegramBot\Entities\Update[]
*/
private function createResultObjects($result, $bot_username)
private function createResultObjects(array $result, $bot_username)
{
$results = [];
if (isset($result[0]['user'])) {
//Response from getChatAdministrators
foreach ($result as $user) {
// We don't need to save the raw_data of the response object!
$user['raw_data'] = null;
$action = Request::getCurrentAction();

$results[] = new ChatMember($user);
}
} else {
//Get Update
foreach ($result as $update) {
// We don't need to save the raw_data of the response object!
$update['raw_data'] = null;
$result_object_types = [
'getChatAdministrators' => ChatMember::class,
'getGameHighScores' => GameHighScore::class,
'sendMediaGroup' => Message::class,
];

$results[] = new Update($update, $bot_username);
}
$object_class = array_key_exists($action, $result_object_types) ? $result_object_types[$action] : Update::class;

foreach ($result as $data) {
// We don't need to save the raw_data of the response object!
$data['raw_data'] = null;

$results[] = new $object_class($data, $bot_username);
}

return $results;
Expand Down
144 changes: 135 additions & 9 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Stream;
use Longman\TelegramBot\Entities\File;
use Longman\TelegramBot\Entities\InputMedia\InputMedia;
use Longman\TelegramBot\Entities\ServerResponse;
use Longman\TelegramBot\Exception\InvalidBotTokenException;
use Longman\TelegramBot\Exception\TelegramException;
Expand Down Expand Up @@ -126,6 +128,13 @@ class Request
*/
private static $limiter_interval;

/**
* Get the current action that is being executed
*
* @var string
*/
private static $current_action;

/**
* Available actions to send
*
Expand Down Expand Up @@ -213,6 +222,30 @@ class Request
'getMe',
];

/**
* Available fields for InputFile helper
*
* This is basically the list of all fields that allow InputFile objects
* for which input can be simplified by providing local path directly as string.
*
* @var array
*/
private static $input_file_fields = [
'setWebhook' => ['certificate'],
'sendPhoto' => ['photo'],
'sendAudio' => ['audio', 'thumb'],
'sendDocument' => ['document', 'thumb'],
'sendVideo' => ['video', 'thumb'],
'sendAnimation' => ['animation', 'thumb'],
'sendVoice' => ['voice', 'thumb'],
'sendVideoNote' => ['video_note', 'thumb'],
'setChatPhoto' => ['photo'],
'sendSticker' => ['sticker'],
'uploadStickerFile' => ['png_sticker'],
'createNewStickerSet' => ['png_sticker'],
'addStickerToSet' => ['png_sticker'],
];

/**
* Initialize
*
Expand Down Expand Up @@ -318,29 +351,116 @@ public static function generateGeneralFakeServerResponse(array $data = [])
* @param array $data
*
* @return array
* @throws TelegramException
*/
private static function setUpRequestParams(array $data)
{
$has_resource = false;
$multipart = [];

// Convert any nested arrays into JSON strings.
array_walk($data, function (&$item) {
is_array($item) && $item = json_encode($item);
});
foreach ($data as $key => &$item) {
if ($key === 'media') {
// Magical media input helper.
$item = self::mediaInputHelper($item, $has_resource, $multipart);
} elseif (array_key_exists(self::$current_action, self::$input_file_fields) && in_array($key, self::$input_file_fields[self::$current_action], true)) {
// Allow absolute paths to local files.
if (is_string($item) && file_exists($item)) {
$item = new Stream(self::encodeFile($item));
}
} elseif (is_array($item)) {
// Convert any nested arrays into JSON strings.
$item = json_encode($item);
}

//Reformat data array in multipart way if it contains a resource
foreach ($data as $key => $item) {
$has_resource |= (is_resource($item) || $item instanceof \GuzzleHttp\Psr7\Stream);
// Reformat data array in multipart way if it contains a resource
$has_resource |= (is_resource($item) || $item instanceof Stream);
$multipart[] = ['name' => $key, 'contents' => $item];
}

if ($has_resource) {
return ['multipart' => $multipart];
}

return ['form_params' => $data];
}

/**
* Magical input media helper to simplify passing media.
*
* This allows the following:
* Request::editMessageMedia([
* ...
* 'media' => new InputMediaPhoto([
* 'caption' => 'Caption!',
* 'media' => Request::encodeFile($local_photo),
* ]),
* ]);
* and
* Request::sendMediaGroup([
* 'media' => [
* new InputMediaPhoto(['media' => Request::encodeFile($local_photo_1)]),
* new InputMediaPhoto(['media' => Request::encodeFile($local_photo_2)]),
* new InputMediaVideo(['media' => Request::encodeFile($local_video_1)]),
* ],
* ]);
* and even
* Request::sendMediaGroup([
* 'media' => [
* new InputMediaPhoto(['media' => $local_photo_1]),
* new InputMediaPhoto(['media' => $local_photo_2]),
* new InputMediaVideo(['media' => $local_video_1]),
* ],
* ]);
*
* @param mixed $item
* @param bool $has_resource
* @param array $multipart
*
* @return mixed
*/
private static function mediaInputHelper($item, &$has_resource, array &$multipart)
{
$was_array = is_array($item);
$was_array || $item = [$item];

foreach ($item as $media_item) {
if (!($media_item instanceof InputMedia)) {
continue;
}

$media = $media_item->getMedia();

// Allow absolute paths to local files.
if (is_string($media) && file_exists($media)) {
$media = new Stream(self::encodeFile($media));
}

if (is_resource($media) || $media instanceof Stream) {
$has_resource = true;
$rnd_key = uniqid('media_', false);
$multipart[] = ['name' => $rnd_key, 'contents' => $media];

// We're literally overwriting the passed media data!
$media_item->media = 'attach://' . $rnd_key;
$media_item->raw_data['media'] = 'attach://' . $rnd_key;
}
}

$was_array || $item = reset($item);

return json_encode($item);
}

/**
* Get the current action that's being executed
*
* @return string
*/
public static function getCurrentAction()
{
return self::$current_action;
}

/**
* Execute HTTP Request
*
Expand Down Expand Up @@ -373,7 +493,7 @@ public static function execute($action, array $data = [])
TelegramLog::update($result);
}
} catch (RequestException $e) {
$result = ($e->getResponse()) ? (string) $e->getResponse()->getBody() : '';
$result = $e->getResponse() ? (string) $e->getResponse()->getBody() : '';
} finally {
//Logging verbose debug output
TelegramLog::endDebugLogTempStream('Verbose HTTP Request output:' . PHP_EOL . '%s' . PHP_EOL);
Expand Down Expand Up @@ -470,8 +590,11 @@ public static function send($action, array $data = [])

self::limitTelegramRequests($action, $data);

// Remember which action is currently being executed.
self::$current_action = $action;

$raw_response = self::execute($action, $data);
$response = json_decode($raw_response, true);
$response = json_decode($raw_response, true);

if (null === $response) {
TelegramLog::debug($raw_response);
Expand All @@ -484,6 +607,9 @@ public static function send($action, array $data = [])
throw new InvalidBotTokenException();
}

// Reset current action after completion.
self::$current_action = null;

return $response;
}

Expand Down
9 changes: 9 additions & 0 deletions tests/unit/Entities/ServerResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
*/
class ServerResponseTest extends TestCase
{
protected function setUp()
{
// Make sure the current action in the Request class is unset.
TestHelpers::setStaticProperty(Request::class, 'current_action', null);
}

public function sendMessageOk()
{
return '{
Expand Down Expand Up @@ -193,6 +199,7 @@ public function testGetUpdatesEmpty()

public function getUserProfilePhotos()
{
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getUserProfilePhotos');
return '{
"ok":true,
"result":{
Expand Down Expand Up @@ -238,6 +245,7 @@ public function testGetUserProfilePhotos()

public function getFile()
{
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getFile');
return '{
"ok":true,
"result":{
Expand Down Expand Up @@ -301,6 +309,7 @@ public function testSetGeneralTestFakeResponse()

public function getStickerSet()
{
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getStickerSet');
return '{
"ok":true,
"result":{
Expand Down