Improve tests

This commit is contained in:
Daniil Gentili 2020-02-09 16:24:57 +01:00
parent 3b21304d8b
commit 47228712a6
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
11 changed files with 276 additions and 74 deletions

View File

@ -31,7 +31,7 @@
"amphp/http-client-cookies": "^1", "amphp/http-client-cookies": "^1",
"amphp/uri": "^0.1", "amphp/uri": "^0.1",
"danog/tg-file-decoder": "^0.1", "danog/tg-file-decoder": "^0.1",
"danog/magicalserializer": "^1.0", "danog/magicalserializer": "^1.0"
}, },
"require-dev": { "require-dev": {
"vlucas/phpdotenv": "^3", "vlucas/phpdotenv": "^3",
@ -41,7 +41,7 @@
"amphp/php-cs-fixer-config": "dev-master", "amphp/php-cs-fixer-config": "dev-master",
"haydenpierce/class-finder": "^0.4", "haydenpierce/class-finder": "^0.4",
"amphp/http-server": "dev-master", "amphp/http-server": "dev-master",
"amphp/http": "^1.6" "amphp/http": "^1.6",
"amphp/websocket-client": "dev-master as 1", "amphp/websocket-client": "dev-master as 1",
"amphp/websocket": "dev-master as 1", "amphp/websocket": "dev-master as 1",
"ext-ctype": "*", "ext-ctype": "*",

View File

@ -19,6 +19,8 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation * @link https://docs.madelineproto.xyz MadelineProto documentation
*/ */
use Amp\Http\Server\HttpServer;
use danog\MadelineProto\API;
use danog\MadelineProto\Logger; use danog\MadelineProto\Logger;
use danog\MadelineProto\RPCErrorException; use danog\MadelineProto\RPCErrorException;
use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriException;
@ -47,17 +49,32 @@ class EventHandler extends \danog\MadelineProto\EventHandler
"Max 1.5GB, parallel upload and download powered by @MadelineProto."; "Max 1.5GB, parallel upload and download powered by @MadelineProto.";
const ADMIN = 'danogentili'; const ADMIN = 'danogentili';
/**
* Whether to allow uploads.
*/
private $UPLOAD;
/** /**
* Array of media objects. * Array of media objects.
* *
* @var array * @var array
*/ */
private $states = []; private $states = [];
/**
* Constructor.
*
* @param API $API API
*/
public function __construct($API)
{
$this->UPLOAD = \class_exists(HttpServer::class);
parent::__construct($API);
}
public function onUpdateNewChannelMessage($update) public function onUpdateNewChannelMessage($update)
{ {
//yield $this->onUpdateNewMessage($update); //yield $this->onUpdateNewMessage($update);
} }
public function report($message) public function report(string $message)
{ {
try { try {
$this->messages->sendMessage(['peer' => self::ADMIN, 'message' => $message]); $this->messages->sendMessage(['peer' => self::ADMIN, 'message' => $message]);
@ -79,10 +96,21 @@ class EventHandler extends \danog\MadelineProto\EventHandler
$peerId = $peer['bot_api_id']; $peerId = $peer['bot_api_id'];
$messageId = $update['message']['id']; $messageId = $update['message']['id'];
if ($this->UPLOAD && $update['message']['message'] === '/getUrl') {
yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a file: ', 'reply_to_msg_id' => $messageId]);
$this->states[$peerId] = $this->UPLOAD;
}
if ($update['message']['message'] === '/start') { if ($update['message']['message'] === '/start') {
return $this->messages->sendMessage(['peer' => $peerId, 'message' => self::START, 'parse_mode' => 'Markdown', 'reply_to_msg_id' => $messageId]); return $this->messages->sendMessage(['peer' => $peerId, 'message' => self::START, 'parse_mode' => 'Markdown', 'reply_to_msg_id' => $messageId]);
} }
if (isset($update['message']['media']['_']) && $update['message']['media']['_'] !== 'messageMediaWebPage') { if (isset($update['message']['media']['_']) && $update['message']['media']['_'] !== 'messageMediaWebPage') {
if ($this->UPLOAD && ($this->states[$peerId] ?? false) === $this->UPLOAD) {
$media = yield $this->getDownloadInfo($this->states[$peerId]);
unset($media['MessageMedia']);
$media = yield
unset($this->states[$peerId]);
return;
}
yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a new name for this file: ', 'reply_to_msg_id' => $messageId]); yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a new name for this file: ', 'reply_to_msg_id' => $messageId]);
$this->states[$peerId] = $update['message']['media']; $this->states[$peerId] = $update['message']['media'];
@ -184,7 +212,7 @@ $settings = [
] ]
], ],
'upload' => [ 'upload' => [
'allow_automatic_upload' => false, // IMPORTANT: for security reasons, upload by URL will still be allowed 'allow_automatic_upload' => false // IMPORTANT: for security reasons, upload by URL will still be allowed
], ],
]; ];

View File

@ -25,7 +25,7 @@ class Exception extends \Exception
public static $rollbar = true; public static $rollbar = true;
public function __toString() public function __toString()
{ {
return $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception' . ($this->message !== '' ? ': ' : '') . $this->message . ' in ' . $this->file . ':' . $this->line . PHP_EOL . \danog\MadelineProto\Magic::$revision . PHP_EOL . 'TL Trace:' . PHP_EOL . $this->getTLTrace(); return $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception'.($this->message !== '' ? ': ' : '').$this->message.' in '.$this->file.':'.$this->line.PHP_EOL.\danog\MadelineProto\Magic::$revision.PHP_EOL.'TL Trace:'.PHP_EOL.$this->getTLTrace();
} }
public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null) public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null)
{ {
@ -37,8 +37,10 @@ class Exception extends \Exception
$this->line = $line; $this->line = $line;
} }
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
if (\strpos($message, 'socket_accept') === false) { if (\strpos($message, 'socket_accept') === false
\danog\MadelineProto\Logger::log($message . ' in ' . \basename($this->file) . ':' . $this->line, \danog\MadelineProto\Logger::FATAL_ERROR); && !\in_array(\basename($this->file), ['PKCS8.php', 'PSS.php'])
) {
\danog\MadelineProto\Logger::log($message.' in '.\basename($this->file).':'.$this->line, \danog\MadelineProto\Logger::FATAL_ERROR);
} }
if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/tgseclib'])) { if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/tgseclib'])) {
return; return;
@ -50,17 +52,24 @@ class Exception extends \Exception
\Rollbar\Rollbar::log(\Rollbar\Payload\Level::error(), $this, \debug_backtrace(0)); \Rollbar\Rollbar::log(\Rollbar\Payload\Level::error(), $this, \debug_backtrace(0));
} }
} }
public static function extension(string $extensionName) /**
* Complain about missing extensions
*
* @param string $extensionName Extension name
*
* @return self
*/
public static function extension(string $extensionName): self
{ {
$additional = 'Try running sudo apt-get install php' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '-' . $extensionName . '.'; $additional = 'Try running sudo apt-get install php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'-'.$extensionName.'.';
if ($extensionName === 'libtgvoip') { if ($extensionName === 'libtgvoip') {
$additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.'; $additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.';
} elseif ($extensionName === 'prime') { } elseif ($extensionName === 'prime') {
$additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.'; $additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.';
} }
$message = 'MadelineProto requires the ' . $extensionName . ' extension to run. ' . $additional; $message = 'MadelineProto requires the '.$extensionName.' extension to run. '.$additional;
if (PHP_SAPI !== 'cli') { if (PHP_SAPI !== 'cli') {
echo $message . '<br>'; echo $message.'<br>';
} }
$file = 'MadelineProto'; $file = 'MadelineProto';
$line = 1; $line = 1;

View File

@ -194,6 +194,13 @@ class MTProto extends AsyncConstruct implements TLCallback
'msg_resend_ans_req', 'msg_resend_ans_req',
]; ];
const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 0]; const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 0];
const POWERED_BY = "<p><small>Powered by <a href='https://docs.madelineproto.xyz'>MadelineProto</a></small></p>";
const NO_CACHE = [
'Cache-Control' => ['no-store, no-cache, must-revalidate, max-age=0', 'post-check=0, pre-check=0'],
'Pragma' => 'no-cache'
];
/** /**
* Instance of wrapper API. * Instance of wrapper API.
* *

View File

@ -513,7 +513,7 @@ trait ResponseHandler
$r = isset($response['_']) ? $response['_'] : \json_encode($response); $r = isset($response['_']) ? $response['_'] : \json_encode($response);
$this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE); $this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE);
if ($botAPI) { if ($botAPI) {
$response = (yield from $this->MTProtoToBotAPI($response)); $response = (yield from $this->API->MTProtoToBotAPI($response));
} }
if (isset($this->outgoing_messages[$request_id]['promise'])) { if (isset($this->outgoing_messages[$request_id]['promise'])) {
// This should not happen but happens, should debug // This should not happen but happens, should debug

View File

@ -549,9 +549,8 @@ trait Files
$constructor = $constructor['MessageMedia']; $constructor = $constructor['MessageMedia'];
} elseif (isset($constructor['InputMedia'])) { } elseif (isset($constructor['InputMedia'])) {
return $constructor; return $constructor;
} else { } else if (isset($constructor['Chat']) || isset($constructor['User'])) {
$constructor = (yield from $this->getPwrChat($constructor['Chat'] ?? $constructor['User'])); throw new Exception("Chat photo file IDs can't be reused to resend chat photos, please use getPwrChat()['photo'], instead");
$constructor = $constructor['photo'];
} }
} }
switch ($constructor['_']) { switch ($constructor['_']) {
@ -800,9 +799,6 @@ trait Files
throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$messageMedia['_']); throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$messageMedia['_']);
} }
} }
private const POWERED_BY = "<p><small>Powered by <a href='https://docs.madelineproto.xyz'>MadelineProto</a></small></p>";
/** /**
* Download file to browser. * Download file to browser.
* *
@ -841,7 +837,7 @@ trait Files
\header("$key: $subValue"); \header("$key: $subValue");
} }
} }
http_response_code($result['code']); \http_response_code($result['code']);
if (!\in_array($result['code'], [Status::OK, Status::PARTIAL_CONTENT])) { if (!\in_array($result['code'], [Status::OK, Status::PARTIAL_CONTENT])) {
yield Tools::echo(self::getExplanation($result['code'])); yield Tools::echo(self::getExplanation($result['code']));
@ -913,10 +909,6 @@ trait Files
$body .= "</body></html>"; $body .= "</body></html>";
return $body; return $body;
} }
private const NO_CACHE = [
'Cache-Control' => ['no-store, no-cache, must-revalidate, max-age=0', 'post-check=0, pre-check=0'],
'Pragma' => 'no-cache'
];
/** /**
* Parse headers. * Parse headers.
* *

View File

@ -20,6 +20,11 @@
namespace danog\MadelineProto\MTProtoTools; namespace danog\MadelineProto\MTProtoTools;
use Amp\Http\Client\Request; use Amp\Http\Client\Request;
use danog\Decoder\FileId;
use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto;
use const danog\Decoder\PHOTO;
use const danog\Decoder\PROFILE_PHOTO;
/** /**
* Manages peers. * Manages peers.
@ -705,7 +710,7 @@ trait PeerHandler
* *
* @return \Generator<array> Chat object * @return \Generator<array> Chat object
*/ */
public function getPwrChat($id, $fullfetch = true, $send = true): \Generator public function getPwrChat($id, bool $fullfetch = true, bool $send = true): \Generator
{ {
$full = $fullfetch ? yield from $this->getFullInfo($id) : (yield from $this->getInfo($id)); $full = $fullfetch ? yield from $this->getFullInfo($id) : (yield from $this->getInfo($id));
$res = ['id' => $full['bot_api_id'], 'type' => $full['type']]; $res = ['id' => $full['bot_api_id'], 'type' => $full['type']];
@ -837,6 +842,34 @@ trait PeerHandler
if ($fullfetch || $send) { if ($fullfetch || $send) {
$this->storeDb($res); $this->storeDb($res);
} }
if (isset($res['photo'])) {
$photo = [];
foreach ([
'small' => $res['photo']['sizes'][0],
'big' => end($res['photo']['sizes']),
] as $type => $size) {
$fileId = new FileId;
$fileId->setId($res['photo']['id'] ?? 0);
$fileId->setAccessHash($res['photo']['access_hash'] ?? 0);
$fileId->setFileReference($res['photo']['file_reference'] ?? '');
$fileId->setDcId($res['photo']['dc_id']);
$fileId->setType(PROFILE_PHOTO);
$fileId->setLocalId($size['location']['local_id']);
$fileId->setVolumeId($size['location']['volume_id']);
$photoSize = new PhotoSizeSourceDialogPhoto;
$photoSize->setDialogId($res['id']);
$photoSize->setDialogPhotoSmall($type === 'small');
$photoSize->setDialogAccessHash($res['access_hash'] ?? 0);
$fileId->setPhotoSizeSource($photoSize);
$photo[$type.'_file_id'] = (string) $fileId;
$photo[$type.'_file_unique_id'] = $fileId->getUniqueBotAPI();
}
$res['photo'] += $photo;
}
return $res; return $res;
} }
private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res): \Generator private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res): \Generator

