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
-a --address Server ip (optional) (example: 127.0.0.1)
-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)
```
1. Access telegram api directly via simple get requests.
Rules:
* 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>
By default it is: `127.0.0.1`
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.
* 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
* 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:
* 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",
"This file is @generated automatically"
],
"content-hash": "75ba1bc8d195a8a0bee6e46757f5aa25",
"content-hash": "8d2aca34a6b1620d5e86005db622b2dd",
"packages": [
{
"name": "amphp/amp",
@ -1362,16 +1362,16 @@
},
{
"name": "danog/madelineproto",
"version": "5.0.28",
"version": "5.0.30",
"source": {
"type": "git",
"url": "https://github.com/danog/MadelineProto.git",
"reference": "32f7b9a69d20f7fba3bed150d90ae46ec050e1fe"
"reference": "daf98b698bce9a082b7a9f8bf52b268030f84674"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/danog/MadelineProto/zipball/32f7b9a69d20f7fba3bed150d90ae46ec050e1fe",
"reference": "32f7b9a69d20f7fba3bed150d90ae46ec050e1fe",
"url": "https://api.github.com/repos/danog/MadelineProto/zipball/daf98b698bce9a082b7a9f8bf52b268030f84674",
"reference": "daf98b698bce9a082b7a9f8bf52b268030f84674",
"shasum": ""
},
"require": {
@ -1454,7 +1454,7 @@
"telegram",
"video"
],
"time": "2020-01-05T17:43:47+00:00"
"time": "2020-01-11T16:34:40+00:00"
},
{
"name": "danog/magicalserializer",
@ -2353,7 +2353,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=7.3.0",
"php": ">=7.4.0",
"ext-json": "*"
},
"platform-dev": []

View File

@ -1,5 +1,7 @@
<?php
chdir(__DIR__);
require_once __DIR__ . '/bootstrap.php';
if (PHP_SAPI !== 'cli') {
@ -17,20 +19,24 @@ $options = getopt($shortopts, $longopts);
$options = [
'address' => $options['address'] ?? $options['a'] ?? '',
'port' => $options['port'] ?? $options['p'] ?? '',
'session' => $options['session'] ?? $options['s'] ?? '',
'session' => (array) ($options['session'] ?? $options['s'] ?? ''),
'help' => isset($options['help']),
];
if ($options['help']) {
$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:
--help Show this message
-a --address Server ip (optional) (example: 127.0.0.1)
-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)
@ -41,11 +47,15 @@ Example:
echo $help;
exit;
}
if ($options['session']) {
$sessionFile = "{$root}/{$options['session']}_session.madeline";
} else {
$sessionFile = "{$root}/session.madeline";
$sessionFiles = [];
foreach ($options['session'] as $session) {
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);

View File

@ -10,42 +10,87 @@ class Client
{
use BotAPI;
/** @var MadelineProto\API */
public $MadelineProto;
private $sessionFile;
private $config;
/** @var MadelineProto\CombinedAPI */
public MadelineProto\CombinedAPI $MadelineProto;
private ?string $defaultSession = null;
/**
* 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'])) {
$this->config['connection_settings']['all']['proxy'] = '\Socket';
$this->config['connection_settings']['all']['proxy_extra'] = [];
if (empty($config['connection_settings']['all']['proxy_extra']['address'])) {
$config['connection_settings']['all']['proxy'] = '\Socket';
$config['connection_settings']['all']['proxy_extra'] = [];
}
foreach ($sessions as &$session) {
$session = $config;
}
unset($session);
$this->sessionFile = $sessionFile;
$this->connect();
if (count($sessions) === 1) {
$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
echo PHP_EOL . 'Starting MadelineProto...' . PHP_EOL;
$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->loop(function() {
yield $this->MadelineProto->start();
$this->MadelineProto->loop(function() use($sessions) {
$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);
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>
*
* @param string|null $session
*
* @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(
[
@ -84,19 +130,21 @@ class Client
$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
*/
public function getHistoryHtml(array $data): \Amp\Promise
public function getHistoryHtml(array $data, ?string $session): \Amp\Promise
{
return call(
function() use ($data) {
$response = yield $this->getHistory($data);
function() use ($data, $session) {
$response = yield $this->getHistory($data, $session);
foreach ($response['messages'] as &$message) {
$message['message'] = $this->formatMessage($message['message'] ?? null, $message['entities'] ?? []);
@ -146,7 +194,7 @@ class Client
$entities = array_reverse($entities);
foreach ($entities as $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'])) {
$textFormate = sprintf($html[$entity['_']], $entity['url'] ?? $text, $text);
@ -154,17 +202,17 @@ class Client
$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);
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);
$endString = $this->mbSubstr($original, $position + $length, $this->mbStrlen($original));
$startString = static::mbSubstr($original, 0, $position);
$endString = static::mbSubstr($original, $position + $length, static::mbStrlen($original));
return $startString . $replacement . $endString;
}
@ -180,13 +228,14 @@ class Client
* ]
* </pre>
*
* @param string|null $session
*
* @return \Amp\Promise
* @throws \Throwable
*/
public function copyMessages(array $data): \Amp\Promise
public function copyMessages(array $data, ?string $session): \Amp\Promise
{
return call(
function() use ($data) {
function() use ($data, $session) {
$data = array_merge(
[
'from_peer' => '',
@ -196,7 +245,7 @@ class Client
$data
);
$response = yield $this->MadelineProto->channels->getMessages(
$response = yield $this->getInstance($session)->channels->getMessages(
[
'channel' => $data['from_peer'],
'id' => $data['id'],
@ -216,9 +265,9 @@ class Client
];
if (static::hasMedia($message, false)) {
$messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения.
$result[] = yield $this->sendMedia($messageData);
$result[] = yield $this->sendMedia($messageData, $session);
} else {
$result[] = yield $this->sendMessage($messageData);
$result[] = yield $this->sendMessage($messageData, $session);
}
}
@ -239,10 +288,11 @@ class Client
* ]
* </pre>
*
* @param string|null $session
*
* @return \Amp\Promise
* @throws \Throwable
*/
public function sendMedia(array $data): \Amp\Promise
public function sendMedia(array $data, ?string $session): \Amp\Promise
{
$data = array_merge(
[
@ -255,7 +305,7 @@ class Client
$data
);
return $this->MadelineProto->messages->sendMedia($data);
return $this->getInstance($session)->messages->sendMedia($data);
}
/**
@ -269,10 +319,11 @@ class Client
* ]
* </pre>
*
* @param string|null $session
*
* @return \Amp\Promise
* @throws \Throwable
*/
public function sendMessage(array $data): \Amp\Promise
public function sendMessage(array $data, ?string $session): \Amp\Promise
{
$data = array_merge(
[
@ -284,7 +335,7 @@ class Client
$data
);
return $this->MadelineProto->messages->sendMessage($data);
return $this->getInstance($session)->messages->sendMessage($data);
}
/**
@ -302,7 +353,7 @@ class Client
*
* @return \Amp\Promise
*/
public function searchGlobal(array $data): \Amp\Promise
public function searchGlobal(array $data, ?string $session): \Amp\Promise
{
$data = array_merge(
[
@ -314,21 +365,22 @@ class Client
],
$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
* @throws \Throwable
*/
public function getMedia(array $data): \Amp\Promise
public function getMedia(array $data, ?string $session): \Amp\Promise
{
return call(
function() use ($data) {
function() use ($data, $session) {
$data = array_merge(
[
'peer' => '',
@ -340,14 +392,14 @@ class Client
);
if (!$data['message']) {
$peerInfo = yield $this->MadelineProto->getInfo($data['peer']);
$peerInfo = yield $this->getInstance($session)->getInfo($data['peer']);
if ($peerInfo['type'] === 'channel') {
$response = yield $this->MadelineProto->channels->getMessages([
$response = yield $this->getInstance($session)->channels->getMessages([
'channel' => $data['peer'],
'id' => $data['id'],
]);
} else {
$response = yield $this->MadelineProto->messages->getMessages(['id' => $data['id']]);
$response = yield $this->getInstance($session)->messages->getMessages(['id' => $data['id']]);
}
$message = $response['messages'][0];
@ -359,7 +411,7 @@ class Client
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']) {
throw new \OutOfRangeException(
@ -368,7 +420,7 @@ class Client
}
$stream = fopen('php://memory', 'rwb');
yield $this->MadelineProto->downloadToStream($info, $stream);
yield $this->getInstance($session)->downloadToStream($info, $stream);
rewind($stream);
return [
@ -387,13 +439,14 @@ class Client
*
* @param array $data
*
* @param string|null $session
*
* @return \Amp\Promise
* @throws \Throwable
*/
public function getMediaPreview(array $data): \Amp\Promise
public function getMediaPreview(array $data, ?string $session): \Amp\Promise
{
return call(
function() use ($data) {
function() use ($data, $session) {
$data = array_merge(
[
'peer' => '',
@ -404,14 +457,14 @@ class Client
);
if (!$data['message']) {
$peerInfo = yield $this->MadelineProto->getInfo($data['peer']);
$peerInfo = yield $this->getInstance($session)->getInfo($data['peer']);
if ($peerInfo['type'] === 'channel') {
$response = yield $this->MadelineProto->channels->getMessages([
$response = yield $this->getInstance($session)->channels->getMessages([
'channel' => $data['peer'],
'id' => $data['id'],
]);
} else {
$response = yield $this->MadelineProto->messages->getMessages(['id' => $data['id']]);
$response = yield $this->getInstance($session)->messages->getMessages(['id' => $data['id']]);
}
$message = $response['messages'][0];
@ -441,12 +494,12 @@ class Client
throw new \UnexpectedValueException('Message has no preview');
}
$info = yield $this->MadelineProto->getDownloadInfo($thumb);
$info = yield $this->getInstance($session)->getDownloadInfo($thumb);
//Фикс для LAYER 100+
//TODO: Удалить, когда снова станет доступна загрузка photoSize
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['size'] = $info['size'];
$infoFull['mime'] = $info['mime'];
@ -454,7 +507,7 @@ class Client
}
$stream = fopen('php://memory', 'rwb');
yield $this->MadelineProto->downloadToStream($info, $stream);
yield $this->getInstance($session)->downloadToStream($info, $stream);
rewind($stream);
return [

View File

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