Skip to content

Commit a87a9e6

Browse files
authored
Merge pull request #934 from noplanman/811-helper_for_media_groups
Helper for sending `InputMedia` objects
2 parents 0efd567 + 7564a3a commit a87a9e6

File tree

5 files changed

+179
-45
lines changed

5 files changed

+179
-45
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Exclamation symbols (:exclamation:) note something of importance e.g. breaking c
55

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

1821
## [0.55.1] - 2019-01-06

src/Entities/Entity.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
use Exception;
1414
use Longman\TelegramBot\Entities\InlineQuery\InlineEntity;
15-
use Longman\TelegramBot\TelegramLog;
15+
use Longman\TelegramBot\Entities\InputMedia\InputMedia;
1616

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

155155
return $this;

src/Entities/ServerResponse.php

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
namespace Longman\TelegramBot\Entities;
1010

11+
use Longman\TelegramBot\Entities\Games\GameHighScore;
12+
use Longman\TelegramBot\Request;
13+
1114
/**
1215
* Class ServerResponse
1316
*
@@ -103,32 +106,27 @@ public function printError($return = false)
103106
* @param string $bot_username
104107
*
105108
* @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
106-
* @throws \Longman\TelegramBot\Exception\TelegramException
107109
*/
108-
private function createResultObject($result, $bot_username)
110+
private function createResultObject(array $result, $bot_username)
109111
{
112+
$action = Request::getCurrentAction();
113+
110114
// We don't need to save the raw_data of the response object!
111115
$result['raw_data'] = null;
112116

113117
$result_object_types = [
114-
'total_count' => 'UserProfilePhotos', //Response from getUserProfilePhotos
115-
'stickers' => 'StickerSet', //Response from getStickerSet
116-
'file_id' => 'File', //Response from getFile
117-
'title' => 'Chat', //Response from getChat
118-
'username' => 'User', //Response from getMe
119-
'user' => 'ChatMember', //Response from getChatMember
120-
'url' => 'WebhookInfo', //Response from getWebhookInfo
118+
'getChat' => Chat::class,
119+
'getChatMember' => ChatMember::class,
120+
'getFile' => File::class,
121+
'getMe' => User::class,
122+
'getStickerSet' => StickerSet::class,
123+
'getUserProfilePhotos' => UserProfilePhotos::class,
124+
'getWebhookInfo' => WebhookInfo::class,
121125
];
122-
foreach ($result_object_types as $type => $object_class) {
123-
if (isset($result[$type])) {
124-
$object_class = __NAMESPACE__ . '\\' . $object_class;
125126

126-
return new $object_class($result);
127-
}
128-
}
127+
$object_class = array_key_exists($action, $result_object_types) ? $result_object_types[$action] : Message::class;
129128

130-
//Response from sendMessage
131-
return new Message($result, $bot_username);
129+
return new $object_class($result, $bot_username);
132130
}
133131

