CombinedApi Support

This commit is contained in:
Alexander Pankratov 2020-01-11 23:53:58 +03:00
parent 4f0ee586e2
commit 420124ab64
5 changed files with 160 additions and 88 deletions

View File

@ -46,14 +46,16 @@ Fast, simple, async php telegram api server:
--help Show this message --help Show this message
-a --address Server ip (optional) (example: 127.0.0.1) -a --address Server ip (optional) (example: 127.0.0.1)
-p --port Server port (optional) (example: 9503) -p --port Server port (optional) (example: 9503)
-s --session Prefix for session file (optional) (example: xtrime) -s --session Prefix for session file (optional) (example: xtrime).
Multiple sessions can be used via CombinedAPI. Example "--session=user --session=bot"
If running multiple sessions, then "session" parameter must be provided with every request.
Also options can be set in .env file (see .env.example) Also options can be set in .env file (see .env.example)
``` ```
1. Access telegram api directly via simple get requests. 1. Access telegram api directly via simple get requests.
Rules: Rules:
* All methods from MadelineProto supported: [Methods List](https://docs.madelineproto.xyz/API_docs/methods/) * All methods from MadelineProto supported: [Methods List](https://docs.madelineproto.xyz/API_docs/methods/)
* Url: `http://%address%:%port%/api/%class%.%method%/?%param1%=%val%` * Url: `http://%address%:%port%/api/%class%.%method%/?%param1%=%val%[&session=%session%]`
* <b>Important: api available only from ip in whitelist.</b> * <b>Important: api available only from ip in whitelist.</b>
By default it is: `127.0.0.1` By default it is: `127.0.0.1`
You can add client ip in .env file to `API_CLIENT_WHITELIST` (use json format) You can add client ip in .env file to `API_CLIENT_WHITELIST` (use json format)
@ -63,6 +65,8 @@ Fast, simple, async php telegram api server:
`?data[peer]=@xtrime&data[message]=Hello!`. Order of parameters does't matter in this case. `?data[peer]=@xtrime&data[message]=Hello!`. Order of parameters does't matter in this case.
* If method requires one or multiple separate parameters (not inside array) then pass parameters with any names but **in strict order**: * If method requires one or multiple separate parameters (not inside array) then pass parameters with any names but **in strict order**:
`http://127.0.0.1:9503/api/getInfo/?id=@xtrime` or `http://127.0.0.1:9503/api/getInfo/?abcd=@xtrime` works the same `http://127.0.0.1:9503/api/getInfo/?id=@xtrime` or `http://127.0.0.1:9503/api/getInfo/?abcd=@xtrime` works the same
* CombinedAPI (multiple sessions) support. If running with multiple sessions use option 'session' to define which session to use for request:
`http://127.0.0.1:9503/api/getSelf/?session=xtrime` or `http://127.0.0.1:9503/api/getInfo/?id=@xtrime&session=xtrime`
Examples: Examples:
* get_info about channel/user: `http://127.0.0.1:9503/api/getInfo/?id=@xtrime` * get_info about channel/user: `http://127.0.0.1:9503/api/getInfo/?id=@xtrime`

14
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "75ba1bc8d195a8a0bee6e46757f5aa25", "content-hash": "8d2aca34a6b1620d5e86005db622b2dd",
"packages": [ "packages": [
{ {
"name": "amphp/amp", "name": "amphp/amp",
@ -1362,16 +1362,16 @@
}, },
{ {
"name": "danog/madelineproto", "name": "danog/madelineproto",
"version": "5.0.28", "version": "5.0.30",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/danog/MadelineProto.git", "url": "https://github.com/danog/MadelineProto.git",
"reference": "32f7b9a69d20f7fba3bed150d90ae46ec050e1fe" "reference": "daf98b698bce9a082b7a9f8bf52b268030f84674"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/danog/MadelineProto/zipball/32f7b9a69d20f7fba3bed150d90ae46ec050e1fe", "url": "https://api.github.com/repos/danog/MadelineProto/zipball/daf98b698bce9a082b7a9f8bf52b268030f84674",
"reference": "32f7b9a69d20f7fba3bed150d90ae46ec050e1fe", "reference": "daf98b698bce9a082b7a9f8bf52b268030f84674",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1454,7 +1454,7 @@
"telegram", "telegram",
"video" "video"
], ],
"time": "2020-01-05T17:43:47+00:00" "time": "2020-01-11T16:34:40+00:00"
}, },
{ {
"name": "danog/magicalserializer", "name": "danog/magicalserializer",
@ -2353,7 +2353,7 @@
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": ">=7.3.0", "php": ">=7.4.0",
"ext-json": "*" "ext-json": "*"
}, },
"platform-dev": [] "platform-dev": []

View File

@ -1,5 +1,7 @@
<?php <?php
chdir(__DIR__);
require_once __DIR__ . '/bootstrap.php'; require_once __DIR__ . '/bootstrap.php';
if (PHP_SAPI !== 'cli') { if (PHP_SAPI !== 'cli') {
@ -17,20 +19,24 @@ $options = getopt($shortopts, $longopts);
$options = [ $options = [
'address' => $options['address'] ?? $options['a'] ?? '', 'address' => $options['address'] ?? $options['a'] ?? '',
'port' => $options['port'] ?? $options['p'] ?? '', 'port' => $options['port'] ?? $options['p'] ?? '',
'session' => $options['session'] ?? $options['s'] ?? '', 'session' => (array) ($options['session'] ?? $options['s'] ?? ''),
'help' => isset($options['help']), 'help' => isset($options['help']),
]; ];
if ($options['help']) { if ($options['help']) {
$help = 'Fast, simple, async php telegram parser: MadelineProto + Swoole Server $help = 'Fast, simple, async php telegram parser: MadelineProto + Swoole Server
usage: php server.php [--help] [-a|--address=127.0.0.1] [-p|--port=9503] [-s|--session=] usage: php server.php [--help] [-a=|--address=127.0.0.1] [-p=|--port=9503] [-s=|--session=]
Options: Options:
--help Show this message --help Show this message
-a --address Server ip (optional) (example: 127.0.0.1) -a --address Server ip (optional) (example: 127.0.0.1)
-p --port Server port (optional) (example: 9503) -p --port Server port (optional) (example: 9503)
-s --session Prefix for session file (optional) (example: xtrime) -s --session Prefix for session file (optional) (example: xtrime).
Multiple sessions can be used via CombinedAPI. Example "--session=user --session=bot"
If running multiple sessions, then "session" parameter must be provided with every request.
See README for example requests.
Also all options can be set in .env file (see .env.example) Also all options can be set in .env file (see .env.example)
@ -41,11 +47,15 @@ Example:
echo $help; echo $help;
exit; exit;
} }
if ($options['session']) {
$sessionFile = "{$root}/{$options['session']}_session.madeline"; $sessionFiles = [];
} else { foreach ($options['session'] as $session) {
$sessionFile = "{$root}/session.madeline"; if (!$session) {
$session = 'session';
}
$session = TelegramApiServer\Client::getSessionFileName($session);
$sessionFiles[$session] = '';
} }
$client = new TelegramApiServer\Client($sessionFile); $client = new TelegramApiServer\Client($sessionFiles);
new TelegramApiServer\Server($client, $options); new TelegramApiServer\Server($client, $options);

View File

@ -10,42 +10,87 @@ class Client
{ {
use BotAPI; use BotAPI;
/** @var MadelineProto\API */ /** @var MadelineProto\CombinedAPI */
public $MadelineProto; public MadelineProto\CombinedAPI $MadelineProto;
private $sessionFile; private ?string $defaultSession = null;
private $config;
/** /**
* Client constructor. * Client constructor.
* *
* @param $sessionFile * @param array $sessions
*/ */
public function __construct($sessionFile) public function __construct(array $sessions)
{ {
$this->config = (array) Config::getInstance()->get('telegram'); $config = (array) Config::getInstance()->get('telegram');
if (empty($this->config['connection_settings']['all']['proxy_extra']['address'])) { if (empty($config['connection_settings']['all']['proxy_extra']['address'])) {
$this->config['connection_settings']['all']['proxy'] = '\Socket'; $config['connection_settings']['all']['proxy'] = '\Socket';
$this->config['connection_settings']['all']['proxy_extra'] = []; $config['connection_settings']['all']['proxy_extra'] = [];
} }
foreach ($sessions as &$session) {
$session = $config;
}
unset($session);
$this->sessionFile = $sessionFile; if (count($sessions) === 1) {
$this->connect(); $this->defaultSession = (string) array_key_first($sessions);
}
$this->connect($sessions);
} }
public function connect() /**
* @param string|null $session
*
* @return string|null
*/
public static function getSessionFileName(?string $session): ?string
{
return $session ? "{$session}.madeline" : null;
}
/**
* @param array $sessions
*/
public function connect(array $sessions): void
{ {
//При каждой инициализации настройки обновляются из массива $config //При каждой инициализации настройки обновляются из массива $config
echo PHP_EOL . 'Starting MadelineProto...' . PHP_EOL; echo PHP_EOL . 'Starting MadelineProto...' . PHP_EOL;
$time = microtime(true); $time = microtime(true);
$this->MadelineProto = new MadelineProto\API($this->sessionFile, $this->config); $this->MadelineProto = new MadelineProto\CombinedAPI('combined_session.madeline', $sessions);
$this->MadelineProto->async(true); $this->MadelineProto->async(true);
$this->MadelineProto->loop(function() { $this->MadelineProto->loop(function() use($sessions) {
yield $this->MadelineProto->start(); $res = [];
foreach ($sessions as $session => $message) {
MadelineProto\Logger::log("Starting session: {$session}", MadelineProto\Logger::WARNING);
$res[] = $this->MadelineProto->instances[$session]->start();
}
yield $this->MadelineProto->all($res);
}); });
$time = round(microtime(true) - $time, 3); $time = round(microtime(true) - $time, 3);
echo PHP_EOL . "TelegramApiServer ready. Elapsed time: $time sec." . PHP_EOL; $sessionsCount = count($sessions);
MadelineProto\Logger::log(
"\nTelegramApiServer ready."
."\nNumber of sessions: {$sessionsCount}."
."\nElapsed time: {$time} sec.\n",
MadelineProto\Logger::WARNING
);
}
/**
* @param string|null $session
*
* @return MadelineProto\API
*/
public function getInstance(?string $session): MadelineProto\API
{
$session = static::getSessionFileName($session) ?: $this->defaultSession;
if (!$session) {
throw new \InvalidArgumentException('Multiple sessions detected. You need to specify which session to use');
}
return $this->MadelineProto->instances[$session];
} }
/** /**
@ -65,10 +110,11 @@ class Client
* ] * ]
* </pre> * </pre>
* *
* @param string|null $session
*
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function getHistory(array $data): \Amp\Promise public function getHistory(array $data, ?string $session = null): \Amp\Promise
{ {
$data = array_merge( $data = array_merge(
[ [
@ -84,19 +130,21 @@ class Client
$data $data
); );
return $this->MadelineProto->messages->getHistory($data); return $this->getInstance($session)->messages->getHistory($data);
} }
/** /**
* @param $data * @param array $data
*
* @param string|null $session
* *
* @return \Amp\Promise * @return \Amp\Promise
*/ */
public function getHistoryHtml(array $data): \Amp\Promise public function getHistoryHtml(array $data, ?string $session): \Amp\Promise
{ {
return call( return call(
function() use ($data) { function() use ($data, $session) {
$response = yield $this->getHistory($data); $response = yield $this->getHistory($data, $session);
foreach ($response['messages'] as &$message) { foreach ($response['messages'] as &$message) {
$message['message'] = $this->formatMessage($message['message'] ?? null, $message['entities'] ?? []); $message['message'] = $this->formatMessage($message['message'] ?? null, $message['entities'] ?? []);
@ -146,7 +194,7 @@ class Client
$entities = array_reverse($entities); $entities = array_reverse($entities);
foreach ($entities as $entity) { foreach ($entities as $entity) {
if (isset($html[$entity['_']])) { if (isset($html[$entity['_']])) {
$text = $this->mbSubstr($message, $entity['offset'], $entity['length']); $text = static::mbSubstr($message, $entity['offset'], $entity['length']);
if (in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) { if (in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
$textFormate = sprintf($html[$entity['_']], $entity['url'] ?? $text, $text); $textFormate = sprintf($html[$entity['_']], $entity['url'] ?? $text, $text);
@ -154,17 +202,17 @@ class Client
$textFormate = sprintf($html[$entity['_']], $text); $textFormate = sprintf($html[$entity['_']], $text);
} }
$message = $this->substringReplace($message, $textFormate, $entity['offset'], $entity['length']); $message = static::substringReplace($message, $textFormate, $entity['offset'], $entity['length']);
} }
} }
$message = nl2br($message); $message = nl2br($message);
return $message; return $message;
} }
private function substringReplace(string $original, string $replacement, int $position, int $length): string private static function substringReplace(string $original, string $replacement, int $position, int $length): string
{ {
$startString = $this->mbSubstr($original, 0, $position); $startString = static::mbSubstr($original, 0, $position);
$endString = $this->mbSubstr($original, $position + $length, $this->mbStrlen($original)); $endString = static::mbSubstr($original, $position + $length, static::mbStrlen($original));
return $startString . $replacement . $endString; return $startString . $replacement . $endString;
} }
@ -180,13 +228,14 @@ class Client
* ] * ]
* </pre> * </pre>
* *
* @param string|null $session
*
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function copyMessages(array $data): \Amp\Promise public function copyMessages(array $data, ?string $session): \Amp\Promise
{ {
return call( return call(
function() use ($data) { function() use ($data, $session) {
$data = array_merge( $data = array_merge(
[ [
'from_peer' => '', 'from_peer' => '',
@ -196,7 +245,7 @@ class Client
$data $data
); );
$response = yield $this->MadelineProto->channels->getMessages( $response = yield $this->getInstance($session)->channels->getMessages(
[ [
'channel' => $data['from_peer'], 'channel' => $data['from_peer'],
'id' => $data['id'], 'id' => $data['id'],
@ -216,9 +265,9 @@ class Client
]; ];
if (static::hasMedia($message, false)) { if (static::hasMedia($message, false)) {
$messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения. $messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения.
$result[] = yield $this->sendMedia($messageData); $result[] = yield $this->sendMedia($messageData, $session);
} else { } else {
$result[] = yield $this->sendMessage($messageData); $result[] = yield $this->sendMessage($messageData, $session);
} }
} }
@ -239,10 +288,11 @@ class Client
* ] * ]
* </pre> * </pre>
* *
* @param string|null $session
*
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function sendMedia(array $data): \Amp\Promise public function sendMedia(array $data, ?string $session): \Amp\Promise
{ {
$data = array_merge( $data = array_merge(
[ [
@ -255,7 +305,7 @@ class Client
$data $data
); );
return $this->MadelineProto->messages->sendMedia($data); return $this->getInstance($session)->messages->sendMedia($data);
} }
/** /**
@ -269,10 +319,11 @@ class Client
* ] * ]
* </pre> * </pre>
* *
* @param string|null $session
*
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function sendMessage(array $data): \Amp\Promise public function sendMessage(array $data, ?string $session): \Amp\Promise
{ {
$data = array_merge( $data = array_merge(
[ [
@ -284,7 +335,7 @@ class Client
$data $data
); );
return $this->MadelineProto->messages->sendMessage($data); return $this->getInstance($session)->messages->sendMessage($data);
} }
/** /**
@ -302,7 +353,7 @@ class Client
* *
* @return \Amp\Promise * @return \Amp\Promise
*/ */
public function searchGlobal(array $data): \Amp\Promise public function searchGlobal(array $data, ?string $session): \Amp\Promise
{ {
$data = array_merge( $data = array_merge(
[ [
@ -314,21 +365,22 @@ class Client
], ],
$data $data
); );
return $this->MadelineProto->messages->searchGlobal($data); return $this->getInstance($session)->messages->searchGlobal($data);
} }
/** /**
* Загружает медиафайл из указанного сообщения в поток * Загружает медиафайл из указанного сообщения в поток
* *
* @param $data * @param array $data
*
* @param string|null $session
* *
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function getMedia(array $data): \Amp\Promise public function getMedia(array $data, ?string $session): \Amp\Promise
{ {
return call( return call(
function() use ($data) { function() use ($data, $session) {
$data = array_merge( $data = array_merge(
[ [
'peer' => '', 'peer' => '',
@ -340,14 +392,14 @@ class Client
); );
if (!$data['message']) { if (!$data['message']) {
$peerInfo = yield $this->MadelineProto->getInfo($data['peer']); $peerInfo = yield $this->getInstance($session)->getInfo($data['peer']);
if ($peerInfo['type'] === 'channel') { if ($peerInfo['type'] === 'channel') {
$response = yield $this->MadelineProto->channels->getMessages([ $response = yield $this->getInstance($session)->channels->getMessages([
'channel' => $data['peer'], 'channel' => $data['peer'],
'id' => $data['id'], 'id' => $data['id'],
]); ]);
} else { } else {
$response = yield $this->MadelineProto->messages->getMessages(['id' => $data['id']]); $response = yield $this->getInstance($session)->messages->getMessages(['id' => $data['id']]);
} }
$message = $response['messages'][0]; $message = $response['messages'][0];
@ -359,7 +411,7 @@ class Client
throw new \UnexpectedValueException('Message has no media'); throw new \UnexpectedValueException('Message has no media');
} }
$info = yield $this->MadelineProto->getDownloadInfo($message); $info = yield $this->getInstance($session)->getDownloadInfo($message);
if ($data['size_limit'] && $info['size'] > $data['size_limit']) { if ($data['size_limit'] && $info['size'] > $data['size_limit']) {
throw new \OutOfRangeException( throw new \OutOfRangeException(
@ -368,7 +420,7 @@ class Client
} }
$stream = fopen('php://memory', 'rwb'); $stream = fopen('php://memory', 'rwb');
yield $this->MadelineProto->downloadToStream($info, $stream); yield $this->getInstance($session)->downloadToStream($info, $stream);
rewind($stream); rewind($stream);
return [ return [
@ -387,13 +439,14 @@ class Client
* *
* @param array $data * @param array $data
* *
* @param string|null $session
*
* @return \Amp\Promise * @return \Amp\Promise
* @throws \Throwable
*/ */
public function getMediaPreview(array $data): \Amp\Promise public function getMediaPreview(array $data, ?string $session): \Amp\Promise
{ {
return call( return call(
function() use ($data) { function() use ($data, $session) {
$data = array_merge( $data = array_merge(
[ [
'peer' => '', 'peer' => '',
@ -404,14 +457,14 @@ class Client
); );
if (!$data['message']) { if (!$data['message']) {
$peerInfo = yield $this->MadelineProto->getInfo($data['peer']); $peerInfo = yield $this->getInstance($session)->getInfo($data['peer']);
if ($peerInfo['type'] === 'channel') { if ($peerInfo['type'] === 'channel') {
$response = yield $this->MadelineProto->channels->getMessages([ $response = yield $this->getInstance($session)->channels->getMessages([
'channel' => $data['peer'], 'channel' => $data['peer'],
'id' => $data['id'], 'id' => $data['id'],
]); ]);
} else { } else {
$response = yield $this->MadelineProto->messages->getMessages(['id' => $data['id']]); $response = yield $this->getInstance($session)->messages->getMessages(['id' => $data['id']]);
} }
$message = $response['messages'][0]; $message = $response['messages'][0];
@ -441,12 +494,12 @@ class Client
throw new \UnexpectedValueException('Message has no preview'); throw new \UnexpectedValueException('Message has no preview');
} }
$info = yield $this->MadelineProto->getDownloadInfo($thumb); $info = yield $this->getInstance($session)->getDownloadInfo($thumb);
//Фикс для LAYER 100+ //Фикс для LAYER 100+
//TODO: Удалить, когда снова станет доступна загрузка photoSize //TODO: Удалить, когда снова станет доступна загрузка photoSize
if (isset($info['thumb_size'])) { if (isset($info['thumb_size'])) {
$infoFull = yield $this->MadelineProto->getDownloadInfo($media); $infoFull = yield $this->getInstance($session)->getDownloadInfo($media);
$infoFull['InputFileLocation']['thumb_size'] = $info['thumb_size']; $infoFull['InputFileLocation']['thumb_size'] = $info['thumb_size'];
$infoFull['size'] = $info['size']; $infoFull['size'] = $info['size'];
$infoFull['mime'] = $info['mime']; $infoFull['mime'] = $info['mime'];
@ -454,7 +507,7 @@ class Client
} }
$stream = fopen('php://memory', 'rwb'); $stream = fopen('php://memory', 'rwb');
yield $this->MadelineProto->downloadToStream($info, $stream); yield $this->getInstance($session)->downloadToStream($info, $stream);
rewind($stream); rewind($stream);
return [ return [

View File

@ -23,8 +23,9 @@ class RequestCallback
'code' => 200, 'code' => 200,
'response' => null, 'response' => null,
]; ];
private $parameters = []; private array $parameters = [];
private $api; private array $api;
private string $session = '';
/** /**
@ -97,9 +98,13 @@ class RequestCallback
} }
$this->parameters = array_merge((array) $post, $get); $this->parameters = array_merge((array) $post, $get);
if (isset($this->parameters['session'])) {
$this->session = $this->parameters['session'];
unset($this->parameters['session']);
}
$this->parameters = array_values($this->parameters); $this->parameters = array_values($this->parameters);
$this->api = $this->path[1] ?? ''; $this->api = explode('.', $this->path[1] ?? '');
return $this; return $this;
} }
@ -142,20 +147,20 @@ class RequestCallback
*/ */
private function callApi() private function callApi()
{ {
if (method_exists($this->client, $this->api)){ $pathSize = count($this->api);
$result = $this->client->{$this->api}(...$this->parameters); if ($pathSize === 1 && method_exists($this->client, $this->api[0])) {
$result = $this->client->{$this->api[0]}(...array_merge($this->parameters, [$this->session]));
} else { } else {
//Проверяем нет ли в MadilineProto такого метода. //Проверяем нет ли в MadilineProto такого метода.
$this->api = explode('.', $this->api); switch ($pathSize) {
switch (count($this->api)) {
case 1: case 1:
$result = $this->client->MadelineProto->{$this->api[0]}(...$this->parameters); $result = $this->client->getInstance($this->session)->{$this->api[0]}(...$this->parameters);
break; break;
case 2: case 2:
$result = $this->client->MadelineProto->{$this->api[0]}->{$this->api[1]}(...$this->parameters); $result = $this->client->getInstance($this->session)->{$this->api[0]}->{$this->api[1]}(...$this->parameters);
break; break;
case 3: case 3:
$result = $this->client->MadelineProto->{$this->api[0]}->{$this->api[1]}->{$this->api[2]}(...$this->parameters); $result = $this->client->getInstance($this->session)->{$this->api[0]}->{$this->api[1]}->{$this->api[2]}(...$this->parameters);
break; break;
default: default:
throw new \UnexpectedValueException('Incorrect method format'); throw new \UnexpectedValueException('Incorrect method format');