View File

@ -83,7 +83,6 @@ class RSA
*/ */
public function encrypt($data): string public function encrypt($data): string
{ {
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['rsa_encrypting'], Logger::VERBOSE);
return (new \tgseclib\Math\BigInteger((string) $data, 256))->powMod($this->e, $this->n)->toBytes(); return (new \tgseclib\Math\BigInteger((string) $data, 256))->powMod($this->e, $this->n)->toBytes();
} }
} }

View File

@ -19,8 +19,11 @@
namespace danog\MadelineProto\TL\Conversion; namespace danog\MadelineProto\TL\Conversion;
use danog\Decoder\FileId;
use danog\MadelineProto\Logger; use danog\MadelineProto\Logger;
use const danog\Decoder\TYPES_IDS;
trait BotAPI trait BotAPI
{ {
private function htmlEntityDecode(string $stuff): string private function htmlEntityDecode(string $stuff): string
@ -173,17 +176,16 @@ trait BotAPI
/** /**
* Convert MTProto parameters to bot API parameters. * Convert MTProto parameters to bot API parameters.
* *
* @param array $data Data * @param array $data Data
* @param array $sent_arguments Sent arguments
* *
* @return \Generator<array> * @return \Generator<array>
*/ */
public function MTProtoToBotAPI(array $data, array $sent_arguments = []): \Generator public function MTProtoToBotAPI(array $data): \Generator
{ {
$newd = []; $newd = [];
if (!isset($data['_'])) { if (!isset($data['_'])) {
foreach ($data as $key => $element) { foreach ($data as $key => $element) {
$newd[$key] = (yield from $this->MTProtoToBotAPI($element, $sent_arguments)); $newd[$key] = (yield from $this->MTProtoToBotAPI($element));
} }
return $newd; return $newd;
} }
@ -191,18 +193,20 @@ trait BotAPI
case 'updateShortSentMessage': case 'updateShortSentMessage':
$newd['message_id'] = $data['id']; $newd['message_id'] = $data['id'];
$newd['date'] = $data['date']; $newd['date'] = $data['date'];
$newd['text'] = $sent_arguments['message']; $newd['text'] = $data['request']['message'];
if ($data['out']) { if ($data['out']) {
$newd['from'] = (yield from $this->getPwrChat($this->authorization['user'])); $newd['from'] = (yield from $this->getPwrChat($this->authorization['user']));
} }
$newd['chat'] = (yield from $this->getPwrChat($sent_arguments['peer'])); $newd['chat'] = yield from $this->getPwrChat($data['request']['peer']);
if (isset($data['entities'])) { if (isset($data['entities'])) {
$newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities'], $sent_arguments)); $newd['entities'] = yield from $this->MTProtoToBotAPI($data['entities']);
} }
if (isset($data['media'])) { if (isset($data['media'])) {
$newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media'], $sent_arguments)); $newd += yield from $this->MTProtoToBotAPI($data['media']);
} }
return $newd; return $newd;
case 'updates':
$data = array_values(array_filter($data['updates'], fn(array $update) => $update['_'] !== 'updateMessageID'))[0];
case 'updateNewChannelMessage': case 'updateNewChannelMessage':
case 'updateNewMessage': case 'updateNewMessage':
return yield from $this->MTProtoToBotAPI($data['message']); return yield from $this->MTProtoToBotAPI($data['message']);
@ -217,7 +221,7 @@ trait BotAPI
} }
$newd['chat'] = (yield from $this->getPwrChat($data['to_id'])); $newd['chat'] = (yield from $this->getPwrChat($data['to_id']));
if (isset($data['entities'])) { if (isset($data['entities'])) {
$newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities'], $sent_arguments)); $newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities']));
} }
if (isset($data['views'])) { if (isset($data['views'])) {
$newd['views'] = $data['views']; $newd['views'] = $data['views'];
@ -241,7 +245,7 @@ trait BotAPI
$newd['forward_from_message_id'] = $data['fwd_from']['channel_post']; $newd['forward_from_message_id'] = $data['fwd_from']['channel_post'];
} }
if (isset($data['media'])) { if (isset($data['media'])) {
$newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media'], $sent_arguments)); $newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media']));
} }
return $newd; return $newd;
case 'messageEntityMention': case 'messageEntityMention':
@ -297,7 +301,7 @@ trait BotAPI
$res['photo'] = []; $res['photo'] = [];
foreach ($data['photo']['sizes'] as $key => $photo) { foreach ($data['photo']['sizes'] as $key => $photo) {
if (\in_array($photo['_'], ['photoCachedSize', 'photoSize'])) { if (\in_array($photo['_'], ['photoCachedSize', 'photoSize'])) {
$res['photo'][$key] = (yield from $this->photosizeToBotAPI($photo, $data['photo'])); $res['photo'][$key] = $this->photosizeToBotAPI($photo, $data['photo']);
} }
} }
return $res; return $res;
@ -307,21 +311,18 @@ trait BotAPI
$type_name = 'document'; $type_name = 'document';
$res = []; $res = [];
if (isset($data['document']['thumbs']) && $data['document']['thumbs'] && \in_array(\end($data['document']['thumbs'])['_'], ['photoCachedSize', 'photoSize'])) { if (isset($data['document']['thumbs']) && $data['document']['thumbs'] && \in_array(\end($data['document']['thumbs'])['_'], ['photoCachedSize', 'photoSize'])) {
$res['thumb'] = (yield from $this->photosizeToBotAPI(\end($data['document']['thumbs']), [], true)); $res['thumb'] = $this->photosizeToBotAPI(\end($data['document']['thumbs']), $data['document'], true);
} }
foreach ($data['document']['attributes'] as $attribute) { foreach ($data['document']['attributes'] as $attribute) {
switch ($attribute['_']) { switch ($attribute['_']) {
case 'documentAttributeFilename': case 'documentAttributeFilename':
$pathinfo = \pathinfo($attribute['file_name']); $pathinfo = \pathinfo($attribute['file_name']);
$res['ext'] = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; $res['ext'] = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : '';
$res['file_name'] = $pathinfo['filename']; $res['file_name'] = $pathinfo['filename'];
break; break;
case 'documentAttributeAudio': case 'documentAttributeAudio':
$audio = $attribute; $audio = $attribute;
$type_name = 'audio'; $type_name = $attribute['voice'] ? 'voice' :'audio';
if ($attribute['voice']) {
$type_name = 'voice';
}
$res['duration'] = $attribute['duration']; $res['duration'] = $attribute['duration'];
if (isset($attribute['performer'])) { if (isset($attribute['performer'])) {
$res['performer'] = $attribute['performer']; $res['performer'] = $attribute['performer'];
@ -344,7 +345,7 @@ trait BotAPI
$res['height'] = $attribute['h']; $res['height'] = $attribute['h'];
break; break;
case 'documentAttributeAnimated': case 'documentAttributeAnimated':
$type_name = 'gif'; $type_name = 'animation';
$res['animated'] = true; $res['animated'] = true;
break; break;
case 'documentAttributeHasStickers': case 'documentAttributeHasStickers':
@ -364,24 +365,32 @@ trait BotAPI
if (isset($audio) && isset($audio['title']) && !isset($res['file_name'])) { if (isset($audio) && isset($audio['title']) && !isset($res['file_name'])) {
$res['file_name'] = $audio['title']; $res['file_name'] = $audio['title'];
if (isset($audio['performer'])) { if (isset($audio['performer'])) {
$res['file_name'] .= ' - ' . $audio['performer']; $res['file_name'] .= ' - '.$audio['performer'];
} }
} }
if (!isset($res['file_name'])) { if (!isset($res['file_name'])) {
$res['file_name'] = $data['document']['access_hash']; $res['file_name'] = $data['document']['access_hash'];
} }
$res['file_name'] .= '_' . $data['document']['id']; $res['file_name'] .= '_'.$data['document']['id'];
if (isset($res['ext'])) { if (isset($res['ext'])) {
$res['file_name'] .= $res['ext']; $res['file_name'] .= $res['ext'];
unset($res['ext']); unset($res['ext']);
} else { } else {
$res['file_name'] .= $this->getExtensionFromMime($data['document']['mime_type']); $res['file_name'] .= $this->getExtensionFromMime($data['document']['mime_type']);
} }
$data['document']['_'] = 'bot_' . $type_name;
$res['file_size'] = $data['document']['size']; $res['file_size'] = $data['document']['size'];
$res['mime_type'] = $data['document']['mime_type']; $res['mime_type'] = $data['document']['mime_type'];
$res['file_id'] = \danog\MadelineProto\Tools::base64urlEncode(\danog\MadelineProto\Tools::rleEncode(yield from $this->TL->serializeObject(['type' => 'File'], $data['document'], 'File') . \chr(2)));
return [$type_name => $res, 'caption' => isset($data['caption']) ? $data['caption'] : '']; $fileId = new FileId;
$fileId->setId($data['document']['id']);
$fileId->setAccessHash($data['document']['access_hash']);
$fileId->setFileReference($data['document']['file_reference'] ?? '');
$fileId->setDcId($data['document']['dc_id']);
$fileId->setType(TYPES_IDS[$type_name]);
$res['file_id'] = (string) $fileId;
$res['file_unique_id'] = $fileId->getUniqueBotAPI();
return [$type_name => $res, 'caption' => $data['caption'] ?? ''];
default: default:
throw new Exception(\sprintf(\danog\MadelineProto\Lang::$current_lang['botapi_conversion_error'], $data['_'])); throw new Exception(\sprintf(\danog\MadelineProto\Lang::$current_lang['botapi_conversion_error'], $data['_']));
} }
@ -586,7 +595,7 @@ trait BotAPI
$multiple_args = [$multiple_args_base]; $multiple_args = [$multiple_args_base];
$i = 0; $i = 0;
foreach ($text_arr as $word) { foreach ($text_arr as $word) {
if ($this->mbStrlen($multiple_args[$i]['message'] . $word) <= $max_length) { if ($this->mbStrlen($multiple_args[$i]['message'].$word) <= $max_length) {
$multiple_args[$i]['message'] .= $word; $multiple_args[$i]['message'] .= $word;
} else { } else {
$i++; $i++;
@ -670,7 +679,7 @@ trait BotAPI
foreach ($initialArray as $item) { foreach ($initialArray as $item) {
$delimOffset += $this->mbStrlen($item); $delimOffset += $this->mbStrlen($item);
//if ($this->mbStrlen($item) > 0) { //if ($this->mbStrlen($item) > 0) {
$finalArray[] = $item . ($delimOffset < $this->mbStrlen($string) ? $string[$delimOffset] : ''); $finalArray[] = $item.($delimOffset < $this->mbStrlen($string) ? $string[$delimOffset] : '');
//} //}
$delimOffset++; $delimOffset++;
} }

View File

@ -39,17 +39,38 @@ use const danog\Decoder\VOICE;
trait BotAPIFiles trait BotAPIFiles
{ {
private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false): \Generator private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false): array
{ {
$ext = '.jpg'; $fileId = new FileId;
//$this->getExtensionFromLocation(['_' => 'inputFileLocation', 'volume_id' => $photoSize['location']['volume_id'], 'local_id' => $photoSize['location']['local_id'], 'secret' => $photoSize['location']['secret'], 'dc_id' => $photoSize['location']['dc_id']], '.jpg'); $fileId->setId($photo['id'] ?? 0);
$photoSize['location']['access_hash'] = $photo['access_hash'] ?? 0; $fileId->setAccessHash($photo['access_hash'] ?? 0);
$photoSize['location']['id'] = $photo['id'] ?? 0; $fileId->setFileReference($photo['file_reference'] ?? '');
$photoSize['location']['secret'] = $photo['location']['secret'] ?? 0; $fileId->setDcId($photo['dc_id'] ?? 0);
$photoSize['location']['dc_id'] = $photo['dc_id'] ?? 0;
$photoSize['location']['_'] = $thumbnail ? 'bot_thumbnail' : 'bot_photo'; $fileId->setLocalId($photoSize['location']['local_id']);
$data = (yield from $this->TL->serializeObject(['type' => 'File'], $photoSize['location'], 'File')).\chr(2); $fileId->setVolumeId($photoSize['location']['volume_id']);
return ['file_id' => \danog\MadelineProto\Tools::base64urlEncode(\danog\MadelineProto\Tools::rleEncode($data)), 'width' => $photoSize['w'], 'height' => $photoSize['h'], 'file_size' => isset($photoSize['size']) ? $photoSize['size'] : \strlen($photoSize['bytes']), 'mime_type' => 'image/jpeg', 'file_name' => $photoSize['location']['volume_id'].'_'.$photoSize['location']['local_id'].$ext];
$photoSizeSource = new PhotoSizeSourceThumbnail;
$photoSizeSource->setThumbType($photoSize['type']);
if ($photo['_'] === 'photo') {
$photoSizeSource->setThumbFileType(PHOTO);
$fileId->setType(PHOTO);
} else {
$photoSizeSource->setThumbFileType(THUMBNAIL);
$fileId->setType(THUMBNAIL);
}
$fileId->setPhotoSizeSource($photoSizeSource);
return [
'file_id' => (string) $fileId,
'file_unique_id' => $fileId->getUniqueBotAPI(),
'width' => $photoSize['w'],
'height' => $photoSize['h'],
'file_size' => $photoSize['size'] ?? \strlen($photoSize['bytes']),
'mime_type' => 'image/jpeg',
'file_name' => $photoSize['location']['volume_id'].'_'.$photoSize['location']['local_id'].'.jpg'
];
} }
/** /**
* Unpack bot API file ID. * Unpack bot API file ID.

View File

@ -40,6 +40,54 @@ class FileIdTest extends TestCase
self::$MadelineProto->botLogin(\getenv('BOT_TOKEN')); self::$MadelineProto->botLogin(\getenv('BOT_TOKEN'));
} }
/**
* Strip file reference from file ID.
*
* @param string $fileId File ID
*
* @return string
*/
public static function stripFileReference(string $fileId): string
{
return FileId::fromBotAPI($fileId)->setFileReference('');
}
/**
* Strip access hash (and possibly ID) from file ID.
*
* @param string $fileId File ID
*
* @return string
*/
public static function stripForChat(string $fileId): string
{
$file = FileId::fromBotAPI($fileId)->setAccessHash(0);
if ($file->getPhotoSizeSource()->getDialogId() < 0) {
$file->setId(0);
}
return $file;
}
/**
* Asserts that two file IDs are equal.
*
* @param string $fileIdAstr File ID A
* @param string $fileIdBstr File ID B
* @param string $message Message
*
* @throws PHPUnit\Framework\AssertionFailedError
*
* @return void
*/
public static function assertFileIdEquals(string $fileIdAstr, string $fileIdBstr, $message = '')
{
$fileIdAstr = self::stripFileReference($fileIdAstr);
$fileIdBstr = self::stripFileReference($fileIdBstr);
if ($fileIdAstr !== $fileIdBstr) {
\var_dump(FileId::fromBotAPI($fileIdAstr), FileId::fromBotAPI($fileIdBstr));
}
self::assertEquals($fileIdAstr, $fileIdBstr, $message);
}
/** /**
* @param string $fileId File ID * @param string $fileId File ID
* @param string $type Expected type * @param string $type Expected type
@ -47,7 +95,7 @@ class FileIdTest extends TestCase
* *
* @dataProvider provideFileIdsAndType * @dataProvider provideFileIdsAndType
*/ */
public function testDownload(string $type, string $fileIdStr, string $origType) public function testDownload(string $type, string $fileIdStr, string $uniqueFileIdStr, array $fullInfo)
{ {
self::$MadelineProto->logger("Trying to download $fileIdStr"); self::$MadelineProto->logger("Trying to download $fileIdStr");
self::$MadelineProto->downloadToFile($fileIdStr, '/dev/null'); self::$MadelineProto->downloadToFile($fileIdStr, '/dev/null');
@ -60,16 +108,62 @@ class FileIdTest extends TestCase
* *
* @dataProvider provideFileIdsAndType * @dataProvider provideFileIdsAndType
*/ */
public function testResend(string $type, string $fileIdStr, string $origType) public function testResendConvert(string $type, string $fileIdStr, string $uniqueFileIdStr, array $fullInfo)
{ {
self::$MadelineProto->logger("Trying to resend $fileIdStr"); self::$MadelineProto->logger("Trying to resend and then reconvert $fileIdStr");
self::$MadelineProto->messages->sendMedia( if ($type === 'profile_photo') {
$chat = self::$MadelineProto->getPwrChat($fullInfo['chat']);
$this->assertArrayHasKey('photo', $chat);
$chat = $chat['photo'];
$this->assertArrayHasKey($fullInfo['type'].'_file_id', $chat);
$this->assertArrayHasKey($fullInfo['type'].'_file_unique_id', $chat);
$chat[$fullInfo['type'].'_file_id'] = self::stripForChat($chat[$fullInfo['type'].'_file_id']);
$this->assertFileIdEquals($fileIdStr, $chat[$fullInfo['type'].'_file_id']);
$this->assertEquals($uniqueFileIdStr, $chat[$fullInfo['type'].'_file_unique_id']);
$this->expectExceptionMessage("Chat photo file IDs can't be reused to resend chat photos, please use getPwrChat()['photo'], instead");
}
$res = self::$MadelineProto->messages->sendMedia(
[ [
'peer' => \getenv('DEST'), 'peer' => \getenv('DEST'),
'media' => $fileIdStr 'media' => $fileIdStr
],
[
'botAPI' => true
] ]
); );
$this->assertTrue(true); if ($type === 'thumbnail') {
$this->assertArrayHasKey($fullInfo[0], $res);
$res = $res[$fullInfo[0]];
$this->assertArrayHasKey('thumb', $res);
$this->assertFileIdEquals($fileIdStr, $res['thumb']['file_id']);
$this->assertEquals($uniqueFileIdStr, $res['thumb']['file_unique_id']);
list($type, $fileIdStr, $uniqueFileIdStr) = $fullInfo;
} else {
$this->assertArrayHasKey($type, $res);
$res = $res[$type];
}
$hasFileId = false;
$hasFileUniqueId = false;
$res = $type === 'photo' ? $res : [$res];
foreach ($res as $subRes) {
$this->assertArrayHasKey('file_id', $subRes);
$this->assertArrayHasKey('file_unique_id', $subRes);
$hasFileId |= self::stripFileReference($fileIdStr) === self::stripFileReference($subRes['file_id']);
$hasFileUniqueId |= $uniqueFileIdStr === $subRes['file_unique_id'];
}
if (\count($res) === 1) {
$this->assertFileIdEquals($fileIdStr, $res[0]['file_id']);
$this->assertEquals($uniqueFileIdStr, $res[0]['file_unique_id']);
} else {
$this->assertTrue((bool) $hasFileUniqueId);
$this->assertTrue((bool) $hasFileId);
}
} }
public function provideFileIdsAndType(): \Generator public function provideFileIdsAndType(): \Generator
@ -77,16 +171,21 @@ class FileIdTest extends TestCase
$dest = \getenv('DEST'); $dest = \getenv('DEST');
$token = \getenv('BOT_TOKEN'); $token = \getenv('BOT_TOKEN');
foreach ($this->provideChats() as $chat) { foreach ($this->provideChats() as $chat) {
$result = \json_decode(\file_get_contents("https://api.telegram.org/bot$token/getChat?chat_id=$chat"), true)['result']['photo']; $result = \json_decode(\file_get_contents("https://api.telegram.org/bot$token/getChat?chat_id=$chat"), true)['result']['photo'] ?? [];
if (!$result) {
continue;
}
yield [ yield [
'profile_photo', 'profile_photo',
$result['small_file_id'], $result['small_file_id'],
'profile_photo', $result['small_file_unique_id'],
['chat' => $chat, 'type' => 'small'],
]; ];
yield [ yield [
'profile_photo', 'profile_photo',
$result['big_file_id'], $result['big_file_id'],
'profile_photo', $result['big_file_unique_id'],
['chat' => $chat, 'type' => 'big'],
]; ];
} }
foreach ($this->provideUrls() as $type => $url) { foreach ($this->provideUrls() as $type => $url) {
@ -111,16 +210,18 @@ class FileIdTest extends TestCase
$botResult = [$botResult]; $botResult = [$botResult];
} }
foreach ($botResult as $subResult) { foreach ($botResult as $subResult) {
yield [ yield $full = [
$type, $type,
$subResult['file_id'], $subResult['file_id'],
$type, $subResult['file_unique_id'],
[],
]; ];
if (isset($subResult['thumb'])) { if (isset($subResult['thumb'])) {
yield [ yield [
'thumbnail', 'thumbnail',
$subResult['thumb']['file_id'], $subResult['thumb']['file_id'],
$type, $subResult['thumb']['file_unique_id'],
$full,
]; ];
} }
} }
@ -129,11 +230,11 @@ class FileIdTest extends TestCase
} }
public function provideChats(): array public function provideChats(): array
{ {
return [\getenv('DEST'), '@MadelineProto']; return [\getenv('DEST'), '@MadelineProto', -382346236];
} }
public function provideUrls(): array public function provideUrls(): array
{ {
return [ $res = [
'sticker' => 'https://github.com/danog/MadelineProto/blob/master/tests/lel.webp?raw=true', 'sticker' => 'https://github.com/danog/MadelineProto/blob/master/tests/lel.webp?raw=true',
'photo' => 'https://github.com/danog/MadelineProto/blob/master/tests/faust.jpg', 'photo' => 'https://github.com/danog/MadelineProto/blob/master/tests/faust.jpg',
'audio' => 'https://github.com/danog/MadelineProto/blob/master/tests/mosconi.mp3?raw=true', 'audio' => 'https://github.com/danog/MadelineProto/blob/master/tests/mosconi.mp3?raw=true',
@ -141,7 +242,10 @@ class FileIdTest extends TestCase
'animation' => 'https://github.com/danog/MadelineProto/blob/master/tests/pony.mp4?raw=true', 'animation' => 'https://github.com/danog/MadelineProto/blob/master/tests/pony.mp4?raw=true',
'document' => 'https://github.com/danog/danog.github.io/raw/master/lol/index_htm_files/0.gif', 'document' => 'https://github.com/danog/danog.github.io/raw/master/lol/index_htm_files/0.gif',
'voice' => 'https://daniil.it/audio_2020-02-01_18-09-08.ogg', 'voice' => 'https://daniil.it/audio_2020-02-01_18-09-08.ogg',
'video_note' => 'https://daniil.it/round.mp4'
]; ];
if (\getenv('TRAVIS_COMMIT')) {
$res['video_note'] = 'https://daniil.it/round.mp4';
}
return $res;
} }
} }