134132
/**
@@ -137,28 +135,26 @@ private function createResultObject($result, $bot_username)
137135
* @param array $result
138136
* @param string $bot_username
139137
*
140-
* @return null|\Longman\TelegramBot\Entities\ChatMember[]|\Longman\TelegramBot\Entities\Update[]
141-
* @throws \Longman\TelegramBot\Exception\TelegramException
138+
* @return \Longman\TelegramBot\Entities\ChatMember[]|\Longman\TelegramBot\Entities\Games\GameHighScore[]|\Longman\TelegramBot\Entities\Message[]|\Longman\TelegramBot\Entities\Update[]
142139
*/
143-
private function createResultObjects($result, $bot_username)
140+
private function createResultObjects(array $result, $bot_username)
144141
{
145142
$results = [];
146-
if (isset($result[0]['user'])) {
147-
//Response from getChatAdministrators
148-
foreach ($result as $user) {
149-
// We don't need to save the raw_data of the response object!
150-
$user['raw_data'] = null;
143+
$action = Request::getCurrentAction();
151144

152-
$results[] = new ChatMember($user);
153-
}
154-
} else {
155-
//Get Update
156-
foreach ($result as $update) {
157-
// We don't need to save the raw_data of the response object!
158-
$update['raw_data'] = null;
145+
$result_object_types = [
146+
'getChatAdministrators' => ChatMember::class,
147+
'getGameHighScores' => GameHighScore::class,
148+
'sendMediaGroup' => Message::class,
149+
];
159150

160-
$results[] = new Update($update, $bot_username);
161-
}
151+
$object_class = array_key_exists($action, $result_object_types) ? $result_object_types[$action] : Update::class;
152+
153+
foreach ($result as $data) {
154+
// We don't need to save the raw_data of the response object!
155+
$data['raw_data'] = null;
156+
157+
$results[] = new $object_class($data, $bot_username);
162158
}
163159

164160
return $results;

src/Request.php

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
use GuzzleHttp\Client;
1414
use GuzzleHttp\Exception\RequestException;
15+
use GuzzleHttp\Psr7\Stream;
1516
use Longman\TelegramBot\Entities\File;
17+
use Longman\TelegramBot\Entities\InputMedia\InputMedia;
1618
use Longman\TelegramBot\Entities\ServerResponse;
1719
use Longman\TelegramBot\Exception\InvalidBotTokenException;
1820
use Longman\TelegramBot\Exception\TelegramException;
@@ -126,6 +128,13 @@ class Request
126128
*/
127129
private static $limiter_interval;
128130

131+
/**
132+
* Get the current action that is being executed
133+
*
134+
* @var string
135+
*/
136+
private static $current_action;
137+
129138
/**
130139
* Available actions to send
131140
*
@@ -213,6 +222,30 @@ class Request
213222
'getMe',
214223
];
215224

225+
/**
226+
* Available fields for InputFile helper
227+
*
228+
* This is basically the list of all fields that allow InputFile objects
229+
* for which input can be simplified by providing local path directly as string.
230+
*
231+
* @var array
232+
*/
233+
private static $input_file_fields = [
234+
'setWebhook' => ['certificate'],
235+
'sendPhoto' => ['photo'],
236+
'sendAudio' => ['audio', 'thumb'],
237+
'sendDocument' => ['document', 'thumb'],
238+
'sendVideo' => ['video', 'thumb'],
239+
'sendAnimation' => ['animation', 'thumb'],
240+
'sendVoice' => ['voice', 'thumb'],
241+
'sendVideoNote' => ['video_note', 'thumb'],
242+
'setChatPhoto' => ['photo'],
243+
'sendSticker' => ['sticker'],
244+
'uploadStickerFile' => ['png_sticker'],
245+
'createNewStickerSet' => ['png_sticker'],
246+
'addStickerToSet' => ['png_sticker'],
247+
];
248+
216249
/**
217250
* Initialize
218251
*
@@ -318,29 +351,116 @@ public static function generateGeneralFakeServerResponse(array $data = [])
318351
* @param array $data
319352
*
320353
* @return array
354+
* @throws TelegramException
321355
*/
322356
private static function setUpRequestParams(array $data)
323357
{
324358
$has_resource = false;
325359
$multipart = [];
326360

327-
// Convert any nested arrays into JSON strings.
328-
array_walk($data, function (&$item) {
329-
is_array($item) && $item = json_encode($item);
330-
});
361+
foreach ($data as $key => &$item) {
362+
if ($key === 'media') {
363+
// Magical media input helper.
364+
$item = self::mediaInputHelper($item, $has_resource, $multipart);
365+
} elseif (array_key_exists(self::$current_action, self::$input_file_fields) && in_array($key, self::$input_file_fields[self::$current_action], true)) {
366+
// Allow absolute paths to local files.
367+
if (is_string($item) && file_exists($item)) {
368+
$item = new Stream(self::encodeFile($item));
369+
}
370+
} elseif (is_array($item)) {
371+
// Convert any nested arrays into JSON strings.
372+
$item = json_encode($item);
373+
}
331374

332-
//Reformat data array in multipart way if it contains a resource
333-
foreach ($data as $key => $item) {
334-
$has_resource |= (is_resource($item) || $item instanceof \GuzzleHttp\Psr7\Stream);
375+
// Reformat data array in multipart way if it contains a resource
376+
$has_resource |= (is_resource($item) || $item instanceof Stream);
335377
$multipart[] = ['name' => $key, 'contents' => $item];
336378
}
379+
337380
if ($has_resource) {
338381
return ['multipart' => $multipart];
339382
}
340383

341384
return ['form_params' => $data];
342385
}
343386

387+
/**
388+
* Magical input media helper to simplify passing media.
389+
*
390+
* This allows the following:
391+
* Request::editMessageMedia([
392+
* ...
393+
* 'media' => new InputMediaPhoto([
394+
* 'caption' => 'Caption!',
395+
* 'media' => Request::encodeFile($local_photo),
396+
* ]),
397+
* ]);
398+
* and
399+
* Request::sendMediaGroup([
400+
* 'media' => [
401+
* new InputMediaPhoto(['media' => Request::encodeFile($local_photo_1)]),
402+
* new InputMediaPhoto(['media' => Request::encodeFile($local_photo_2)]),
403+
* new InputMediaVideo(['media' => Request::encodeFile($local_video_1)]),
404+
* ],
405+
* ]);
406+
* and even
407+
* Request::sendMediaGroup([
408+
* 'media' => [
409+
* new InputMediaPhoto(['media' => $local_photo_1]),
410+
* new InputMediaPhoto(['media' => $local_photo_2]),
411+
* new InputMediaVideo(['media' => $local_video_1]),
412+
* ],
413+
* ]);
414+
*
415+
* @param mixed $item
416+
* @param bool $has_resource
417+
* @param array $multipart
418+
*
419+
* @return mixed
420+
*/
421+
private static function mediaInputHelper($item, &$has_resource, array &$multipart)
422+
{
423+
$was_array = is_array($item);
424+
$was_array || $item = [$item];
425+
426+
foreach ($item as $media_item) {
427+
if (!($media_item instanceof InputMedia)) {
428+
continue;
429+
}
430+
431+
$media = $media_item->getMedia();
432+
433+
// Allow absolute paths to local files.
434+
if (is_string($media) && file_exists($media)) {
435+
$media = new Stream(self::encodeFile($media));
436+
}
437+
438+
if (is_resource($media) || $media instanceof Stream) {
439+
$has_resource = true;
440+
$rnd_key = uniqid('media_', false);
441+
$multipart[] = ['name' => $rnd_key, 'contents' => $media];
442+
443+
// We're literally overwriting the passed media data!
444+
$media_item->media = 'attach://' . $rnd_key;
445+
$media_item->raw_data['media'] = 'attach://' . $rnd_key;
446+
}
447+
}
448+
449+
$was_array || $item = reset($item);
450+
451+
return json_encode($item);
452+
}
453+
454+
/**
455+
* Get the current action that's being executed
456+
*
457+
* @return string
458+
*/
459+
public static function getCurrentAction()
460+
{
461+
return self::$current_action;
462+
}
463+
344464
/**
345465
* Execute HTTP Request
346466
*
@@ -373,7 +493,7 @@ public static function execute($action, array $data = [])
373493
TelegramLog::update($result);
374494
}
375495
} catch (RequestException $e) {
376-
$result = ($e->getResponse()) ? (string) $e->getResponse()->getBody() : '';
496+
$result = $e->getResponse() ? (string) $e->getResponse()->getBody() : '';
377497
} finally {
378498
//Logging verbose debug output
379499
TelegramLog::endDebugLogTempStream('Verbose HTTP Request output:' . PHP_EOL . '%s' . PHP_EOL);
@@ -470,8 +590,11 @@ public static function send($action, array $data = [])
470590

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

593+
// Remember which action is currently being executed.
594+
self::$current_action = $action;
595+
473596
$raw_response = self::execute($action, $data);
474-
$response = json_decode($raw_response, true);
597+
$response = json_decode($raw_response, true);
475598

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

610+
// Reset current action after completion.
611+
self::$current_action = null;
612+
487613
return $response;
488614
}
489615

tests/unit/Entities/ServerResponseTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
*/
2626
class ServerResponseTest extends TestCase
2727
{
28+
protected function setUp()
29+
{
30+
// Make sure the current action in the Request class is unset.
31+
TestHelpers::setStaticProperty(Request::class, 'current_action', null);
32+
}
33+
2834
public function sendMessageOk()
2935
{
3036
return '{
@@ -193,6 +199,7 @@ public function testGetUpdatesEmpty()
193199

194200
public function getUserProfilePhotos()
195201
{
202+
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getUserProfilePhotos');
196203
return '{
197204
"ok":true,
198205
"result":{
@@ -238,6 +245,7 @@ public function testGetUserProfilePhotos()
238245

239246
public function getFile()
240247
{
248+
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getFile');
241249
return '{
242250
"ok":true,
243251
"result":{
@@ -301,6 +309,7 @@ public function testSetGeneralTestFakeResponse()
301309

302310
public function getStickerSet()
303311
{
312+
TestHelpers::setStaticProperty(Request::class, 'current_action', 'getStickerSet');
304313
return '{
305314
"ok":true,
306315
"result":{

0 commit comments

Comments
 (0)