Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
d8319e39d3 | |||
8164279427 | |||
806f50bc2d | |||
d0189b1ef2 | |||
47b808ca9b | |||
f7ecafb086 | |||
901dc39d93 | |||
b0d25f53cb | |||
fb20b2d213 | |||
dc165eb30a | |||
34d46ea717 | |||
fb1df72a21 | |||
26a10f012a | |||
bfc8009778 | |||
725265969a | |||
f516a59082 | |||
a9bf26c9c0 | |||
73384f0bb4 | |||
f03705e4aa | |||
b0b850efe4 | |||
8445455e64 | |||
ad85b0a6c0 | |||
906a0ab888 | |||
bbc25e9aec | |||
bda520122d | |||
eca8542be4 | |||
a376f0bf82 | |||
8b2caed419 | |||
77c7d81e33 | |||
3139f8a211 | |||
f4cb2d83a1 | |||
3d956b9002 | |||
26bb35e921 | |||
37d3f94af2 | |||
def6199437 | |||
ac47a23639 | |||
a937d1d994 | |||
5dc330119b | |||
662f521409 | |||
|
d814943168 | ||
787f06a314 | |||
17ed5c0991 | |||
0d1ef0aebe | |||
d74efbfd08 | |||
0a2e325229 | |||
0ca0707529 | |||
6ce4de6091 | |||
7e281c1589 | |||
6eedd54ad3 | |||
da88415e8d | |||
14d14fdc3c | |||
1d16cf807b | |||
b22a193014 | |||
ba59131926 | |||
9511ebab38 | |||
e5ab5cabac | |||
43bd176908 | |||
|
e4aa39c624 | ||
a21f8205e5 | |||
d31b798df9 | |||
d49f59eec5 | |||
e3f3e19cf5 | |||
cdeb93b165 | |||
118cf244b1 | |||
4f69701cfc | |||
6076118320 | |||
967c9b5a93 | |||
|
463e807dbe | ||
|
34f34964a2 | ||
|
58a3efacc6 | ||
76ca2b19ab | |||
1da9cad6b9 | |||
76c4fe480c | |||
297dca7f2c | |||
ca2cbe4ecb | |||
05a859ea50 | |||
f6772e49de | |||
4fcfe1ccd2 | |||
d792263e86 | |||
db24e8e871 | |||
f18e197319 | |||
e5c430dfc9 | |||
f5e4d29db1 | |||
a6b4c0f5cf | |||
16040141ae | |||
05b6266669 | |||
1cd41cc159 | |||
5560b140f5 | |||
86fc8b6e6e | |||
c9ebdd0fa8 | |||
b0bafaf431 | |||
63c52fef50 | |||
afaf80d549 | |||
5779732cd0 | |||
d558d17d1f | |||
2b387b5c0f | |||
3d9d975f79 | |||
6984893103 | |||
50ee77c229 | |||
4123941df3 | |||
f90159c8bd | |||
164000b591 | |||
7a550311ac | |||
bc13e61526 | |||
a3a331542b | |||
1c99fd2658 | |||
bcadbcbe53 | |||
89da6d5a45 | |||
0001a45cd2 | |||
26d746d749 |
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -125,9 +125,4 @@ tempConv
|
|||
extracted.json
|
||||
.phpunit.result.cache
|
||||
|
||||
src/danog/MadelineProto/VoIP.php
|
||||
src/danog/MadelineProto/VoIP/AckHandler.php
|
||||
src/danog/MadelineProto/VoIP/MessageHandler.php
|
||||
src/danog/MadelineProto/OpusStream.php
|
||||
|
||||
ponyScripts
|
||||
|
|
|
@ -5,8 +5,7 @@ $config->getFinder()
|
|||
->in(__DIR__ . '/src')
|
||||
->in(__DIR__ . '/tests')
|
||||
->in(__DIR__ . '/examples')
|
||||
->in(__DIR__ . '/tools')
|
||||
->in(__DIR__);
|
||||
->in(__DIR__ . '/tools');
|
||||
|
||||
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
|
||||
|
||||
|
|
|
@ -86,8 +86,7 @@ You can find examples for nearly every MadelineProto function in
|
|||
* [Handling updates (new messages)](https://docs.madelineproto.xyz/docs/UPDATES.html)
|
||||
* [Self-restart on webhosts](https://docs.madelineproto.xyz/docs/UPDATES.html#self-restart-on-webhosts)
|
||||
* [Async Event driven](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven)
|
||||
* [Multi-account: Async Event driven](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven)
|
||||
* [Async Callback](https://docs.madelineproto.xyz/docs/UPDATES.html#async-callback)
|
||||
* [Async Event driven multi-account](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven-multiaccount)
|
||||
* [Noop (default)](https://docs.madelineproto.xyz/docs/UPDATES.html#noop)
|
||||
* [Fetch all updates from the beginning](https://docs.madelineproto.xyz/docs/UPDATES.html#fetch-all-updates-from-the-beginning)
|
||||
* [Settings](https://docs.madelineproto.xyz/docs/SETTINGS.html)
|
||||
|
@ -352,7 +351,7 @@ You can find examples for nearly every MadelineProto function in
|
|||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.getTmpPassword.html" name="account.getTmpPassword">Get temporary payment password: account.getTmpPassword</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.getAdminLog.html" name="channels.getAdminLog">Get the admin log of a channel/supergroup: channels.getAdminLog</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getSearchCounters.html" name="messages.getSearchCounters">Get the number of results that would be found by a messages.search call with the same parameters: messages.getSearchCounters</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.getParticipants.html" name="channels.getParticipants">Get the participants of a channel: channels.getParticipants</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.getParticipants.html" name="channels.getParticipants">Get the participants of a supergroup/channel: channels.getParticipants</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.getTheme.html" name="account.getTheme">Get theme information: account.getTheme</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getUnreadMentions.html" name="messages.getUnreadMentions">Get unread messages where we were mentioned: messages.getUnreadMentions</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.getWebAuthorizations.html" name="account.getWebAuthorizations">Get web login widget authorizations: account.getWebAuthorizations</a>
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
"homepage": "https://docs.madelineproto.xyz",
|
||||
"keywords": ["telegram", "mtproto", "protocol", "bytes", "messenger", "client", "PHP", "video", "stickers", "audio", "files", "GB"],
|
||||
"conflict": {
|
||||
"krakjoe/pthreads-polyfill": "*"
|
||||
"krakjoe/pthreads-polyfill": "*",
|
||||
"ext-pthreads": "*"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"danog/primemodule": "^1",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-json": "*",
|
||||
"ext-xml": "*",
|
||||
|
@ -34,30 +36,28 @@
|
|||
"danog/magicalserializer": "^1.0",
|
||||
"league/uri": "^6",
|
||||
"danog/ipc": "^0.1",
|
||||
"tivie/htaccess-parser": "^0.2.3",
|
||||
"amphp/log": "^1.1",
|
||||
"danog/loop": "^0.1.0",
|
||||
"danog/tgseclib": "^3",
|
||||
"amphp/redis": "^1.0"
|
||||
"amphp/redis": "^1.0",
|
||||
"symfony/polyfill-php80": "^1.18",
|
||||
"amphp/websocket-client": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"vlucas/phpdotenv": "^3",
|
||||
"phpdocumentor/reflection-docblock": "^4.3",
|
||||
"ennexa/amp-update-cache": "dev-master",
|
||||
"phpunit/phpunit": "^8",
|
||||
"phpunit/phpunit": "^9",
|
||||
"amphp/php-cs-fixer-config": "dev-master",
|
||||
"haydenpierce/class-finder": "^0.4",
|
||||
"amphp/http-server": "dev-master",
|
||||
"amphp/http": "^1.6",
|
||||
"amphp/websocket-client": "dev-master as 1",
|
||||
"amphp/websocket": "dev-master as 1",
|
||||
"ext-ctype": "*",
|
||||
"danog/7to70": "^1",
|
||||
"danog/7to5": "^1",
|
||||
"vimeo/psalm": "dev-master",
|
||||
"phpstan/phpstan": "^0.12.14",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
"danog/phpdoc": "^0.1.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libtgvoip": "Install the php-libtgvoip extension to make phone calls (https://github.com/danog/php-libtgvoip)"
|
||||
|
@ -97,9 +97,14 @@
|
|||
],
|
||||
"build": [
|
||||
"@docs",
|
||||
"@phpdoc",
|
||||
"@cs-fix",
|
||||
"@psalm"
|
||||
],
|
||||
"phpdoc": [
|
||||
"@phpdocInternal",
|
||||
"@phpdocMain"
|
||||
],
|
||||
"check": [
|
||||
"@cs",
|
||||
"@test"
|
||||
|
@ -109,6 +114,8 @@
|
|||
"cs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff --dry-run",
|
||||
"cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff",
|
||||
"psalm": "psalm",
|
||||
"phpdocMain": "php tools/phpdoc.php",
|
||||
"phpdocInternal": "phpdoc docs/docs/PHPInternal",
|
||||
"docs": "php tools/build_docs.php",
|
||||
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text --config tests/phpunit.xml"
|
||||
}
|
||||
|
|
2
docs
2
docs
|
@ -1 +1 @@
|
|||
Subproject commit 57562236aade5d13a01c18a2290aa647686eebd5
|
||||
Subproject commit e83c3ea026fc2142b6558378d0c9636c8c29f248
|
|
@ -22,6 +22,10 @@
|
|||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\EventHandler;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\Settings\Database\Mysql;
|
||||
use danog\MadelineProto\Settings\Database\Postgres;
|
||||
use danog\MadelineProto\Settings\Database\Redis;
|
||||
|
||||
/*
|
||||
* Various ways to load MadelineProto
|
||||
|
@ -79,6 +83,7 @@ class MyEventHandler extends EventHandler
|
|||
if ($update['message']['_'] === 'messageEmpty' || $update['message']['out'] ?? false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$res = \json_encode($update, JSON_PRETTY_PRINT);
|
||||
yield $this->messages->sendMessage(['peer' => $update, 'message' => "<code>$res</code>", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
|
||||
if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
|
||||
|
@ -86,14 +91,14 @@ class MyEventHandler extends EventHandler
|
|||
}
|
||||
}
|
||||
}
|
||||
$settings = [
|
||||
'logger' => [
|
||||
'logger_level' => Logger::ULTRA_VERBOSE
|
||||
],
|
||||
'serialization' => [
|
||||
'serialization_interval' => 30,
|
||||
],
|
||||
];
|
||||
|
||||
$settings = new Settings;
|
||||
$settings->getLogger()->setLevel(Logger::LEVEL_ULTRA_VERBOSE);
|
||||
|
||||
// You can also use Redis, MySQL or PostgreSQL
|
||||
// $settings->setDb((new Redis)->setDatabase(0)->setPassword('pony'));
|
||||
// $settings->setDb((new Postgres)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));
|
||||
// $settings->setDb((new Mysql)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));
|
||||
|
||||
$MadelineProto = new API('bot.madeline', $settings);
|
||||
|
||||
|
|
|
@ -21,9 +21,7 @@
|
|||
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\EventHandler;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\RPCErrorException;
|
||||
|
||||
/*
|
||||
* Various ways to load MadelineProto
|
||||
|
@ -60,7 +58,7 @@ class MyEventHandler extends EventHandler
|
|||
*
|
||||
* @param array $update Update
|
||||
*
|
||||
* @return void
|
||||
* @return \Generator
|
||||
*/
|
||||
public function onUpdateNewChannelMessage(array $update): \Generator
|
||||
{
|
||||
|
@ -80,17 +78,9 @@ class MyEventHandler extends EventHandler
|
|||
}
|
||||
$res = \json_encode($update, JSON_PRETTY_PRINT);
|
||||
|
||||
try {
|
||||
yield $this->messages->sendMessage(['peer' => $update, 'message' => "<code>$res</code>", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
|
||||
if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
|
||||
yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
|
||||
}
|
||||
} catch (RPCErrorException $e) {
|
||||
$this->report("Surfaced: $e");
|
||||
} catch (Exception $e) {
|
||||
if (\stripos($e->getMessage(), 'invalid constructor given') === false) {
|
||||
$this->report("Surfaced: $e");
|
||||
}
|
||||
yield $this->messages->sendMessage(['peer' => $update, 'message' => "<code>$res</code>", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
|
||||
if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
|
||||
yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
|
||||
use danog\MadelineProto\APIWrapper;
|
||||
|
||||
\set_include_path(\get_include_path().':'.\realpath(\dirname(__FILE__).'/MadelineProto/'));
|
||||
|
||||
/*
|
||||
* Various ways to load MadelineProto
|
||||
*/
|
||||
|
@ -68,6 +66,9 @@ class SecretHandler extends \danog\MadelineProto\EventHandler
|
|||
if ($update['message']['message'] === 'request') {
|
||||
yield $this->requestSecretChat($update);
|
||||
}
|
||||
if ($update['message']['message'] === 'ping') {
|
||||
yield $this->messages->sendMessage(['message' => 'lmao', 'peer' => $update]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle secret chat messages.
|
||||
|
@ -78,55 +79,49 @@ class SecretHandler extends \danog\MadelineProto\EventHandler
|
|||
*/
|
||||
public function onUpdateNewEncryptedMessage(array $update): \Generator
|
||||
{
|
||||
try {
|
||||
if (isset($update['message']['decrypted_message']['media'])) {
|
||||
$this->logger(yield $this->downloadToDir($update, '.'));
|
||||
}
|
||||
if (isset($this->sent[$update['message']['chat_id']])) {
|
||||
return;
|
||||
}
|
||||
$secret_media = [];
|
||||
|
||||
// Photo uploaded as document, secret chat
|
||||
$secret_media['document_photo'] = ['peer' => $update, 'file' => 'tests/faust.jpg', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/faust.jpg'), 'caption' => 'This file was uploaded using MadelineProto', 'file_name' => 'faust.jpg', 'size' => \filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeImageSize', 'w' => 1280, 'h' => 914]]]]];
|
||||
|
||||
// Photo, secret chat
|
||||
$secret_media['photo'] = ['peer' => $update, 'file' => 'tests/faust.jpg', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaPhoto', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'caption' => 'This file was uploaded using MadelineProto', 'size' => \filesize('tests/faust.jpg'), 'w' => 1280, 'h' => 914]]];
|
||||
|
||||
// GIF, secret chat
|
||||
$secret_media['gif'] = ['peer' => $update, 'file' => 'tests/pony.mp4', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/pony.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/pony.mp4'), 'caption' => 'test', 'file_name' => 'pony.mp4', 'size' => \filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeAnimated']]]]];
|
||||
|
||||
// Sticker, secret chat
|
||||
$secret_media['sticker'] = ['peer' => $update, 'file' => 'tests/lel.webp', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/lel.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/lel.webp'), 'caption' => 'test', 'file_name' => 'lel.webp', 'size' => \filesize('tests/lel.webp'), 'attributes' => [['_' => 'documentAttributeSticker', 'alt' => 'LEL', 'stickerset' => ['_' => 'inputStickerSetEmpty']]]]]];
|
||||
|
||||
// Document, secrey chat
|
||||
$secret_media['document'] = ['peer' => $update, 'file' => 'tests/60', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => 'magic/magic', 'caption' => 'test', 'file_name' => 'magic.magic', 'size' => \filesize('tests/60'), 'attributes' => [['_' => 'documentAttributeFilename', 'file_name' => 'fairy']]]]];
|
||||
|
||||
// Video, secret chat
|
||||
$secret_media['video'] = ['peer' => $update, 'file' => 'tests/swing.mp4', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/swing.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/swing.mp4'), 'caption' => 'test', 'file_name' => 'swing.mp4', 'size' => \filesize('tests/swing.mp4'), 'attributes' => [['_' => 'documentAttributeVideo', 'duration' => 5, 'w' => 1280, 'h' => 720]]]]];
|
||||
|
||||
// audio, secret chat
|
||||
$secret_media['audio'] = ['peer' => $update, 'file' => 'tests/mosconi.mp3', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'file_name' => 'mosconi.mp3', 'size' => \filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => false, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]];
|
||||
|
||||
$secret_media['voice'] = ['peer' => $update, 'file' => 'tests/mosconi.mp3', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'file_name' => 'mosconi.mp3', 'size' => \filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => true, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]];
|
||||
|
||||
foreach ($secret_media as $type => $smessage) {
|
||||
$promises = $this->messages->sendEncryptedFile($smessage);
|
||||
}
|
||||
yield $promises;
|
||||
|
||||
$i = 0;
|
||||
while ($i < 10) {
|
||||
$this->logger("SENDING MESSAGE $i TO ".$update['message']['chat_id']);
|
||||
// You can also use the sendEncrypted parameter for more options in secret chats
|
||||
yield $this->messages->sendMessage(['peer' => $update, 'message' => (string) ($i++)]);
|
||||
}
|
||||
$this->sent[$update['message']['chat_id']] = true;
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
\danog\MadelineProto\Logger::log($e);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
\danog\MadelineProto\Logger::log($e);
|
||||
if (isset($update['message']['decrypted_message']['media'])) {
|
||||
$this->logger(yield $this->downloadToDir($update, '.'));
|
||||
}
|
||||
if (isset($this->sent[$update['message']['chat_id']])) {
|
||||
return;
|
||||
}
|
||||
$secret_media = [];
|
||||
|
||||
// Photo uploaded as document, secret chat
|
||||
$secret_media['document_photo'] = ['peer' => $update, 'file' => 'tests/faust.jpg', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/faust.jpg'), 'caption' => 'This file was uploaded using MadelineProto', 'file_name' => 'faust.jpg', 'size' => \filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeImageSize', 'w' => 1280, 'h' => 914]]]]];
|
||||
|
||||
// Photo, secret chat
|
||||
$secret_media['photo'] = ['peer' => $update, 'file' => 'tests/faust.jpg', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaPhoto', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'caption' => 'This file was uploaded using MadelineProto', 'size' => \filesize('tests/faust.jpg'), 'w' => 1280, 'h' => 914]]];
|
||||
|
||||
// GIF, secret chat
|
||||
$secret_media['gif'] = ['peer' => $update, 'file' => 'tests/pony.mp4', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/pony.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/pony.mp4'), 'caption' => 'test', 'file_name' => 'pony.mp4', 'size' => \filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeAnimated']]]]];
|
||||
|
||||
// Sticker, secret chat
|
||||
$secret_media['sticker'] = ['peer' => $update, 'file' => 'tests/lel.webp', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/lel.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/lel.webp'), 'caption' => 'test', 'file_name' => 'lel.webp', 'size' => \filesize('tests/lel.webp'), 'attributes' => [['_' => 'documentAttributeSticker', 'alt' => 'LEL', 'stickerset' => ['_' => 'inputStickerSetEmpty']]]]]];
|
||||
|
||||
// Document, secrey chat
|
||||
$secret_media['document'] = ['peer' => $update, 'file' => 'tests/60', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => 'magic/magic', 'caption' => 'test', 'file_name' => 'magic.magic', 'size' => \filesize('tests/60'), 'attributes' => [['_' => 'documentAttributeFilename', 'file_name' => 'fairy']]]]];
|
||||
|
||||
// Video, secret chat
|
||||
$secret_media['video'] = ['peer' => $update, 'file' => 'tests/swing.mp4', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/swing.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/swing.mp4'), 'caption' => 'test', 'file_name' => 'swing.mp4', 'size' => \filesize('tests/swing.mp4'), 'attributes' => [['_' => 'documentAttributeVideo', 'duration' => 5, 'w' => 1280, 'h' => 720]]]]];
|
||||
|
||||
// audio, secret chat
|
||||
$secret_media['audio'] = ['peer' => $update, 'file' => 'tests/mosconi.mp3', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'file_name' => 'mosconi.mp3', 'size' => \filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => false, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]];
|
||||
|
||||
$secret_media['voice'] = ['peer' => $update, 'file' => 'tests/mosconi.mp3', 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => \file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => \mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'file_name' => 'mosconi.mp3', 'size' => \filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => true, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]];
|
||||
|
||||
foreach ($secret_media as $type => $smessage) {
|
||||
$promises = $this->messages->sendEncryptedFile($smessage);
|
||||
}
|
||||
yield $promises;
|
||||
|
||||
$i = 0;
|
||||
while ($i < 10) {
|
||||
$this->logger("SENDING MESSAGE $i TO ".$update['message']['chat_id']);
|
||||
// You can also use the sendEncrypted parameter for more options in secret chats
|
||||
yield $this->messages->sendMessage(['peer' => $update, 'message' => (string) ($i++)]);
|
||||
}
|
||||
$this->sent[$update['message']['chat_id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +134,7 @@ if (\file_exists('.env')) {
|
|||
echo 'Loading settings...'.PHP_EOL;
|
||||
$settings = \json_decode(\getenv('MTPROTO_SETTINGS'), true) ?: [];
|
||||
|
||||
$MadelineProto = new \danog\MadelineProto\API('s.madeline', $settings);
|
||||
$MadelineProto = new \danog\MadelineProto\API('secret.madeline', $settings);
|
||||
|
||||
// Reduce boilerplate with new wrapper method
|
||||
$MadelineProto->startAndLoop(SecretHandler::class);
|
||||
|
|
2
schemas
2
schemas
|
@ -1 +1 @@
|
|||
Subproject commit a878ff05408129b97eed651a0a5ab5bb66ebe1d0
|
||||
Subproject commit 8fd40f0a4170769465e4943e4a5da37bd58f7e88
|
|
@ -19,29 +19,86 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Promise;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\Settings\Ipc as SettingsIpc;
|
||||
use danog\MadelineProto\Settings\Logger as SettingsLogger;
|
||||
|
||||
/**
|
||||
* Main API wrapper for MadelineProto.
|
||||
*/
|
||||
class API extends InternalDoc
|
||||
{
|
||||
/**
|
||||
* Release version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RELEASE = MTProto::RELEASE;
|
||||
/**
|
||||
* We're not logged in.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const NOT_LOGGED_IN = MTProto::NOT_LOGGED_IN;
|
||||
/**
|
||||
* We're waiting for the login code.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const WAITING_CODE = MTProto::WAITING_CODE;
|
||||
/**
|
||||
* We're waiting for parameters to sign up.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const WAITING_SIGNUP = MTProto::WAITING_SIGNUP;
|
||||
/**
|
||||
* We're waiting for the 2FA password.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const WAITING_PASSWORD = MTProto::WAITING_PASSWORD;
|
||||
/**
|
||||
* We're logged in.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const LOGGED_IN = MTProto::LOGGED_IN;
|
||||
/**
|
||||
* Secret chat was not found.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SECRET_EMPTY = MTProto::SECRET_EMPTY;
|
||||
/**
|
||||
* Secret chat was requested.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SECRET_REQUESTED = MTProto::SECRET_REQUESTED;
|
||||
/**
|
||||
* Secret chat was found.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SECRET_READY = MTProto::SECRET_READY;
|
||||
use \danog\Serializable;
|
||||
use \danog\MadelineProto\ApiWrappers\Start;
|
||||
use \danog\MadelineProto\ApiWrappers\Templates;
|
||||
/**
|
||||
* Session path.
|
||||
* Session paths.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $session = '';
|
||||
public SessionPaths $session;
|
||||
|
||||
/**
|
||||
* Instance of MadelineProto.
|
||||
*
|
||||
* @var null|MTProto
|
||||
* @var null|MTProto|Client
|
||||
*/
|
||||
public $API;
|
||||
|
||||
|
@ -83,7 +140,6 @@ class API extends InternalDoc
|
|||
*/
|
||||
private bool $destructing = false;
|
||||
|
||||
|
||||
/**
|
||||
* API wrapper (to avoid circular references).
|
||||
*
|
||||
|
@ -92,27 +148,28 @@ class API extends InternalDoc
|
|||
private $wrapper;
|
||||
|
||||
/**
|
||||
* Global session unlock callback.
|
||||
* Unlock callback.
|
||||
*
|
||||
* @var callback
|
||||
* @var ?callable
|
||||
*/
|
||||
private $unlock;
|
||||
|
||||
private $unlock = null;
|
||||
|
||||
/**
|
||||
* Magic constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
* @param string $session Session name
|
||||
* @param array|Settings $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct(string $session, array $settings = []): void
|
||||
public function __magic_construct(string $session, $settings = []): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
$settings = Settings::parseFromLegacy($settings);
|
||||
$this->session = new SessionPaths($session);
|
||||
$this->wrapper = new APIWrapper($this, $this->exportNamespace());
|
||||
|
||||
Magic::classExists();
|
||||
$this->setInitPromise($this->__construct_async($session, $settings));
|
||||
$this->setInitPromise($this->internalInitAPI($settings));
|
||||
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
|
||||
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
|
||||
continue;
|
||||
|
@ -125,19 +182,91 @@ class API extends InternalDoc
|
|||
/**
|
||||
* Async constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
* @param Settings|SettingsEmpty|SettingsIpc $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __construct_async(string $session, array $settings = []): \Generator
|
||||
private function internalInitAPI(SettingsAbstract $settings): \Generator
|
||||
{
|
||||
Logger::constructorFromSettings($settings);
|
||||
$this->session = $session = Tools::absolute($session);
|
||||
[$unserialized, $this->unlock] = yield from Serialization::legacyUnserialize($session);
|
||||
if ($unserialized) {
|
||||
Logger::constructorFromSettings($settings instanceof Settings
|
||||
? $settings->getLogger()
|
||||
: ($settings instanceof SettingsLogger ? $settings : new SettingsLogger));
|
||||
|
||||
if (yield from $this->connectToMadelineProto($settings)) {
|
||||
return; // OK
|
||||
}
|
||||
if (!$settings instanceof Settings) {
|
||||
$newSettings = new Settings;
|
||||
$newSettings->merge($settings);
|
||||
$settings = $newSettings;
|
||||
}
|
||||
|
||||
$appInfo = $settings->getAppInfo();
|
||||
if (!$appInfo->hasApiInfo()) {
|
||||
$app = yield from $this->APIStart($settings);
|
||||
if (!$app) {
|
||||
$this->forceInit(true);
|
||||
die();
|
||||
}
|
||||
$appInfo->setApiId($app['api_id']);
|
||||
$appInfo->setApiHash($app['api_hash']);
|
||||
}
|
||||
$this->API = new MTProto($settings, $this->wrapper);
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to full instance.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function reconnectFull(): \Generator
|
||||
{
|
||||
if ($this->API instanceof Client) {
|
||||
yield $this->API->stopIpcServer();
|
||||
yield $this->API->disconnect();
|
||||
yield from $this->connectToMadelineProto(new SettingsEmpty, true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Connect to MadelineProto.
|
||||
*
|
||||
* @param SettingsAbstract $settings Settings
|
||||
* @param bool $forceFull Whether to force full initialization
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function connectToMadelineProto(SettingsAbstract $settings, bool $forceFull = false): \Generator
|
||||
{
|
||||
if ($settings instanceof SettingsIpc) {
|
||||
$forceFull = $forceFull || $settings->getSlow();
|
||||
} elseif ($settings instanceof Settings) {
|
||||
$forceFull = $forceFull || $settings->getIpc()->getSlow();
|
||||
}
|
||||
|
||||
[$unserialized, $this->unlock] = yield Tools::timeoutWithDefault(
|
||||
Serialization::unserialize($this->session, $settings, $forceFull),
|
||||
30000,
|
||||
[0, null]
|
||||
);
|
||||
|
||||
if ($unserialized === 0) {
|
||||
// Timeout
|
||||
throw new \RuntimeException("Could not connect to MadelineProto, please check the logs for more details.");
|
||||
} elseif ($unserialized instanceof \Throwable) {
|
||||
// IPC server error, try fetching full session
|
||||
return yield from $this->connectToMadelineProto($settings, true);
|
||||
} elseif ($unserialized instanceof ChannelledSocket) {
|
||||
// Success, IPC client
|
||||
$this->API = new Client($unserialized, $this->session, Logger::$default);
|
||||
$this->APIFactory();
|
||||
return true;
|
||||
} elseif ($unserialized) {
|
||||
// Success, full session
|
||||
$unserialized->storage = $unserialized->storage ?? [];
|
||||
$unserialized->session = $session;
|
||||
$unserialized->session = $this->session;
|
||||
APIWrapper::link($this, $unserialized);
|
||||
APIWrapper::link($this->wrapper, $this);
|
||||
AbstractAPIFactory::link($this->wrapper->getFactory(), $this);
|
||||
|
@ -146,30 +275,17 @@ class API extends InternalDoc
|
|||
|
||||
unset($unserialized);
|
||||
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
if ($settings instanceof SettingsIpc) {
|
||||
$settings = new SettingsEmpty;
|
||||
}
|
||||
yield from $this->API->wakeup($settings, $this->wrapper);
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) {
|
||||
$app = (yield from $this->APIStart($settings));
|
||||
if (!$app) {
|
||||
$this->forceInit(true);
|
||||
die();
|
||||
}
|
||||
$settings['app_info']['api_id'] = $app['api_id'];
|
||||
$settings['app_info']['api_hash'] = $app['api_hash'];
|
||||
}
|
||||
$this->API = new MTProto($settings);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakeup function.
|
||||
*
|
||||
|
@ -189,13 +305,17 @@ class API extends InternalDoc
|
|||
$this->init();
|
||||
if (!$this->oldInstance) {
|
||||
$this->logger->logger('Shutting down MadelineProto ('.static::class.')');
|
||||
$this->destructing = true;
|
||||
if ($this->API) {
|
||||
$this->API->destructing = true;
|
||||
if ($this->API instanceof Tools) {
|
||||
$this->API->destructing = true;
|
||||
}
|
||||
$this->API->unreference();
|
||||
}
|
||||
$this->destructing = true;
|
||||
if (isset($this->wrapper)) {
|
||||
if (isset($this->wrapper) && (!Magic::$signaled || $this->gettingApiId)) {
|
||||
$this->logger->logger('Prompting final serialization...');
|
||||
Tools::wait($this->wrapper->serialize());
|
||||
$this->logger->logger('Done final serialization!');
|
||||
}
|
||||
if ($this->unlock) {
|
||||
($this->unlock)();
|
||||
|
@ -212,16 +332,14 @@ class API extends InternalDoc
|
|||
private function APIFactory(): void
|
||||
{
|
||||
if ($this->API && $this->API->inited()) {
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
if (!$this->{$namespace}) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
if ($this->API instanceof MTProto) {
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
if (!$this->{$namespace}) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->methods = self::getInternalMethodList($this->API);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
if ($this->API->event_handler && \class_exists($this->API->event_handler) && \is_subclass_of($this->API->event_handler, EventHandler::class)) {
|
||||
$this->API->setEventHandler($this->API->event_handler);
|
||||
}
|
||||
$this->methods = self::getInternalMethodList($this->API, MTProto::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +371,7 @@ class API extends InternalDoc
|
|||
* @param API[] $instances Instances of madeline
|
||||
* @param string[]|string $eventHandler Event handler(s)
|
||||
*
|
||||
* @return Promise
|
||||
* @return void
|
||||
*/
|
||||
public static function startAndLoopMulti(array $instances, $eventHandler): void
|
||||
{
|
||||
|
@ -288,13 +406,30 @@ class API extends InternalDoc
|
|||
*/
|
||||
public function startAndLoopAsync(string $eventHandler): \Generator
|
||||
{
|
||||
$errors = [];
|
||||
$this->async(true);
|
||||
|
||||
if ($this->API instanceof Client) {
|
||||
yield $this->API->stopIpcServer();
|
||||
yield $this->API->disconnect();
|
||||
yield from $this->connectToMadelineProto(new SettingsEmpty, true);
|
||||
}
|
||||
|
||||
$started = false;
|
||||
while (true) {
|
||||
try {
|
||||
yield $this->start();
|
||||
yield $this->setEventHandler($eventHandler);
|
||||
$started = true;
|
||||
return yield from $this->API->loop();
|
||||
} catch (\Throwable $e) {
|
||||
$errors = [\time() => $errors[\time()] ?? 0];
|
||||
$errors[\time()]++;
|
||||
if ($errors[\time()] > 10 && (!$this->inited() || !$started)) {
|
||||
$this->logger->logger("More than 10 errors in a second and not inited, exiting!", Logger::FATAL_ERROR);
|
||||
return;
|
||||
}
|
||||
echo $e;
|
||||
$this->logger->logger((string) $e, Logger::FATAL_ERROR);
|
||||
$this->report("Surfaced: $e");
|
||||
}
|
||||
|
|
|
@ -20,25 +20,23 @@ namespace danog\MadelineProto;
|
|||
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
|
||||
use function Amp\File\put;
|
||||
use function Amp\File\rename as renameAsync;
|
||||
use function Amp\File\open;
|
||||
|
||||
final class APIWrapper
|
||||
{
|
||||
/**
|
||||
* MTProto instance.
|
||||
*
|
||||
* @var ?MTProto
|
||||
* @var MTProto|null|Client
|
||||
*/
|
||||
private ?MTProto $API = null;
|
||||
private $API = null;
|
||||
|
||||
/**
|
||||
* Session path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $session = '';
|
||||
public SessionPaths $session;
|
||||
|
||||
/**
|
||||
* Getting API ID flag.
|
||||
|
@ -90,6 +88,11 @@ final class APIWrapper
|
|||
* @var AbstractAPIFactory
|
||||
*/
|
||||
private AbstractAPIFactory $factory;
|
||||
|
||||
/**
|
||||
* Property storage.
|
||||
*/
|
||||
public array $storage = [];
|
||||
/**
|
||||
* API wrapper.
|
||||
*
|
||||
|
@ -112,30 +115,38 @@ final class APIWrapper
|
|||
*/
|
||||
public static function link($a, $b): void
|
||||
{
|
||||
foreach (self::__sleep() as $var) {
|
||||
foreach (self::properties() as $var) {
|
||||
Tools::setVar($a, $var, Tools::getVar($b, $var));
|
||||
}
|
||||
Tools::setVar($a, 'session', Tools::getVar($b, 'session'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep function.
|
||||
*
|
||||
* @internal
|
||||
* Property list.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function __sleep(): array
|
||||
public static function properties(): array
|
||||
{
|
||||
return ['API', 'webApiTemplate', 'gettingApiId', 'myTelegramOrgWrapper', 'storage', 'lua']; //, 'async'];
|
||||
return ['API', 'webApiTemplate', 'gettingApiId', 'myTelegramOrgWrapper', 'storage', 'lua'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return self::properties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MTProto instance.
|
||||
*
|
||||
* @return MTProto|null
|
||||
* @return Client|MTProto|null
|
||||
*/
|
||||
public function &getAPI(): ?MTProto
|
||||
public function &getAPI()
|
||||
{
|
||||
return $this->API;
|
||||
}
|
||||
|
@ -169,62 +180,52 @@ final class APIWrapper
|
|||
*/
|
||||
public function getIpcPath(): string
|
||||
{
|
||||
return (new SessionPaths($this->session))->getIpcPath();
|
||||
return $this->session->getIpcPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize session.
|
||||
*
|
||||
* @return Promise
|
||||
* @return Promise<bool>
|
||||
*/
|
||||
public function serialize(): Promise
|
||||
{
|
||||
if (!$this->session) {
|
||||
Logger::log("Not serializing, no session");
|
||||
return new Success();
|
||||
if ($this->API === null && !$this->gettingApiId) {
|
||||
return new Success(false);
|
||||
}
|
||||
if ($this->API instanceof FastAPI) {
|
||||
Logger::log("Not serializing, IPC client");
|
||||
return new Success();
|
||||
if ($this->API instanceof Client) {
|
||||
return new Success(false);
|
||||
}
|
||||
return Tools::callFork((function (): \Generator {
|
||||
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
|
||||
$this->API->flushSettings = false;
|
||||
$this->API->__construct($this->API->settings);
|
||||
}
|
||||
if ($this->API === null && !$this->gettingApiId) {
|
||||
return false;
|
||||
}
|
||||
if ($this->API) {
|
||||
yield from $this->API->initAsynchronously();
|
||||
}
|
||||
$this->serialized = \time();
|
||||
$realpaths = new SessionPaths($this->session);
|
||||
Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_EX);
|
||||
Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
if (!$this->gettingApiId) {
|
||||
$update_closure = $this->API->settings['updates']['callback'];
|
||||
if ($this->API->settings['updates']['callback'] instanceof \Closure) {
|
||||
$this->API->settings['updates']['callback'] = [$this->API, 'noop'];
|
||||
}
|
||||
$logger_closure = $this->API->settings['logger']['logger_param'];
|
||||
if ($this->API->settings['logger']['logger_param'] instanceof \Closure) {
|
||||
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
|
||||
}
|
||||
}
|
||||
$wrote = yield put($realpaths->getTempPath(), \serialize($this));
|
||||
yield renameAsync($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
if (!$this->gettingApiId) {
|
||||
$this->API->settings['updates']['callback'] = $update_closure;
|
||||
$this->API->settings['logger']['logger_param'] = $logger_closure;
|
||||
}
|
||||
$unlock();
|
||||
|
||||
yield from $this->session->serialize(
|
||||
$this->API ? yield from $this->API->serializeSession($this) : $this,
|
||||
$this->session->getSessionPath()
|
||||
);
|
||||
|
||||
if ($this->API) {
|
||||
yield from $this->session->storeLightState($this->API);
|
||||
}
|
||||
Logger::log('Done serializing');
|
||||
return $wrote;
|
||||
|
||||
|
||||
// Truncate legacy session
|
||||
yield (yield open($this->session->getLegacySessionPath(), 'w'))->close();
|
||||
|
||||
Logger::log('Saved session!');
|
||||
return true;
|
||||
})());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session path.
|
||||
*
|
||||
* @return SessionPaths
|
||||
*/
|
||||
public function getSession(): SessionPaths
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Async\AsyncConstruct;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
|
||||
abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
{
|
||||
|
@ -31,14 +32,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
* @var string
|
||||
*/
|
||||
private string $namespace = '';
|
||||
/**
|
||||
* MTProto instance.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var MTProto
|
||||
*/
|
||||
public $API;
|
||||
/**
|
||||
* Whether lua is being used.
|
||||
*
|
||||
|
@ -58,9 +51,15 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
/**
|
||||
* Method list.
|
||||
*
|
||||
* @var string[]
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected array $methods = [];
|
||||
|
||||
/**
|
||||
* Main API instance.
|
||||
*/
|
||||
private API $mainAPI;
|
||||
|
||||
/**
|
||||
* Export APIFactory instance with the specified namespace.
|
||||
*
|
||||
|
@ -88,10 +87,21 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
*/
|
||||
protected static function link(self $a, self $b): void
|
||||
{
|
||||
$a->API =& $b->API;
|
||||
$a->lua =& $b->lua;
|
||||
$a->async =& $b->async;
|
||||
$a->methods =& $b->methods;
|
||||
if ($b instanceof API) {
|
||||
$a->mainAPI = $b;
|
||||
$b->mainAPI = $b;
|
||||
} elseif ($a instanceof API) {
|
||||
$a->mainAPI = $a;
|
||||
$b->mainAPI = $a;
|
||||
} else {
|
||||
$a->mainAPI =& $b->mainAPI;
|
||||
}
|
||||
if (!$b->inited()) {
|
||||
$a->setInitPromise($b->initAsynchronously());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enable or disable async.
|
||||
|
@ -142,13 +152,24 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
$keys = APIWrapper::__sleep();
|
||||
$keys = APIWrapper::properties();
|
||||
$res = [];
|
||||
foreach ($keys as $key) {
|
||||
$res[$key] = Tools::getVar($this, $key);
|
||||
if (Tools::hasVar($this, $key)) {
|
||||
$res[$key] = Tools::getVar($this, $key);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
/**
|
||||
* Sleep function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* Call async wrapper function.
|
||||
*
|
||||
|
@ -162,14 +183,18 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
public function __call_async(string $name, array $arguments): \Generator
|
||||
{
|
||||
yield from $this->initAsynchronously();
|
||||
|
||||
$lower_name = \strtolower($name);
|
||||
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
|
||||
$name = $this->namespace.$name;
|
||||
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
|
||||
$aargs['apifactory'] = true;
|
||||
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
|
||||
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
|
||||
return yield from $this->mainAPI->API->methodCallAsyncRead($name, $args, $aargs);
|
||||
}
|
||||
if ($lower_name === 'seteventhandler'
|
||||
|| ($lower_name === 'loop' && !isset($arguments[0]))
|
||||
) {
|
||||
yield from $this->mainAPI->reconnectFull();
|
||||
}
|
||||
$res = $this->methods[$lower_name](...$arguments);
|
||||
return $res instanceof \Generator ? yield from $res : yield $res;
|
||||
|
@ -177,8 +202,8 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
/**
|
||||
* Get fully resolved method list for object, including snake_case and camelCase variants.
|
||||
*
|
||||
* @param API $value Value
|
||||
* @param string $class Custom class name
|
||||
* @param API|MTProto|Client $value Value
|
||||
* @param string $class Custom class name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
@ -20,28 +20,57 @@
|
|||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\TLSchema;
|
||||
use danog\MadelineProto\TL\TL;
|
||||
use danog\MadelineProto\TL\TLCallback;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
|
||||
class AnnotationsBuilder
|
||||
{
|
||||
/**
|
||||
* Reflection classes.
|
||||
*/
|
||||
private array $reflectionClasses = [];
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private Logger $logger;
|
||||
/**
|
||||
* Namespace.
|
||||
*/
|
||||
private string $namespace;
|
||||
/**
|
||||
* TL instance.
|
||||
*/
|
||||
private TL $TL;
|
||||
/**
|
||||
* Settings.
|
||||
*/
|
||||
private array $settings;
|
||||
/**
|
||||
* Output file.
|
||||
*/
|
||||
private string $output;
|
||||
public function __construct(Logger $logger, array $settings, string $output, array $reflectionClasses, string $namespace)
|
||||
{
|
||||
$this->reflectionClasses = $reflectionClasses;
|
||||
$this->logger = $logger;
|
||||
$this->namespace = $namespace;
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$this->TL = new TL(new class($logger) {
|
||||
public function __construct($logger)
|
||||
public Logger $logger;
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
});
|
||||
$this->TL->init($settings['tl_schema']);
|
||||
$tlSchema = new TLSchema;
|
||||
$tlSchema->mergeArray($settings);
|
||||
$this->TL->init($tlSchema);
|
||||
$this->settings = $settings;
|
||||
$this->output = $output;
|
||||
}
|
||||
public function mkAnnotations()
|
||||
public function mkAnnotations(): void
|
||||
{
|
||||
\danog\MadelineProto\Logger::log('Generating annotations...', \danog\MadelineProto\Logger::NOTICE);
|
||||
$this->setProperties();
|
||||
|
@ -143,9 +172,9 @@ class AnnotationsBuilder
|
|||
$internalDoc[$namespace][$method]['return'] = $type;
|
||||
}
|
||||
$class = new \ReflectionClass($this->reflectionClasses['MTProto']);
|
||||
$methods = $class->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC);
|
||||
$methods = $class->getMethods((\ReflectionMethod::IS_STATIC & \ReflectionMethod::IS_PUBLIC) | \ReflectionMethod::IS_PUBLIC);
|
||||
$class = new \ReflectionClass(Tools::class);
|
||||
$methods = \array_merge($methods, $class->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC));
|
||||
$methods = \array_merge($methods, $class->getMethods((\ReflectionMethod::IS_STATIC & \ReflectionMethod::IS_PUBLIC) | \ReflectionMethod::IS_PUBLIC));
|
||||
foreach ($methods as $key => $method) {
|
||||
$name = $method->getName();
|
||||
if ($method == 'methodCallAsyncRead') {
|
||||
|
@ -248,7 +277,7 @@ class AnnotationsBuilder
|
|||
$paramList = \rtrim($paramList, ', ');
|
||||
$doc .= ")";
|
||||
$async = true;
|
||||
if ($hasReturnValue) {
|
||||
if ($hasReturnValue && $static) {
|
||||
$doc .= ': ';
|
||||
if ($type->allowsNull()) {
|
||||
$doc .= '?';
|
||||
|
@ -282,7 +311,35 @@ class AnnotationsBuilder
|
|||
if (!$type) {
|
||||
Logger::log("{$name} has no return type!", Logger::FATAL_ERROR);
|
||||
}
|
||||
$internalDoc['InternalDoc'][$name]['method'] = $method->getDocComment() ?? '';
|
||||
$promise = '\\'.Promise::class;
|
||||
$phpdoc = $method->getDocComment() ?? '';
|
||||
$phpdoc = \str_replace("@return \\Generator", "@return $promise", $phpdoc);
|
||||
$phpdoc = \str_replace("@return \\Promise", "@return $promise", $phpdoc);
|
||||
$phpdoc = \str_replace("@return Promise", "@return $promise", $phpdoc);
|
||||
if ($hasReturnValue && $async && \preg_match("/@return (.*)/", $phpdoc, $matches)) {
|
||||
$ret = $matches[1];
|
||||
$new = $ret;
|
||||
if ($type && !str_contains($ret, '<')) {
|
||||
$new = '';
|
||||
if ($type->allowsNull()) {
|
||||
$new .= '?';
|
||||
}
|
||||
if (!$type->isBuiltin()) {
|
||||
$new .= '\\';
|
||||
}
|
||||
$new .= $type->getName() === 'self' ? $this->reflectionClasses['API'] : $type->getName();
|
||||
}
|
||||
$phpdoc = \str_replace("@return ".$ret, "@return mixed", $phpdoc);
|
||||
if (!str_contains($phpdoc, '@psalm-return')) {
|
||||
$phpdoc = \str_replace("@return ", "@psalm-return $new|$promise<$new>\n * @return ", $phpdoc);
|
||||
}
|
||||
}
|
||||
$phpdoc = \preg_replace(
|
||||
"/@psalm-return \\\\Generator<(?:[^,]+), (?:[^,]+), (?:[^,]+), (.+)>/",
|
||||
"@psalm-return $promise<$1>",
|
||||
$phpdoc
|
||||
);
|
||||
$internalDoc['InternalDoc'][$name]['method'] = $phpdoc;
|
||||
$internalDoc['InternalDoc'][$name]['method'] .= "\n ".\implode("\n ", \explode("\n", $doc));
|
||||
}
|
||||
\fwrite($handle, "<?php\n");
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use danog\MadelineProto\Lang;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\MyTelegramOrgWrapper;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\Tools;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
|
||||
|
@ -31,40 +34,47 @@ trait Start
|
|||
/**
|
||||
* Start API ID generation process.
|
||||
*
|
||||
* @param array $settings Settings
|
||||
* @param Settings $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function APIStart(array $settings): \Generator
|
||||
private function APIStart(Settings $settings): \Generator
|
||||
{
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
if (Magic::$isIpcWorker) {
|
||||
throw new \danog\MadelineProto\Exception('Not inited!');
|
||||
}
|
||||
if ($this->getWebAPITemplate() === 'legacy') {
|
||||
$this->setWebAPITemplate($settings->getTemplates()->getHtmlTemplate());
|
||||
}
|
||||
$app = null;
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
$stdout = getStdout();
|
||||
yield $stdout->write('You did not define a valid API ID/API hash. Do you want to define it now manually, or automatically? (m/a)
|
||||
Note that you can also provide the API parameters directly in the code using the settings: https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id'.PHP_EOL);
|
||||
if (\strpos(yield Tools::readLine('Your choice (m/a): '), 'm') !== false) {
|
||||
yield $stdout->write('1) Login to my.telegram.org
|
||||
2) Go to API development tools
|
||||
3) App title: your app\'s name, can be anything
|
||||
Short name: your app\'s short name, can be anything
|
||||
URL: your app/website\'s URL, or t.me/yourusername
|
||||
Platform: anything
|
||||
Description: Describe your app here
|
||||
4) Click on create application'.PHP_EOL);
|
||||
$app['api_id'] = yield Tools::readLine('5) Enter your API ID: ');
|
||||
$app['api_hash'] = yield Tools::readLine('6) Enter your API hash: ');
|
||||
$prepare = Lang::$current_lang['apiChooseManualAuto'].PHP_EOL;
|
||||
$prepare .= \sprintf(Lang::$current_lang['apiChooseManualAutoTip'], 'https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id');
|
||||
$prepare .= PHP_EOL;
|
||||
yield $stdout->write($prepare);
|
||||
if (\strpos(yield Tools::readLine(Lang::$current_lang['apiChoosePrompt']), 'm') !== false) {
|
||||
yield $stdout->write("1) ".Lang::$current_lang['apiManualInstructions0'].PHP_EOL);
|
||||
yield $stdout->write("2) ".Lang::$current_lang['apiManualInstructions1'].PHP_EOL);
|
||||
yield $stdout->write("3) ");
|
||||
foreach (['App title', 'Short name', 'URL', 'Platform', 'Description'] as $k => $key) {
|
||||
yield $stdout->write($k ? " $key: " : "$key: ");
|
||||
yield $stdout->write(Lang::$current_lang["apiAppInstructionsManual$k"].PHP_EOL);
|
||||
}
|
||||
yield $stdout->write("4) ".Lang::$current_lang['apiManualInstructions2'].PHP_EOL);
|
||||
|
||||
$app['api_id'] = yield Tools::readLine("5) ".Lang::$current_lang['apiManualPrompt0']);
|
||||
$app['api_hash'] = yield Tools::readLine("6) ".Lang::$current_lang['apiManualPrompt1']);
|
||||
return $app;
|
||||
}
|
||||
$this->myTelegramOrgWrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine('Enter a phone number that is already registered on Telegram: '));
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: '));
|
||||
yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine(Lang::$current_lang['apiAutoPrompt0']));
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine(Lang::$current_lang['apiAutoPrompt1']));
|
||||
if (!(yield from $this->myTelegramOrgWrapper->hasApp())) {
|
||||
$app_title = yield Tools::readLine('Enter the app\'s name, can be anything: ');
|
||||
$short_name = yield Tools::readLine('Enter the app\'s short name, can be anything: ');
|
||||
$url = yield Tools::readLine('Enter the app/website\'s URL, or t.me/yourusername: ');
|
||||
$description = yield Tools::readLine('Describe your app: ');
|
||||
$app_title = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto0']);
|
||||
$short_name = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto1']);
|
||||
$url = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto2']);
|
||||
$description = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto4']);
|
||||
$app = (yield from $this->myTelegramOrgWrapper->createApp(['app_title' => $app_title, 'app_shortname' => $short_name, 'app_url' => $url, 'app_platform' => 'web', 'app_desc' => $description]));
|
||||
} else {
|
||||
$app = (yield from $this->myTelegramOrgWrapper->getApp());
|
||||
|
@ -81,7 +91,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
} elseif (isset($_POST['phone_number'])) {
|
||||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
}
|
||||
} elseif (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
if (isset($_POST['code'])) {
|
||||
|
@ -89,7 +99,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
if (yield from $this->myTelegramOrgWrapper->hasApp()) {
|
||||
return yield from $this->myTelegramOrgWrapper->getApp();
|
||||
}
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
} elseif (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
|
||||
$app['api_id'] = (int) $_POST['api_id'];
|
||||
$app['api_hash'] = $_POST['api_hash'];
|
||||
|
@ -99,7 +109,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
$this->myTelegramOrgWrapper = null;
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['app_title'], $_POST['app_shortname'], $_POST['app_url'], $_POST['app_platform'], $_POST['app_desc'])) {
|
||||
|
@ -107,18 +117,18 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
$this->gettingApiId = false;
|
||||
return $app;
|
||||
}
|
||||
yield from $this->webAPIEcho("You didn't provide all of the required parameters!");
|
||||
yield $this->webAPIEcho(Lang::$current_lang['apiParamsError']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private function webAPIPhoneLogin(array $settings): \Generator
|
||||
private function webAPIPhoneLogin(Settings $settings): \Generator
|
||||
{
|
||||
try {
|
||||
$this->myTelegramOrgWrapper = new MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login($_POST['phone_number']);
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
} catch (\Throwable $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
private function webAPICompleteLogin(): \Generator
|
||||
|
@ -126,9 +136,9 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
try {
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin($_POST['code']);
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
private function webAPICreateApp(): \Generator
|
||||
|
@ -139,9 +149,9 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
$app = (yield from $this->myTelegramOrgWrapper->createApp($params));
|
||||
return $app;
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().' Try again.');
|
||||
yield $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().' Try again.');
|
||||
yield $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Lang;
|
||||
|
||||
use function Amp\ByteStream\getOutputBufferStream;
|
||||
|
||||
trait Templates
|
||||
|
@ -28,7 +31,7 @@ trait Templates
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
private $webApiTemplate = '<!DOCTYPE html><html><head><title>MadelineProto</title></head><body><h1>MadelineProto</h1><p>%s</p><form method="POST">%s<button type="submit"/>Go</button></form></body></html>';
|
||||
private $webApiTemplate = 'legacy';
|
||||
/**
|
||||
* Generate page from template.
|
||||
*
|
||||
|
@ -39,7 +42,7 @@ trait Templates
|
|||
*/
|
||||
private function webAPIEchoTemplate(string $message, string $form): string
|
||||
{
|
||||
return \sprintf($this->webApiTemplate, $message, $form);
|
||||
return \sprintf($this->webApiTemplate, $message, $form, Lang::$current_lang['go']);
|
||||
}
|
||||
/**
|
||||
* Get web API login HTML template string.
|
||||
|
@ -53,7 +56,7 @@ trait Templates
|
|||
/**
|
||||
* Set web API login HTML template string.
|
||||
*
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
public function setWebAPITemplate(string $template): void
|
||||
{
|
||||
|
@ -64,72 +67,86 @@ trait Templates
|
|||
*
|
||||
* @param string $message Message to echo
|
||||
*
|
||||
* @return \Generator
|
||||
* @return Promise
|
||||
*/
|
||||
private function webAPIEcho(string $message = ''): \Generator
|
||||
private function webAPIEcho(string $message = ''): Promise
|
||||
{
|
||||
$stdout = getOutputBufferStream();
|
||||
$message = \htmlentities($message);
|
||||
if (!isset($this->myTelegramOrgWrapper)) {
|
||||
if (isset($_POST['type'])) {
|
||||
if ($_POST['type'] === 'manual') {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your API ID and API hash<br><b>'.$message.'</b><ol>
|
||||
<li>Login to my.telegram.org</li>
|
||||
<li>Go to API development tools</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>App title: your app's name, can be anything</li>
|
||||
<li>Short name: your app's short name, only numbers and letters</li>
|
||||
<li>Platform: Web</li>
|
||||
<li>Description: describe your app here</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click on create application</li>
|
||||
</ol>', '<input type="string" name="api_id" placeholder="API ID" required/><input type="string" name="api_hash" placeholder="API hash" required/>'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiManualWeb']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
$title .= "<ol>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions0'])."</li>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions1'])."</li>";
|
||||
$title .= "<li><ul>";
|
||||
foreach (['App title', 'Short name', 'URL', 'Platform', 'Description'] as $k => $key) {
|
||||
$title .= "<li>$key: ";
|
||||
$title .= \htmlentities(Lang::$current_lang["apiAppInstructionsManual$k"]);
|
||||
$title .= "</li>";
|
||||
}
|
||||
$title .= "</li></ul>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions2'])."</li>";
|
||||
$title .= "</ol>";
|
||||
$form = '<input type="string" name="api_id" placeholder="API ID" required/>';
|
||||
$form .= '<input type="string" name="api_hash" placeholder="API hash" required/>';
|
||||
} else {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter a phone number that is <b>already registered</b> on telegram to get the API ID<br><b>'.$message.'</b>', '<input type="text" name="phone_number" placeholder="Phone number" required/>'));
|
||||
$title = Lang::$current_lang['apiAutoWeb'];
|
||||
$title .= "<br><b>$message</b>";
|
||||
$phone = \htmlentities(Lang::$current_lang['loginUserPhoneWeb']);
|
||||
$form = "<input type='text' name='phone_number' placeholder='$phone' required/>";
|
||||
}
|
||||
} else {
|
||||
if ($message) {
|
||||
$message = '<br><br>'.$message;
|
||||
}
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Do you want to enter the API id and the API hash manually or automatically?<br>Note that you can also provide it directly in the code using the <a href="https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id">settings</a>.<b>'.$message.'</b>', '<select name="type"><option value="automatic">Automatically</option><option value="manual">Manually</option></select>'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiChooseManualAutoWeb']);
|
||||
$title .= "<br>";
|
||||
$title .= \sprintf(Lang::$current_lang['apiChooseManualAutoTipWeb'], 'https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id');
|
||||
$title .= "<b>$message</b>";
|
||||
|
||||
$automatically = \htmlentities(Lang::$current_lang['apiChooseAutomaticallyWeb']);
|
||||
$manually = \htmlentities(Lang::$current_lang['apiChooseManuallyWeb']);
|
||||
|
||||
$form = "<select name='type'><option value='automatic'>$automatically</option><option value='manual'>$manually</option></select>";
|
||||
}
|
||||
} else {
|
||||
if (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>'.$message.'</b>', '<input type="text" name="code" placeholder="Code" required/>'));
|
||||
$title = \htmlentities(Lang::$current_lang['loginUserCode']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
|
||||
$code = \htmlentities(Lang::$current_lang['loginUserPhoneCodeWeb']);
|
||||
$form = "<input type='text' name='code' placeholder='$code' required/>";
|
||||
} else {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter the API info<br><b>'.$message.'</b>', '<input type="hidden" name="creating_app" value="yes" required/>
|
||||
Enter the app name, can be anything: <br><input type="text" name="app_title" required/><br>
|
||||
<br>Enter the app's short name, alphanumeric, 5-32 chars: <br><input type="text" name="app_shortname" required/><br>
|
||||
<br>Enter the app/website URL, or https://t.me/yourusername: <br><input type="text" name="app_url" required/><br>
|
||||
<br>Enter the app platform: <br>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="android" checked> Android
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="ios"> iOS
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="wp"> Windows Phone
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="bb"> BlackBerry
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="desktop"> Desktop
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="web"> Web
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="ubp"> Ubuntu phone
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="other"> Other (specify in description)
|
||||
</label>
|
||||
<br><br>Enter the app description, can be anything: <br><textarea name="app_desc" required></textarea><br><br>
|
||||
'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiAppWeb']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
|
||||
$form = '<input type="hidden" name="creating_app" value="yes" required/>';
|
||||
foreach (['app_title', 'app_shortname', 'app_url', 'app_platform', 'app_desc'] as $k => $field) {
|
||||
$desc = \htmlentities(Lang::$current_lang["apiAppInstructionsAuto$k"]);
|
||||
if ($field == 'app_platform') {
|
||||
$form .= "$desc<br>";
|
||||
foreach ([
|
||||
'android' => 'Android',
|
||||
'ios' => 'iOS',
|
||||
'wp' => 'Windows Phone',
|
||||
'bb' => 'BlackBerry',
|
||||
'desktop' => 'Desktop',
|
||||
'web' => 'Web',
|
||||
'ubp' => 'Ubuntu phone',
|
||||
'other' => \htmlentities(Lang::$current_lang['apiAppInstructionsAutoTypeOther'])
|
||||
] as $key => $desc) {
|
||||
$form .= "<label><input type='radio' name='app_platform' value='$key' checked> $desc</label>";
|
||||
}
|
||||
} elseif ($field === 'app_desc') {
|
||||
$form .= "$desc<br><textarea name='$field' required></textarea><br><br>";
|
||||
} else {
|
||||
$form .= "$desc<br><input type='text' name='$field' required/><br><br>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return getOutputBufferStream()->write($this->webAPIEchoTemplate($title, $form));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class AsyncConstruct
|
|||
/**
|
||||
* Async init promise.
|
||||
*
|
||||
* @var Promise
|
||||
* @var Promise|null|boolean
|
||||
*/
|
||||
private $asyncInitPromise;
|
||||
/**
|
||||
|
@ -93,10 +93,9 @@ class AsyncConstruct
|
|||
$this->asyncInitPromise = Tools::call($promise);
|
||||
$this->asyncInitPromise->onResolve(
|
||||
function (?\Throwable $error, $result): void {
|
||||
if ($error) {
|
||||
throw $error;
|
||||
if (!$error) {
|
||||
$this->asyncInitPromise = null;
|
||||
}
|
||||
$this->asyncInitPromise = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CombinedAPI module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Loop;
|
||||
use function Amp\Promise\all;
|
||||
|
||||
class CombinedAPI
|
||||
{
|
||||
use \danog\Serializable;
|
||||
public $session;
|
||||
public $instance_paths = [];
|
||||
public $instances = [];
|
||||
public $timeout = 5;
|
||||
public $serialization_interval = 30;
|
||||
public $serialized = 0;
|
||||
protected $async;
|
||||
public function __magic_construct($session, $paths = [])
|
||||
{
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\danog\MadelineProto\Magic::classExists();
|
||||
$realpaths = new SessionPaths($session);
|
||||
$this->session = $realpaths->getSessionPath();
|
||||
foreach ($paths as $path => $settings) {
|
||||
$this->addInstance($path, $settings);
|
||||
}
|
||||
if (\file_exists($realpaths->getSessionPath())) {
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$lock = \fopen($realpaths->getLockPath(), 'r');
|
||||
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
|
||||
\flock($lock, LOCK_SH);
|
||||
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
|
||||
try {
|
||||
$tounserialize = \file_get_contents($realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$deserialized = \unserialize($tounserialize);
|
||||
/*foreach ($deserialized['instance_paths'] as $path) {
|
||||
$this->addInstance($path, isset($paths[$path]) ? $paths[$path] : []);
|
||||
}*/
|
||||
$this->event_handler = $deserialized['event_handler'];
|
||||
$this->event_handler_instance = $deserialized['event_handler_instance'];
|
||||
if ($this->event_handler !== null) {
|
||||
$this->setEventHandler($this->event_handler);
|
||||
}
|
||||
}
|
||||
foreach ($paths as $path => $settings) {
|
||||
$this->addInstance($path, $settings);
|
||||
}
|
||||
}
|
||||
public function addInstance($path, $settings = [])
|
||||
{
|
||||
if (isset($this->instances[$path]) && isset($this->instance_paths[$path])) {
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->referenceInstance($path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
\danog\MadelineProto\Logger::constructor(3);
|
||||
\danog\MadelineProto\Logger::log("INSTANTIATING {$path}...");
|
||||
$instance = new \danog\MadelineProto\API($path, $settings);
|
||||
$this->instance_paths[$path] = $path;
|
||||
$this->instances[$path] = $instance;
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->referenceInstance($path);
|
||||
}
|
||||
}
|
||||
public function removeInstance($path)
|
||||
{
|
||||
if (isset($this->instance_paths[$path])) {
|
||||
unset($this->instance_paths[$path]);
|
||||
}
|
||||
if (isset($this->instances[$path])) {
|
||||
unset($this->instances[$path]);
|
||||
}
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->removeInstance($path);
|
||||
}
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
$this->serialize();
|
||||
}
|
||||
public function serialize($filename = '')
|
||||
{
|
||||
/*foreach ($this->instances as $instance) {
|
||||
$instance->serialize();
|
||||
}*/
|
||||
if (\is_null($this->session)) {
|
||||
return;
|
||||
}
|
||||
if ($filename === '') {
|
||||
$filename = $this->session;
|
||||
}
|
||||
Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
|
||||
$realpaths = new SessionPaths($filename);
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$lock = \fopen($realpaths->getLockPath(), 'w');
|
||||
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
\flock($lock, LOCK_EX);
|
||||
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
$wrote = \file_put_contents($realpaths->getTempPath(), \serialize(['event_handler' => $this->event_handler, 'event_handler_instance' => $this->event_handler_instance, 'instance_paths' => $this->instance_paths]));
|
||||
\rename($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$this->serialized = \time();
|
||||
return $wrote;
|
||||
}
|
||||
public $event_handler;
|
||||
private $event_handler_instance;
|
||||
private $event_handler_methods = [];
|
||||
public function getEventHandler()
|
||||
{
|
||||
return $this->event_handler_instance;
|
||||
}
|
||||
public function setEventHandler($event_handler)
|
||||
{
|
||||
if (!\class_exists($event_handler) || !\is_subclass_of($event_handler, '\\danog\\MadelineProto\\CombinedEventHandler')) {
|
||||
throw new \danog\MadelineProto\Exception('Wrong event handler was defined');
|
||||
}
|
||||
$this->event_handler = $event_handler;
|
||||
if (!$this->event_handler_instance instanceof $this->event_handler) {
|
||||
$class_name = $this->event_handler;
|
||||
$this->event_handler_instance = new $class_name($this);
|
||||
} else {
|
||||
$this->event_handler_instance->__construct($this);
|
||||
}
|
||||
$this->event_handler_methods = [];
|
||||
foreach (\get_class_methods($this->event_handler) as $method) {
|
||||
if ($method === 'onLoop') {
|
||||
$this->loop_callback = [$this->event_handler_instance, 'onLoop'];
|
||||
} elseif ($method === 'onAny') {
|
||||
foreach (\end($this->instances)->API->getTL()->getConstructors()->by_id as $constructor) {
|
||||
if ($constructor['type'] === 'Update' && !isset($this->event_handler_methods[$constructor['predicate']])) {
|
||||
$this->event_handler_methods[$constructor['predicate']] = [$this->event_handler_instance, 'onAny'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$method_name = \lcfirst(\substr($method, 2));
|
||||
$this->event_handler_methods[$method_name] = [$this->event_handler_instance, $method];
|
||||
}
|
||||
}
|
||||
}
|
||||
public function eventUpdateHandler($update, $instance)
|
||||
{
|
||||
if (isset($this->event_handler_methods[$update['_']])) {
|
||||
return $this->event_handler_methods[$update['_']]($update, $instance);
|
||||
}
|
||||
}
|
||||
private $loop_callback;
|
||||
public function async($async)
|
||||
{
|
||||
$this->async = $async;
|
||||
foreach ($this->instances as $instance) {
|
||||
$instance->async($async);
|
||||
}
|
||||
}
|
||||
public function setLoopCallback($callback)
|
||||
{
|
||||
$this->loop_callback = $callback;
|
||||
}
|
||||
public function getUpdates($params = [])
|
||||
{
|
||||
}
|
||||
public function loop($max_forks = 0)
|
||||
{
|
||||
if (\is_callable($max_forks)) {
|
||||
return \danog\MadelineProto\Tools::wait($max_forks());
|
||||
}
|
||||
$loops = [];
|
||||
foreach ($this->instances as $path => $instance) {
|
||||
\danog\MadelineProto\Tools::wait($instance->initAsynchronously());
|
||||
if ($instance->API->authorized !== MTProto::LOGGED_IN) {
|
||||
continue;
|
||||
}
|
||||
if (!$instance->API->settings['updates']['handle_updates']) {
|
||||
$instance->API->settings['updates']['handle_updates'] = true;
|
||||
$instance->API->startUpdateSystem();
|
||||
}
|
||||
$instance->setCallback(function ($update) use ($path) {
|
||||
return $this->eventUpdateHandler($update, $path);
|
||||
}, ['async' => false]);
|
||||
if ($this->loop_callback !== null) {
|
||||
$instance->setLoopCallback($this->loop_callback, ['async' => false]);
|
||||
}
|
||||
$loops[] = \danog\MadelineProto\Tools::call($instance->loop(0, ['async' => true]));
|
||||
}
|
||||
Loop::repeat($this->serialization_interval * 1000, function () {
|
||||
\danog\MadelineProto\Logger::log('Serializing combined event handler');
|
||||
$this->serialize();
|
||||
});
|
||||
\danog\MadelineProto\Logger::log('Started update loop', \danog\MadelineProto\Logger::NOTICE);
|
||||
\danog\MadelineProto\Tools::wait(all($loops));
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CombinedEventHandler module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
abstract class CombinedEventHandler
|
||||
{
|
||||
private $CombinedAPI;
|
||||
public function __construct($CombinedAPI)
|
||||
{
|
||||
$this->CombinedAPI = $CombinedAPI;
|
||||
foreach ($CombinedAPI->instances as $path => $instance) {
|
||||
$this->referenceInstance($path);
|
||||
}
|
||||
}
|
||||
final public function __sleep()
|
||||
{
|
||||
$keys = \method_exists($this, '__magic_sleep') ? $this->__magic_sleep() : \get_object_vars($this);
|
||||
unset($keys['CombinedAPI']);
|
||||
if (isset($this->CombinedAPI) && $this->CombinedAPI instanceof CombinedAPI) {
|
||||
foreach ($this->CombinedAPI->instance_paths as $path) {
|
||||
unset($keys[$path]);
|
||||
}
|
||||
} else {
|
||||
foreach ($keys as $key => $value) {
|
||||
if ($value instanceof API && $key === $value->session) {
|
||||
unset($keys[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return \array_keys($keys);
|
||||
}
|
||||
final public function referenceInstance($path)
|
||||
{
|
||||
$this->{$path} = $this->CombinedAPI->instances[$path];
|
||||
}
|
||||
final public function removeInstance($path)
|
||||
{
|
||||
if (isset($this->{$path})) {
|
||||
unset($this->{$path});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,11 +21,13 @@ namespace danog\MadelineProto;
|
|||
|
||||
use Amp\ByteStream\ClosedException;
|
||||
use Amp\Deferred;
|
||||
use Amp\Failure;
|
||||
use danog\MadelineProto\Loop\Connection\CheckLoop;
|
||||
use danog\MadelineProto\Loop\Connection\HttpWaitLoop;
|
||||
use danog\MadelineProto\Loop\Connection\PingLoop;
|
||||
use danog\MadelineProto\Loop\Connection\ReadLoop;
|
||||
use danog\MadelineProto\Loop\Connection\WriteLoop;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
use danog\MadelineProto\MTProtoSession\Session;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
||||
|
@ -39,10 +41,13 @@ use danog\MadelineProto\Stream\Transport\WsStream;
|
|||
*
|
||||
* Manages connection to Telegram datacenters
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Connection extends Session
|
||||
class Connection
|
||||
{
|
||||
use Session;
|
||||
use \danog\Serializable;
|
||||
/**
|
||||
* Writer loop.
|
||||
|
@ -101,9 +106,9 @@ class Connection extends Session
|
|||
/**
|
||||
* Date of last chunk received.
|
||||
*
|
||||
* @var integer
|
||||
* @var float
|
||||
*/
|
||||
private $lastChunk = 0;
|
||||
private float $lastChunk = 0;
|
||||
/**
|
||||
* Logger instance.
|
||||
*
|
||||
|
@ -166,24 +171,6 @@ class Connection extends Session
|
|||
{
|
||||
return $this->needsReconnect;
|
||||
}
|
||||
/**
|
||||
* Check if the socket is writing stuff.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWriting(): bool
|
||||
{
|
||||
return $this->writing;
|
||||
}
|
||||
/**
|
||||
* Check if the socket is reading stuff.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isReading(): bool
|
||||
{
|
||||
return $this->reading;
|
||||
}
|
||||
/**
|
||||
* Set writing boolean.
|
||||
*
|
||||
|
@ -191,7 +178,7 @@ class Connection extends Session
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writing(bool $writing)
|
||||
public function writing(bool $writing): void
|
||||
{
|
||||
$this->shared->writing($writing, $this->id);
|
||||
}
|
||||
|
@ -202,7 +189,7 @@ class Connection extends Session
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reading(bool $reading)
|
||||
public function reading(bool $reading): void
|
||||
{
|
||||
$this->shared->reading($reading, $this->id);
|
||||
}
|
||||
|
@ -218,9 +205,9 @@ class Connection extends Session
|
|||
/**
|
||||
* Get the receive date of the latest chunk of data from the socket.
|
||||
*
|
||||
* @return int
|
||||
* @return float
|
||||
*/
|
||||
public function getLastChunk(): int
|
||||
public function getLastChunk(): float
|
||||
{
|
||||
return $this->lastChunk;
|
||||
}
|
||||
|
@ -294,7 +281,7 @@ class Connection extends Session
|
|||
*/
|
||||
public function isHttp(): bool
|
||||
{
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::class, HttpsStream::class]);
|
||||
}
|
||||
/**
|
||||
* Check if is a media connection.
|
||||
|
@ -319,7 +306,9 @@ class Connection extends Session
|
|||
*
|
||||
* @param ConnectionContext $ctx Connection context
|
||||
*
|
||||
* @return \Amp\Promise
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<mixed, StreamInterface, mixed, void>
|
||||
*/
|
||||
public function connect(ConnectionContext $ctx): \Generator
|
||||
{
|
||||
|
@ -348,14 +337,15 @@ class Connection extends Session
|
|||
if (!isset($this->waiter)) {
|
||||
$this->waiter = new HttpWaitLoop($this);
|
||||
}
|
||||
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::getName()) || $this->ctx->hasStreamName(WsStream::getName()))) {
|
||||
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::class) || $this->ctx->hasStreamName(WsStream::class))) {
|
||||
$this->pinger = new PingLoop($this);
|
||||
}
|
||||
foreach ($this->new_outgoing as $message_id) {
|
||||
if ($this->outgoing_messages[$message_id]['unencrypted']) {
|
||||
$promise = $this->outgoing_messages[$message_id]['promise'];
|
||||
\Amp\Loop::defer(function () use ($promise) {
|
||||
$promise->fail(new Exception('Restart because we were reconnected'));
|
||||
foreach ($this->new_outgoing as $message_id => $message) {
|
||||
if ($message->isUnencrypted()) {
|
||||
\Amp\Loop::defer(function () use ($message) {
|
||||
if (!($message->getState() & OutgoingMessage::STATE_REPLIED)) {
|
||||
$message->reply(new Failure(new Exception('Restart because we were reconnected')));
|
||||
}
|
||||
});
|
||||
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
|
||||
}
|
||||
|
@ -370,67 +360,133 @@ class Connection extends Session
|
|||
$this->pinger->start();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Apply method abstractions.
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $arguments Arguments
|
||||
*
|
||||
* @return \Generator Whether we need to resolve a queue promise
|
||||
*/
|
||||
private function methodAbstractions(string &$method, array &$arguments): \Generator
|
||||
{
|
||||
if ($method === 'messages.importChatInvite' && isset($arguments['hash']) && \is_string($arguments['hash']) && \preg_match('@(?:t|telegram)\\.(?:me|dog)/(joinchat/)?([a-z0-9_-]*)@i', $arguments['hash'], $matches)) {
|
||||
if ($matches[1] === '') {
|
||||
$method = 'channels.joinChannel';
|
||||
$arguments['channel'] = $matches[2];
|
||||
} else {
|
||||
$arguments['hash'] = $matches[2];
|
||||
}
|
||||
} elseif ($method === 'messages.checkChatInvite' && isset($arguments['hash']) && \is_string($arguments['hash']) && \preg_match('@(?:t|telegram)\\.(?:me|dog)/joinchat/([a-z0-9_-]*)@i', $arguments['hash'], $matches)) {
|
||||
$arguments['hash'] = $matches[1];
|
||||
} elseif ($method === 'channels.joinChannel' && isset($arguments['channel']) && \is_string($arguments['channel']) && \preg_match('@(?:t|telegram)\\.(?:me|dog)/(joinchat/)?([a-z0-9_-]*)@i', $arguments['channel'], $matches)) {
|
||||
if ($matches[1] !== '') {
|
||||
$method = 'messages.importChatInvite';
|
||||
$arguments['hash'] = $matches[2];
|
||||
}
|
||||
} elseif ($method === 'messages.sendMessage' && isset($arguments['peer']['_']) && \in_array($arguments['peer']['_'], ['inputEncryptedChat', 'updateEncryption', 'updateEncryptedChatTyping', 'updateEncryptedMessagesRead', 'updateNewEncryptedMessage', 'encryptedMessage', 'encryptedMessageService'])) {
|
||||
$method = 'messages.sendEncrypted';
|
||||
$arguments = ['peer' => $arguments['peer'], 'message' => $arguments];
|
||||
if (!isset($arguments['message']['_'])) {
|
||||
$arguments['message']['_'] = 'decryptedMessage';
|
||||
}
|
||||
if (!isset($arguments['message']['ttl'])) {
|
||||
$arguments['message']['ttl'] = 0;
|
||||
}
|
||||
if (isset($arguments['message']['reply_to_msg_id'])) {
|
||||
$arguments['message']['reply_to_random_id'] = $arguments['message']['reply_to_msg_id'];
|
||||
}
|
||||
} elseif ($method === 'messages.sendEncryptedFile' || $method === 'messages.uploadEncryptedFile') {
|
||||
if (isset($arguments['file'])) {
|
||||
if ((!\is_array($arguments['file']) || !(isset($arguments['file']['_']) && $this->API->getTL()->getConstructors()->findByPredicate($arguments['file']['_']) === 'InputEncryptedFile')) && $this->API->getSettings()->getFiles()->getAllowAutomaticUpload()) {
|
||||
$arguments['file'] = (yield from $this->API->uploadEncrypted($arguments['file']));
|
||||
}
|
||||
if (isset($arguments['file']['key'])) {
|
||||
$arguments['message']['media']['key'] = $arguments['file']['key'];
|
||||
}
|
||||
if (isset($arguments['file']['iv'])) {
|
||||
$arguments['message']['media']['iv'] = $arguments['file']['iv'];
|
||||
}
|
||||
if (isset($arguments['file']['size'])) {
|
||||
$arguments['message']['media']['size'] = $arguments['file']['size'];
|
||||
}
|
||||
}
|
||||
$arguments['queuePromise'] = new Deferred;
|
||||
return $arguments['queuePromise'];
|
||||
} elseif (\in_array($method, ['messages.addChatUser', 'messages.deleteChatUser', 'messages.editChatAdmin', 'messages.editChatPhoto', 'messages.editChatTitle', 'messages.getFullChat', 'messages.exportChatInvite', 'messages.editChatAdmin', 'messages.migrateChat']) && isset($arguments['chat_id']) && (!\is_numeric($arguments['chat_id']) || $arguments['chat_id'] < 0)) {
|
||||
$res = (yield from $this->API->getInfo($arguments['chat_id']));
|
||||
if ($res['type'] !== 'chat') {
|
||||
throw new \danog\MadelineProto\Exception('chat_id is not a chat id (only normal groups allowed, not supergroups)!');
|
||||
}
|
||||
$arguments['chat_id'] = $res['chat_id'];
|
||||
} elseif ($method === 'photos.updateProfilePhoto') {
|
||||
if (isset($arguments['id'])) {
|
||||
if (!\is_array($arguments['id'])) {
|
||||
$method = 'photos.uploadProfilePhoto';
|
||||
$arguments['file'] = $arguments['id'];
|
||||
}
|
||||
} elseif (isset($arguments['file'])) {
|
||||
$method = 'photos.uploadProfilePhoto';
|
||||
}
|
||||
} elseif ($method === 'photos.uploadProfilePhoto') {
|
||||
if (isset($arguments['file'])) {
|
||||
if (\is_array($arguments['file']) && !\in_array($arguments['file']['_'], ['inputFile', 'inputFileBig'])) {
|
||||
$method = 'photos.uploadProfilePhoto';
|
||||
$arguments['id'] = $arguments['file'];
|
||||
}
|
||||
} elseif (isset($arguments['id'])) {
|
||||
$method = 'photos.updateProfilePhoto';
|
||||
}
|
||||
} elseif ($method === 'messages.uploadMedia') {
|
||||
if (!isset($arguments['peer']) && !$this->API->getSelf()['bot']) {
|
||||
$arguments['peer'] = 'me';
|
||||
}
|
||||
}
|
||||
if ($method === 'messages.sendEncrypted' || $method === 'messages.sendEncryptedService') {
|
||||
$arguments['queuePromise'] = new Deferred;
|
||||
return $arguments['queuePromise'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Send an MTProto message.
|
||||
*
|
||||
* Structure of message array:
|
||||
* [
|
||||
* // only in outgoing messages
|
||||
* 'body' => deserialized body, (optional if container)
|
||||
* 'serialized_body' => 'serialized body', (optional if container)
|
||||
* 'contentRelated' => bool,
|
||||
* '_' => 'predicate',
|
||||
* 'promise' => deferred promise that gets resolved when a response to the message is received (optional),
|
||||
* 'send_promise' => deferred promise that gets resolved when the message is sent (optional),
|
||||
* 'file' => bool (optional),
|
||||
* 'type' => 'type' (optional),
|
||||
* 'queue' => queue ID (optional),
|
||||
* 'container' => [message ids] (optional),
|
||||
*
|
||||
* // only in incoming messages
|
||||
* 'content' => deserialized body,
|
||||
* 'seq_no' => number (optional),
|
||||
* 'from_container' => bool (optional),
|
||||
*
|
||||
* // can be present in both
|
||||
* 'response' => message id (optional),
|
||||
* 'msg_id' => message id (optional),
|
||||
* 'sent' => timestamp,
|
||||
* 'tries' => number
|
||||
* ]
|
||||
*
|
||||
* @param array $message The message to send
|
||||
* @param boolean $flush Whether to flush the message right away
|
||||
* @param OutgoingMessage $message The message to send
|
||||
* @param boolean $flush Whether to flush the message right away
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function sendMessage(array $message, bool $flush = true): \Generator
|
||||
public function sendMessage(OutgoingMessage $message, bool $flush = true): \Generator
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
if (!isset($message['serialized_body'])) {
|
||||
$body = \is_object($message['body']) ? yield from $message['body'] : $message['body'];
|
||||
$refreshNext = $message['refreshReferences'] ?? false;
|
||||
if ($refreshNext) {
|
||||
$message->trySend();
|
||||
$promise = $message->getSendPromise();
|
||||
if (!$message->hasSerializedBody() || $message->shouldRefreshReferences()) {
|
||||
$body = yield from $message->getBody();
|
||||
if ($message->shouldRefreshReferences()) {
|
||||
$this->API->referenceDatabase->refreshNext(true);
|
||||
}
|
||||
if ($message['method']) {
|
||||
$body = (yield from $this->API->getTL()->serializeMethod($message['_'], $body));
|
||||
if ($message->isMethod()) {
|
||||
$method = $message->getConstructor();
|
||||
$queuePromise = yield from $this->methodAbstractions($method, $body);
|
||||
$body = yield from $this->API->getTL()->serializeMethod($method, $body);
|
||||
} else {
|
||||
$body['_'] = $message['_'];
|
||||
$body = (yield from $this->API->getTL()->serializeObject(['type' => ''], $body, $message['_']));
|
||||
$body['_'] = $message->getConstructor();
|
||||
$body = yield from $this->API->getTL()->serializeObject(['type' => ''], $body, $message->getConstructor());
|
||||
}
|
||||
if ($refreshNext) {
|
||||
if ($message->shouldRefreshReferences()) {
|
||||
$this->API->referenceDatabase->refreshNext(false);
|
||||
}
|
||||
$message['serialized_body'] = $body;
|
||||
$message->setSerializedBody($body);
|
||||
unset($body);
|
||||
}
|
||||
$message['send_promise'] = $deferred;
|
||||
$this->pending_outgoing[$this->pending_outgoing_key++] = $message;
|
||||
$this->pendingOutgoing[$this->pendingOutgoingKey++] = $message;
|
||||
if (isset($queuePromise)) {
|
||||
$queuePromise->resolve();
|
||||
}
|
||||
if ($flush && isset($this->writer)) {
|
||||
$this->writer->resume();
|
||||
}
|
||||
return yield $deferred->promise();
|
||||
return yield $promise;
|
||||
}
|
||||
/**
|
||||
* Flush pending packets.
|
||||
|
@ -477,7 +533,7 @@ class Connection extends Session
|
|||
*
|
||||
* @return MTProto
|
||||
*/
|
||||
public function getExtra()
|
||||
public function getExtra(): MTProto
|
||||
{
|
||||
return $this->API;
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ class ContextConnector implements Connector
|
|||
$this->fromDns = $fromDns;
|
||||
$this->logger = $dataCenter->getAPI()->getLogger();
|
||||
}
|
||||
public function connect(string $uri, ?ConnectContext $ctx = null, ?CancellationToken $token = null): Promise
|
||||
public function connect(string $uri, ?ConnectContext $context = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
return Tools::call((function () use ($uri, $ctx, $token): \Generator {
|
||||
$ctx = $ctx ?? new ConnectContext();
|
||||
return Tools::call((function () use ($uri, $context, $token): \Generator {
|
||||
$ctx = $context ?? new ConnectContext();
|
||||
$token = $token ?? new NullCancellationToken();
|
||||
$ctxs = $this->dataCenter->generateContexts(0, $uri, $ctx);
|
||||
if (empty($ctxs)) {
|
||||
|
@ -55,7 +55,7 @@ class ContextConnector implements Connector
|
|||
$this->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
|
||||
return $result->getSocket();
|
||||
} catch (\Throwable $e) {
|
||||
if (@\constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
if (\defined('MADELINEPROTO_TEST') && \constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
$this->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR);
|
||||
|
|
|
@ -34,6 +34,7 @@ use Amp\Failure;
|
|||
use Amp\Internal;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use JsonSerializable;
|
||||
use ReflectionGenerator;
|
||||
|
||||
/**
|
||||
|
@ -43,7 +44,7 @@ use ReflectionGenerator;
|
|||
* value is sent into the generator, while a failure reason is thrown into the generator. Using a coroutine,
|
||||
* asynchronous code can be written without callbacks and be structured like synchronous code.
|
||||
*/
|
||||
final class Coroutine implements Promise, \ArrayAccess
|
||||
final class Coroutine implements Promise, \ArrayAccess, JsonSerializable
|
||||
{
|
||||
use Internal\Placeholder {
|
||||
fail as internalFail;
|
||||
|
@ -99,7 +100,7 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
* @param \Throwable|null $exception Exception to be thrown into the generator.
|
||||
* @param mixed $value Value to be sent into the generator.
|
||||
*/
|
||||
$this->onResolve = function ($exception, $value) {
|
||||
$this->onResolve = function ($exception, $value): void {
|
||||
$this->exception = $exception;
|
||||
$this->value = $value;
|
||||
if (!$this->immediate) {
|
||||
|
@ -176,8 +177,10 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
}
|
||||
/**
|
||||
* @param \Throwable $reason Failure reason.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fail(\Throwable $reason)
|
||||
public function fail(\Throwable $reason): void
|
||||
{
|
||||
$this->resolve(new Failure($reason));
|
||||
}
|
||||
|
@ -199,7 +202,7 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
return $result[$offset];
|
||||
})());
|
||||
}
|
||||
public function offsetSet($offset, $value)
|
||||
public function offsetSet($offset, $value): Promise
|
||||
{
|
||||
return Tools::call((function () use ($offset, $value): \Generator {
|
||||
$result = yield $this;
|
||||
|
@ -209,7 +212,7 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
return $result[$offset] = $value;
|
||||
})());
|
||||
}
|
||||
public function offsetUnset($offset)
|
||||
public function offsetUnset($offset): Promise
|
||||
{
|
||||
return Tools::call((function () use ($offset): \Generator {
|
||||
$result = yield $this;
|
||||
|
@ -234,7 +237,7 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
{
|
||||
return Tools::call((function () use ($name, $arguments): \Generator {
|
||||
$result = yield $this;
|
||||
return $result->{$name}($arguments);
|
||||
return $result->{$name}(...$arguments);
|
||||
})());
|
||||
}
|
||||
/**
|
||||
|
@ -270,4 +273,20 @@ final class Coroutine implements Promise, \ArrayAccess
|
|||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private const WARNING = 'To obtain a result from a Coroutine object, yield the result or disable async (not recommended). See https://docs.madelineproto.xyz/docs/ASYNC.html for more information on async.';
|
||||
public function __debugInfo()
|
||||
{
|
||||
return [self::WARNING];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return self::WARNING;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ use Amp\Websocket\Client\Handshake;
|
|||
use Amp\Websocket\Client\Rfc6455Connector;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
||||
use danog\MadelineProto\Stream\Common\BufferedRawStream;
|
||||
use danog\MadelineProto\Stream\Common\UdpBufferedStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
|
@ -48,9 +49,6 @@ use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
|
|||
use danog\MadelineProto\Stream\MTProtoTransport\IntermediatePaddedStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\IntermediateStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
|
||||
use danog\MadelineProto\Stream\Proxy\HttpProxy;
|
||||
use danog\MadelineProto\Stream\Proxy\SocksProxy;
|
||||
use danog\MadelineProto\Stream\StreamInterface;
|
||||
use danog\MadelineProto\Stream\Transport\DefaultStream;
|
||||
use danog\MadelineProto\Stream\Transport\WssStream;
|
||||
use danog\MadelineProto\Stream\Transport\WsStream;
|
||||
|
@ -64,13 +62,13 @@ class DataCenter
|
|||
/**
|
||||
* All socket connections to DCs.
|
||||
*
|
||||
* @var array<string, DataCenterConnection>
|
||||
* @var array<string|int, DataCenterConnection>
|
||||
*/
|
||||
public $sockets = [];
|
||||
/**
|
||||
* Current DC ID.
|
||||
*
|
||||
* @var string
|
||||
* @var string|int
|
||||
*/
|
||||
public $curdc = 0;
|
||||
/**
|
||||
|
@ -88,9 +86,9 @@ class DataCenter
|
|||
/**
|
||||
* Settings.
|
||||
*
|
||||
* @var array
|
||||
* @var ConnectionSettings
|
||||
*/
|
||||
private $settings = [];
|
||||
private $settings;
|
||||
/**
|
||||
* HTTP client.
|
||||
*
|
||||
|
@ -100,13 +98,13 @@ class DataCenter
|
|||
/**
|
||||
* DNS over HTTPS client.
|
||||
*
|
||||
* @var \Amp\DoH\Rfc8484StubResolver
|
||||
* @var Rfc8484StubResolver|Rfc1035StubResolver
|
||||
*/
|
||||
private $DoHClient;
|
||||
/**
|
||||
* Non-proxied DNS over HTTPS client.
|
||||
*
|
||||
* @var \Amp\DoH\Rfc8484StubResolver
|
||||
* @var Rfc8484StubResolver|Rfc1035StubResolver
|
||||
*/
|
||||
private $nonProxiedDoHClient;
|
||||
/**
|
||||
|
@ -124,7 +122,7 @@ class DataCenter
|
|||
/**
|
||||
* DoH connector.
|
||||
*/
|
||||
private Rfc6455Connector $webSocketConnnector;
|
||||
private Rfc6455Connector $webSocketConnector;
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
|
@ -132,16 +130,23 @@ class DataCenter
|
|||
}
|
||||
public function __wakeup()
|
||||
{
|
||||
if (\is_array($this->settings)) {
|
||||
$settings = new ConnectionSettings;
|
||||
$settings->mergeArray(['connection_settings' => $this->settings]);
|
||||
$this->settings = $settings;
|
||||
}
|
||||
$array = [];
|
||||
foreach ($this->sockets as $id => $socket) {
|
||||
if ($socket instanceof Connection) {
|
||||
if ($socket->temp_auth_key) {
|
||||
if ($socket instanceof \danog\MadelineProto\Connection) {
|
||||
if (isset($socket->temp_auth_key) && $socket->temp_auth_key) {
|
||||
$array[$id]['tempAuthKey'] = $socket->temp_auth_key;
|
||||
}
|
||||
if ($socket->auth_key) {
|
||||
if (isset($socket->auth_key) && $socket->auth_key) {
|
||||
$array[$id]['permAuthKey'] = $socket->auth_key;
|
||||
/** @psalm-suppress UndefinedPropertyFetch */
|
||||
$array[$id]['permAuthKey']['authorized'] = $socket->authorized;
|
||||
}
|
||||
$array[$id] = [];
|
||||
}
|
||||
}
|
||||
$this->setDataCenterConnections($array);
|
||||
|
@ -185,19 +190,19 @@ class DataCenter
|
|||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param MTProto $API Main MTProto instance
|
||||
* @param array $dclist DC IP list
|
||||
* @param array $settings Settings
|
||||
* @param boolean $reconnectAll Whether to reconnect to all DCs or just to changed ones
|
||||
* @param CookieJar $jar Cookie jar
|
||||
* @param MTProto $API Main MTProto instance
|
||||
* @param array $dclist DC IP list
|
||||
* @param ConnectionSettings $settings Settings
|
||||
* @param boolean $reconnectAll Whether to reconnect to all DCs or just to changed ones
|
||||
* @param CookieJar $jar Cookie jar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct($API, array $dclist, array $settings, bool $reconnectAll = true, CookieJar $jar = null)
|
||||
public function __magic_construct($API, array $dclist, ConnectionSettings $settings, bool $reconnectAll = true, CookieJar $jar = null)
|
||||
{
|
||||
$this->API = $API;
|
||||
$changed = [];
|
||||
$changedSettings = $this->settings !== $settings;
|
||||
$changedSettings = $settings->hasChanged();
|
||||
if (!$reconnectAll) {
|
||||
$changed = [];
|
||||
$test = $API->getCachedConfig()['test_mode'] ?? false ? 'test' : 'main';
|
||||
|
@ -213,7 +218,6 @@ class DataCenter
|
|||
$this->settings = $settings;
|
||||
foreach ($this->sockets as $key => $socket) {
|
||||
if ($socket instanceof DataCenterConnection && !\strpos($key, '_bk')) {
|
||||
//$this->API->logger->logger(\sprintf(Lang::$current_lang['dc_con_stop'], $key), Logger::VERBOSE);
|
||||
if ($reconnectAll || isset($changed[$id])) {
|
||||
$this->API->logger->logger("Disconnecting all before reconnect!");
|
||||
$socket->needReconnect(true);
|
||||
|
@ -230,14 +234,17 @@ class DataCenter
|
|||
$DoHHTTPClient = (new HttpClientBuilder())->interceptNetwork(new CookieInterceptor($this->CookieJar))->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this, true))))->build();
|
||||
$DoHConfig = new DoHConfig([new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'), new Nameserver('https://dns.google/resolve')], $DoHHTTPClient);
|
||||
$nonProxiedDoHConfig = new DoHConfig([new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'), new Nameserver('https://dns.google/resolve')]);
|
||||
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost ? new Rfc1035StubResolver() : new Rfc8484StubResolver($DoHConfig);
|
||||
$this->nonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost ? new Rfc1035StubResolver() : new Rfc8484StubResolver($nonProxiedDoHConfig);
|
||||
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost || !$settings->getUseDoH()
|
||||
? new Rfc1035StubResolver()
|
||||
: new Rfc8484StubResolver($DoHConfig);
|
||||
$this->nonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost || !$settings->getUseDoH()
|
||||
? new Rfc1035StubResolver()
|
||||
: new Rfc8484StubResolver($nonProxiedDoHConfig);
|
||||
|
||||
$this->dnsConnector = new DnsConnector(new Rfc1035StubResolver());
|
||||
if (\class_exists(Rfc6455Connector::class)) {
|
||||
$this->webSocketConnnector = new Rfc6455Connector($this->HTTPClient);
|
||||
}
|
||||
$this->webSocketConnector = new Rfc6455Connector($this->HTTPClient);
|
||||
}
|
||||
$this->settings->applyChanges();
|
||||
}
|
||||
/**
|
||||
* Set VoIP endpoints.
|
||||
|
@ -283,7 +290,7 @@ class DataCenter
|
|||
$this->API->logger->logger('OK!', Logger::WARNING);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
if (@\constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
if (\defined("MADELINEPROTO_TEST") && \constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
$this->API->logger->logger("Connection failed ({$dc_number}): ".$e->getMessage(), Logger::ERROR);
|
||||
|
@ -304,149 +311,120 @@ class DataCenter
|
|||
{
|
||||
$ctxs = [];
|
||||
$combos = [];
|
||||
$dc_config_number = isset($this->settings[$dc_number]) ? $dc_number : 'all';
|
||||
$test = $this->settings[$dc_config_number]['test_mode'] ? 'test' : 'main';
|
||||
$ipv6 = $this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4';
|
||||
switch ($this->settings[$dc_config_number]['protocol']) {
|
||||
case 'abridged':
|
||||
case 'tcp_abridged':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [AbridgedStream::getName(), []]];
|
||||
$test = $this->settings->getTestMode() ? 'test' : 'main';
|
||||
$ipv6 = $this->settings->getIpv6() ? 'ipv6' : 'ipv4';
|
||||
switch ($this->settings->getProtocol()) {
|
||||
case AbridgedStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [AbridgedStream::class, []]];
|
||||
break;
|
||||
case 'intermediate':
|
||||
case 'tcp_intermediate':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediateStream::getName(), []]];
|
||||
case IntermediateStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [IntermediateStream::class, []]];
|
||||
break;
|
||||
case 'obfuscated2':
|
||||
$this->settings[$dc_config_number]['protocol'] = 'tcp_intermediate_padded';
|
||||
$this->settings[$dc_config_number]['obfuscated'] = true;
|
||||
// no break
|
||||
case 'intermediate_padded':
|
||||
case 'tcp_intermediate_padded':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediatePaddedStream::getName(), []]];
|
||||
case IntermediatePaddedStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [IntermediatePaddedStream::class, []]];
|
||||
break;
|
||||
case 'full':
|
||||
case 'tcp_full':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [FullStream::getName(), []]];
|
||||
case FullStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [FullStream::class, []]];
|
||||
break;
|
||||
case 'http':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpStream::getName(), []]];
|
||||
case HttpStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpStream::class, []]];
|
||||
break;
|
||||
case 'https':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
|
||||
case HttpsStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
||||
break;
|
||||
case 'udp':
|
||||
$default = [[DefaultStream::getName(), []], [UdpBufferedStream::getName(), []]];
|
||||
case UdpBufferedStream::class:
|
||||
$default = [[DefaultStream::class, []], [UdpBufferedStream::class, []]];
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Lang::$current_lang['protocol_invalid']);
|
||||
}
|
||||
if ($this->settings[$dc_config_number]['obfuscated'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
if ($this->settings->getObfuscated() && !\in_array($default[2][0], [HttpsStream::class, HttpStream::class])) {
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
}
|
||||
if ($this->settings[$dc_config_number]['transport'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) {
|
||||
switch ($this->settings[$dc_config_number]['transport']) {
|
||||
case 'tcp':
|
||||
if ($this->settings[$dc_config_number]['obfuscated']) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
if ($this->settings->getTransport() && !\in_array($default[2][0], [HttpsStream::class, HttpStream::class])) {
|
||||
switch ($this->settings->getTransport()) {
|
||||
case DefaultStream::class:
|
||||
if ($this->settings->getObfuscated()) {
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
}
|
||||
break;
|
||||
case 'wss':
|
||||
$default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
case WssStream::class:
|
||||
$default = [[DefaultStream::class, []], [WssStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
break;
|
||||
case 'ws':
|
||||
$default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
case WsStream::class:
|
||||
$default = [[DefaultStream::class, []], [WsStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$dc_number) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []]];
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []]];
|
||||
}
|
||||
$combos[] = $default;
|
||||
if (!isset($this->settings[$dc_config_number]['do_not_retry'])) {
|
||||
if ($this->settings->getRetry()) {
|
||||
if (isset($this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) && $this->dclist[$test][$ipv6][$dc_number]['tcpo_only'] || isset($this->dclist[$test][$ipv6][$dc_number]['secret'])) {
|
||||
$extra = isset($this->dclist[$test][$ipv6][$dc_number]['secret']) ? ['secret' => $this->dclist[$test][$ipv6][$dc_number]['secret']] : [];
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), $extra], [IntermediatePaddedStream::getName(), []]];
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, $extra], [IntermediatePaddedStream::class, []]];
|
||||
}
|
||||
if (\is_iterable($this->settings[$dc_config_number]['proxy'])) {
|
||||
$proxies = $this->settings[$dc_config_number]['proxy'];
|
||||
$proxy_extras = $this->settings[$dc_config_number]['proxy_extra'];
|
||||
} else {
|
||||
$proxies = [$this->settings[$dc_config_number]['proxy']];
|
||||
$proxy_extras = [$this->settings[$dc_config_number]['proxy_extra']];
|
||||
}
|
||||
foreach ($proxies as $key => $proxy) {
|
||||
// Convert old settings
|
||||
if ($proxy === '\\Socket') {
|
||||
$proxy = DefaultStream::getName();
|
||||
}
|
||||
if ($proxy === '\\SocksProxy') {
|
||||
$proxy = SocksProxy::getName();
|
||||
}
|
||||
if ($proxy === '\\HttpProxy') {
|
||||
$proxy = HttpProxy::getName();
|
||||
}
|
||||
if ($proxy === '\\MTProxySocket') {
|
||||
$proxy = ObfuscatedStream::getName();
|
||||
}
|
||||
if ($proxy === DefaultStream::getName()) {
|
||||
$proxyCombos = [];
|
||||
foreach ($this->settings->getProxies() as $proxy => $extras) {
|
||||
if (!$dc_number && $proxy === ObfuscatedStream::class) {
|
||||
continue;
|
||||
}
|
||||
if (!$dc_number && $proxy === ObfuscatedStream::getName()) {
|
||||
continue;
|
||||
}
|
||||
$extra = $proxy_extras[$key];
|
||||
if (!isset(\class_implements($proxy)[StreamInterface::class])) {
|
||||
throw new Exception(Lang::$current_lang['proxy_class_invalid']);
|
||||
}
|
||||
if ($proxy === ObfuscatedStream::getName() && \in_array(\strlen($extra['secret']), [17, 34])) {
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [$proxy, $extra], [IntermediatePaddedStream::getName(), []]];
|
||||
}
|
||||
foreach ($combos as $k => $orig) {
|
||||
$combo = [];
|
||||
if ($proxy === ObfuscatedStream::getName()) {
|
||||
$combo = $orig;
|
||||
if ($combo[\count($combo) - 2][0] === ObfuscatedStream::getName()) {
|
||||
$combo[\count($combo) - 2][1] = $extra;
|
||||
} else {
|
||||
$mtproto = \end($combo);
|
||||
$combo[\count($combo) - 1] = [$proxy, $extra];
|
||||
$combo[] = $mtproto;
|
||||
}
|
||||
} else {
|
||||
if ($orig[1][0] === BufferedRawStream::getName()) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 2), \array_slice($orig, 2)];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
} elseif (\in_array($orig[1][0], [WsStream::getName(), WssStream::getName()])) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 1), \array_slice($orig, 1)];
|
||||
$first[] = [BufferedRawStream::getName(), []];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
}
|
||||
foreach ($extras as $extra) {
|
||||
if ($proxy === ObfuscatedStream::class && \in_array(\strlen($extra['secret']), [17, 34])) {
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [$proxy, $extra], [IntermediatePaddedStream::class, []]];
|
||||
}
|
||||
foreach ($combos as $orig) {
|
||||
$combo = [];
|
||||
if ($proxy === ObfuscatedStream::class) {
|
||||
$combo = $orig;
|
||||
if ($combo[\count($combo) - 2][0] === ObfuscatedStream::class) {
|
||||
$combo[\count($combo) - 2][1] = $extra;
|
||||
} else {
|
||||
$mtproto = \end($combo);
|
||||
$combo[\count($combo) - 1] = [$proxy, $extra];
|
||||
$combo[] = $mtproto;
|
||||
}
|
||||
} else {
|
||||
if ($orig[1][0] === BufferedRawStream::class) {
|
||||
[$first, $second] = [\array_slice($orig, 0, 2), \array_slice($orig, 2)];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
} elseif (\in_array($orig[1][0], [WsStream::class, WssStream::class])) {
|
||||
[$first, $second] = [\array_slice($orig, 0, 1), \array_slice($orig, 1)];
|
||||
$first[] = [BufferedRawStream::class, []];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
}
|
||||
}
|
||||
$proxyCombos []= $combo;
|
||||
}
|
||||
\array_unshift($combos, $combo);
|
||||
//unset($combos[$k]);
|
||||
}
|
||||
}
|
||||
$combos = \array_merge($proxyCombos, $combos);
|
||||
if ($dc_number) {
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
||||
}
|
||||
$combos = \array_unique($combos, SORT_REGULAR);
|
||||
}
|
||||
/* @var $context \Amp\ConnectContext */
|
||||
$context = $context ?? (new ConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings[$dc_config_number]['timeout']);
|
||||
$context = $context ?? (new ConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings->getTimeout())->withBindTo($this->settings->getBindTo());
|
||||
foreach ($combos as $combo) {
|
||||
foreach ([true, false] as $useDoH) {
|
||||
$ipv6Combos = [$this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4', $this->settings[$dc_config_number]['ipv6'] ? 'ipv4' : 'ipv6'];
|
||||
$ipv6Combos = [
|
||||
$this->settings->getIpv6() ? 'ipv6' : 'ipv4',
|
||||
$this->settings->getIpv6() ? 'ipv4' : 'ipv6'
|
||||
];
|
||||
foreach ($ipv6Combos as $ipv6) {
|
||||
// This is only for non-MTProto connections
|
||||
if (!$dc_number) {
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
if ($stream[0] === DefaultStream::class && $stream[1] === []) {
|
||||
$stream[1] = $useDoH ? new DoHConnector($this, $ctx) : $this->dnsConnector;
|
||||
}
|
||||
/** @var array{0: class-string, 1: mixed} $stream */
|
||||
$ctx->addStream(...$stream);
|
||||
}
|
||||
$ctxs[] = $ctx;
|
||||
|
@ -466,49 +444,50 @@ class DataCenter
|
|||
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
|
||||
foreach (\array_unique([$port, 443, 80, 88, 5222]) as $port) {
|
||||
$stream = \end($combo)[0];
|
||||
if ($stream === HttpsStream::getName()) {
|
||||
if ($stream === HttpsStream::class) {
|
||||
if (\strpos($dc_number, '_cdn') !== false) {
|
||||
continue;
|
||||
}
|
||||
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\\D+/', '', $dc_number)];
|
||||
$subdomain = $this->settings->getSslSubdomains()[\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiw_test1' : 'apiw1';
|
||||
$path = $this->settings->getTestMode() ? 'apiw_test1' : 'apiw1';
|
||||
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
|
||||
} elseif ($stream === HttpStream::getName()) {
|
||||
} elseif ($stream === HttpStream::class) {
|
||||
$uri = 'tcp://'.$address.':'.$port.'/api';
|
||||
} else {
|
||||
$uri = 'tcp://'.$address.':'.$port;
|
||||
}
|
||||
if ($combo[1][0] === WssStream::getName()) {
|
||||
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if ($combo[1][0] === WssStream::class) {
|
||||
$subdomain = $this->settings->getSslSubdomains()[\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
|
||||
$path = $this->settings->getTestMode() ? 'apiws_test' : 'apiws';
|
||||
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
|
||||
} elseif ($combo[1][0] === WsStream::getName()) {
|
||||
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\\D+/', '', $dc_number)];
|
||||
} elseif ($combo[1][0] === WsStream::class) {
|
||||
$subdomain = $this->settings->getSslSubdomains()[\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
|
||||
$path = $this->settings->getTestMode() ? 'apiws_test' : 'apiws';
|
||||
//$uri = 'tcp://' . $subdomain . '.web.telegram.org:' . $port . '/' . $path;
|
||||
$uri = 'tcp://'.$address.':'.$port.'/'.$path;
|
||||
}
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())->setDc($dc_number)->setTest($this->settings[$dc_config_number]['test_mode'])->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
$ctx = (new ConnectionContext())->setDc($dc_number)->setTest($this->settings->getTestMode())->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
if ($stream[0] === DefaultStream::class && $stream[1] === []) {
|
||||
$stream[1] = $useDoH ? new DoHConnector($this, $ctx) : $this->dnsConnector;
|
||||
}
|
||||
if (\in_array($stream[0], [WsStream::class, WssStream::class]) && $stream[1] === []) {
|
||||
if (!\class_exists(Handshake::class)) {
|
||||
throw new Exception('Please install amphp/websocket-client by running "composer require amphp/websocket-client:dev-master"');
|
||||
}
|
||||
$stream[1] = $this->webSocketConnnector;
|
||||
$stream[1] = $this->webSocketConnector;
|
||||
}
|
||||
/** @var array{0: class-string, 1: mixed} $stream */
|
||||
$ctx->addStream(...$stream);
|
||||
}
|
||||
$ctxs[] = $ctx;
|
||||
|
@ -519,7 +498,7 @@ class DataCenter
|
|||
if (empty($ctxs)) {
|
||||
unset($this->sockets[$dc_number]);
|
||||
$this->API->logger->logger("No info for DC {$dc_number}", Logger::ERROR);
|
||||
} elseif (@\constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
} elseif (\defined('MADELINEPROTO_TEST') && \constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
return [$ctxs[0]];
|
||||
}
|
||||
return $ctxs;
|
||||
|
@ -574,7 +553,9 @@ class DataCenter
|
|||
*
|
||||
* @param string $url URL to fetch
|
||||
*
|
||||
* @return \Generator<string>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, \Amp\Promise<string>, mixed, string>
|
||||
*/
|
||||
public function fileGetContents(string $url): \Generator
|
||||
{
|
||||
|
@ -607,7 +588,9 @@ class DataCenter
|
|||
*
|
||||
* @param string $dc DC ID
|
||||
*
|
||||
* @return \Generator<Connection>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, \Amp\Promise, mixed, Connection>
|
||||
*/
|
||||
public function waitGetConnection(string $dc): \Generator
|
||||
{
|
||||
|
@ -627,7 +610,7 @@ class DataCenter
|
|||
/**
|
||||
* Get all DataCenterConnection instances.
|
||||
*
|
||||
* @return array<string, DataCenterConnection>
|
||||
* @return array<int|string, DataCenterConnection>
|
||||
*/
|
||||
public function getDataCenterConnections(): array
|
||||
{
|
||||
|
@ -675,8 +658,8 @@ class DataCenter
|
|||
*/
|
||||
public function getDcs($all = true): array
|
||||
{
|
||||
$test = $this->settings['all']['test_mode'] ? 'test' : 'main';
|
||||
$ipv6 = $this->settings['all']['ipv6'] ? 'ipv6' : 'ipv4';
|
||||
$test = $this->settings->getTestMode() ? 'test' : 'main';
|
||||
$ipv6 = $this->settings->getIpv6() ? 'ipv6' : 'ipv4';
|
||||
return $all ? \array_keys((array) $this->dclist[$test][$ipv6]) : \array_keys((array) $this->sockets);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,19 @@ use Amp\Promise;
|
|||
use Amp\Success;
|
||||
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
|
||||
use danog\MadelineProto\MTProto\AuthKey;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
|
||||
use danog\MadelineProto\Stream\Transport\WssStream;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Datacenter connection.
|
||||
*/
|
||||
class DataCenterConnection implements JsonSerializable
|
||||
{
|
||||
const READ_WEIGHT = 1;
|
||||
|
@ -64,13 +69,13 @@ class DataCenterConnection implements JsonSerializable
|
|||
/**
|
||||
* Connections open to a certain DC.
|
||||
*
|
||||
* @var array<string, Connection>
|
||||
* @var array<int, Connection>
|
||||
*/
|
||||
private $connections = [];
|
||||
/**
|
||||
* Connection weights.
|
||||
*
|
||||
* @var array<string, int>
|
||||
* @var array<int, int>
|
||||
*/
|
||||
private $availableConnections = [];
|
||||
/**
|
||||
|
@ -159,7 +164,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
/**
|
||||
* Check if auth key is present.
|
||||
*
|
||||
* @param boolean|null $temp Whether to fetch the temporary auth key
|
||||
* @param bool $temp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -171,18 +176,18 @@ class DataCenterConnection implements JsonSerializable
|
|||
* Set auth key.
|
||||
*
|
||||
* @param AuthKey|null $key The auth key
|
||||
* @param boolean|null $temp Whether to set the temporary auth key
|
||||
* @param bool $temp
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAuthKey(?AuthKey $key, bool $temp = true)
|
||||
public function setAuthKey(?AuthKey $key, bool $temp = true): void
|
||||
{
|
||||
$this->{$temp ? 'tempAuthKey' : 'permAuthKey'} = $key;
|
||||
}
|
||||
/**
|
||||
* Get temporary authorization key.
|
||||
*
|
||||
* @return AuthKey
|
||||
* @return TempAuthKey
|
||||
*/
|
||||
public function getTempAuthKey(): TempAuthKey
|
||||
{
|
||||
|
@ -191,7 +196,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
/**
|
||||
* Get permanent authorization key.
|
||||
*
|
||||
* @return AuthKey
|
||||
* @return PermAuthKey
|
||||
*/
|
||||
public function getPermAuthKey(): PermAuthKey
|
||||
{
|
||||
|
@ -222,9 +227,9 @@ class DataCenterConnection implements JsonSerializable
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setTempAuthKey(?TempAuthKey $key)
|
||||
public function setTempAuthKey(?TempAuthKey $key): void
|
||||
{
|
||||
return $this->setAuthKey($key, true);
|
||||
$this->setAuthKey($key, true);
|
||||
}
|
||||
/**
|
||||
* Set permanent authorization key.
|
||||
|
@ -233,9 +238,9 @@ class DataCenterConnection implements JsonSerializable
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPermAuthKey(?PermAuthKey $key)
|
||||
public function setPermAuthKey(?PermAuthKey $key): void
|
||||
{
|
||||
return $this->setAuthKey($key, false);
|
||||
$this->setAuthKey($key, false);
|
||||
}
|
||||
/**
|
||||
* Bind temporary and permanent auth keys.
|
||||
|
@ -323,7 +328,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush()
|
||||
public function flush(): void
|
||||
{
|
||||
foreach ($this->connections as $socket) {
|
||||
$socket->flush();
|
||||
|
@ -361,10 +366,10 @@ class DataCenterConnection implements JsonSerializable
|
|||
$this->ctx = $ctx->getCtx();
|
||||
$this->datacenter = $ctx->getDc();
|
||||
$media = $ctx->isMedia() || $ctx->isCDN();
|
||||
$count = $media ? $this->API->settings['connection_settings']['media_socket_count']['min'] : 1;
|
||||
$count = $media ? $this->API->getSettings()->getConnection()->getMinMediaSocketCount() : 1;
|
||||
if ($count > 1) {
|
||||
if (!$this->robinLoop) {
|
||||
$this->robinLoop = new PeriodicLoopInternal($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period'] * 1000);
|
||||
$this->robinLoop = new PeriodicLoopInternal($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->getSettings()->getConnection()->getRobinPeriod() * 1000);
|
||||
}
|
||||
$this->robinLoop->start();
|
||||
}
|
||||
|
@ -393,7 +398,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*
|
||||
* @param integer $count Number of sockets to open
|
||||
*
|
||||
* @return void
|
||||
* @return \Generator
|
||||
*/
|
||||
private function connectMore(int $count): \Generator
|
||||
{
|
||||
|
@ -420,11 +425,11 @@ class DataCenterConnection implements JsonSerializable
|
|||
$backup = $this->connections[$id]->backupSession();
|
||||
$list = '';
|
||||
foreach ($backup as $k => $message) {
|
||||
if (($message['_'] ?? '') === 'msgs_state_req') {
|
||||
if ($message->getConstructor() === 'msgs_state_req') {
|
||||
unset($backup[$k]);
|
||||
continue;
|
||||
}
|
||||
$list .= $message['_'] ?? '-';
|
||||
$list .= $message->getConstructor();
|
||||
$list .= ', ';
|
||||
}
|
||||
$this->API->logger->logger("Backed up {$list} from DC {$this->datacenter}.{$id}");
|
||||
|
@ -476,14 +481,15 @@ class DataCenterConnection implements JsonSerializable
|
|||
$this->backup = [];
|
||||
$count = \count($backup);
|
||||
$this->API->logger->logger("Restoring {$count} messages to DC {$this->datacenter}");
|
||||
/** @var OutgoingMessage */
|
||||
foreach ($backup as $message) {
|
||||
if (isset($message['seqno'])) {
|
||||
unset($message['seqno']);
|
||||
if ($message->hasSeqno()) {
|
||||
$message->setSeqno(null);
|
||||
}
|
||||
if (isset($message['msg_id'])) {
|
||||
unset($message['msg_id']);
|
||||
if ($message->hasMsgId()) {
|
||||
$message->setMsgId(null);
|
||||
}
|
||||
if (isset($message['body'])) {
|
||||
if (!($message->getState() & OutgoingMessage::STATE_REPLIED)) {
|
||||
Tools::callFork($this->getConnection()->sendMessage($message, false));
|
||||
}
|
||||
}
|
||||
|
@ -503,16 +509,18 @@ class DataCenterConnection implements JsonSerializable
|
|||
*
|
||||
* @param integer $id Connection ID
|
||||
*
|
||||
* @return boolean
|
||||
* @return bool|int
|
||||
*/
|
||||
public function hasConnection(int $id = -1): bool
|
||||
public function hasConnection(int $id = -1)
|
||||
{
|
||||
return $id < 0 ? \count($this->connections) : isset($this->connections[$id]);
|
||||
}
|
||||
/**
|
||||
* Get best socket in round robin, asynchronously.
|
||||
*
|
||||
* @return \Generator<Connection>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise, mixed, Connection>
|
||||
*/
|
||||
public function waitGetConnection(): \Generator
|
||||
{
|
||||
|
@ -558,7 +566,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
$count += 50;
|
||||
}
|
||||
} elseif ($min < 100) {
|
||||
$max = $this->isMedia() || $this->isCDN() ? $this->API->settings['connection_settings']['media_socket_count']['max'] : 1;
|
||||
$max = $this->isMedia() || $this->isCDN() ? $this->API->getSettings()->getConnection()->getMaxMediaSocketCount() : 1;
|
||||
if (\count($this->availableConnections) < $max) {
|
||||
$this->connectMore(2);
|
||||
} else {
|
||||
|
@ -619,7 +627,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*/
|
||||
public function isHttp(): bool
|
||||
{
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::class, HttpsStream::class]);
|
||||
}
|
||||
/**
|
||||
* Check if is connected directly by IP address.
|
||||
|
@ -628,7 +636,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*/
|
||||
public function byIPAddress(): bool
|
||||
{
|
||||
return !$this->ctx->hasStreamName(WssStream::getName()) && !$this->ctx->hasStreamName(HttpsStream::getName());
|
||||
return !$this->ctx->hasStreamName(WssStream::class) && !$this->ctx->hasStreamName(HttpsStream::class);
|
||||
}
|
||||
/**
|
||||
* Check if is a media connection.
|
||||
|
@ -651,12 +659,20 @@ class DataCenterConnection implements JsonSerializable
|
|||
/**
|
||||
* Get DC-specific settings.
|
||||
*
|
||||
* @return array
|
||||
* @return ConnectionSettings
|
||||
*/
|
||||
public function getSettings(): array
|
||||
public function getSettings(): ConnectionSettings
|
||||
{
|
||||
$dc_config_number = isset($this->API->settings['connection_settings'][$this->datacenter]) ? $this->datacenter : 'all';
|
||||
return $this->API->settings['connection_settings'][$dc_config_number];
|
||||
return $this->API->getSettings()->getConnection();
|
||||
}
|
||||
/**
|
||||
* Get global settings.
|
||||
*
|
||||
* @return Settings
|
||||
*/
|
||||
public function getGenericSettings(): Settings
|
||||
{
|
||||
return $this->API->getSettings();
|
||||
}
|
||||
/**
|
||||
* JSON serialize function.
|
||||
|
|
|
@ -5,6 +5,9 @@ namespace danog\MadelineProto\Db;
|
|||
use Amp\Loop;
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
/**
|
||||
* Array caching trait.
|
||||
*/
|
||||
trait ArrayCacheTrait
|
||||
{
|
||||
/**
|
||||
|
@ -16,10 +19,18 @@ trait ArrayCacheTrait
|
|||
*/
|
||||
protected array $ttlValues = [];
|
||||
|
||||
/**
|
||||
* TTL interval.
|
||||
*/
|
||||
protected int $ttl = 5 * 60;
|
||||
/**
|
||||
* TTL cleanup interval.
|
||||
*/
|
||||
private int $ttlCheckInterval = 60;
|
||||
|
||||
protected string $ttl = '+5 minutes';
|
||||
private string $ttlCheckInterval = '+1 minute';
|
||||
|
||||
/**
|
||||
* Cache cleanup watcher ID.
|
||||
*/
|
||||
private ?string $cacheCleanupId = null;
|
||||
|
||||
protected function getCache(string $key, $default = null)
|
||||
|
@ -27,7 +38,7 @@ trait ArrayCacheTrait
|
|||
if (!isset($this->ttlValues[$key])) {
|
||||
return $default;
|
||||
}
|
||||
$this->ttlValues[$key] = \strtotime($this->ttl);
|
||||
$this->ttlValues[$key] = \time() + $this->ttl;
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
|
@ -40,7 +51,7 @@ trait ArrayCacheTrait
|
|||
protected function setCache(string $key, $value): void
|
||||
{
|
||||
$this->cache[$key] = $value;
|
||||
$this->ttlValues[$key] = \strtotime($this->ttl);
|
||||
$this->ttlValues[$key] = \time() + $this->ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +66,7 @@ trait ArrayCacheTrait
|
|||
|
||||
protected function startCacheCleanupLoop(): void
|
||||
{
|
||||
$this->cacheCleanupId = Loop::repeat(\strtotime($this->ttlCheckInterval, 0) * 1000, fn () => $this->cleanupCache());
|
||||
$this->cacheCleanupId = Loop::repeat($this->ttlCheckInterval * 1000, fn () => $this->cleanupCache());
|
||||
}
|
||||
protected function stopCacheCleanupLoop(): void
|
||||
{
|
||||
|
@ -81,8 +92,8 @@ trait ArrayCacheTrait
|
|||
|
||||
Logger::log(
|
||||
\sprintf(
|
||||
"cache for table:%s; keys left: %s; keys removed: %s",
|
||||
$this->table,
|
||||
"cache for table: %s; keys left: %s; keys removed: %s",
|
||||
(string) $this,
|
||||
\count($this->cache),
|
||||
$oldCount
|
||||
),
|
||||
|
|
|
@ -5,14 +5,70 @@ namespace danog\MadelineProto\Db;
|
|||
use Amp\Producer;
|
||||
use Amp\Promise;
|
||||
|
||||
/**
|
||||
* DB array interface.
|
||||
*
|
||||
* @template T as mixed
|
||||
*/
|
||||
interface DbArray extends DbType, \ArrayAccess, \Countable
|
||||
{
|
||||
/**
|
||||
* Get Array copy.
|
||||
*
|
||||
* @psalm-return Promise<array<string|int, T>>
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function getArrayCopy(): Promise;
|
||||
/**
|
||||
* Check if element is set.
|
||||
*
|
||||
* @param string|int $key
|
||||
*
|
||||
* @psalm-return Promise<bool>
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function isset($key): Promise;
|
||||
public function offsetGet($offset): Promise;
|
||||
public function offsetSet($offset, $value);
|
||||
public function offsetUnset($offset): Promise;
|
||||
/**
|
||||
* Get element.
|
||||
*
|
||||
* @param string|int $index
|
||||
*
|
||||
* @psalm-return Promise<T>
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function offsetGet($index): Promise;
|
||||
/**
|
||||
* Set element.
|
||||
*
|
||||
* @param string|int $index
|
||||
* @param mixed $value
|
||||
*
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($index, $value);
|
||||
/**
|
||||
* Unset element.
|
||||
*
|
||||
* @param string|int $index Offset
|
||||
* @return Promise
|
||||
*/
|
||||
public function offsetUnset($index): Promise;
|
||||
/**
|
||||
* Count number of elements.
|
||||
*
|
||||
* @return Promise<integer>
|
||||
*/
|
||||
public function count(): Promise;
|
||||
/**
|
||||
* Get iterator.
|
||||
*
|
||||
* @return Producer<array{0: string|int, 1: T}>
|
||||
*/
|
||||
public function getIterator(): Producer;
|
||||
|
||||
/**
|
||||
|
@ -20,9 +76,9 @@ interface DbArray extends DbType, \ArrayAccess, \Countable
|
|||
* @internal
|
||||
* @see DbArray::isset();
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $index
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset);
|
||||
public function offsetExists($index);
|
||||
}
|
||||
|
|
|
@ -3,54 +3,74 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\Database\Memory;
|
||||
use danog\MadelineProto\Settings\Database\Mysql;
|
||||
use danog\MadelineProto\Settings\Database\Postgres;
|
||||
use danog\MadelineProto\Settings\Database\Redis;
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract;
|
||||
|
||||
class DbPropertiesFactory
|
||||
/**
|
||||
* This factory class initializes the correct database backend for MadelineProto.
|
||||
*/
|
||||
abstract class DbPropertiesFactory
|
||||
{
|
||||
/**
|
||||
* @param array $dbSettings
|
||||
* @param string $namePrefix
|
||||
* @param string $propertyType
|
||||
* @param string $name
|
||||
* Indicates a simple K-V array stored in a database backend.
|
||||
* Values can be objects or other arrays, but when lots of nesting is required, it's best to split the array into multiple arrays.
|
||||
*/
|
||||
const TYPE_ARRAY = 'array';
|
||||
/**
|
||||
* @param DatabaseAbstract $dbSettings
|
||||
* @param string $table
|
||||
* @param self::TYPE_*|array $propertyType
|
||||
* @param $value
|
||||
* @param DriverArray|null $value
|
||||
*
|
||||
* @return Promise<DbType>
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @uses \danog\MadelineProto\Db\MemoryArray
|
||||
* @uses \danog\MadelineProto\Db\SharedMemoryArray
|
||||
* @uses \danog\MadelineProto\Db\MysqlArray
|
||||
* @uses \danog\MadelineProto\Db\PostgresArray
|
||||
* @uses \danog\MadelineProto\Db\RedisArray
|
||||
*/
|
||||
public static function get(array $dbSettings, string $namePrefix, string $propertyType, string $name, $value = null): Promise
|
||||
public static function get(DatabaseAbstract $dbSettings, string $table, $propertyType, $value = null): Promise
|
||||
{
|
||||
$class = __NAMESPACE__;
|
||||
$config = $propertyType['config'] ?? [];
|
||||
$propertyType = \is_array($propertyType) ? $propertyType['type'] : $propertyType;
|
||||
$propertyType = \strtolower($propertyType);
|
||||
$class = !($config['enableCache'] ?? true) && !$dbSettings instanceof Memory
|
||||
? __NAMESPACE__.'\\NullCache'
|
||||
: __NAMESPACE__;
|
||||
|
||||
switch (\strtolower($dbSettings['type'])) {
|
||||
case 'memory':
|
||||
switch (true) {
|
||||
case $dbSettings instanceof Memory:
|
||||
$class .= '\\Memory';
|
||||
break;
|
||||
case 'mysql':
|
||||
case $dbSettings instanceof Mysql:
|
||||
$class .= '\\Mysql';
|
||||
break;
|
||||
case 'postgres':
|
||||
case $dbSettings instanceof Postgres:
|
||||
$class .= '\\Postgres';
|
||||
break;
|
||||
case 'redis':
|
||||
case $dbSettings instanceof Redis:
|
||||
$class .= '\\Redis';
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown dbType: {$dbSettings['type']}");
|
||||
throw new \InvalidArgumentException("Unknown dbType: ".\get_class($dbSettings));
|
||||
|
||||
}
|
||||
|
||||
/** @var DbType $class */
|
||||
switch (\strtolower($propertyType)) {
|
||||
case 'array':
|
||||
case self::TYPE_ARRAY:
|
||||
$class .= 'Array';
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown $propertyType: {$propertyType}");
|
||||
}
|
||||
|
||||
return $class::getInstance($name, $value, $namePrefix, $dbSettings[$dbSettings['type']]??[]);
|
||||
return $class::getInstance($table, $value, $dbSettings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,24 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* Include this trait and call DbPropertiesTrait::initDb to use MadelineProto's database backend for properties.
|
||||
*
|
||||
* You will have to define a `$dbProperties` static array property, with a list of properties you want to store to a database.
|
||||
*
|
||||
* @see DbPropertiesFactory For a list of allowed property types
|
||||
*
|
||||
* @property array<string, DbPropertiesFactory::TYPE_*> $dbProperties
|
||||
*/
|
||||
trait DbPropertiesTrait
|
||||
{
|
||||
/**
|
||||
* Initialize database instance.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param MTProto $MadelineProto
|
||||
* @param boolean $reset
|
||||
* @return \Generator
|
||||
|
@ -18,16 +30,22 @@ trait DbPropertiesTrait
|
|||
if (empty(static::$dbProperties)) {
|
||||
throw new \LogicException(static::class.' must have $dbProperties');
|
||||
}
|
||||
$dbSettings = $MadelineProto->settings['db'];
|
||||
$dbSettings = $MadelineProto->settings->getDb();
|
||||
$prefix = static::getSessionId($MadelineProto);
|
||||
|
||||
$promises = [];
|
||||
foreach (static::$dbProperties as $property => $type) {
|
||||
if ($reset) {
|
||||
unset($this->{$property});
|
||||
} else {
|
||||
$this->{$property} = yield DbPropertiesFactory::get($dbSettings, $prefix, $type, $property, $this->{$property});
|
||||
$table = "{$prefix}_{$property}";
|
||||
$promises[$property] = DbPropertiesFactory::get($dbSettings, $table, $type, $this->{$property});
|
||||
}
|
||||
}
|
||||
$promises = yield Tools::all($promises);
|
||||
foreach ($promises as $key => $data) {
|
||||
$this->{$key} = $data;
|
||||
}
|
||||
}
|
||||
|
||||
private static function getSessionId(MTProto $madelineProto): string
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\Database\DatabaseAbstract;
|
||||
|
||||
interface DbType
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param null $value
|
||||
* @param string $tablePrefix
|
||||
* @param array $settings
|
||||
* @param string $table
|
||||
* @param null|DbType|array $previous
|
||||
* @param DatabaseAbstract $settings
|
||||
*
|
||||
* @return Promise<self>
|
||||
*/
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise;
|
||||
public static function getInstance(string $table, $previous, $settings): Promise;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,16 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
|
||||
use Amp\Mysql\ConnectionConfig;
|
||||
use Amp\Mysql\Pool;
|
||||
use Amp\Sql\Common\ConnectionPool;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Mysql as DatabaseMysql;
|
||||
|
||||
use function Amp\Mysql\Pool;
|
||||
|
||||
/**
|
||||
* MySQL driver wrapper.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Mysql
|
||||
{
|
||||
/** @var Pool[] */
|
||||
|
@ -29,23 +35,17 @@ class Mysql
|
|||
*
|
||||
* @return \Generator<Pool>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 3306,
|
||||
string $user = 'root',
|
||||
string $password = '',
|
||||
string $db = 'MadelineProto',
|
||||
int $maxConnections = ConnectionPool::DEFAULT_MAX_CONNECTIONS,
|
||||
int $idleTimeout = ConnectionPool::DEFAULT_IDLE_TIMEOUT
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabaseMysql $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = ConnectionConfig::fromString(
|
||||
"host={$host} port={$port} user={$user} password={$password} db={$db}"
|
||||
);
|
||||
$config = ConnectionConfig::fromString("host=".\str_replace("tcp://", "", $settings->getUri()))
|
||||
->withUser($settings->getUsername())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
yield from static::createDb($config);
|
||||
static::$connections[$dbKey] = pool($config, $maxConnections, $idleTimeout);
|
||||
static::$connections[$dbKey] = new Pool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
|
||||
}
|
||||
|
||||
return static::$connections[$dbKey];
|
||||
|
|
|
@ -4,10 +4,16 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
|
||||
use Amp\Postgres\ConnectionConfig;
|
||||
use Amp\Postgres\Pool;
|
||||
use Amp\Sql\Common\ConnectionPool;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Postgres as DatabasePostgres;
|
||||
|
||||
use function Amp\Postgres\Pool;
|
||||
|
||||
/**
|
||||
* Postgres driver wrapper.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Postgres
|
||||
{
|
||||
/** @var Pool[] */
|
||||
|
@ -29,23 +35,17 @@ class Postgres
|
|||
*
|
||||
* @return \Generator<Pool>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 5432,
|
||||
string $user = 'root',
|
||||
string $password = '',
|
||||
string $db = 'MadelineProto',
|
||||
int $maxConnections = ConnectionPool::DEFAULT_MAX_CONNECTIONS,
|
||||
int $idleTimeout = ConnectionPool::DEFAULT_IDLE_TIMEOUT
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabasePostgres $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = ConnectionConfig::fromString(
|
||||
"host={$host} port={$port} user={$user} password={$password} db={$db}"
|
||||
);
|
||||
$config = ConnectionConfig::fromString("host=".\str_replace("tcp://", "", $settings->getUri()))
|
||||
->withUser($settings->getUsername())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
yield from static::createDb($config);
|
||||
static::$connections[$dbKey] = pool($config, $maxConnections, $idleTimeout);
|
||||
static::$connections[$dbKey] = new Pool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
|
||||
}
|
||||
|
||||
return static::$connections[$dbKey];
|
||||
|
|
|
@ -5,7 +5,13 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
use Amp\Redis\Config;
|
||||
use Amp\Redis\Redis as RedisRedis;
|
||||
use Amp\Redis\RemoteExecutorFactory;
|
||||
use danog\MadelineProto\Settings\Database\Redis as DatabaseRedis;
|
||||
|
||||
/**
|
||||
* Redis driver wrapper.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Redis
|
||||
{
|
||||
/** @var RedisRedis[] */
|
||||
|
@ -17,7 +23,6 @@ class Redis
|
|||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $db
|
||||
*
|
||||
* @param int $maxConnections
|
||||
* @param int $idleTimeout
|
||||
*
|
||||
|
@ -25,19 +30,17 @@ class Redis
|
|||
* @throws \Amp\Sql\FailureException
|
||||
* @throws \Throwable
|
||||
*
|
||||
* @return \Generator<RedisRedis>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, \Amp\Promise<void>, mixed, RedisRedis>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 6379,
|
||||
string $password = '',
|
||||
int $db = 0
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabaseRedis $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = Config::fromUri(
|
||||
"{$host}:{$port}?password={$password}&db={$db}"
|
||||
);
|
||||
$config = Config::fromUri($settings->getUri())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
static::$connections[$dbKey] = new RedisRedis((new RemoteExecutorFactory($config))->createQueryExecutor());
|
||||
yield static::$connections[$dbKey]->ping();
|
||||
|
|
|
@ -2,24 +2,146 @@
|
|||
|
||||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\DatabaseAbstract;
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
use ReflectionClass;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
/**
|
||||
* Array caching trait.
|
||||
*/
|
||||
abstract class DriverArray implements DbArray
|
||||
{
|
||||
protected string $table;
|
||||
|
||||
use ArrayCacheTrait;
|
||||
|
||||
public function __destruct()
|
||||
/**
|
||||
* Initialize connection.
|
||||
*/
|
||||
abstract public function initConnection(DatabaseAbstract $settings): \Generator;
|
||||
/**
|
||||
* Initialize on startup.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
abstract public function initStartup(): \Generator;
|
||||
|
||||
/**
|
||||
* Create table for property.
|
||||
*
|
||||
* @return \Generator
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
abstract protected function prepareTable(): \Generator;
|
||||
|
||||
/**
|
||||
* Rename table.
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @return \Generator
|
||||
*/
|
||||
abstract protected function renameTable(string $from, string $to): \Generator;
|
||||
|
||||
/**
|
||||
* Get the value of table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable(): string
|
||||
{
|
||||
$this->stopCacheCleanupLoop();
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
|
||||
public function offsetExists($index): bool
|
||||
/**
|
||||
* Set the value of table.
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTable(string $table): self
|
||||
{
|
||||
throw new \RuntimeException('Native isset not support promises. Use isset method');
|
||||
$this->table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract protected function initConnection(array $settings): \Generator;
|
||||
/**
|
||||
* @param string $table
|
||||
* @param DbArray|array|null $previous
|
||||
* @param DatabaseAbstract $settings
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @psalm-return Promise<static>
|
||||
*/
|
||||
public static function getInstance(string $table, $previous, $settings): Promise
|
||||
{
|
||||
if ($previous instanceof static && $previous->getTable() === $table) {
|
||||
$instance = &$previous;
|
||||
} else {
|
||||
$instance = new static();
|
||||
$instance->setTable($table);
|
||||
}
|
||||
|
||||
/** @psalm-suppress UndefinedPropertyAssignment */
|
||||
$instance->dbSettings = $settings;
|
||||
$instance->ttl = $settings->getCacheTtl();
|
||||
|
||||
$instance->startCacheCleanupLoop();
|
||||
|
||||
return call(static function () use ($instance, $previous, $settings) {
|
||||
yield from $instance->initConnection($settings);
|
||||
yield from $instance->prepareTable();
|
||||
|
||||
if ($instance !== $previous) {
|
||||
if ($previous instanceof DriverArray) {
|
||||
yield from $previous->initStartup();
|
||||
}
|
||||
yield from static::renameTmpTable($instance, $previous);
|
||||
if ($instance instanceof SqlArray) {
|
||||
Logger::log("Preparing statements...");
|
||||
yield from $instance->prepareStatements();
|
||||
}
|
||||
yield from static::migrateDataToDb($instance, $previous);
|
||||
} elseif ($instance instanceof SqlArray) {
|
||||
Logger::log("Preparing statements...");
|
||||
yield from $instance->prepareStatements();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename table of old database, if the new one is not a temporary table name.
|
||||
*
|
||||
* Otherwise, simply change name of table in new database to match old table name.
|
||||
*
|
||||
* @param self $new New db
|
||||
* @param DbArray|array|null $old Old db
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected static function renameTmpTable(self $new, $old): \Generator
|
||||
{
|
||||
if ($old instanceof static && $old->getTable()) {
|
||||
if (
|
||||
$old->getTable() !== $new->getTable() &&
|
||||
\mb_strpos($new->getTable(), 'tmp') !== 0
|
||||
) {
|
||||
yield from $new->renameTable($old->getTable(), $new->getTable());
|
||||
} else {
|
||||
$new->setTable($old->getTable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self $new
|
||||
|
@ -31,7 +153,7 @@ abstract class DriverArray implements DbArray
|
|||
protected static function migrateDataToDb(self $new, $old): \Generator
|
||||
{
|
||||
if (!empty($old) && !$old instanceof static) {
|
||||
Logger::log('Converting database.', Logger::ERROR);
|
||||
Logger::log('Converting database to '.\get_class($new), Logger::ERROR);
|
||||
|
||||
if ($old instanceof DbArray) {
|
||||
$old = yield $old->getArrayCopy();
|
||||
|
@ -44,7 +166,7 @@ abstract class DriverArray implements DbArray
|
|||
$counter++;
|
||||
if ($counter % 500 === 0) {
|
||||
yield $new->offsetSet($key, $item);
|
||||
Logger::log("Loading data to table {$new->table}: $counter/$total", Logger::WARNING);
|
||||
Logger::log("Loading data to table {$new}: $counter/$total", Logger::WARNING);
|
||||
} else {
|
||||
$new->offsetSet($key, $item);
|
||||
}
|
||||
|
@ -52,4 +174,48 @@ abstract class DriverArray implements DbArray
|
|||
Logger::log('Converting database done.', Logger::ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->stopCacheCleanupLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['table', 'dbSettings'];
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
if (isset($this->settings) && \is_array($this->settings)) {
|
||||
$clazz = (new ReflectionClass($this))->getProperty('dbSettings')->getType()->getName();
|
||||
/**
|
||||
* @var SettingsAbstract
|
||||
* @psalm-suppress UndefinedThisPropertyAssignment
|
||||
*/
|
||||
$this->dbSettings = new $clazz;
|
||||
$this->dbSettings->mergeArray($this->settings);
|
||||
unset($this->settings);
|
||||
}
|
||||
}
|
||||
public function offsetExists($index): bool
|
||||
{
|
||||
throw new \RuntimeException('Native isset not support promises. Use isset method');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,13 @@ use Amp\Producer;
|
|||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Memory;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
/**
|
||||
* Memory database backend.
|
||||
*/
|
||||
class MemoryArray extends \ArrayIterator implements DbArray
|
||||
{
|
||||
protected function __construct($array = [], $flags = 0)
|
||||
|
@ -15,17 +20,28 @@ class MemoryArray extends \ArrayIterator implements DbArray
|
|||
parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
|
||||
}
|
||||
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
|
||||
/**
|
||||
* Get instance.
|
||||
*
|
||||
* @param string $table
|
||||
* @param mixed $previous
|
||||
* @param Memory $settings
|
||||
* @return Promise<self>
|
||||
*/
|
||||
public static function getInstance(string $table, $previous, $settings): Promise
|
||||
{
|
||||
return call(static function () use ($value) {
|
||||
if ($value instanceof MemoryArray) {
|
||||
return $value;
|
||||
return call(static function () use ($previous) {
|
||||
if ($previous instanceof MemoryArray) {
|
||||
return $previous;
|
||||
}
|
||||
if ($value instanceof DbArray) {
|
||||
if ($previous instanceof DbArray) {
|
||||
Logger::log("Loading database to memory. Please wait.", Logger::WARNING);
|
||||
$value = yield $value->getArrayCopy();
|
||||
if ($previous instanceof DriverArray) {
|
||||
yield from $previous->initStartup();
|
||||
}
|
||||
$previous = yield $previous->getArrayCopy();
|
||||
}
|
||||
return new static($value);
|
||||
return new static($previous);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,170 +3,68 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Mysql\Pool;
|
||||
use Amp\Producer;
|
||||
use Amp\Promise;
|
||||
use Amp\Sql\ResultSet;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Mysql;
|
||||
use danog\MadelineProto\Logger;
|
||||
use function Amp\call;
|
||||
use danog\MadelineProto\Settings\Database\Mysql as DatabaseMysql;
|
||||
|
||||
/**
|
||||
* MySQL database backend.
|
||||
*/
|
||||
class MysqlArray extends SqlArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
protected DatabaseMysql $dbSettings;
|
||||
private Pool $db;
|
||||
|
||||
public function __sleep(): array
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
/**
|
||||
* Initialize on startup.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initStartup(): \Generator
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
yield from $this->initConnection($this->dbSettings);
|
||||
yield from $this->prepareStatements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if key isset.
|
||||
* Prepare statements.
|
||||
*
|
||||
* @param $key
|
||||
*
|
||||
* @return Promise<bool> true if the offset exists, otherwise false
|
||||
* @return \Generator
|
||||
*/
|
||||
public function isset($key): Promise
|
||||
protected function prepareStatements(): \Generator
|
||||
{
|
||||
return call(fn () => yield $this->offsetGet($key) !== null);
|
||||
}
|
||||
|
||||
|
||||
public function offsetGet($offset): Promise
|
||||
{
|
||||
return call(function () use ($offset) {
|
||||
if ($cached = $this->getCache($offset)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$row = yield $this->request(
|
||||
"SELECT `value` FROM `{$this->table}` WHERE `key` = :index LIMIT 1",
|
||||
['index' => $offset]
|
||||
);
|
||||
|
||||
if ($value = $this->getValue($row)) {
|
||||
$this->setCache($offset, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value for an offset.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetset.php
|
||||
*
|
||||
* @param string $index <p>
|
||||
* The index to set for.
|
||||
* </p>
|
||||
* @param $value
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
|
||||
public function offsetSet($index, $value): Promise
|
||||
{
|
||||
if ($this->getCache($index) === $value) {
|
||||
return new Success();
|
||||
}
|
||||
|
||||
$this->setCache($index, $value);
|
||||
|
||||
$request = $this->request(
|
||||
"
|
||||
$this->get = yield $this->db->prepare(
|
||||
"SELECT `value` FROM `{$this->table}` WHERE `key` = :index LIMIT 1"
|
||||
);
|
||||
$this->set = yield $this->db->prepare("
|
||||
INSERT INTO `{$this->table}`
|
||||
SET `key` = :index, `value` = :value
|
||||
ON DUPLICATE KEY UPDATE `value` = :value
|
||||
",
|
||||
[
|
||||
'index' => $index,
|
||||
'value' => \serialize($value),
|
||||
]
|
||||
);
|
||||
|
||||
//Ensure that cache is synced with latest insert in case of concurrent requests.
|
||||
$request->onResolve(fn () => $this->setCache($index, $value));
|
||||
|
||||
return $request;
|
||||
");
|
||||
$this->unset = yield $this->db->prepare("
|
||||
DELETE FROM `{$this->table}`
|
||||
WHERE `key` = :index
|
||||
");
|
||||
$this->count = yield $this->db->prepare("
|
||||
SELECT count(`key`) as `count` FROM `{$this->table}`
|
||||
");
|
||||
$this->iterate = yield $this->db->prepare("
|
||||
SELECT `key`, `value` FROM `{$this->table}`
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unset value for an offset.
|
||||
* Get value from row.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetunset.php
|
||||
*
|
||||
* @param string $index <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
* @param array $row
|
||||
* @return null|mixed
|
||||
*/
|
||||
public function offsetUnset($index): Promise
|
||||
{
|
||||
$this->unsetCache($index);
|
||||
|
||||
return $this->request(
|
||||
"
|
||||
DELETE FROM `{$this->table}`
|
||||
WHERE `key` = :index
|
||||
",
|
||||
['index' => $index]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array copy.
|
||||
*
|
||||
* @return Promise<array>
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function getArrayCopy(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$iterator = $this->getIterator();
|
||||
$result = [];
|
||||
while (yield $iterator->advance()) {
|
||||
[$key, $value] = $iterator->getCurrent();
|
||||
$result[$key] = $value;
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
public function getIterator(): Producer
|
||||
{
|
||||
return new Producer(function (callable $emit) {
|
||||
$request = yield $this->db->execute("SELECT `key`, `value` FROM `{$this->table}`");
|
||||
|
||||
while (yield $request->advance()) {
|
||||
$row = $request->getCurrent();
|
||||
yield $emit([$row['key'], $this->getValue($row)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.count.php
|
||||
* @return Promise<int> The number of elements or public properties in the associated
|
||||
* array or object, respectively.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function count(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$row = yield $this->request("SELECT count(`key`) as `count` FROM `{$this->table}`");
|
||||
return $row[0]['count'] ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
private function getValue(array $row)
|
||||
protected function getValue(array $row)
|
||||
{
|
||||
if ($row) {
|
||||
if (!empty($row[0]['value'])) {
|
||||
|
@ -177,31 +75,32 @@ class MysqlArray extends SqlArray
|
|||
return null;
|
||||
}
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabaseMysql $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Mysql::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['user'],
|
||||
$settings['password'],
|
||||
$settings['database'],
|
||||
$settings['max_connections'],
|
||||
$settings['idle_timeout']
|
||||
);
|
||||
$this->db = yield from Mysql::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create table for property.
|
||||
*
|
||||
* @return array|null
|
||||
* @return \Generator
|
||||
*
|
||||
* @throws \Throwable
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise, mixed, mixed>
|
||||
*/
|
||||
protected function prepareTable(): \Generator
|
||||
{
|
||||
Logger::log("Creating/checking table {$this->table}", Logger::WARNING);
|
||||
return yield $this->request("
|
||||
return yield $this->db->query("
|
||||
CREATE TABLE IF NOT EXISTS `{$this->table}`
|
||||
(
|
||||
`key` VARCHAR(255) NOT NULL,
|
||||
|
@ -218,55 +117,12 @@ class MysqlArray extends SqlArray
|
|||
protected function renameTable(string $from, string $to): \Generator
|
||||
{
|
||||
Logger::log("Renaming table {$from} to {$to}", Logger::WARNING);
|
||||
yield $this->request("
|
||||
yield $this->db->query("
|
||||
DROP TABLE IF EXISTS `{$to}`;
|
||||
");
|
||||
|
||||
yield $this->db->query("
|
||||
ALTER TABLE `{$from}` RENAME TO `{$to}`;
|
||||
");
|
||||
|
||||
yield $this->request("
|
||||
DROP TABLE IF EXISTS `{$from}`;
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform async request to db.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function request(string $query, array $params = []): Promise
|
||||
{
|
||||
return call(function () use ($query, $params) {
|
||||
Logger::log([$query, $params], Logger::VERBOSE);
|
||||
|
||||
if (empty($this->db) || !$this->db->isAlive()) {
|
||||
Logger::log('No database connection', Logger::WARNING);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($params['index'])
|
||||
&& !\mb_check_encoding($params['index'], 'UTF-8')
|
||||
) {
|
||||
$params['index'] = \mb_convert_encoding($params['index'], 'UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$request = yield $this->db->execute($query, $params);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e->getMessage(), Logger::ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if ($request instanceof ResultSet) {
|
||||
while (yield $request->advance()) {
|
||||
$result[] = $request->getCurrent();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
15
src/danog/MadelineProto/Db/NullCache/MysqlArray.php
Normal file
15
src/danog/MadelineProto/Db/NullCache/MysqlArray.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Db\MysqlArray as DbMysqlArray;
|
||||
|
||||
/**
|
||||
* MySQL database backend, no caching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MysqlArray extends DbMysqlArray
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
44
src/danog/MadelineProto/Db/NullCache/NullCacheTrait.php
Normal file
44
src/danog/MadelineProto/Db/NullCache/NullCacheTrait.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
/**
|
||||
* Trait that disables database caching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait NullCacheTrait
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function getCache(string $key, $default = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Save item in cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @param $value
|
||||
*/
|
||||
protected function setCache(string $key, $value): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove key from cache.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
protected function unsetCache(string $key): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function startCacheCleanupLoop(): void
|
||||
{
|
||||
}
|
||||
protected function stopCacheCleanupLoop(): void
|
||||
{
|
||||
}
|
||||
}
|
15
src/danog/MadelineProto/Db/NullCache/PostgresArray.php
Normal file
15
src/danog/MadelineProto/Db/NullCache/PostgresArray.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Db\PostgresArray as DbPostgresArray;
|
||||
|
||||
/**
|
||||
* Postgres database backend, no caching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PostgresArray extends DbPostgresArray
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
15
src/danog/MadelineProto/Db/NullCache/RedisArray.php
Normal file
15
src/danog/MadelineProto/Db/NullCache/RedisArray.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Db\RedisArray as DbRedisArray;
|
||||
|
||||
/**
|
||||
* Redis database backend, no caching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RedisArray extends DbRedisArray
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
|
@ -2,49 +2,111 @@
|
|||
|
||||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Postgres\ByteA;
|
||||
use Amp\Postgres\Pool;
|
||||
use Amp\Producer;
|
||||
use Amp\Promise;
|
||||
use Amp\Sql\ResultSet;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Postgres;
|
||||
use danog\MadelineProto\Logger;
|
||||
use function Amp\call;
|
||||
use danog\MadelineProto\Settings\Database\Postgres as DatabasePostgres;
|
||||
|
||||
/**
|
||||
* Postgres database backend.
|
||||
*/
|
||||
class PostgresArray extends SqlArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
public DatabasePostgres $dbSettings;
|
||||
private Pool $db;
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
/**
|
||||
* Prepare statements.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function prepareStatements(): \Generator
|
||||
{
|
||||
$this->get = yield $this->db->prepare(
|
||||
"SELECT value FROM \"{$this->table}\" WHERE key = :index LIMIT 1",
|
||||
);
|
||||
$this->set = yield $this->db->prepare("
|
||||
INSERT INTO \"{$this->table}\"
|
||||
(key,value)
|
||||
VALUES (:index, :value)
|
||||
ON CONFLICT (key) DO UPDATE SET value = :value
|
||||
");
|
||||
$this->unset = yield $this->db->prepare("
|
||||
DELETE FROM \"{$this->table}\"
|
||||
WHERE key = :index
|
||||
");
|
||||
$this->count = yield $this->db->prepare("
|
||||
SELECT count(key) as count FROM \"{$this->table}\"
|
||||
");
|
||||
$this->iterate = yield $this->db->prepare("
|
||||
SELECT key, value FROM \"{$this->table}\"
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize on startup.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initStartup(): \Generator
|
||||
{
|
||||
yield from $this->initConnection($this->dbSettings);
|
||||
yield from $this->prepareStatements();
|
||||
}
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabasePostgres $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Postgres::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['user'],
|
||||
$settings['password'],
|
||||
$settings['database'],
|
||||
$settings['max_connections'],
|
||||
$settings['idle_timeout']
|
||||
);
|
||||
$this->db = yield from Postgres::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from row.
|
||||
*
|
||||
* @param array $row
|
||||
* @return null|mixed
|
||||
*/
|
||||
protected function getValue(array $row)
|
||||
{
|
||||
if ($row) {
|
||||
if (!empty($row[0]['value'])) {
|
||||
$row = \reset($row);
|
||||
}
|
||||
if (!$row['value']) {
|
||||
return $row['value'];
|
||||
}
|
||||
if ($row['value'][0] === '\\') {
|
||||
$row['value'] = \hex2bin(\substr($row['value'], 2));
|
||||
}
|
||||
return \unserialize($row['value']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value for an offset.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetset.php
|
||||
*
|
||||
* @param string $index <p>
|
||||
* @param string|int $index <p>
|
||||
* The index to set for.
|
||||
* </p>
|
||||
* @param $value
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
|
||||
public function offsetSet($index, $value): Promise
|
||||
{
|
||||
if ($this->getCache($index) === $value) {
|
||||
|
@ -53,16 +115,11 @@ class PostgresArray extends SqlArray
|
|||
|
||||
$this->setCache($index, $value);
|
||||
|
||||
$request = $this->request(
|
||||
"
|
||||
INSERT INTO \"{$this->table}\"
|
||||
(key,value)
|
||||
VALUES (:index, :value)
|
||||
ON CONFLICT (key) DO UPDATE SET value = :value
|
||||
",
|
||||
$request = $this->execute(
|
||||
$this->set,
|
||||
[
|
||||
'index' => $index,
|
||||
'value' => \serialize($value),
|
||||
'value' => new ByteA(\serialize($value)),
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -75,210 +132,45 @@ class PostgresArray extends SqlArray
|
|||
/**
|
||||
* Create table for property.
|
||||
*
|
||||
* @return array|null
|
||||
* @return \Generator
|
||||
*
|
||||
* @throws \Throwable
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise, mixed, void>
|
||||
*/
|
||||
protected function prepareTable(): \Generator
|
||||
{
|
||||
Logger::log("Creating/checking table {$this->table}", Logger::WARNING);
|
||||
|
||||
yield $this->request("
|
||||
yield $this->db->query("
|
||||
CREATE TABLE IF NOT EXISTS \"{$this->table}\"
|
||||
(
|
||||
\"key\" VARCHAR(255) NOT NULL,
|
||||
\"value\" BYTEA NULL,
|
||||
\"ts\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT \"{$this->table}_pkey\" PRIMARY KEY(\"key\")
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
");
|
||||
|
||||
yield $this->request("
|
||||
yield $this->db->query("
|
||||
DROP TRIGGER IF exists \"{$this->table}_update_ts_trigger\" ON \"{$this->table}\";
|
||||
");
|
||||
|
||||
yield $this->request("
|
||||
yield $this->db->query("
|
||||
CREATE TRIGGER \"{$this->table}_update_ts_trigger\" BEFORE UPDATE ON \"{$this->table}\" FOR EACH ROW EXECUTE PROCEDURE update_ts();
|
||||
");
|
||||
}
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if key isset.
|
||||
*
|
||||
* @param $key
|
||||
*
|
||||
* @return Promise<bool> true if the offset exists, otherwise false
|
||||
*/
|
||||
public function isset($key): Promise
|
||||
{
|
||||
return call(fn () => yield $this->offsetGet($key) !== null);
|
||||
}
|
||||
|
||||
|
||||
public function offsetGet($offset): Promise
|
||||
{
|
||||
return call(function () use ($offset) {
|
||||
if ($cached = $this->getCache($offset)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$row = yield $this->request(
|
||||
"SELECT value FROM \"{$this->table}\" WHERE key = :index LIMIT 1",
|
||||
['index' => $offset]
|
||||
);
|
||||
|
||||
if ($value = $this->getValue($row)) {
|
||||
$this->setCache($offset, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset value for an offset.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetunset.php
|
||||
*
|
||||
* @param string $index <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function offsetUnset($index): Promise
|
||||
{
|
||||
$this->unsetCache($index);
|
||||
|
||||
return $this->request(
|
||||
"
|
||||
DELETE FROM \"{$this->table}\"
|
||||
WHERE key = :index
|
||||
",
|
||||
['index' => $index]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array copy.
|
||||
*
|
||||
* @return Promise<array>
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function getArrayCopy(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$iterator = $this->getIterator();
|
||||
$result = [];
|
||||
while (yield $iterator->advance()) {
|
||||
[$key, $value] = $iterator->getCurrent();
|
||||
$result[$key] = $value;
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
public function getIterator(): Producer
|
||||
{
|
||||
return new Producer(function (callable $emit) {
|
||||
$request = yield $this->db->execute("SELECT key, value FROM \"{$this->table}\"");
|
||||
|
||||
while (yield $request->advance()) {
|
||||
$row = $request->getCurrent();
|
||||
yield $emit([$row['key'], $this->getValue($row)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.count.php
|
||||
* @return Promise<int> The number of elements or public properties in the associated
|
||||
* array or object, respectively.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function count(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$row = yield $this->request("SELECT count(key) as count FROM \"{$this->table}\"");
|
||||
return $row[0]['count'] ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
private function getValue(array $row)
|
||||
{
|
||||
if ($row) {
|
||||
if (!empty($row[0]['value'])) {
|
||||
$row = \reset($row);
|
||||
}
|
||||
return \unserialize($row['value']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected function renameTable(string $from, string $to): \Generator
|
||||
{
|
||||
Logger::log("Renaming table {$from} to {$to}", Logger::WARNING);
|
||||
yield $this->request("
|
||||
|
||||
yield $this->db->query("
|
||||
DROP TABLE IF EXISTS \"{$to}\";
|
||||
");
|
||||
|
||||
yield $this->db->query("
|
||||
ALTER TABLE \"{$from}\" RENAME TO \"{$to}\";
|
||||
");
|
||||
|
||||
yield $this->request("
|
||||
DROP TABLE IF EXISTS \"{$from}\";
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform async request to db.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function request(string $query, array $params = []): Promise
|
||||
{
|
||||
return call(function () use ($query, $params) {
|
||||
Logger::log([$query, $params], Logger::VERBOSE);
|
||||
|
||||
if (empty($this->db) || !$this->db->isAlive()) {
|
||||
Logger::log('No database connection', Logger::WARNING);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($params['index'])
|
||||
&& !\mb_check_encoding($params['index'], 'UTF-8')
|
||||
) {
|
||||
$params['index'] = \mb_convert_encoding($params['index'], 'UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$request = yield $this->db->execute($query, $params);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e->getMessage(), Logger::ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if ($request instanceof ResultSet) {
|
||||
while (yield $request->advance()) {
|
||||
$result[] = $request->getCurrent();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,22 +8,41 @@ use Amp\Redis\Redis as RedisRedis;
|
|||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Redis as Redis;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Redis as DatabaseRedis;
|
||||
use Generator;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
class RedisArray extends SqlArray
|
||||
/**
|
||||
* Redis database backend.
|
||||
*/
|
||||
class RedisArray extends DriverArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
protected DatabaseRedis $dbSettings;
|
||||
private RedisRedis $db;
|
||||
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
/**
|
||||
* Initialize on startup.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initStartup(): \Generator
|
||||
{
|
||||
return $this->initConnection($this->dbSettings);
|
||||
}
|
||||
/**
|
||||
* @return Generator
|
||||
*
|
||||
* @psalm-return Generator<int, Success<null>, mixed, void>
|
||||
*/
|
||||
protected function prepareTable(): Generator
|
||||
{
|
||||
yield new Success;
|
||||
}
|
||||
|
||||
|
||||
protected function renameTable(string $from, string $to): \Generator
|
||||
{
|
||||
Logger::log("Renaming table {$from} to {$to}", Logger::WARNING);
|
||||
|
@ -39,22 +58,20 @@ class RedisArray extends SqlArray
|
|||
}
|
||||
}
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabaseRedis $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Redis::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['password'],
|
||||
$settings['database']
|
||||
);
|
||||
$this->db = yield from Redis::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redis key name.
|
||||
*
|
||||
|
@ -194,8 +211,10 @@ class RedisArray extends SqlArray
|
|||
return new Producer(function (callable $emit) {
|
||||
$request = $this->db->scan($this->itKey());
|
||||
|
||||
$len = \strlen($this->rKey(''));
|
||||
while (yield $request->advance()) {
|
||||
yield $emit([$key = $request->getCurrent(), \unserialize(yield $this->db->get($key))]);
|
||||
$key = $request->getCurrent();
|
||||
yield $emit([\substr($key, $len), \unserialize(yield $this->db->get($key))]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,83 +2,203 @@
|
|||
|
||||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Producer;
|
||||
use Amp\Promise;
|
||||
use Amp\Sql\ResultSet;
|
||||
use Amp\Sql\Statement;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
/**
|
||||
* Generic SQL database backend.
|
||||
*/
|
||||
abstract class SqlArray extends DriverArray
|
||||
{
|
||||
/**
|
||||
* Create table for property.
|
||||
*
|
||||
* @return array|null
|
||||
* @throws \Throwable
|
||||
*/
|
||||
abstract protected function prepareTable(): \Generator;
|
||||
protected Statement $get;
|
||||
protected Statement $set;
|
||||
protected Statement $unset;
|
||||
protected Statement $count;
|
||||
|
||||
abstract protected function renameTable(string $from, string $to): \Generator;
|
||||
protected Statement $iterate;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param DbArray|array|null $value
|
||||
* @param string $tablePrefix
|
||||
* @param array $settings
|
||||
* Prepare statements.
|
||||
*
|
||||
* @return Promise
|
||||
* @return \Generator
|
||||
*/
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
|
||||
abstract protected function prepareStatements(): \Generator;
|
||||
|
||||
/**
|
||||
* Get value from row.
|
||||
*
|
||||
* @param array $row
|
||||
* @return null|mixed
|
||||
*/
|
||||
abstract protected function getValue(array $row);
|
||||
|
||||
|
||||
public function getIterator(): Producer
|
||||
{
|
||||
$tableName = "{$tablePrefix}_{$name}";
|
||||
if ($value instanceof static && $value->table === $tableName) {
|
||||
$instance = &$value;
|
||||
} else {
|
||||
$instance = new static();
|
||||
$instance->table = $tableName;
|
||||
}
|
||||
return new Producer(function (callable $emit) {
|
||||
$request = yield $this->iterate->execute();
|
||||
|
||||
$instance->settings = $settings;
|
||||
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
|
||||
|
||||
$instance->startCacheCleanupLoop();
|
||||
|
||||
return call(static function () use ($instance, $value, $settings) {
|
||||
yield from $instance->initConnection($settings);
|
||||
yield from $instance->prepareTable();
|
||||
|
||||
// Skip migrations if its same object
|
||||
if ($instance !== $value) {
|
||||
if ($value instanceof DriverArray) {
|
||||
yield from $value->initConnection($value->settings);
|
||||
}
|
||||
yield from static::renameTmpTable($instance, $value);
|
||||
yield from static::migrateDataToDb($instance, $value);
|
||||
while (yield $request->advance()) {
|
||||
$row = $request->getCurrent();
|
||||
yield $emit([$row['key'], $this->getValue($row)]);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
});
|
||||
}
|
||||
public function getArrayCopy(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$iterator = $this->getIterator();
|
||||
$result = [];
|
||||
while (yield $iterator->advance()) {
|
||||
[$key, $value] = $iterator->getCurrent();
|
||||
$result[$key] = $value;
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename table of old database, if the new one is not a temporary table name.
|
||||
* Check if key isset.
|
||||
*
|
||||
* Otherwise, change name of table in new database to match old table name.
|
||||
* @param $key
|
||||
*
|
||||
* @param self $new New db
|
||||
* @param DbArray|array|null $old Old db
|
||||
*
|
||||
* @return \Generator
|
||||
* @return Promise<bool> true if the offset exists, otherwise false
|
||||
*/
|
||||
protected static function renameTmpTable(self $new, $old): \Generator
|
||||
public function isset($key): Promise
|
||||
{
|
||||
if ($old instanceof static && $old->table) {
|
||||
if (
|
||||
$old->table !== $new->table &&
|
||||
\mb_strpos($new->table, 'tmp') !== 0
|
||||
) {
|
||||
yield from $new->renameTable($old->table, $new->table);
|
||||
} else {
|
||||
$new->table = $old->table;
|
||||
return call(fn () => yield $this->offsetGet($key) !== null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unset value for an offset.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetunset.php
|
||||
*
|
||||
* @param string|int $index <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function offsetUnset($index): Promise
|
||||
{
|
||||
$this->unsetCache($index);
|
||||
|
||||
return $this->execute(
|
||||
$this->unset,
|
||||
['index' => $index]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.count.php
|
||||
* @return Promise<int> The number of elements or public properties in the associated
|
||||
* array or object, respectively.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function count(): Promise
|
||||
{
|
||||
return call(function () {
|
||||
$row = yield $this->execute($this->count);
|
||||
return $row[0]['count'] ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
public function offsetGet($offset): Promise
|
||||
{
|
||||
return call(function () use ($offset) {
|
||||
if ($cached = $this->getCache($offset)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$row = yield $this->execute($this->get, ['index' => $offset]);
|
||||
|
||||
if ($value = $this->getValue($row)) {
|
||||
$this->setCache($offset, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set value for an offset.
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayiterator.offsetset.php
|
||||
*
|
||||
* @param string|int $index <p>
|
||||
* The index to set for.
|
||||
* </p>
|
||||
* @param $value
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function offsetSet($index, $value): Promise
|
||||
{
|
||||
if ($this->getCache($index) === $value) {
|
||||
return new Success();
|
||||
}
|
||||
|
||||
$this->setCache($index, $value);
|
||||
|
||||
$request = $this->execute(
|
||||
$this->set,
|
||||
[
|
||||
'index' => $index,
|
||||
'value' => \serialize($value),
|
||||
]
|
||||
);
|
||||
|
||||
//Ensure that cache is synced with latest insert in case of concurrent requests.
|
||||
$request->onResolve(fn () => $this->setCache($index, $value));
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform async request to db.
|
||||
*
|
||||
* @param Statement $query
|
||||
* @param array $params
|
||||
*
|
||||
* @return Promise
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function execute(Statement $stmt, array $params = []): Promise
|
||||
{
|
||||
return call(function () use ($stmt, $params) {
|
||||
if (
|
||||
!empty($params['index'])
|
||||
&& !\mb_check_encoding($params['index'], 'UTF-8')
|
||||
) {
|
||||
$params['index'] = \mb_convert_encoding($params['index'], 'UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$request = yield $stmt->execute($params);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e->getMessage(), Logger::ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if ($request instanceof ResultSet) {
|
||||
while (yield $request->advance()) {
|
||||
$result[] = $request->getCurrent();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,10 +52,10 @@ class DoHConnector implements Connector
|
|||
$this->dataCenter = $dataCenter;
|
||||
$this->ctx = $ctx;
|
||||
}
|
||||
public function connect(string $uri, ?ConnectContext $socketContext = null, ?CancellationToken $token = null): Promise
|
||||
public function connect(string $uri, ?ConnectContext $context = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
return Tools::call((function () use ($uri, $socketContext, $token): \Generator {
|
||||
$socketContext = $socketContext ?? new ConnectContext();
|
||||
return Tools::call((function () use ($uri, $context, $token): \Generator {
|
||||
$socketContext = $context ?? new ConnectContext();
|
||||
$token = $token ?? new NullCancellationToken();
|
||||
$attempt = 0;
|
||||
$uris = [];
|
||||
|
@ -113,14 +113,17 @@ class DoHConnector implements Connector
|
|||
}
|
||||
$flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT;
|
||||
$timeout = $socketContext->getConnectTimeout();
|
||||
$e = null;
|
||||
foreach ($uris as $builtUri) {
|
||||
try {
|
||||
$streamContext = \stream_context_create($socketContext->withoutTlsContext()->toStreamContextArray());
|
||||
/** @psalm-suppress NullArgument */
|
||||
if (!($socket = @\stream_socket_client($builtUri, $errno, $errstr, null, $flags, $streamContext))) {
|
||||
throw new ConnectException(\sprintf('Connection to %s failed: [Error #%d] %s%s', $uri, $errno, $errstr, $failures ? '; previous attempts: '.\implode($failures) : ''), $errno);
|
||||
}
|
||||
\stream_set_blocking($socket, false);
|
||||
$deferred = new Deferred();
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$watcher = Loop::onWritable($socket, [$deferred, 'resolve']);
|
||||
$id = $token->subscribe([$deferred, 'fail']);
|
||||
try {
|
||||
|
@ -156,8 +159,9 @@ class DoHConnector implements Connector
|
|||
|
||||
// This is reached if either all URIs failed or the maximum number of attempts is reached.
|
||||
/** @noinspection PhpUndefinedVariableInspection */
|
||||
|
||||
throw $e;
|
||||
if ($e) {
|
||||
throw $e;
|
||||
}
|
||||
})());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Settings\TLSchema;
|
||||
use danog\MadelineProto\TL\TL;
|
||||
|
||||
// This code was written a few years ago: it is garbage, and has to be rewritten
|
||||
|
@ -38,17 +39,25 @@ class DocsBuilder
|
|||
use \danog\MadelineProto\DocsBuilder\Methods;
|
||||
use \danog\MadelineProto\DocsBuilder\Constructors;
|
||||
public $td = false;
|
||||
public function __construct($logger, $settings)
|
||||
protected array $settings;
|
||||
protected string $index;
|
||||
protected Logger $logger;
|
||||
protected TL $TL;
|
||||
protected array $tdDescriptions;
|
||||
public function __construct(Logger $logger, array $settings)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$this->TL = new TL(new class($logger) {
|
||||
public function __construct($logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
});
|
||||
$this->TL->init($settings['tl_schema']);
|
||||
$new = new TLSchema;
|
||||
$new->mergeArray($settings);
|
||||
$this->TL->init($new);
|
||||
if (isset($settings['tl_schema']['td']) && !isset($settings['tl_schema']['telegram'])) {
|
||||
$this->td = true;
|
||||
}
|
||||
|
@ -72,7 +81,7 @@ class DocsBuilder
|
|||
|
||||
public $types = [];
|
||||
public $any = '*';
|
||||
public function mkDocs()
|
||||
public function mkDocs(): void
|
||||
{
|
||||
\danog\MadelineProto\Logger::log('Generating documentation index...', \danog\MadelineProto\Logger::NOTICE);
|
||||
\file_put_contents($this->index, $this->template('index', $this->settings['title'], $this->settings['description']));
|
||||
|
@ -116,8 +125,8 @@ class DocsBuilder
|
|||
}
|
||||
}
|
||||
}
|
||||
if (isset($this->td_descriptions['types'][$otype])) {
|
||||
$header = "{$this->td_descriptions['types'][$otype]}\n\n$header";
|
||||
if (isset($this->tdDescriptions['types'][$otype])) {
|
||||
$header = "{$this->tdDescriptions['types'][$otype]}\n\n$header";
|
||||
}
|
||||
$header = \sprintf(
|
||||
$this->templates['Type'],
|
||||
|
@ -142,7 +151,7 @@ class DocsBuilder
|
|||
}
|
||||
\danog\MadelineProto\Logger::log('Done!', \danog\MadelineProto\Logger::NOTICE);
|
||||
}
|
||||
public static function addToLang(string $key, string $value = '', bool $force = false)
|
||||
public static function addToLang(string $key, string $value = '', bool $force = false): void
|
||||
{
|
||||
if (!isset(\danog\MadelineProto\Lang::$lang['en'][$key]) || $force) {
|
||||
\danog\MadelineProto\Lang::$lang['en'][$key] = $value;
|
||||
|
@ -152,11 +161,11 @@ class DocsBuilder
|
|||
* Get formatted template string.
|
||||
*
|
||||
* @param string $name Template name
|
||||
* @param string[] ...$params Params
|
||||
* @param string ...$params Params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function template(string $name, ...$params): string
|
||||
protected function template(string $name, string ...$params): string
|
||||
{
|
||||
return \sprintf($this->templates[$name], ...$params);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use danog\MadelineProto\Tools;
|
|||
|
||||
trait Constructors
|
||||
{
|
||||
public function mkConstructors()
|
||||
public function mkConstructors(): void
|
||||
{
|
||||
foreach (\glob('constructors/'.$this->any) as $unlink) {
|
||||
\unlink($unlink);
|
||||
|
@ -87,6 +87,7 @@ trait Constructors
|
|||
if (!isset($this->TL->getDescriptions()['constructors'][$constructor])) {
|
||||
$this->addToLang('object_'.$constructor);
|
||||
if (\danog\MadelineProto\Lang::$lang['en']['object_'.$constructor] !== '') {
|
||||
/** @psalm-suppress InvalidArrayAssignment */
|
||||
$this->TL->getDescriptions()['constructors'][$constructor]['description'] = \danog\MadelineProto\Lang::$lang['en']['object_'.$constructor];
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +151,7 @@ trait Constructors
|
|||
if (!isset($this->TL->getDescriptions()['constructors'][$constructor]['params'][$param['name']])) {
|
||||
$this->addToLang('object_'.$constructor.'_param_'.$param['name'].'_type_'.$param['type']);
|
||||
if (isset($this->TL->getDescriptions()['constructors'][$constructor]['description'])) {
|
||||
/** @psalm-suppress InvalidArrayAssignment */
|
||||
$this->TL->getDescriptions()['constructors'][$constructor]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['object_'.$constructor.'_param_'.$param['name'].'_type_'.$param['type']];
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +177,7 @@ trait Constructors
|
|||
$description = isset($this->TL->getDescriptions()['constructors'][$constructor]) ? $this->TL->getDescriptions()['constructors'][$constructor]['description'] : $constructor.' attributes, type and example';
|
||||
$symFile = \str_replace('.', '_', $constructor.$layer);
|
||||
$redir = $symFile !== $constructor.$layer ? "\nredirect_from: /API_docs/constructors/{$symFile}.html" : '';
|
||||
$description = \rtrim(\explode("\n", $description)[0], ':');
|
||||
$header = '---
|
||||
title: '.$constructor.'
|
||||
description: '.$description.'
|
||||
|
|
|
@ -24,7 +24,7 @@ use danog\MadelineProto\Tools;
|
|||
|
||||
trait Methods
|
||||
{
|
||||
public function mkMethods()
|
||||
public function mkMethods(): void
|
||||
{
|
||||
static $bots;
|
||||
if (!$bots) {
|
||||
|
@ -84,22 +84,22 @@ trait Methods
|
|||
$param[$type_or_subtype] = '['.StrTools::markdownEscape($param[$type_or_subtype]).'](../'.$type_or_bare_type.'/'.$param[$type_or_subtype].'.md)';
|
||||
$params .= "'".$param['name']."' => ".(isset($param['subtype']) ? '\\['.$param[$type_or_subtype].'\\]' : $param[$type_or_subtype]).', ';
|
||||
}
|
||||
if (!isset($this->td_descriptions['methods'][$method])) {
|
||||
if (!isset($this->tdDescriptions['methods'][$method])) {
|
||||
$this->addToLang('method_'.$method);
|
||||
if (\danog\MadelineProto\Lang::$lang['en']['method_'.$method] !== '') {
|
||||
$this->td_descriptions['methods'][$method]['description'] = \danog\MadelineProto\Lang::$lang['en']['method_'.$method];
|
||||
$this->tdDescriptions['methods'][$method]['description'] = \danog\MadelineProto\Lang::$lang['en']['method_'.$method];
|
||||
}
|
||||
}
|
||||
$md_method = '['.$phpMethod.']('.$method.'.md)';
|
||||
$this->docs_methods[$method] = '$MadelineProto->'.$md_method.'(\\['.$params.'\\]) === [$'.StrTools::markdownEscape($type).'](../types/'.$php_type.'.md)<a name="'.$method.'"></a>
|
||||
|
||||
';
|
||||
if (isset($this->td_descriptions['methods'][$method])) {
|
||||
$desc = \Parsedown::instance()->line(\trim(\explode("\n", $this->td_descriptions['methods'][$method]['description'])[0], '.'));
|
||||
if (isset($this->tdDescriptions['methods'][$method])) {
|
||||
$desc = \Parsedown::instance()->line(\trim(\explode("\n", $this->tdDescriptions['methods'][$method]['description'])[0], '.'));
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadHTML(\mb_convert_encoding($desc, 'HTML-ENTITIES', 'UTF-8'));
|
||||
$desc = $dom->textContent;
|
||||
$this->human_docs_methods[$this->td_descriptions['methods'][$method]['description'].': '.$method] = '* <a href="'.$method.'.html" name="'.$method.'">'.$desc.': '.$method.'</a>
|
||||
$this->human_docs_methods[$this->tdDescriptions['methods'][$method]['description'].': '.$method] = '* <a href="'.$method.'.html" name="'.$method.'">'.$desc.': '.$method.'</a>
|
||||
|
||||
';
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ trait Methods
|
|||
| Name | Type | Required |
|
||||
|----------|---------------|----------|
|
||||
';
|
||||
if (isset($this->td_descriptions['methods'][$method]) && !empty($data['params'])) {
|
||||
if (isset($this->tdDescriptions['methods'][$method]) && !empty($data['params'])) {
|
||||
$table = '### Parameters:
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|
@ -164,13 +164,13 @@ trait Methods
|
|||
$human_ptype = 'File path or '.$ptype;
|
||||
}
|
||||
$type_or_bare_type = \ctype_upper(Tools::end(\explode('.', $param[$type_or_subtype]))[0]) || \in_array($param[$type_or_subtype], ['!X', 'X', 'bytes', 'true', 'false', 'double', 'string', 'Bool', 'int', 'long', 'int128', 'int256', 'int512', 'int53']) ? 'types' : 'constructors';
|
||||
if (!isset($this->td_descriptions['methods'][$method]['params'][$param['name']])) {
|
||||
if (isset($this->td_descriptions['methods'][$method]['description'])) {
|
||||
$this->td_descriptions['methods'][$method]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['method_'.$method.'_param_'.$param['name'].'_type_'.$param['type']] ?? '';
|
||||
if (!isset($this->tdDescriptions['methods'][$method]['params'][$param['name']])) {
|
||||
if (isset($this->tdDescriptions['methods'][$method]['description'])) {
|
||||
$this->tdDescriptions['methods'][$method]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['method_'.$method.'_param_'.$param['name'].'_type_'.$param['type']] ?? '';
|
||||
}
|
||||
}
|
||||
if (isset($this->td_descriptions['methods'][$method])) {
|
||||
$table .= '|'.StrTools::markdownEscape($param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.StrTools::markdownEscape($human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.$this->td_descriptions['methods'][$method]['params'][$param['name']].' | '.(isset($param['pow']) || ($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty')) && $id['type'] === $param['type'] || ($id = $this->TL->getConstructors($this->td)->findByPredicate('input'.$param['type'].'Empty')) && $id['type'] === $param['type'] ? 'Optional' : 'Yes').'|';
|
||||
if (isset($this->tdDescriptions['methods'][$method])) {
|
||||
$table .= '|'.StrTools::markdownEscape($param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.StrTools::markdownEscape($human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.$this->tdDescriptions['methods'][$method]['params'][$param['name']].' | '.(isset($param['pow']) || ($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty')) && $id['type'] === $param['type'] || ($id = $this->TL->getConstructors($this->td)->findByPredicate('input'.$param['type'].'Empty')) && $id['type'] === $param['type'] ? 'Optional' : 'Yes').'|';
|
||||
} else {
|
||||
$table .= '|'.StrTools::markdownEscape($param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.StrTools::markdownEscape($human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.(isset($param['pow']) || ($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty')) && $id['type'] === $param['type'] || ($id = $this->TL->getConstructors($this->td)->findByPredicate('input'.$param['type'].'Empty')) && $id['type'] === $param['type'] ? 'Optional' : 'Yes').'|';
|
||||
}
|
||||
|
@ -200,14 +200,15 @@ trait Methods
|
|||
$pwr_params = "parseMode - string\n";
|
||||
}
|
||||
}
|
||||
$description = isset($this->td_descriptions['methods'][$method]) ? $this->td_descriptions['methods'][$method]['description'] : $method.' parameters, return type and example';
|
||||
$description = isset($this->tdDescriptions['methods'][$method]) ? $this->tdDescriptions['methods'][$method]['description'] : $method.' parameters, return type and example';
|
||||
$symFile = \str_replace('.', '_', $method);
|
||||
$redir = $symFile !== $method ? "\nredirect_from: /API_docs/methods/{$symFile}.html" : '';
|
||||
$description = \rtrim(\explode("\n", $description)[0], ':');
|
||||
$header = $this->template('Method', $method, $description, $redir, StrTools::markdownEscape($method));
|
||||
if ($this->td) {
|
||||
$header .= "YOU CANNOT USE THIS METHOD IN MADELINEPROTO\n\n\n\n\n";
|
||||
}
|
||||
$header .= isset($this->td_descriptions['methods'][$method]) ? $this->td_descriptions['methods'][$method]['description'].PHP_EOL.PHP_EOL : '';
|
||||
$header .= isset($this->tdDescriptions['methods'][$method]) ? $this->tdDescriptions['methods'][$method]['description'].PHP_EOL.PHP_EOL : '';
|
||||
$table .= '
|
||||
|
||||
';
|
||||
|
|
|
@ -19,27 +19,64 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Db\DbPropertiesTrait;
|
||||
|
||||
/**
|
||||
* Event handler.
|
||||
*/
|
||||
class EventHandler extends InternalDoc
|
||||
abstract class EventHandler extends InternalDoc
|
||||
{
|
||||
use DbPropertiesTrait {
|
||||
DbPropertiesTrait::initDb as private internalInitDb;
|
||||
}
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param APIWrapper|null $MadelineProto MadelineProto instance
|
||||
* Whether the event handler was started.
|
||||
*/
|
||||
public function __construct(?APIWrapper $MadelineProto)
|
||||
private bool $startedInternal = false;
|
||||
/**
|
||||
* API instance.
|
||||
*/
|
||||
protected MTProto $API;
|
||||
public function __construct($API) // BC
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Internal constructor.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param APIWrapper $MadelineProto MadelineProto instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initInternal(APIWrapper $MadelineProto): void
|
||||
{
|
||||
if (!$MadelineProto) {
|
||||
return;
|
||||
}
|
||||
self::link($this, $MadelineProto->getFactory());
|
||||
$this->API =& $MadelineProto->getAPI();
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start method handler.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function startInternal(): \Generator
|
||||
{
|
||||
if ($this->startedInternal) {
|
||||
return;
|
||||
}
|
||||
if (isset(static::$dbProperties)) {
|
||||
yield from $this->internalInitDb($this->API);
|
||||
}
|
||||
if (\method_exists($this, 'onStart')) {
|
||||
yield $this->onStart();
|
||||
}
|
||||
$this->startedInternal = true;
|
||||
}
|
||||
/**
|
||||
* Get peers where to send error reports.
|
||||
*
|
||||
|
@ -49,4 +86,14 @@ class EventHandler extends InternalDoc
|
|||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API instance.
|
||||
*
|
||||
* @return MTProto
|
||||
*/
|
||||
public function getAPI(): MTProto
|
||||
{
|
||||
return $this->API;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
/**
|
||||
* Basic exception.
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
use TL\PrettyException;
|
||||
|
@ -75,8 +78,10 @@ class Exception extends \Exception
|
|||
* ExceptionErrorHandler.
|
||||
*
|
||||
* Error handler
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
public static function exceptionErrorHandler($errno = 0, $errstr = null, $errfile = null, $errline = null)
|
||||
public static function exceptionErrorHandler($errno = 0, $errstr = null, $errfile = null, $errline = null): bool
|
||||
{
|
||||
// If error is suppressed with @, don't throw an exception
|
||||
if (\error_reporting() === 0 || \strpos($errstr, 'headers already sent') || $errfile && (\strpos($errfile, 'vendor/amphp') !== false || \strpos($errfile, 'vendor/league') !== false)) {
|
||||
|
@ -88,8 +93,10 @@ class Exception extends \Exception
|
|||
* ExceptionErrorHandler.
|
||||
*
|
||||
* Error handler
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function exceptionHandler($exception)
|
||||
public static function exceptionHandler($exception): void
|
||||
{
|
||||
Logger::log($exception, Logger::FATAL_ERROR);
|
||||
Magic::shutdown(1);
|
||||
|
|
|
@ -19,151 +19,4 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\File\StatCache;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\get;
|
||||
use function Amp\File\isfile;
|
||||
use function Amp\File\unlink;
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* IPC API wrapper for MadelineProto.
|
||||
*/
|
||||
class FastAPI extends API
|
||||
{
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct(string $session, array $settings = []): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
$this->setInitPromise($this->__construct_async($session, $settings));
|
||||
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
|
||||
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->{$key}) {
|
||||
$this->{$key} = $this->exportNamespace($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Async constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __construct_async(string $session, array $settings = []): \Generator
|
||||
{
|
||||
$this->logger = Logger::constructorFromSettings($settings);
|
||||
$session = new SessionPaths($session);
|
||||
if (!$client = yield from $this->checkInit($session, $settings)) {
|
||||
try {
|
||||
yield unlink($session->getIpcPath());
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
StatCache::clear($session->getIpcPath());
|
||||
Server::startMe($session);
|
||||
$inited = false;
|
||||
$this->logger->logger("Waiting for IPC server to start...");
|
||||
for ($x = 0; $x < 30; $x++) {
|
||||
yield Tools::sleep(1);
|
||||
StatCache::clear($session->getIpcPath());
|
||||
if ($client = yield from $this->checkInit($session, $settings)) {
|
||||
$inited = true;
|
||||
break;
|
||||
}
|
||||
Server::startMe($session);
|
||||
}
|
||||
if (!$client) {
|
||||
throw new Exception("The IPC server isn't running, please check logs!");
|
||||
}
|
||||
}
|
||||
$this->API = new Client($client, $this->logger);
|
||||
$this->methods = self::getInternalMethodList($this->API, MTProto::class);
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
}
|
||||
/**
|
||||
* Try initializing session.
|
||||
*
|
||||
* @param SessionPaths $session Session paths
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function checkInit(SessionPaths $session, array $settings): \Generator
|
||||
{
|
||||
StatCache::clear($session->getIpcPath());
|
||||
StatCache::clear($session->getSessionPath());
|
||||
if (!(yield exists($session->getSessionPath()))
|
||||
|| (yield exists($session->getIpcPath())
|
||||
&& yield isfile($session->getIpcPath())
|
||||
&& yield get($session->getIpcPath()) === Server::NOT_INITED)
|
||||
) { // Should init API ID|session
|
||||
Logger::log("Session not initialized, initializing it now...");
|
||||
$API = new API($session->getSessionPath(), $settings);
|
||||
yield from $API->initAsynchronously();
|
||||
unset($API);
|
||||
Logger::log("Destroying temporary MadelineProto...");
|
||||
while (\gc_collect_cycles());
|
||||
Logger::log("Destroyed temporary MadelineProto!");
|
||||
return null; // Should start IPC server
|
||||
}
|
||||
return yield from $this->tryConnect($session->getIpcPath());
|
||||
}
|
||||
/**
|
||||
* Try connecting to IPC socket.
|
||||
*
|
||||
* @param string $ipcPath IPC path
|
||||
*
|
||||
* @return \Generator<ChannelledSocket|null>
|
||||
*/
|
||||
private function tryConnect(string $ipcPath): \Generator
|
||||
{
|
||||
Logger::log("Trying to connect to IPC socket...");
|
||||
try {
|
||||
\clearstatcache(true, $ipcPath);
|
||||
return yield connect($ipcPath);
|
||||
} catch (\Throwable $e) {
|
||||
$e = $e->getMessage();
|
||||
Logger::log("$e while connecting to IPC socket");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start MadelineProto and the event handler (enables async).
|
||||
*
|
||||
* Also initializes error reporting, catching and reporting all errors surfacing from the event loop.
|
||||
*
|
||||
* @param string $eventHandler Event handler class name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startAndLoop(string $eventHandler): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
/**
|
||||
* Start multiple instances of MadelineProto and the event handlers (enables async).
|
||||
*
|
||||
* @param API[] $instances Instances of madeline
|
||||
* @param string[]|string $eventHandler Event handler(s)
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public static function startAndLoopMulti(array $instances, $eventHandler): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
}
|
||||
\class_alias(API::class, '\\danog\\MadelineProto\\FastAPI');
|
||||
|
|
|
@ -33,9 +33,9 @@ interface FileCallbackInterface
|
|||
/**
|
||||
* Invoke callback.
|
||||
*
|
||||
* @param int $percent Percent
|
||||
* @param int $speed Speed in mbps
|
||||
* @param int $time Time
|
||||
* @param float $percent Percent
|
||||
* @param float $speed Speed in mbps
|
||||
* @param float $time Time
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -18,83 +18,53 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProtoTools\FilesLogic;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* IPC client.
|
||||
*/
|
||||
class Client
|
||||
class Client extends ClientAbstract
|
||||
{
|
||||
use \danog\MadelineProto\Wrappers\Start;
|
||||
use \danog\MadelineProto\Wrappers\Templates;
|
||||
use FilesLogic;
|
||||
|
||||
/**
|
||||
* IPC server socket.
|
||||
* Session.
|
||||
*/
|
||||
private ChannelledSocket $server;
|
||||
/**
|
||||
* Requests promise array.
|
||||
*/
|
||||
private array $requests = [];
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
public Logger $logger;
|
||||
protected SessionPaths $session;
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param ChannelledSocket $socket IPC client socket
|
||||
* @param Logger $logger Logger
|
||||
* @param ChannelledSocket $socket IPC client socket
|
||||
* @param SessionPaths $session Session paths
|
||||
* @param Logger $logger Logger
|
||||
*/
|
||||
public function __construct(ChannelledSocket $server, Logger $logger)
|
||||
public function __construct(ChannelledSocket $server, SessionPaths $session, Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->server = $server;
|
||||
Tools::callFork($this->loop());
|
||||
$this->session = $session;
|
||||
Tools::callFork($this->loopInternal());
|
||||
}
|
||||
/**
|
||||
* Logger.
|
||||
* Run the provided async callable.
|
||||
*
|
||||
* @param string $param Parameter
|
||||
* @param int $level Logging level
|
||||
* @param string $file File where the message originated
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logger($param, int $level = Logger::NOTICE, string $file = ''): void
|
||||
{
|
||||
if ($file === null) {
|
||||
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
|
||||
}
|
||||
isset($this->logger) ? $this->logger->logger($param, $level, $file) : Logger::$default->logger($param, $level, $file);
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
* @param callable $callback Async callable to run
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function loop(): \Generator
|
||||
public function loop(callable $callback): \Generator
|
||||
{
|
||||
while ($payload = yield $this->server->receive()) {
|
||||
[$id, $payload] = $payload;
|
||||
if (!isset($this->requests[$id])) {
|
||||
Logger::log("Got response for non-existing ID $id!");
|
||||
} else {
|
||||
$promise = $this->requests[$id];
|
||||
unset($this->requests[$id]);
|
||||
if ($payload instanceof ExitFailure) {
|
||||
$promise->fail($payload->getException());
|
||||
} else {
|
||||
$promise->resolve($payload);
|
||||
}
|
||||
unset($promise);
|
||||
}
|
||||
}
|
||||
return yield $callback();
|
||||
}
|
||||
/**
|
||||
* Unreference.
|
||||
|
@ -103,23 +73,185 @@ class Client
|
|||
*/
|
||||
public function unreference(): void
|
||||
{
|
||||
if (isset($this->server)) {
|
||||
Tools::wait($this->server->disconnect());
|
||||
}
|
||||
Tools::wait($this->disconnect());
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
* Stop IPC server instance.
|
||||
*
|
||||
* @param string $function Function name
|
||||
* @param array $arguments Arguments
|
||||
* @internal
|
||||
*/
|
||||
public function stopIpcServer(): Promise
|
||||
{
|
||||
$this->run = false;
|
||||
return $this->server->send(Server::SHUTDOWN);
|
||||
}
|
||||
/**
|
||||
* Restart IPC server instance.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function restartIpcServer(): Promise
|
||||
{
|
||||
return $this->server->send(Server::SHUTDOWN);
|
||||
}
|
||||
/**
|
||||
* Whether we're an IPC client instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isIpc(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file from URL.
|
||||
*
|
||||
* @param string|FileCallbackInterface $url URL of file
|
||||
* @param integer $size Size of file
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call(string $function, array $arguments): \Generator
|
||||
public function uploadFromUrl($url, int $size = 0, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
$this->requests []= $deferred = new Deferred;
|
||||
yield $this->server->send([$function, $arguments]);
|
||||
return yield $deferred->promise();
|
||||
if (\is_object($url) && $url instanceof FileCallbackInterface) {
|
||||
$cb = $url;
|
||||
$url = $url->getFile();
|
||||
}
|
||||
$params = [$url, $size, $fileName, &$cb, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('uploadFromUrl', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Upload file from callable.
|
||||
*
|
||||
* The callable must accept two parameters: int $offset, int $size
|
||||
* The callable must return a string with the contest of the file at the specified offset and size.
|
||||
*
|
||||
* @param mixed $callable Callable
|
||||
* @param integer $size File size
|
||||
* @param string $mime Mime type
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $seekable Whether chunks can be fetched out of order
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, mixed>
|
||||
*/
|
||||
public function uploadFromCallable(callable $callable, int $size, string $mime, string $fileName = '', $cb = null, bool $seekable = true, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
|
||||
$cb = $callable;
|
||||
$callable = $callable->getFile();
|
||||
}
|
||||
$params = [&$callable, $size, $mime, $fileName, &$cb, $seekable, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
$wrapper->wrap($callable, false);
|
||||
return yield from $this->__call('uploadFromCallable', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Reupload telegram file.
|
||||
*
|
||||
* @param mixed $media Telegram file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, mixed>
|
||||
*/
|
||||
public function uploadFromTgfile($media, $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($media) && $media instanceof FileCallbackInterface) {
|
||||
$cb = $media;
|
||||
$media = $media->getFile();
|
||||
}
|
||||
$params = [$media, &$cb, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('uploadFromTgfile', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file to directory.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param string|FileCallbackInterface $dir Directory where to download the file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator Downloaded file path
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, mixed>
|
||||
*/
|
||||
public function downloadToDir($messageMedia, $dir, $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($dir) && $dir instanceof FileCallbackInterface) {
|
||||
$cb = $dir;
|
||||
$dir = $dir->getFile();
|
||||
}
|
||||
$params = [$messageMedia, $dir, &$cb];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToDir', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param string|FileCallbackInterface $file Downloaded file path
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator Downloaded file path
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, mixed>
|
||||
*/
|
||||
public function downloadToFile($messageMedia, $file, $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($file) && $file instanceof FileCallbackInterface) {
|
||||
$cb = $file;
|
||||
$file = $file->getFile();
|
||||
}
|
||||
$params = [$messageMedia, $file, &$cb];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToFile', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file to callable.
|
||||
* The callable must accept two parameters: string $payload, int $offset
|
||||
* The callable will be called (possibly out of order, depending on the value of $seekable).
|
||||
* The callable should return the number of written bytes.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param callable|FileCallbackInterface $callable Chunk callback
|
||||
* @param callable $cb Status callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param bool $seekable Whether the callable can be called out of order
|
||||
* @param int $offset Offset where to start downloading
|
||||
* @param int $end Offset where to stop downloading (inclusive)
|
||||
* @param int $part_size Size of each chunk
|
||||
*
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, mixed>
|
||||
*/
|
||||
public function downloadToCallable($messageMedia, callable $callable, $cb = null, bool $seekable = true, int $offset = 0, int $end = -1, int $part_size = null): \Generator
|
||||
{
|
||||
$messageMedia = (yield from $this->getDownloadInfo($messageMedia));
|
||||
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
|
||||
$cb = $callable;
|
||||
$callable = $callable->getFile();
|
||||
}
|
||||
$params = [$messageMedia, &$callable, &$cb, $seekable, $offset, $end, $part_size, ];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($callable, false);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToCallable', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Placeholder.
|
||||
|
|
148
src/danog/MadelineProto/Ipc/ClientAbstract.php
Normal file
148
src/danog/MadelineProto/Ipc/ClientAbstract.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/**
|
||||
* API wrapper module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* IPC client.
|
||||
*/
|
||||
abstract class ClientAbstract
|
||||
{
|
||||
/**
|
||||
* IPC server socket.
|
||||
*/
|
||||
protected ChannelledSocket $server;
|
||||
/**
|
||||
* Requests promise array.
|
||||
*
|
||||
* @var Deferred[]
|
||||
*/
|
||||
private array $requests = [];
|
||||
/**
|
||||
* Wrappers array.
|
||||
*
|
||||
* @var Wrapper[]
|
||||
*/
|
||||
private array $wrappers = [];
|
||||
/**
|
||||
* Whether to run loop.
|
||||
*/
|
||||
protected bool $run = true;
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
public Logger $logger;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Logger.
|
||||
*
|
||||
* @param string $param Parameter
|
||||
* @param int $level Logging level
|
||||
* @param string $file File where the message originated
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logger($param, int $level = Logger::NOTICE, string $file = ''): void
|
||||
{
|
||||
if ($file === null) {
|
||||
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
|
||||
}
|
||||
isset($this->logger) ? $this->logger->logger($param, $level, $file) : Logger::$default->logger($param, $level, $file);
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function loopInternal(): \Generator
|
||||
{
|
||||
do {
|
||||
while ($payload = yield $this->server->receive()) {
|
||||
[$id, $payload] = $payload;
|
||||
if (!isset($this->requests[$id])) {
|
||||
Logger::log("Got response for non-existing ID $id!");
|
||||
} else {
|
||||
$promise = $this->requests[$id];
|
||||
unset($this->requests[$id]);
|
||||
if (isset($this->wrappers[$id])) {
|
||||
yield $this->wrappers[$id]->disconnect();
|
||||
unset($this->wrappers[$id]);
|
||||
}
|
||||
if ($payload instanceof ExitFailure) {
|
||||
$promise->fail($payload->getException());
|
||||
} else {
|
||||
$promise->resolve($payload);
|
||||
}
|
||||
unset($promise);
|
||||
}
|
||||
}
|
||||
if ($this->run) {
|
||||
$this->logger("Reconnecting to IPC server!");
|
||||
yield $this->server->disconnect();
|
||||
if ($this instanceof Client) {
|
||||
Server::startMe($this->session);
|
||||
$this->server = yield connect($this->session->getIpcPath());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while ($this->run);
|
||||
}
|
||||
/**
|
||||
* Disconnect cleanly from main instance.
|
||||
*
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, Promise, mixed, void>
|
||||
*/
|
||||
public function disconnect(): \Generator
|
||||
{
|
||||
$this->run = false;
|
||||
yield $this->server->disconnect();
|
||||
foreach ($this->wrappers as $w) {
|
||||
yield from $w->disconnect();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param string|int $function Function name
|
||||
* @param array|Wrapper $arguments Arguments
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call($function, $arguments): \Generator
|
||||
{
|
||||
$this->requests []= $deferred = new Deferred;
|
||||
if ($arguments instanceof Wrapper) {
|
||||
$this->wrappers[\count($this->requests) - 1] = $arguments;
|
||||
}
|
||||
yield $this->server->send([$function, $arguments]);
|
||||
return yield $deferred->promise();
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ final class ExitFailure
|
|||
/** @var self|null */
|
||||
private $previous;
|
||||
|
||||
/** @var self|null */
|
||||
/** @var string|null */
|
||||
private $localized;
|
||||
|
||||
public function __construct(\Throwable $exception)
|
||||
|
@ -48,7 +48,7 @@ final class ExitFailure
|
|||
}
|
||||
}
|
||||
|
||||
public function getException()
|
||||
public function getException(): object
|
||||
{
|
||||
$previous = $this->previous ? $this->previous->getException() : null;
|
||||
|
||||
|
|
64
src/danog/MadelineProto/Ipc/IpcState.php
Normal file
64
src/danog/MadelineProto/Ipc/IpcState.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
/**
|
||||
* IPC state class.
|
||||
*/
|
||||
final class IpcState
|
||||
{
|
||||
/**
|
||||
* Startup time.
|
||||
*/
|
||||
private float $startupTime;
|
||||
/**
|
||||
* Startup ID.
|
||||
*/
|
||||
private int $startupId;
|
||||
/**
|
||||
* Exception.
|
||||
*/
|
||||
private ?ExitFailure $exception;
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param integer $startupId
|
||||
* @param \Throwable $exception
|
||||
*/
|
||||
public function __construct(int $startupId, \Throwable $exception = null)
|
||||
{
|
||||
$this->startupTime = \microtime(true);
|
||||
$this->startupId = $startupId;
|
||||
$this->exception = $exception ? new ExitFailure($exception) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startup time.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getStartupTime(): float
|
||||
{
|
||||
return $this->startupTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startup ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStartupId(): int
|
||||
{
|
||||
return $this->startupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception.
|
||||
*
|
||||
* @return ?\Throwable
|
||||
*/
|
||||
public function getException(): ?\Throwable
|
||||
{
|
||||
return $this->exception ? $this->exception->getException() : null;
|
||||
}
|
||||
}
|
|
@ -2,17 +2,25 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
final class ProcessRunner extends RunnerAbstract
|
||||
{
|
||||
/** @var string|null Cached path to located PHP binary. */
|
||||
private static $binaryPath;
|
||||
|
||||
/**
|
||||
* Resources.
|
||||
*/
|
||||
private static array $resources = [];
|
||||
/**
|
||||
* Runner.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start(string $session): void
|
||||
public static function start(string $session, int $startupId): void
|
||||
{
|
||||
if (\PHP_SAPI === "cli") {
|
||||
$binary = \PHP_BINARY;
|
||||
|
@ -28,16 +36,23 @@ final class ProcessRunner extends RunnerAbstract
|
|||
|
||||
$runner = self::getScriptPath();
|
||||
|
||||
if (\strtolower(\substr(PHP_OS, 0, 3)) === 'win') {
|
||||
$binary = \str_replace("Program Files", "PROGRA~1", $binary);
|
||||
// Pray there are no spaces in the name, escapeshellarg would help but windows doesn't want quotes in the program name
|
||||
} else {
|
||||
$binary = \escapeshellarg($binary);
|
||||
}
|
||||
$command = \implode(" ", [
|
||||
'nohup',
|
||||
\escapeshellarg($binary),
|
||||
$binary,
|
||||
self::formatOptions($options),
|
||||
$runner,
|
||||
'madeline-ipc',
|
||||
\escapeshellarg($session),
|
||||
'&>/dev/null &'
|
||||
$startupId
|
||||
]);
|
||||
\proc_close(\proc_open($command, [], $foo));
|
||||
Logger::log("Starting process with $command");
|
||||
|
||||
self::$resources []= \proc_open($command, [], $foo);
|
||||
}
|
||||
private static function locateBinary(): string
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class RunnerAbstract
|
|||
/**
|
||||
* If using madeline.php, simply return madeline.php path.
|
||||
*/
|
||||
if (\defined(\MADELINE_PHP::class)) {
|
||||
if (\defined('MADELINE_PHP')) {
|
||||
return \MADELINE_PHP;
|
||||
}
|
||||
// Write process runner to external file if inside a PHAR different from madeline.phar,
|
||||
|
@ -60,8 +60,9 @@ abstract class RunnerAbstract
|
|||
* Runner.
|
||||
*
|
||||
* @param string $session Session path
|
||||
* @param int $startup ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public static function start(string $session): void;
|
||||
abstract public static function start(string $session, int $startupId): void;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Parallel\Context\ContextException;
|
||||
use danog\MadelineProto\Magic;
|
||||
|
||||
|
@ -15,21 +14,18 @@ final class WebRunner extends RunnerAbstract
|
|||
* Resources.
|
||||
*/
|
||||
private static array $resources = [];
|
||||
/**
|
||||
* Socket.
|
||||
*
|
||||
* @var ResourceOutputStream
|
||||
*/
|
||||
private $res;
|
||||
|
||||
/**
|
||||
* Start.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start(string $session): void
|
||||
public static function start(string $session, int $startupId): void
|
||||
{
|
||||
if (!isset($_SERVER['SERVER_NAME'])) {
|
||||
throw new ContextException("Could not initialize web runner!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::$runPath) {
|
||||
|
@ -46,7 +42,7 @@ final class WebRunner extends RunnerAbstract
|
|||
}
|
||||
$rootDir = \dirname($rootDir).DIRECTORY_SEPARATOR;
|
||||
$uriDir = \dirname($uri);
|
||||
if ($uriDir !== '/') {
|
||||
if ($uriDir !== '/' && $uriDir !== '\\') {
|
||||
$uriDir .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
|
@ -79,7 +75,7 @@ final class WebRunner extends RunnerAbstract
|
|||
}
|
||||
|
||||
$params = [
|
||||
'argv' => ['madeline-ipc', $session],
|
||||
'argv' => ['madeline-ipc', $session, $startupId],
|
||||
'cwd' => Magic::getcwd()
|
||||
];
|
||||
|
||||
|
|
|
@ -16,21 +16,22 @@
|
|||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
use Amp\Deferred;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Ipc\IpcState;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Settings\Ipc;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
(static function (): void {
|
||||
if (\defined(\MADELINE_ENTRY::class)) {
|
||||
if (\defined('MADELINE_ENTRY')) {
|
||||
// Already called
|
||||
return;
|
||||
}
|
||||
\define(\MADELINE_ENTRY::class, 1);
|
||||
if (!\defined(\MADELINE_WORKER_TYPE::class)) {
|
||||
\define('MADELINE_ENTRY', 1);
|
||||
if (!\defined('MADELINE_WORKER_TYPE')) {
|
||||
if (\count(\debug_backtrace(0)) !== 1) {
|
||||
// We're not being included directly
|
||||
return;
|
||||
|
@ -45,8 +46,15 @@ use danog\MadelineProto\Tools;
|
|||
\trigger_error("Not enough arguments!", E_USER_ERROR);
|
||||
exit(1);
|
||||
}
|
||||
\define(\MADELINE_WORKER_TYPE::class, \array_shift($arguments));
|
||||
\define(\MADELINE_WORKER_ARGS::class, $arguments);
|
||||
\define('MADELINE_WORKER_TYPE', \array_shift($arguments));
|
||||
\define('MADELINE_WORKER_ARGS', $arguments);
|
||||
}
|
||||
|
||||
if (\defined('SIGHUP')) {
|
||||
try {
|
||||
\pcntl_signal(SIGHUP, fn () => null);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
if (!\class_exists(API::class)) {
|
||||
$paths = [
|
||||
|
@ -74,37 +82,48 @@ use danog\MadelineProto\Tools;
|
|||
\trigger_error("IPC session $ipcPath does not exist!", E_USER_ERROR);
|
||||
exit(1);
|
||||
}
|
||||
if (\function_exists(\cli_set_process_title::class)) {
|
||||
if (\function_exists('cli_set_process_title')) {
|
||||
@\cli_set_process_title("MadelineProto worker $ipcPath");
|
||||
}
|
||||
if (\function_exists('posix_setsid')) {
|
||||
@\posix_setsid();
|
||||
}
|
||||
if (isset($_GET['cwd'])) {
|
||||
@\chdir($_GET['cwd']);
|
||||
}
|
||||
\define(\MADELINE_WORKER::class, 1);
|
||||
\define('MADELINE_WORKER', 1);
|
||||
|
||||
$runnerId = \MADELINE_WORKER_ARGS[1];
|
||||
$session = new SessionPaths($ipcPath);
|
||||
|
||||
try {
|
||||
Magic::classExists();
|
||||
Magic::$script_cwd = $_GET['cwd'] ?? Magic::getcwd();
|
||||
$API = new API($ipcPath);
|
||||
|
||||
$API = new API($ipcPath, (new Ipc)->setSlow(true));
|
||||
$API->init();
|
||||
if ($API->hasEventHandler()) {
|
||||
unset($API);
|
||||
\gc_collect_cycles();
|
||||
Logger::log("Session has event handler, can't start IPC server like this!");
|
||||
$ipc = (new SessionPaths($ipcPath))->getIpcPath();
|
||||
@\unlink($ipc);
|
||||
\file_put_contents($ipc, Server::EVENT_HANDLER);
|
||||
} else {
|
||||
$API->initSelfRestart();
|
||||
Tools::wait((new Deferred)->promise());
|
||||
$API->initSelfRestart();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
Tools::wait($session->storeIpcState(new IpcState($runnerId)));
|
||||
Tools::wait(Server::waitShutdown());
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log((string) $e, Logger::FATAL_ERROR);
|
||||
Tools::wait($API->report("Surfaced: $e"));
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log("Got exception $e in IPC server, exiting...", Logger::FATAL_ERROR);
|
||||
\trigger_error("Got exception $e in IPC server, exiting...", E_USER_ERROR);
|
||||
if ($e->getMessage() === 'Not inited!') {
|
||||
$ipc = (new SessionPaths($ipcPath))->getIpcPath();
|
||||
@\unlink($ipc);
|
||||
\file_put_contents($ipc, Server::NOT_INITED);
|
||||
Logger::log("$e", Logger::FATAL_ERROR);
|
||||
Logger::log("Got exception in IPC server, exiting...", Logger::FATAL_ERROR);
|
||||
$ipc = Tools::wait($session->getIpcState());
|
||||
if (!($ipc && $ipc->getStartupId() === $runnerId && !$ipc->getException())) {
|
||||
Logger::log("Reporting error!");
|
||||
Tools::wait($session->storeIpcState(new IpcState($runnerId, $e)));
|
||||
Logger::log("Reported error!");
|
||||
} else {
|
||||
Logger::log("Not reporting error!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,17 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\IpcServer;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Promise;
|
||||
use danog\Loop\SignalLoop;
|
||||
use danog\MadelineProto\Ipc\Runner\ProcessRunner;
|
||||
use danog\MadelineProto\Ipc\Runner\WebRunner;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Settings\Ipc;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
|
@ -34,47 +38,105 @@ class Server extends SignalLoop
|
|||
{
|
||||
use InternalLoop;
|
||||
/**
|
||||
* Session not initialized, should initialize.
|
||||
* Shutdown server.
|
||||
*/
|
||||
const NOT_INITED = 'not inited';
|
||||
const SHUTDOWN = 0;
|
||||
/**
|
||||
* Session uses event handler, should start from main event handler file.
|
||||
* Boolean to shut down worker, if started.
|
||||
*/
|
||||
const EVENT_HANDLER = 'event';
|
||||
private static bool $shutdown = false;
|
||||
/**
|
||||
* Deferred to shut down worker, if started.
|
||||
*/
|
||||
private static ?Deferred $shutdownDeferred = null;
|
||||
/**
|
||||
* IPC server.
|
||||
*/
|
||||
private IpcServer $server;
|
||||
protected IpcServer $server;
|
||||
/**
|
||||
* Callback IPC server.
|
||||
*/
|
||||
private ServerCallback $callback;
|
||||
/**
|
||||
* IPC settings.
|
||||
*/
|
||||
private Ipc $settings;
|
||||
/**
|
||||
* Set IPC path.
|
||||
*
|
||||
* @param string $path IPC path
|
||||
* @param SessionPaths $session Session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIpcPath(string $path): void
|
||||
public function setIpcPath(SessionPaths $session): void
|
||||
{
|
||||
$this->server = new IpcServer($path);
|
||||
self::$shutdownDeferred = new Deferred;
|
||||
$this->server = new IpcServer($session->getIpcPath());
|
||||
$this->callback = new ServerCallback($this->API);
|
||||
$this->callback->setIpcPath($session);
|
||||
}
|
||||
public function start(): bool
|
||||
{
|
||||
return $this instanceof ServerCallback ? parent::start() : $this->callback->start() && parent::start();
|
||||
}
|
||||
/**
|
||||
* Start IPC server in background.
|
||||
*
|
||||
* @param string $session Session path
|
||||
* @param SessionPaths $session Session path
|
||||
*
|
||||
* @return void
|
||||
* @return Promise
|
||||
*/
|
||||
public static function startMe(string $session): void
|
||||
public static function startMe(SessionPaths $session): Promise
|
||||
{
|
||||
$id = Tools::randomInt(2000000000);
|
||||
try {
|
||||
Logger::log("Starting IPC server $session (process)");
|
||||
ProcessRunner::start($session);
|
||||
WebRunner::start($session);
|
||||
return;
|
||||
ProcessRunner::start($session, $id);
|
||||
WebRunner::start($session, $id);
|
||||
return Tools::call(self::monitor($session, $id));
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e);
|
||||
}
|
||||
Logger::log("Starting IPC server $session (web)");
|
||||
WebRunner::start($session);
|
||||
try {
|
||||
Logger::log("Starting IPC server $session (web)");
|
||||
WebRunner::start($session, $id);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e);
|
||||
}
|
||||
return Tools::call(self::monitor($session, $id));
|
||||
}
|
||||
/**
|
||||
* Monitor session.
|
||||
*
|
||||
* @param SessionPaths $session
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private static function monitor(SessionPaths $session, int $id): \Generator
|
||||
{
|
||||
while (true) {
|
||||
$state = yield $session->getIpcState();
|
||||
if ($state && $state->getStartupId() === $id) {
|
||||
if ($e = $state->getException()) {
|
||||
Logger::log("IPC server got exception $e");
|
||||
return $e;
|
||||
}
|
||||
Logger::log("IPC server started successfully!");
|
||||
return true;
|
||||
}
|
||||
yield Tools::sleep(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Wait for shutdown.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public static function waitShutdown(): Promise
|
||||
{
|
||||
return self::$shutdownDeferred->promise();
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
|
@ -87,24 +149,35 @@ class Server extends SignalLoop
|
|||
Tools::callFork($this->clientLoop($socket));
|
||||
}
|
||||
$this->server->close();
|
||||
if (isset($this->callback)) {
|
||||
$this->callback->signal(null);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Client handler loop.
|
||||
*
|
||||
* @param ChannelledSocket $socket Client
|
||||
*
|
||||
* @return \Generator
|
||||
* @return \Generator|Promise
|
||||
*/
|
||||
private function clientLoop(ChannelledSocket $socket): \Generator
|
||||
protected function clientLoop(ChannelledSocket $socket)
|
||||
{
|
||||
$this->API->logger("Accepted IPC client connection!");
|
||||
|
||||
$id = 0;
|
||||
$payload = null;
|
||||
try {
|
||||
while ($payload = yield $socket->receive()) {
|
||||
Tools::callFork($this->clientRequest($socket, $id++, $payload));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} finally {
|
||||
yield $socket->disconnect();
|
||||
if ($payload === self::SHUTDOWN) {
|
||||
$this->signal(null);
|
||||
if (self::$shutdownDeferred) {
|
||||
self::$shutdownDeferred->resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -112,17 +185,30 @@ class Server extends SignalLoop
|
|||
*
|
||||
* @param ChannelledSocket $socket Socket
|
||||
* @param integer $id Request ID
|
||||
* @param array $payload Payload
|
||||
* @param array|Wrapper $payload Payload
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function clientRequest(ChannelledSocket $socket, int $id, $payload): \Generator
|
||||
private function clientRequest(ChannelledSocket $socket, int $id, $payload): \Generator
|
||||
{
|
||||
try {
|
||||
if ($payload[1] instanceof Wrapper) {
|
||||
$wrapper = $payload[1];
|
||||
$payload[1] = $this->callback->unwrap($wrapper);
|
||||
}
|
||||
$result = $this->API->{$payload[0]}(...$payload[1]);
|
||||
$result = $result instanceof \Generator ? yield from $result : yield $result;
|
||||
$result = $result instanceof \Generator
|
||||
? yield from $result
|
||||
: ($result instanceof Promise
|
||||
? yield $result
|
||||
: $result);
|
||||
} catch (\Throwable $e) {
|
||||
$this->API->logger("Got error while calling IPC method: $e", Logger::ERROR);
|
||||
$result = new ExitFailure($e);
|
||||
} finally {
|
||||
if (isset($wrapper)) {
|
||||
yield $wrapper->disconnect();
|
||||
}
|
||||
}
|
||||
try {
|
||||
yield $socket->send([$id, $result]);
|
||||
|
@ -144,4 +230,18 @@ class Server extends SignalLoop
|
|||
{
|
||||
return "IPC server";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set IPC settings.
|
||||
*
|
||||
* @param Ipc $settings IPC settings
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSettings(Ipc $settings): self
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
97
src/danog/MadelineProto/Ipc/ServerCallback.php
Normal file
97
src/danog/MadelineProto/Ipc/ServerCallback.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* IPC callback server.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Ipc\IpcServer;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Loop;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
|
||||
/**
|
||||
* IPC callback server.
|
||||
*/
|
||||
class ServerCallback extends Server
|
||||
{
|
||||
/**
|
||||
* Timeout watcher list, indexed by socket ID.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private $watcherList = [];
|
||||
/**
|
||||
* Timeout watcher list, indexed by socket ID.
|
||||
*
|
||||
* @var array<int, ChannelledSocket>
|
||||
*/
|
||||
private $socketList = [];
|
||||
/**
|
||||
* Counter.
|
||||
*/
|
||||
private int $id = 0;
|
||||
/**
|
||||
* Set IPC path.
|
||||
*
|
||||
* @param SessionPaths $session Session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIpcPath(SessionPaths $session): void
|
||||
{
|
||||
$this->server = new IpcServer($session->getIpcCallbackPath());
|
||||
}
|
||||
/**
|
||||
* Client handler loop.
|
||||
*
|
||||
* @param ChannelledSocket $socket Client
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
protected function clientLoop(ChannelledSocket $socket)
|
||||
{
|
||||
$id = $this->id++;
|
||||
$this->API->logger("Accepted IPC callback connection, assigning ID $id!");
|
||||
$this->socketList[$id] = $socket;
|
||||
$this->watcherList[$id] = Loop::delay(30*1000, function () use ($id) {
|
||||
unset($this->watcherList[$id], $this->socketList[$id]);
|
||||
});
|
||||
|
||||
return $socket->send($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwrap value.
|
||||
*
|
||||
* @param Wrapper $wrapper
|
||||
* @return mixed
|
||||
*/
|
||||
protected function unwrap(Wrapper $wrapper)
|
||||
{
|
||||
$id = $wrapper->getRemoteId();
|
||||
if (!isset($this->socketList[$id])) {
|
||||
throw new Exception("IPC timeout, could not find callback socket!");
|
||||
}
|
||||
$socket = $this->socketList[$id];
|
||||
Loop::cancel($this->watcherList[$id]);
|
||||
unset($this->watcherList[$id], $this->socketList[$id]);
|
||||
return $wrapper->unwrap($socket);
|
||||
}
|
||||
}
|
214
src/danog/MadelineProto/Ipc/Wrapper.php
Normal file
214
src/danog/MadelineProto/Ipc/Wrapper.php
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\ByteStream\InputStream as ByteStreamInputStream;
|
||||
use Amp\ByteStream\OutputStream as ByteStreamOutputStream;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Parallel\Sync\ExitFailure;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Ipc\Wrapper\InputStream;
|
||||
use danog\MadelineProto\Ipc\Wrapper\Obj;
|
||||
use danog\MadelineProto\Ipc\Wrapper\OutputStream;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* Callback payload wrapper.
|
||||
*/
|
||||
class Wrapper extends ClientAbstract
|
||||
{
|
||||
/**
|
||||
* Payload data.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $data;
|
||||
/**
|
||||
* Callbacks.
|
||||
*
|
||||
* @var callable[]
|
||||
*/
|
||||
private array $callbacks = [];
|
||||
/**
|
||||
* Callbacks IDs.
|
||||
*
|
||||
* @var (int|array{0: class-string<Obj>, array<string, int>})[]
|
||||
*/
|
||||
private array $callbackIds = [];
|
||||
/**
|
||||
* Callback ID.
|
||||
*/
|
||||
private int $id = 0;
|
||||
/**
|
||||
* Remote socket ID.
|
||||
*/
|
||||
private int $remoteId = 0;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param mixed $data Payload data
|
||||
* @param SessionPaths $ipc IPC URI
|
||||
*
|
||||
* @return \Generator
|
||||
* @psalm-return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, Wrapper>
|
||||
*/
|
||||
public static function create(&$data, SessionPaths $session, Logger $logger): \Generator
|
||||
{
|
||||
$instance = new self;
|
||||
$instance->data = &$data;
|
||||
$instance->logger = $logger;
|
||||
$instance->run = false;
|
||||
|
||||
$logger->logger("Connecting to callback IPC server...");
|
||||
$instance->server = yield connect($session->getIpcCallbackPath());
|
||||
$logger->logger("Connected to callback IPC server!");
|
||||
|
||||
$instance->remoteId = yield $instance->server->receive();
|
||||
$logger->logger("Got ID {$instance->remoteId} from callback IPC server!");
|
||||
|
||||
Tools::callFork($instance->receiverLoop());
|
||||
return $instance;
|
||||
}
|
||||
/**
|
||||
* Serialization function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['data', 'callbackIds', 'remoteId'];
|
||||
}
|
||||
/**
|
||||
* Wrap a certain callback object.
|
||||
*
|
||||
* @param object|callable $callback Callback to wrap
|
||||
* @param bool $wrapObjects Whether to wrap object methods, too
|
||||
*
|
||||
* @param-out int $callback Callback ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wrap(&$callback, bool $wrapObjects = true): void
|
||||
{
|
||||
if (\is_object($callback) && $wrapObjects) {
|
||||
$ids = [];
|
||||
foreach (\get_class_methods($callback) as $method) {
|
||||
$id = $this->id++;
|
||||
$this->callbacks[$id] = [$callback, $method];
|
||||
$ids[$method] = $id;
|
||||
}
|
||||
$class = Obj::class;
|
||||
if ($callback instanceof ByteStreamInputStream) {
|
||||
$class = InputStream::class;
|
||||
} elseif ($callback instanceof ByteStreamOutputStream) {
|
||||
$class = OutputStream::class;
|
||||
}
|
||||
if ($class !== Obj::class && \method_exists($callback, 'seek')) {
|
||||
$class = "Seekable$class";
|
||||
}
|
||||
$callback = [$class, $ids]; // Will be re-filled later
|
||||
$this->callbackIds[] = &$callback;
|
||||
} elseif (\is_callable($callback)) {
|
||||
$id = $this->id++;
|
||||
$this->callbacks[$id] = self::copy($callback);
|
||||
$callback = $id;
|
||||
$this->callbackIds[] = &$callback;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get copy of data.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return mixed
|
||||
*/
|
||||
private static function copy($data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* Receiver loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function receiverLoop(): \Generator
|
||||
{
|
||||
$id = 0;
|
||||
$payload = null;
|
||||
try {
|
||||
while ($payload = yield $this->server->receive()) {
|
||||
Tools::callFork($this->clientRequest($id++, $payload));
|
||||
}
|
||||
} finally {
|
||||
yield $this->server->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client request.
|
||||
*
|
||||
* @param integer $id Request ID
|
||||
* @param array $payload Payload
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function clientRequest(int $id, $payload): \Generator
|
||||
{
|
||||
try {
|
||||
$result = $this->callbacks[$payload[0]](...$payload[1]);
|
||||
$result = $result instanceof \Generator ? yield from $result : yield $result;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while calling reverse IPC method: $e", Logger::ERROR);
|
||||
$result = new ExitFailure($e);
|
||||
}
|
||||
try {
|
||||
yield $this->server->send([$id, $result]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while trying to send result of reverse method: $e", Logger::ERROR);
|
||||
try {
|
||||
yield $this->server->send([$id, new ExitFailure($e)]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while trying to send error of error of reverse method: $e", Logger::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get remote socket ID.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRemoteId(): int
|
||||
{
|
||||
return $this->remoteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set socket and unwrap data.
|
||||
*
|
||||
* @param ChannelledSocket $server Socket.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function unwrap(ChannelledSocket $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
Tools::callFork($this->loopInternal());
|
||||
|
||||
foreach ($this->callbackIds as &$id) {
|
||||
if (\is_int($id)) {
|
||||
$id = fn (...$args): \Generator => $this->__call($id, $args);
|
||||
} else {
|
||||
[$class, $ids] = $id;
|
||||
$id = new $class($this, $ids);
|
||||
}
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
}
|
33
src/danog/MadelineProto/Ipc/Wrapper/FileCallback.php
Normal file
33
src/danog/MadelineProto/Ipc/Wrapper/FileCallback.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
|
||||
class FileCallback extends Obj implements FileCallbackInterface
|
||||
{
|
||||
/**
|
||||
* Get file.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->__call('getFile');
|
||||
}
|
||||
/**
|
||||
* Invoke callback.
|
||||
*
|
||||
* @param float $percent Percent
|
||||
* @param float $speed Speed in mbps
|
||||
* @param float $time Time
|
||||
*
|
||||
* @psalm-suppress MethodSignatureMismatch
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke($percent, $speed, $time)
|
||||
{
|
||||
return $this->__call('__invoke', [$percent, $speed, $time]);
|
||||
}
|
||||
}
|
25
src/danog/MadelineProto/Ipc/Wrapper/InputStream.php
Normal file
25
src/danog/MadelineProto/Ipc/Wrapper/InputStream.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\ByteStream\InputStream as AmpInputStream;
|
||||
use Amp\ByteStream\PendingReadError;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
class InputStream extends Obj implements AmpInputStream
|
||||
{
|
||||
/**
|
||||
* Reads data from the stream.
|
||||
*
|
||||
* @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
|
||||
*
|
||||
* @psalm-return Promise<string|null>
|
||||
*
|
||||
* @throws PendingReadError Thrown if another read operation is still pending.
|
||||
*/
|
||||
public function read(): Promise
|
||||
{
|
||||
return Tools::call($this->__call('read'));
|
||||
}
|
||||
}
|
45
src/danog/MadelineProto/Ipc/Wrapper/Obj.php
Normal file
45
src/danog/MadelineProto/Ipc/Wrapper/Obj.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
/**
|
||||
* Generic callback wrapper object.
|
||||
*/
|
||||
class Obj
|
||||
{
|
||||
/**
|
||||
* Method list.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $methods = [];
|
||||
/**
|
||||
* Wrapper.
|
||||
*/
|
||||
private Wrapper $wrapper;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Wrapper $wrapper
|
||||
* @param array $methods
|
||||
*/
|
||||
public function __construct(Wrapper $wrapper, array $methods)
|
||||
{
|
||||
$this->wrapper = $wrapper;
|
||||
$this->methods = $methods;
|
||||
}
|
||||
/**
|
||||
* Call method.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return \Generator<mixed, mixed, mixed, mixed>
|
||||
*/
|
||||
public function __call(string $name, array $arguments = []): \Generator
|
||||
{
|
||||
return $this->wrapper->__call($this->methods[$name], $arguments);
|
||||
}
|
||||
}
|
44
src/danog/MadelineProto/Ipc/Wrapper/OutputStream.php
Normal file
44
src/danog/MadelineProto/Ipc/Wrapper/OutputStream.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\ByteStream\ClosedException;
|
||||
use Amp\ByteStream\OutputStream as AmpOutputStream;
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
class OutputStream extends Obj implements AmpOutputStream
|
||||
{
|
||||
/**
|
||||
* Writes data to the stream.
|
||||
*
|
||||
* @param string $data Bytes to write.
|
||||
*
|
||||
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||
*
|
||||
* @throws ClosedException If the stream has already been closed.
|
||||
* @throws StreamException If writing to the stream fails.
|
||||
*/
|
||||
public function write(string $data): Promise
|
||||
{
|
||||
return Tools::call($this->__call('write', [$data]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the stream as no longer writable. Optionally writes a final data chunk before. Note that this is not the
|
||||
* same as forcefully closing the stream. This method waits for all pending writes to complete before closing the
|
||||
* stream. Socket streams implementing this interface should only close the writable side of the stream.
|
||||
*
|
||||
* @param string $finalData Bytes to write.
|
||||
*
|
||||
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||
*
|
||||
* @throws ClosedException If the stream has already been closed.
|
||||
* @throws StreamException If writing to the stream fails.
|
||||
*/
|
||||
public function end(string $finalData = ""): Promise
|
||||
{
|
||||
return Tools::call($this->__call('write', [$finalData]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
class SeekableInputStream extends InputStream
|
||||
{
|
||||
use SeekableTrait;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
class SeekableOutputStream extends OutputStream
|
||||
{
|
||||
use SeekableTrait;
|
||||
}
|
27
src/danog/MadelineProto/Ipc/Wrapper/SeekableTrait.php
Normal file
27
src/danog/MadelineProto/Ipc/Wrapper/SeekableTrait.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
trait SeekableTrait
|
||||
{
|
||||
/**
|
||||
* Set the handle's internal pointer position.
|
||||
*
|
||||
* $whence values:
|
||||
*
|
||||
* SEEK_SET - Set position equal to offset bytes.
|
||||
* SEEK_CUR - Set position to current location plus offset.
|
||||
* SEEK_END - Set position to end-of-file plus offset.
|
||||
*
|
||||
* @param int $position
|
||||
* @param int $whence
|
||||
* @return \Amp\Promise<int> New offset position.
|
||||
*/
|
||||
public function seek(int $position, int $whence = \SEEK_SET): Promise
|
||||
{
|
||||
return Tools::call($this->__call('seek', [$position, $whence]));
|
||||
}
|
||||
}
|
18
src/danog/MadelineProto/Ipc/Wrapper/WrapMethodTrait.php
Normal file
18
src/danog/MadelineProto/Ipc/Wrapper/WrapMethodTrait.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
trait WrapMethodTrait
|
||||
{
|
||||
abstract public function __call($name, $args);
|
||||
public function wrap(...$args): \Generator
|
||||
{
|
||||
$new = yield from Wrapper::create($args, $this->session->getIpcCallbackPath(), $this->logger);
|
||||
foreach ($args as &$arg) {
|
||||
$new->wrap($arg);
|
||||
}
|
||||
return $this->__call(__FUNCTION__, $new);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
@ -23,27 +23,13 @@ class Lang
|
|||
'it' =>
|
||||
[
|
||||
'phpseclib_fork' => 'Per favore installa questo fork di phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istanziamento dei DataCenter...',
|
||||
'load_rsa' => 'Caricamento delle chiavi RSA...',
|
||||
'TL_translation' => 'Translazione degli schemi TL...',
|
||||
'dh_prime_check_0' => 'Esecuzione dei check dh_prime (0/3)...',
|
||||
'nearest_dc' => 'Siamo in %s, il DC più vicino è %d.',
|
||||
'serialization_ofd' => 'La serializzazione non è aggiornata, reistanziamento dell\'oggetto in corso!',
|
||||
'getupdates_deserialization' => 'Ottenimento aggiornamenti dopo deserializzazione...',
|
||||
'shutdown_reader_pool' => 'Chiusura pool di lettura, %d thread rimasti',
|
||||
'threading_on' => 'IL THREADING È ABILITATO',
|
||||
'socket_reader' => 'Lettore socket su DC %s: ',
|
||||
'socket_status_1' => 'CREAZIONE',
|
||||
'socket_status_2' => 'INVIO',
|
||||
'socket_status_3' => 'ATTESA',
|
||||
'socket_status_4' => 'PRONTO',
|
||||
'socket_status_5' => 'AVVIATO',
|
||||
'api_not_set' => 'Devi specificare una chiave ed un ID API, ottienili su https://my.telegram.org',
|
||||
'session_corrupted' => 'La sessione si è corrotta!',
|
||||
'resetSession_seqno' => 'Resettando ID sessione e numero di sequenza sul DC %s...',
|
||||
'gen_perm_auth_key' => 'Generando chiave di autorizzazione permanente per il DC %s...',
|
||||
'gen_temp_auth_key' => 'Generando chiave di autorizzazione temporanea per il DC %s...',
|
||||
'copy_auth_dcs' => 'Copiando autorizzazione dal DC %s al DC %s...',
|
||||
'write_client_info' => 'Scrittura info sul client (eseguendo nel contempo il metodo %s)...',
|
||||
'config_updated' => 'La configurazione è stata aggiornata!',
|
||||
'length_not_4' => 'La lunghezza non è uguale a 4',
|
||||
|
@ -57,29 +43,12 @@ class Lang
|
|||
'encode_double_error' => 'Non sono riuscito a codificare il numero a virgola mobile fornito',
|
||||
'file_not_exist' => 'Il file specificato non esiste',
|
||||
'deserialization_error' => 'C\'è stato un errore durante la deserializzazione',
|
||||
'rsa_init' => 'Istanziamento di \\tgseclib\\Crypt\\RSA in corso...',
|
||||
'loading_key' => 'Caricamento della chiave in corso...',
|
||||
'computing_fingerprint' => 'Calcolo del fingerprint in corso...',
|
||||
'rsa_encrypting' => 'Criptando con chiave RSA...',
|
||||
'rpc_tg_error' => 'Telegram ha ritornato un errore RPC: %s (%s), causato da %s:%s%sTL trace:',
|
||||
'v_error' => '506572206661766f726520616767696f726e612071756573746120696e7374616c6c617a696f6e65206469204d6164656c696e6550726f746f20636f6e206769742070756c6c206520636f6d706f73657220757064617465',
|
||||
'v_tgerror' => '506572206661766f726520616767696f726e61207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'Nessuna modalità di logging è stata specificata!',
|
||||
'constructor_function_uncalled' => 'Il metodo costruttore non è stato ancora chiamato! Per favore chiama il metodo costruttore prima di usare questo metodo.',
|
||||
'proxy_class_invalid' => 'È stata specificata una classe proxy errata!',
|
||||
'socket_con_error' => 'Connessione fallita.',
|
||||
'protocol_not_implemented' => 'Questo protocollo non è stato ancora implementato.',
|
||||
'protocol_invalid' => 'È stato fornito un protocollo non valido',
|
||||
'nothing_in_socket' => 'Non c\'è niente nel socket!',
|
||||
'wrong_length_read' => 'ATTENZIONE: Non sono stati letti abbastanza byte (dovevo leggere %s, ho letto %s)!',
|
||||
'no_data_in_socket' => 'Non ci sono dati nel socket!',
|
||||
'dc_con_start' => 'Connessione al DC %s in corso...',
|
||||
'dc_con_stop' => 'Disconnessione dal DC %s in corso...',
|
||||
'dc_con_test_start' => 'Connessione al DC %s (server %s, %s, %s)...',
|
||||
'script_not_exist' => 'Lo script fornito non esiste',
|
||||
'apifactory_start' => 'Sto avviando la fabbrica di API...',
|
||||
'madelineproto_ready' => 'MadelineProto è pronto!',
|
||||
'logout_error' => 'C\'è stato un errore durante il logout!',
|
||||
'logout_ok' => 'Il logout è stato eseguito correttamente!',
|
||||
'already_loggedIn' => 'Questa istanza di MadelineProto è già loggata, prima faccio il logout...',
|
||||
'login_ok' => 'Il login è stato eseguito correttamente!',
|
||||
|
@ -96,10 +65,8 @@ class Lang
|
|||
'signing_up' => 'Mi sto registrando su telegram come utente normale...',
|
||||
'signup_ok' => 'Mi sono registrato su Telegram!',
|
||||
'2fa_uncalled' => 'Non sto aspettando la password, chiama prima le funzioni phoneLogin e completePhoneLogin!',
|
||||
'getting_dialogs' => 'Sto ottenendo la lista delle chat...',
|
||||
'libtgvoip_required' => 'È necessario installare l\'estensione php-libtgvoip per accettare e gestire chiamate vocali, vistate https://docs.madelineproto.xyz per più info.',
|
||||
'peer_not_in_db' => 'Questo utente/gruppo/canale non è presente nel database interno MadelineProto',
|
||||
'calling_user' => 'Sto chiamando %s...',
|
||||
'generating_a' => 'Sto generando a...',
|
||||
'generating_g_a' => 'Sto generando g_a...',
|
||||
'call_error_1' => 'Impossibile trovare ed accettare la chiamata %s',
|
||||
|
@ -135,62 +102,138 @@ class Lang
|
|||
'sec_peer_not_in_db' => 'La chat segreta non è presente nel database interno MadelineProto',
|
||||
'stream_handle_invalid' => 'Il valore fornito non è uno stream',
|
||||
'length_too_big' => 'Il valore fornito è troppo lungo',
|
||||
'deserialize_not_str' => 'Il valore generato non è una stringa',
|
||||
'type_extract_error_id' => 'Non sono riuscito ad estrarre il tipo %s con ID %s',
|
||||
'vector_invalid' => 'ID vettore non valido: ',
|
||||
'constructor_not_found' => 'Costruttore non trovato per tipo: ',
|
||||
'rand_bytes_too_small' => 'random_bytes è troppo corto!',
|
||||
'botapi_conversion_error' => 'NOn sono risucito a convertire %s in un oggetto bot API',
|
||||
'botapi_conversion_error' => 'Non sono risucito a convertire %s in un oggetto bot API',
|
||||
'non_text_conversion' => 'Non posso ancora convertire messaggi media',
|
||||
'last_byte_invalid' => 'L\'ultimo byte non è valido',
|
||||
'file_type_invalid' => 'È stato fornito un tipo file errato',
|
||||
'recreate_temp_auth_key' => 'Sono stato costretto a rigenerare la chiave di autorizzazione temporanea',
|
||||
'resetting_auth_key' => 'ATTENZIONE: Sto resettando la chiave temporanea...',
|
||||
'shutting_down_reader_pool' => 'Chisura pool di lettura',
|
||||
'shutting_down_handler_pool' => 'Chiusura pool di gestione per DC %s, %d thread rimasti',
|
||||
'secret_chat_skipping' => 'Non ho la chat segreta %s nel database, ignorando messaggio',
|
||||
'fingerprint_mismatch' => 'Fingerprint della chiave non valido',
|
||||
'msg_data_length_too_big' => 'message_data_length è troppo grande',
|
||||
'length_not_divisible_16' => 'La lunghezza dei dati decifrati non è divisibile per 16',
|
||||
'msg_key_mismatch' => 'msg_key non valido',
|
||||
'rand_bytes_too_short' => 'random_bytes è troppo corto!',
|
||||
'resending_unsupported' => 'IL riinvio di messaggi non è ancora supportato',
|
||||
'unrecognized_dec_msg' => 'È stato ricevuto un messaggio decifrato sconosciuto: ',
|
||||
'serializing_madelineproto' => 'Sto serializzando MadelineProto...',
|
||||
'req_pq' => 'Sto richiedendo pq...',
|
||||
'done' => 'Fatto!',
|
||||
'cdn_reupload' => 'Il file non è disponibile sul nostro CDN, richiedo la copia!',
|
||||
'stored_on_cdn' => 'Il file è scaricabile tramite CDN!',
|
||||
'apiAppInstructionsAuto0' => 'Inserisci il nome dell\'app, può essere qualsiasi cosa: ',
|
||||
'apiAppInstructionsAuto1' => 'Inserisci il nome ridotto dell\'app, caratteri alfanumerici: ',
|
||||
'apiAppInstructionsAuto2' => 'Inserisci il sito internet dell\'app, oppure t.me/username: ',
|
||||
'apiAppInstructionsAuto3' => 'Inserisci la piattaforma dell\'app: ',
|
||||
'apiAppInstructionsAuto4' => 'Descrivi la tua app: ',
|
||||
'apiAppInstructionsManual0' => 'titolo dell\'app, può essere qualsiasi cosa',
|
||||
'apiAppInstructionsManual1' => 'il nome ridotto dell\'app, caratteri alfanumerici: ',
|
||||
'apiAppInstructionsManual2' => 'L\'indirizzo del tuo sito, oppure t.me/username',
|
||||
'apiAppInstructionsManual3' => 'Qualsiasi',
|
||||
'apiAppInstructionsManual4' => 'Descrivi la tua app',
|
||||
'apiAutoPrompt0' => 'Inserisci un numero di telefono che è già registrato su Telegram: ',
|
||||
'apiAutoPrompt1' => 'Inserisci il codice di verifica che hai ricevuto su Telegram: ',
|
||||
'apiChooseManualAutoTip' => 'Nota che puoi anche fornire i parametri API direttamente nelle impostazioni: %s',
|
||||
'apiChoosePrompt' => 'La tua scelta (m/a): ',
|
||||
'apiError' => 'ERRORE: %s. Prova ancora.',
|
||||
'apiManualInstructions0' => 'Effettua il login su my.telegram.org',
|
||||
'apiManualInstructions1' => 'Vai su API development tools',
|
||||
'apiManualInstructions2' => 'Clicca su create application',
|
||||
'apiManualPrompt0' => 'Inserisci il tuo API ID: ',
|
||||
'apiManualPrompt1' => 'Inserisci il tuo API hash: ',
|
||||
'apiParamsError' => 'Non hai fornito tutti i parametri richiesti!',
|
||||
'loginBot' => 'Inserisci il tuo bot token: ',
|
||||
'loginChoosePrompt' => 'Vuoi effettuare il login come utente o come bot (u/b)? ',
|
||||
'loginNoCode' => 'Non hai fornito un codice di verifica!',
|
||||
'loginNoName' => 'Non hai fornito un nome!',
|
||||
'loginNoPass' => 'Non hai fornito la password!',
|
||||
'loginUser' => 'Inserisci il tuo numero di telefono: ',
|
||||
'loginUserPass' => 'Inserisci la tua password (suggerimento %s): ',
|
||||
'loginUserPassHint' => 'Suggerimento: %s',
|
||||
'loginUserPassWeb' => 'Inserisci la tua password: ',
|
||||
'loginUserCode' => 'Inserisci il codice di verifica: ',
|
||||
'signupFirstName' => 'Inserisci il tuo nome: ',
|
||||
'signupFirstNameWeb' => 'Nome',
|
||||
'signupLastName' => 'Inserisci il tuo cognome: ',
|
||||
'signupLastNameWeb' => 'Cognome',
|
||||
'signupWeb' => 'Registrazione',
|
||||
'go' => 'Vai',
|
||||
'loginChoosePromptWeb' => 'Vuoi effettuare il login come utente o come bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'Utente',
|
||||
'loginBotTokenWeb' => 'Token del bot',
|
||||
'loginUserPhoneCodeWeb' => 'Codice di verifica',
|
||||
'loginUserPhoneWeb' => 'Numero di telefono',
|
||||
'apiAutoWeb' => 'Inserisci un numero di telefono <b>già registrato su Telegram</b> per ottenere l'API ID',
|
||||
'apiChooseManualAuto' => 'You did not define a valid API ID/API hash. Do you want to define it now manually, or automatically? (m/a)',
|
||||
'apiManualWeb' => 'Inserisci il tuo API ID e API hash',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Altro (specificare nella descrizione)',
|
||||
'apiAppWeb' => 'Inserire informazioni API',
|
||||
'apiChooseAutomaticallyWeb' => 'Automaticamente',
|
||||
'apiChooseManualAutoTipWeb' => 'Nota che puoi anche fornire i parametri API direttamente nelle <a target="_blank" href="%s">impostazioni</a>.',
|
||||
'apiChooseManualAutoWeb' => 'Vuoi configurare il tuo API ID/hash manualmente o automaticamente?',
|
||||
'apiChooseManuallyWeb' => 'Manualmente',
|
||||
],
|
||||
'en' =>
|
||||
[
|
||||
'req_pq' => 'Requesting pq...',
|
||||
'go' => 'Go',
|
||||
'apiChooseManualAuto' => 'Do you want to enter the API id and the API hash manually or automatically? (m/a)',
|
||||
'apiChooseManualAutoWeb' => 'Do you want to enter the API id and the API hash manually or automatically?',
|
||||
'apiChooseManualAutoTip' => 'Note that you can also provide them directly in the code using the settings: %s',
|
||||
'apiChooseManualAutoTipWeb' => 'Note that you can also provide them directly in the code using the <a target="_blank" href="%s">settings</a>.',
|
||||
'apiChoosePrompt' => 'Your choice (m/a): ',
|
||||
'apiChooseAutomaticallyWeb' => 'Automatically',
|
||||
'apiChooseManuallyWeb' => 'Manually',
|
||||
'apiManualInstructions0' => 'Login to my.telegram.org',
|
||||
'apiManualInstructions1' => 'Go to API development tools',
|
||||
'apiManualInstructions2' => 'Click on create application',
|
||||
'apiAppInstructionsManual0' => 'your app\'s name, can be anything',
|
||||
'apiAppInstructionsManual1' => 'your app\'s short name, alphanumeric',
|
||||
'apiAppInstructionsManual2' => 'your app/website\'s URL, or t.me/yourusername',
|
||||
'apiAppInstructionsManual3' => 'anything',
|
||||
'apiAppInstructionsManual4' => 'Describe your app here',
|
||||
'apiManualWeb' => 'Enter your API ID and API hash',
|
||||
'apiManualPrompt0' => 'Enter your API ID: ',
|
||||
'apiManualPrompt1' => 'Enter your API hash: ',
|
||||
'apiAutoWeb' => 'Enter a phone number that is <b>already registered</b> on telegram to get the API ID',
|
||||
'apiAutoPrompt0' => 'Enter a phone number that is already registered on Telegram: ',
|
||||
'apiAutoPrompt1' => 'Enter the verification code you received in Telegram: ',
|
||||
'apiAppWeb' => 'Enter API information',
|
||||
'apiAppInstructionsAuto0' => 'Enter the app\'s name, can be anything: ',
|
||||
'apiAppInstructionsAuto1' => 'Enter the app\'s short name, alphanumeric: ',
|
||||
'apiAppInstructionsAuto2' => 'Enter the app/website\'s URL, or t.me/yourusername: ',
|
||||
'apiAppInstructionsAuto3' => 'Enter the app platform: ',
|
||||
'apiAppInstructionsAuto4' => 'Describe your app: ',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Other (specify in description)',
|
||||
'apiParamsError' => 'You didn\'t provide all of the required parameters!',
|
||||
'apiError' => 'ERROR: %s. Try again.',
|
||||
'loginChoosePrompt' => 'Do you want to login as user or bot (u/b)? ',
|
||||
'loginChoosePromptWeb' => 'Do you want to login as user or bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'User',
|
||||
'loginBot' => 'Enter your bot token: ',
|
||||
'loginUser' => 'Enter your phone number: ',
|
||||
'loginUserCode' => 'Enter the code: ',
|
||||
'loginUserPass' => 'Enter your password (hint %s): ',
|
||||
'loginUserPassWeb' => 'Enter your password: ',
|
||||
'loginUserPassHint' => 'Hint: %s',
|
||||
'signupFirstName' => 'Enter your first name: ',
|
||||
'signupLastName' => 'Enter your last name (can be empty): ',
|
||||
'signupWeb' => 'Sign up please',
|
||||
'signupFirstNameWeb' => 'First name',
|
||||
'signupLastNameWeb' => 'Last name',
|
||||
'loginNoCode' => 'You didn\'t provide a phone code!',
|
||||
'loginNoPass' => 'You didn\'t provide the password!',
|
||||
'loginNoName' => 'You didn\'t provide the first name!',
|
||||
'loginBotTokenWeb' => 'Bot token',
|
||||
'loginUserPhoneWeb' => 'Phone number',
|
||||
'loginUserPhoneCodeWeb' => 'Code',
|
||||
'done' => 'Done!',
|
||||
'cdn_reupload' => 'File is not stored on CDN, requesting reupload!',
|
||||
'stored_on_cdn' => 'File is stored on CDN!',
|
||||
'serializing_madelineproto' => 'Serializing MadelineProto...',
|
||||
'phpseclib_fork' => 'Please install this fork of phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istantiating DataCenter...',
|
||||
'load_rsa' => 'Loading RSA keys...',
|
||||
'TL_translation' => 'Translating TL schemas...',
|
||||
'dh_prime_check_0' => 'Executing dh_prime checks (0/3)...',
|
||||
'nearest_dc' => 'We\'re in %s, nearest DC is %d.',
|
||||
'serialization_ofd' => 'Serialization is out of date, reconstructing object!',
|
||||
'getupdates_deserialization' => 'Getting updates after deserialization...',
|
||||
'shutdown_reader_pool' => 'Shutting down reader pool, %d jobs left',
|
||||
'threading_on' => 'THREADING IS ENABLED',
|
||||
'socket_reader' => 'Socket reader on DC %s: ',
|
||||
'socket_status_1' => 'CREATING',
|
||||
'socket_status_2' => 'SUBMITTING',
|
||||
'socket_status_3' => 'WAITING',
|
||||
'socket_status_4' => 'READY',
|
||||
'socket_status_5' => 'WORKING',
|
||||
'api_not_set' => 'You must provide an api key and an api id, get your own @ my.telegram.org',
|
||||
'session_corrupted' => 'The session is corrupted!',
|
||||
'resetSession_seqno' => 'Resetting session id and seq_no in DC %s...',
|
||||
'gen_perm_auth_key' => 'Generating permanent authorization key for DC %s...',
|
||||
'gen_temp_auth_key' => 'Generating temporary authorization key for DC %s...',
|
||||
'copy_auth_dcs' => 'Copying authorization from DC %s to DC %s...',
|
||||
'write_client_info' => 'Writing client info (also executing %s)...',
|
||||
'config_updated' => 'Updated config!',
|
||||
'length_not_4' => 'Length is not equal to 4',
|
||||
|
@ -204,29 +247,12 @@ class Lang
|
|||
'encode_double_error' => 'Could not properly encode double',
|
||||
'file_not_exist' => 'File does not exist',
|
||||
'deserialization_error' => 'An error occurred on deserialization',
|
||||
'rsa_init' => 'Istantiating \\tgseclib\\Crypt\\RSA...',
|
||||
'loading_key' => 'Loading key...',
|
||||
'computing_fingerprint' => 'Computing fingerprint...',
|
||||
'rsa_encrypting' => 'Encrypting with rsa key...',
|
||||
'rpc_tg_error' => 'Telegram returned an RPC error: %s (%s), caused by %s:%s%sTL trace:',
|
||||
'v_error' => '506c656173652075706461746520746f20746865206c61746573742076657273696f6e206f66204d6164656c696e6550726f746f2e',
|
||||
'v_tgerror' => '506c6561736520757064617465207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'No mode was specified!',
|
||||
'constructor_function_uncalled' => 'The constructor function wasn\'t called! Please call the constructor function before using this method.',
|
||||
'proxy_class_invalid' => 'Invalid proxy class provided!',
|
||||
'socket_con_error' => 'Connection: couldn\'t connect to socket.',
|
||||
'protocol_not_implemented' => 'Connection: This protocol isn\'t implemented yet.',
|
||||
'protocol_invalid' => 'Connection: invalid protocol specified.',
|
||||
'nothing_in_socket' => 'Nothing in the socket!',
|
||||
'wrong_length_read' => 'WARNING: Wrong length was read (should\'ve read %s, read %s)!',
|
||||
'no_data_in_socket' => 'No data in the socket!',
|
||||
'dc_con_start' => 'Connecting to DC %s...',
|
||||
'dc_con_stop' => 'Disconnecting from DC %s...',
|
||||
'dc_con_test_start' => 'Connecting to DC %s (%s server, %s, %s)...',
|
||||
'script_not_exist' => 'Provided script does not exist',
|
||||
'apifactory_start' => 'Running APIFactory...',
|
||||
'madelineproto_ready' => 'MadelineProto is ready!',
|
||||
'logout_error' => 'An error occurred while logging out!',
|
||||
'logout_ok' => 'Logged out successfully!',
|
||||
'already_loggedIn' => 'This instance of MadelineProto is already logged in. Logging out first...',
|
||||
'login_ok' => 'Logged in successfully!',
|
||||
|
@ -243,10 +269,8 @@ class Lang
|
|||
'signing_up' => 'Signing up as a normal user...',
|
||||
'signup_ok' => 'Signed up in successfully!',
|
||||
'2fa_uncalled' => 'I\'m not waiting for the password! Please call the phoneLogin and the completePhoneLogin methods first!',
|
||||
'getting_dialogs' => 'Getting dialogs...',
|
||||
'libtgvoip_required' => 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.',
|
||||
'peer_not_in_db' => 'This peer is not present in the internal peer database',
|
||||
'calling_user' => 'Calling %s...',
|
||||
'generating_a' => 'Generating a...',
|
||||
'generating_g_a' => 'Generating g_a...',
|
||||
'call_error_1' => 'Could not find and accept call %s',
|
||||
|
@ -276,65 +300,91 @@ class Lang
|
|||
'long_not_64' => 'Given value is not 64 bytes long',
|
||||
'array_invalid' => 'You didn\'t provide a valid array',
|
||||
'predicate_not_set' => 'Predicate (value under _) was not set!',
|
||||
'type_extract_error' => 'Could not extract type "%s"',
|
||||
'type_extract_error' => 'Could not extract type "%s", you should update MadelineProto!',
|
||||
'method_not_found' => 'Could not find method: ',
|
||||
'params_missing' => 'Missing required parameter',
|
||||
'sec_peer_not_in_db' => 'This secret peer is not present in the internal peer database',
|
||||
'stream_handle_invalid' => 'An invalid stream handle was provided.',
|
||||
'length_too_big' => 'Length is too big',
|
||||
'deserialize_not_str' => 'Deserialize: Generated value isn\'t a string',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s',
|
||||
'vector_invalid' => 'Invalid vector constructor: ',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s, you should update MadelineProto!',
|
||||
'constructor_not_found' => 'Constructor not found for type: ',
|
||||
'rand_bytes_too_small' => 'Random_bytes is too small!',
|
||||
'botapi_conversion_error' => 'Can\'t convert %s to a bot API object',
|
||||
'non_text_conversion' => 'Can\'t convert non text messages yet!',
|
||||
'last_byte_invalid' => 'Invalid last byte',
|
||||
'file_type_invalid' => 'Invalid file type detected (%s)',
|
||||
'recreate_temp_auth_key' => 'I had to recreate the temporary authorization key',
|
||||
'resetting_auth_key' => 'WARNING: Resetting auth key...',
|
||||
'shutting_down_reader_pool' => 'Shutting down reader pool ',
|
||||
'shutting_down_handler_pool' => 'Shutting down handler pool for dc %s, %d jobs left',
|
||||
'secret_chat_skipping' => 'I do not have the secret chat %s in the database, skipping message...',
|
||||
'fingerprint_mismatch' => 'Key fingerprint mismatch',
|
||||
'msg_data_length_too_big' => 'Message_data_length is too big',
|
||||
'length_not_divisible_16' => 'Length of decrypted data is not divisible by 16',
|
||||
'msg_key_mismatch' => 'Msg_key mismatch',
|
||||
'rand_bytes_too_short' => 'Random_bytes is too short!',
|
||||
'resending_unsupported' => 'Resending of messages is not yet supported',
|
||||
'unrecognized_dec_msg' => 'Unrecognized decrypted message received: ',
|
||||
],
|
||||
];
|
||||
|
||||
// THIS WILL BE OVERWRITTEN BY $lang["en"]
|
||||
public static $current_lang = [
|
||||
'req_pq' => 'Requesting pq...',
|
||||
'go' => 'Go',
|
||||
'apiChooseManualAuto' => 'Do you want to enter the API id and the API hash manually or automatically? (m/a)',
|
||||
'apiChooseManualAutoWeb' => 'Do you want to enter the API id and the API hash manually or automatically?',
|
||||
'apiChooseManualAutoTip' => 'Note that you can also provide them directly in the code using the settings: %s',
|
||||
'apiChooseManualAutoTipWeb' => 'Note that you can also provide them directly in the code using the <a href="%s">settings</a>.',
|
||||
'apiChoosePrompt' => 'Your choice (m/a): ',
|
||||
'apiChooseAutomaticallyWeb' => 'Automatically',
|
||||
'apiChooseManuallyWeb' => 'Manually',
|
||||
'apiManualInstructions0' => 'Login to my.telegram.org',
|
||||
'apiManualInstructions1' => 'Go to API development tools',
|
||||
'apiManualInstructions2' => 'Click on create application',
|
||||
'apiAppInstructionsManual0' => 'your app\'s name, can be anything',
|
||||
'apiAppInstructionsManual1' => 'your app\'s short name, alphanumeric',
|
||||
'apiAppInstructionsManual2' => 'your app/website\'s URL, or t.me/yourusername',
|
||||
'apiAppInstructionsManual3' => 'anything',
|
||||
'apiAppInstructionsManual4' => 'Describe your app here',
|
||||
'apiManualWeb' => 'Enter your API ID and API hash',
|
||||
'apiManualPrompt0' => 'Enter your API ID: ',
|
||||
'apiManualPrompt1' => 'Enter your API hash: ',
|
||||
'apiAutoWeb' => 'Enter a phone number that is <b>already registered</b> on telegram to get the API ID',
|
||||
'apiAutoPrompt0' => 'Enter a phone number that is already registered on Telegram: ',
|
||||
'apiAutoPrompt1' => 'Enter the verification code you received in Telegram: ',
|
||||
'apiAppWeb' => 'Enter API information',
|
||||
'apiAppInstructionsAuto0' => 'Enter the app\'s name, can be anything: ',
|
||||
'apiAppInstructionsAuto1' => 'Enter the app\'s short name, alphanumeric: ',
|
||||
'apiAppInstructionsAuto2' => 'Enter the app/website\'s URL, or t.me/yourusername: ',
|
||||
'apiAppInstructionsAuto3' => 'Enter the app platform: ',
|
||||
'apiAppInstructionsAuto4' => 'Describe your app: ',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Other (specify in description)',
|
||||
'apiParamsError' => 'You didn\'t provide all of the required parameters!',
|
||||
'apiError' => 'ERROR: %s. Try again.',
|
||||
'loginChoosePrompt' => 'Do you want to login as user or bot (u/b)? ',
|
||||
'loginChoosePromptWeb' => 'Do you want to login as user or bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'User',
|
||||
'loginBot' => 'Enter your bot token: ',
|
||||
'loginUser' => 'Enter your phone number: ',
|
||||
'loginUserCode' => 'Enter the code: ',
|
||||
'loginUserPass' => 'Enter your password (hint %s): ',
|
||||
'loginUserPassWeb' => 'Enter your password: ',
|
||||
'loginUserPassHint' => 'Hint: %s',
|
||||
'signupFirstName' => 'Enter your first name: ',
|
||||
'signupLastName' => 'Enter your last name (can be empty): ',
|
||||
'signupWeb' => 'Sign up please',
|
||||
'signupFirstNameWeb' => 'First name',
|
||||
'signupLastNameWeb' => 'Last name',
|
||||
'loginNoCode' => 'You didn\'t provide a phone code!',
|
||||
'loginNoPass' => 'You didn\'t provide the password!',
|
||||
'loginNoName' => 'You didn\'t provide the first name!',
|
||||
'loginBotTokenWeb' => 'Bot token',
|
||||
'loginUserPhoneWeb' => 'Phone number',
|
||||
'loginUserPhoneCodeWeb' => 'Code',
|
||||
'done' => 'Done!',
|
||||
'cdn_reupload' => 'File is not stored on CDN, requesting reupload!',
|
||||
'stored_on_cdn' => 'File is stored on CDN!',
|
||||
'serializing_madelineproto' => 'Serializing MadelineProto...',
|
||||
'phpseclib_fork' => 'Please install this fork of phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istantiating DataCenter...',
|
||||
'load_rsa' => 'Loading RSA keys...',
|
||||
'TL_translation' => 'Translating TL schemas...',
|
||||
'dh_prime_check_0' => 'Executing dh_prime checks (0/3)...',
|
||||
'nearest_dc' => 'We\'re in %s, nearest DC is %d.',
|
||||
'serialization_ofd' => 'Serialization is out of date, reconstructing object!',
|
||||
'getupdates_deserialization' => 'Getting updates after deserialization...',
|
||||
'shutdown_reader_pool' => 'Shutting down reader pool, %d jobs left',
|
||||
'threading_on' => 'THREADING IS ENABLED',
|
||||
'socket_reader' => 'Socket reader on DC %s: ',
|
||||
'socket_status_1' => 'CREATING',
|
||||
'socket_status_2' => 'SUBMITTING',
|
||||
'socket_status_3' => 'WAITING',
|
||||
'socket_status_4' => 'READY',
|
||||
'socket_status_5' => 'WORKING',
|
||||
'api_not_set' => 'You must provide an api key and an api id, get your own @ my.telegram.org',
|
||||
'session_corrupted' => 'The session is corrupted!',
|
||||
'resetSession_seqno' => 'Resetting session id and seq_no in DC %s...',
|
||||
'gen_perm_auth_key' => 'Generating permanent authorization key for DC %s...',
|
||||
'gen_temp_auth_key' => 'Generating temporary authorization key for DC %s...',
|
||||
'copy_auth_dcs' => 'Copying authorization from DC %s to DC %s...',
|
||||
'write_client_info' => 'Writing client info (also executing %s)...',
|
||||
'config_updated' => 'Updated config!',
|
||||
'length_not_4' => 'Length is not equal to 4',
|
||||
|
@ -348,29 +398,12 @@ class Lang
|
|||
'encode_double_error' => 'Could not properly encode double',
|
||||
'file_not_exist' => 'File does not exist',
|
||||
'deserialization_error' => 'An error occurred on deserialization',
|
||||
'rsa_init' => 'Istantiating \\tgseclib\\Crypt\\RSA...',
|
||||
'loading_key' => 'Loading key...',
|
||||
'computing_fingerprint' => 'Computing fingerprint...',
|
||||
'rsa_encrypting' => 'Encrypting with rsa key...',
|
||||
'rpc_tg_error' => 'Telegram returned an RPC error: %s (%s), caused by %s:%s%sTL trace:',
|
||||
'v_error' => '506c656173652075706461746520746f20746865206c61746573742076657273696f6e206f66204d6164656c696e6550726f746f2e',
|
||||
'v_tgerror' => '506c6561736520757064617465207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'No mode was specified!',
|
||||
'constructor_function_uncalled' => 'The constructor function wasn\'t called! Please call the constructor function before using this method.',
|
||||
'proxy_class_invalid' => 'Invalid proxy class provided!',
|
||||
'socket_con_error' => 'Connection: couldn\'t connect to socket.',
|
||||
'protocol_not_implemented' => 'Connection: This protocol isn\'t implemented yet.',
|
||||
'protocol_invalid' => 'Connection: invalid protocol specified.',
|
||||
'nothing_in_socket' => 'Nothing in the socket!',
|
||||
'wrong_length_read' => 'WARNING: Wrong length was read (should\'ve read %s, read %s)!',
|
||||
'no_data_in_socket' => 'No data in the socket!',
|
||||
'dc_con_start' => 'Connecting to DC %s...',
|
||||
'dc_con_stop' => 'Disconnecting from DC %s...',
|
||||
'dc_con_test_start' => 'Connecting to DC %s (%s server, %s, %s)...',
|
||||
'script_not_exist' => 'Provided script does not exist',
|
||||
'apifactory_start' => 'Running APIFactory...',
|
||||
'madelineproto_ready' => 'MadelineProto is ready!',
|
||||
'logout_error' => 'An error occurred while logging out!',
|
||||
'logout_ok' => 'Logged out successfully!',
|
||||
'already_loggedIn' => 'This instance of MadelineProto is already logged in. Logging out first...',
|
||||
'login_ok' => 'Logged in successfully!',
|
||||
|
@ -387,10 +420,8 @@ class Lang
|
|||
'signing_up' => 'Signing up as a normal user...',
|
||||
'signup_ok' => 'Signed up in successfully!',
|
||||
'2fa_uncalled' => 'I\'m not waiting for the password! Please call the phoneLogin and the completePhoneLogin methods first!',
|
||||
'getting_dialogs' => 'Getting dialogs...',
|
||||
'libtgvoip_required' => 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.',
|
||||
'peer_not_in_db' => 'This peer is not present in the internal peer database',
|
||||
'calling_user' => 'Calling %s...',
|
||||
'generating_a' => 'Generating a...',
|
||||
'generating_g_a' => 'Generating g_a...',
|
||||
'call_error_1' => 'Could not find and accept call %s',
|
||||
|
@ -420,32 +451,22 @@ class Lang
|
|||
'long_not_64' => 'Given value is not 64 bytes long',
|
||||
'array_invalid' => 'You didn\'t provide a valid array',
|
||||
'predicate_not_set' => 'Predicate (value under _) was not set!',
|
||||
'type_extract_error' => 'Could not extract type "%s"',
|
||||
'type_extract_error' => 'Could not extract type "%s", you should update MadelineProto!',
|
||||
'method_not_found' => 'Could not find method: ',
|
||||
'params_missing' => 'Missing required parameter',
|
||||
'sec_peer_not_in_db' => 'This secret peer is not present in the internal peer database',
|
||||
'stream_handle_invalid' => 'An invalid stream handle was provided.',
|
||||
'length_too_big' => 'Length is too big',
|
||||
'deserialize_not_str' => 'Deserialize: Generated value isn\'t a string',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s',
|
||||
'vector_invalid' => 'Invalid vector constructor: ',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s, you should update MadelineProto!',
|
||||
'constructor_not_found' => 'Constructor not found for type: ',
|
||||
'rand_bytes_too_small' => 'Random_bytes is too small!',
|
||||
'botapi_conversion_error' => 'Can\'t convert %s to a bot API object',
|
||||
'non_text_conversion' => 'Can\'t convert non text messages yet!',
|
||||
'last_byte_invalid' => 'Invalid last byte',
|
||||
'file_type_invalid' => 'Invalid file type detected (%s)',
|
||||
'recreate_temp_auth_key' => 'I had to recreate the temporary authorization key',
|
||||
'resetting_auth_key' => 'WARNING: Resetting auth key...',
|
||||
'shutting_down_reader_pool' => 'Shutting down reader pool ',
|
||||
'shutting_down_handler_pool' => 'Shutting down handler pool for dc %s, %d jobs left',
|
||||
'secret_chat_skipping' => 'I do not have the secret chat %s in the database, skipping message...',
|
||||
'fingerprint_mismatch' => 'Key fingerprint mismatch',
|
||||
'msg_data_length_too_big' => 'Message_data_length is too big',
|
||||
'length_not_divisible_16' => 'Length of decrypted data is not divisible by 16',
|
||||
'msg_key_mismatch' => 'Msg_key mismatch',
|
||||
'rand_bytes_too_short' => 'Random_bytes is too short!',
|
||||
'resending_unsupported' => 'Resending of messages is not yet supported',
|
||||
'unrecognized_dec_msg' => 'Unrecognized decrypted message received: ',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Async parameters class.
|
||||
* IPC light state.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
@ -17,49 +16,46 @@
|
|||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Async;
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
/**
|
||||
* Async parameters class.
|
||||
* Light state.
|
||||
*
|
||||
* Manages asynchronous generation of method parameters
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @internal
|
||||
*/
|
||||
class AsyncParameters
|
||||
final class LightState
|
||||
{
|
||||
/**
|
||||
* Async callable.
|
||||
* Event handler class name.
|
||||
*
|
||||
* @var callable
|
||||
* @var null|class-string<EventHandler>
|
||||
*/
|
||||
private $callable;
|
||||
/**
|
||||
* Create async parameters.
|
||||
*
|
||||
* @param callable $callable Async callable that will return parameters
|
||||
*/
|
||||
public function __construct(callable $callable)
|
||||
private ?string $eventHandler = null;
|
||||
|
||||
public function __construct(MTProto $API)
|
||||
{
|
||||
$this->callable = $callable;
|
||||
if ($API->hasEventHandler()) {
|
||||
$this->eventHandler = \get_class($API->getEventHandler());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create async parameters.
|
||||
* Check whether we can start IPC.
|
||||
*
|
||||
* @param callable $callable Async callable that will return parameters
|
||||
* @return boolean
|
||||
*/
|
||||
public function setCallable(callable $callable): void
|
||||
public function canStartIpc(): bool
|
||||
{
|
||||
$this->callable = $callable;
|
||||
return !$this->eventHandler || \class_exists($this->eventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameters asynchronously.
|
||||
* Get event handler class name.
|
||||
*
|
||||
* @return \Generator<array>|\Amp\Promise<array>
|
||||
* @return null|class-string<EventHandler>
|
||||
*/
|
||||
public function getParameters()
|
||||
public function getEventHandler(): ?string
|
||||
{
|
||||
$callable = $this->callable;
|
||||
return $callable();
|
||||
return $this->eventHandler;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ namespace danog\MadelineProto;
|
|||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Failure;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Settings\Logger as SettingsLogger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use function Amp\ByteStream\getStderr;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
|
@ -31,9 +33,21 @@ use function Amp\ByteStream\getStdout;
|
|||
*/
|
||||
class Logger
|
||||
{
|
||||
/**
|
||||
* @internal ANSI foreground color escapes
|
||||
*/
|
||||
const FOREGROUND = ['default' => 39, 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'light_gray' => 37, 'dark_gray' => 90, 'light_red' => 91, 'light_green' => 92, 'light_yellow' => 93, 'light_blue' => 94, 'light_magenta' => 95, 'light_cyan' => 96, 'white' => 97];
|
||||
/**
|
||||
* @internal ANSI background color escapes
|
||||
*/
|
||||
const BACKGROUND = ['default' => 49, 'black' => 40, 'red' => 41, 'magenta' => 45, 'yellow' => 43, 'green' => 42, 'blue' => 44, 'cyan' => 46, 'light_gray' => 47, 'dark_gray' => 100, 'light_red' => 101, 'light_green' => 102, 'light_yellow' => 103, 'light_blue' => 104, 'light_magenta' => 105, 'light_cyan' => 106, 'white' => 107];
|
||||
/**
|
||||
* @internal ANSI modifier escapes
|
||||
*/
|
||||
const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6];
|
||||
/**
|
||||
* @internal ANSI reset modifier escapes
|
||||
*/
|
||||
const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28];
|
||||
/**
|
||||
* Logging mode.
|
||||
|
@ -44,7 +58,7 @@ class Logger
|
|||
/**
|
||||
* Optional logger parameter.
|
||||
*
|
||||
* @var mixed
|
||||
* @var null|string|callable
|
||||
*/
|
||||
public $optional = null;
|
||||
/**
|
||||
|
@ -93,116 +107,160 @@ class Logger
|
|||
* Log rotation loop ID.
|
||||
*/
|
||||
private string $rotateId = '';
|
||||
/**
|
||||
* PSR logger.
|
||||
*/
|
||||
private PsrLogger $psr;
|
||||
/**
|
||||
* Ultra verbose logging.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const ULTRA_VERBOSE = 5;
|
||||
/**
|
||||
* Verbose logging.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const VERBOSE = 4;
|
||||
/**
|
||||
* Notice logging.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const NOTICE = 3;
|
||||
/**
|
||||
* Warning logging.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const WARNING = 2;
|
||||
/**
|
||||
* Error logging.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const ERROR = 1;
|
||||
/**
|
||||
* Log only fatal errors.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const FATAL_ERROR = 0;
|
||||
|
||||
/**
|
||||
* Disable logger (DEPRECATED).
|
||||
*
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
const NO_LOGGER = 0;
|
||||
/**
|
||||
* Default logger (syslog).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const DEFAULT_LOGGER = 1;
|
||||
/**
|
||||
* File logger.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const FILE_LOGGER = 2;
|
||||
/**
|
||||
* Echo logger.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const ECHO_LOGGER = 3;
|
||||
/**
|
||||
* Callable logger.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const CALLABLE_LOGGER = 4;
|
||||
|
||||
/**
|
||||
* Ultra verbose level.
|
||||
*/
|
||||
const LEVEL_ULTRA_VERBOSE = self::ULTRA_VERBOSE;
|
||||
/**
|
||||
* Verbose level.
|
||||
*/
|
||||
const LEVEL_VERBOSE = self::VERBOSE;
|
||||
/**
|
||||
* Notice level.
|
||||
*/
|
||||
const LEVEL_NOTICE = self::NOTICE;
|
||||
/**
|
||||
* Warning level.
|
||||
*/
|
||||
const LEVEL_WARNING = self::WARNING;
|
||||
/**
|
||||
* Error level.
|
||||
*/
|
||||
const LEVEL_ERROR = self::ERROR;
|
||||
/**
|
||||
* Fatal error level.
|
||||
*/
|
||||
const LEVEL_FATAL = self::FATAL_ERROR;
|
||||
|
||||
/**
|
||||
* Default logger (syslog).
|
||||
*/
|
||||
const LOGGER_DEFAULT = self::DEFAULT_LOGGER;
|
||||
/**
|
||||
* Echo logger.
|
||||
*/
|
||||
const LOGGER_ECHO = self::ECHO_LOGGER;
|
||||
/**
|
||||
* File logger.
|
||||
*/
|
||||
const LOGGER_FILE = self::FILE_LOGGER;
|
||||
/**
|
||||
* Callable logger.
|
||||
*/
|
||||
const LOGGER_CALLABLE = self::CALLABLE_LOGGER;
|
||||
|
||||
/**
|
||||
* Construct global static logger from MadelineProto settings.
|
||||
*
|
||||
* @param array $settings Settings array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function constructorFromSettings(array $settings)
|
||||
{
|
||||
if (!self::$default) {
|
||||
// The getLogger function will automatically init the static logger, but we'll do it again anyway
|
||||
self::$default = self::getLoggerFromSettings(MTProto::parseSettings($settings));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get logger from MadelineProto settings.
|
||||
*
|
||||
* @param array $settings Settings array
|
||||
* @param string $prefix Optional prefix for log messages
|
||||
* @param SettingsLogger $settings Settings instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getLoggerFromSettings(array $settings, string $prefix = ''): self
|
||||
public static function constructorFromSettings(SettingsLogger $settings): self
|
||||
{
|
||||
if (!isset($settings['logger']['logger_param']) && isset($settings['logger']['param'])) {
|
||||
$settings['logger']['logger_param'] = $settings['logger']['param'];
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && isset($settings['logger']['logger_param']) && $settings['logger']['logger_param'] === 'MadelineProto.log') {
|
||||
$settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
$logger = new self($settings['logger']['logger'], $settings['logger']['logger_param'] ?? '', $prefix, $settings['logger']['logger_level'] ?? Logger::VERBOSE, $settings['logger']['max_size'] ?? 100 * 1024 * 1024);
|
||||
if (!self::$default) {
|
||||
self::$default = $logger;
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', 1);
|
||||
\ini_set('error_log', $settings['logger']['logger'] === self::FILE_LOGGER ? $settings['logger']['logger_param'] : Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$logger->logger('Could not enable PHP logging');
|
||||
}
|
||||
}
|
||||
return $logger;
|
||||
return self::$default = new self($settings);
|
||||
}
|
||||
/**
|
||||
* Construct global logger.
|
||||
*
|
||||
* @param int $mode One of the logger constants
|
||||
* @param mixed $optional Optional parameter for logger
|
||||
* @param string $prefix Prefix for log messages
|
||||
* @param int $level Default logging level
|
||||
* @param int $max_size Maximum size for logfile
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function constructor(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024)
|
||||
{
|
||||
self::$default = new self($mode, $optional, $prefix, $level, $max_size);
|
||||
}
|
||||
/**
|
||||
* Construct global logger.
|
||||
*
|
||||
* @param int $mode One of the logger constants
|
||||
* @param mixed $optional Optional parameter for logger
|
||||
* @param string $prefix Prefix for log messages
|
||||
* @param int $level Default logging level
|
||||
* @param int $max_size Maximum size for logfile
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 10 * 1024 * 1024)
|
||||
{
|
||||
if ($mode === null) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['no_mode_specified']);
|
||||
}
|
||||
if ($mode === self::NO_LOGGER) {
|
||||
$mode = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') ? Logger::ECHO_LOGGER : Logger::FILE_LOGGER;
|
||||
}
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
$mode = Logger::FILE_LOGGER;
|
||||
}
|
||||
$level = \max($level, self::NOTICE);
|
||||
$max_size = \max($max_size, 100 * 1024);
|
||||
|
||||
$this->mode = $mode;
|
||||
$this->optional = $mode == self::FILE_LOGGER ? Tools::absolute($optional) : $optional;
|
||||
/**
|
||||
* Construct logger.
|
||||
*
|
||||
* @param SettingsLogger $settings
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function __construct(SettingsLogger $settings, string $prefix = '')
|
||||
{
|
||||
$this->psr = new PsrLogger($this);
|
||||
$this->prefix = $prefix === '' ? '' : ', '.$prefix;
|
||||
$this->level = $level;
|
||||
if ($this->mode === self::FILE_LOGGER && !\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) {
|
||||
$this->optional = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
if ($this->mode === self::FILE_LOGGER && !\preg_match('/\\.log$/', $this->optional)) {
|
||||
$this->optional .= '.log';
|
||||
}
|
||||
if ($mode === self::FILE_LOGGER && $max_size !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $max_size) {
|
||||
\unlink($this->optional);
|
||||
|
||||
$this->mode = $settings->getType();
|
||||
$this->optional = $settings->getExtra();
|
||||
$this->level = $settings->getLevel();
|
||||
|
||||
$maxSize = $settings->getMaxSize();
|
||||
|
||||
if ($this->mode === self::FILE_LOGGER) {
|
||||
if (!\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) {
|
||||
$this->optional = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
if (!str_ends_with($this->optional, '.log')) {
|
||||
$this->optional .= '.log';
|
||||
}
|
||||
if ($maxSize !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $maxSize) {
|
||||
\unlink($this->optional);
|
||||
}
|
||||
}
|
||||
$this->colors[self::ULTRA_VERBOSE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['dim']]);
|
||||
$this->colors[self::VERBOSE] = \implode(';', [self::FOREGROUND['green'], self::SET['bold']]);
|
||||
|
@ -219,16 +277,19 @@ class Logger
|
|||
} elseif ($this->mode === self::FILE_LOGGER) {
|
||||
Snitch::logFile($this->optional);
|
||||
$this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a'));
|
||||
if ($max_size !== -1) {
|
||||
if ($maxSize !== -1) {
|
||||
$optional = &$this->optional;
|
||||
$stdout = &$this->stdout;
|
||||
$this->rotateId = Loop::repeat(
|
||||
10*1000,
|
||||
function () use ($max_size) {
|
||||
\clearstatcache(true, $this->optional);
|
||||
if (\file_exists($this->optional) && \filesize($this->optional) >= $max_size) {
|
||||
$this->stdout = null;
|
||||
\unlink($this->optional);
|
||||
$this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a'));
|
||||
$this->logger("Automatically truncated logfile to $max_size");
|
||||
static function () use ($maxSize, $optional, &$stdout) {
|
||||
\clearstatcache(true, $optional);
|
||||
if (\file_exists($optional) && \filesize($optional) >= $maxSize) {
|
||||
$stdout->close();
|
||||
$stdout = null;
|
||||
\unlink($optional);
|
||||
$stdout = new ResourceOutputStream(\fopen($optional, 'a'));
|
||||
self::log("Automatically truncated logfile to $maxSize");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -244,6 +305,20 @@ class Logger
|
|||
$this->stdout = getStderr();
|
||||
}
|
||||
}
|
||||
|
||||
self::$default = $this;
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', "1");
|
||||
\ini_set('error_log', $this->mode === self::FILE_LOGGER
|
||||
? $this->optional
|
||||
: Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$this->logger('Could not enable PHP logging');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Destructor function.
|
||||
|
@ -325,4 +400,14 @@ class Logger
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PSR logger.
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function getPsrLogger(): LoggerInterface
|
||||
{
|
||||
return $this->psr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
namespace danog\MadelineProto\Loop;
|
||||
|
||||
use danog\MadelineProto\EventHandler;
|
||||
use danog\MadelineProto\InternalDoc;
|
||||
|
||||
/**
|
||||
|
@ -41,6 +42,6 @@ trait APILoop
|
|||
public function __construct(InternalDoc $API)
|
||||
{
|
||||
$this->API = $API;
|
||||
$this->setLogger($API->getLogger());
|
||||
$this->setLogger($API instanceof EventHandler ? $API->getAPI()->getLogger() : $API->logger);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class CheckLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$connection = $this->connection;
|
||||
$shared = $this->datacenterConnection;
|
||||
$timeout = $shared->getSettings()['timeout'];
|
||||
$timeout = $shared->getSettings()->getTimeout();
|
||||
$timeoutMs = $timeout * 1000;
|
||||
$timeoutResend = $timeout * $timeout;
|
||||
// Typically 25 seconds, good enough
|
||||
|
@ -79,40 +79,41 @@ class CheckLoop extends ResumableSignalLoop
|
|||
continue;
|
||||
}
|
||||
if (!isset($connection->new_outgoing[$message_id])) {
|
||||
$API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id);
|
||||
$API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]);
|
||||
continue;
|
||||
}
|
||||
$message = $connection->new_outgoing[$message_id];
|
||||
$chr = \ord($chr);
|
||||
switch ($chr & 7) {
|
||||
case 0:
|
||||
$API->logger->logger('Wrong message status 0 for '.$connection->outgoing_messages[$message_id]['_'], \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$API->logger->logger("Wrong message status 0 for $message", \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
if ($connection->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
|
||||
$connection->gotResponseForOutgoingMessageId($message_id);
|
||||
if ($message->getConstructor() === 'msgs_state_req') {
|
||||
$connection->gotResponseForOutgoingMessage($message);
|
||||
break;
|
||||
}
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
|
||||
$API->logger->logger("Message $message not received by server, resending...", \danog\MadelineProto\Logger::ERROR);
|
||||
$connection->methodRecall('watcherId', ['message_id' => $message_id, 'postpone' => true]);
|
||||
break;
|
||||
case 4:
|
||||
if ($chr & 32) {
|
||||
if ($connection->outgoing_messages[$message_id]['sent'] + $timeoutResend < \time()) {
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and is being processed for way too long, resending request...', \danog\MadelineProto\Logger::ERROR);
|
||||
if ($message->getSent() + $timeoutResend < \time()) {
|
||||
$API->logger->logger("Message $message received by server and is being processed for way too long, resending request...", \danog\MadelineProto\Logger::ERROR);
|
||||
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
|
||||
} else {
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and is being processed, waiting...', \danog\MadelineProto\Logger::ERROR);
|
||||
$API->logger->logger("Message $message received by server and is being processed, waiting...", \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
} elseif ($chr & 64) {
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and was already processed, requesting reply...', \danog\MadelineProto\Logger::ERROR);
|
||||
$API->logger->logger("Message $message received by server and was already processed, requesting reply...", \danog\MadelineProto\Logger::ERROR);
|
||||
$reply[] = $message_id;
|
||||
} elseif ($chr & 128) {
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and was already sent, requesting reply...', \danog\MadelineProto\Logger::ERROR);
|
||||
$API->logger->logger("Message $message received by server and was already sent, requesting reply...", \danog\MadelineProto\Logger::ERROR);
|
||||
$reply[] = $message_id;
|
||||
} else {
|
||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server, waiting...', \danog\MadelineProto\Logger::ERROR);
|
||||
$API->logger->logger("Message $message received by server, waiting...", \danog\MadelineProto\Logger::ERROR);
|
||||
$reply[] = $message_id;
|
||||
}
|
||||
}
|
||||
|
@ -128,16 +129,19 @@ class CheckLoop extends ResumableSignalLoop
|
|||
$list = '';
|
||||
// Don't edit this here pls
|
||||
foreach ($message_ids as $message_id) {
|
||||
$list .= $connection->outgoing_messages[$message_id]['_'].', ';
|
||||
$list .= $connection->outgoing_messages[$message_id]->getConstructor().', ';
|
||||
}
|
||||
$API->logger->logger("Still missing {$list} on DC {$datacenter}, sending state request", \danog\MadelineProto\Logger::ERROR);
|
||||
yield from $connection->objectCall('msgs_state_req', ['msg_ids' => $message_ids], ['promise' => $deferred]);
|
||||
}
|
||||
} else {
|
||||
foreach ($connection->new_outgoing as $message_id) {
|
||||
if (isset($connection->outgoing_messages[$message_id]['sent']) && $connection->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $connection->outgoing_messages[$message_id]['unencrypted']) {
|
||||
$API->logger->logger('Still missing '.$connection->outgoing_messages[$message_id]['_'].' with message id '.$message_id." on DC {$datacenter}, resending", \danog\MadelineProto\Logger::ERROR);
|
||||
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
|
||||
foreach ($connection->new_outgoing as $message_id => $message) {
|
||||
if ($message->wasSent()
|
||||
&& $message->getSent() + $timeout < \time()
|
||||
&& $message->isUnencrypted()
|
||||
) {
|
||||
$API->logger->logger("Still missing $message on DC $datacenter, resending", \danog\MadelineProto\Logger::ERROR);
|
||||
$connection->methodRecall('', ['message_id' => $message->getMsgId(), 'postpone' => true]);
|
||||
}
|
||||
}
|
||||
$connection->flush();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
namespace danog\MadelineProto\Loop\Connection;
|
||||
|
||||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
|
||||
/**
|
||||
* HttpWait loop.
|
||||
|
@ -56,8 +57,16 @@ class HttpWaitLoop extends ResumableSignalLoop
|
|||
}
|
||||
}
|
||||
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
||||
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!empty($connection->pending_outgoing) || !empty($connection->new_outgoing) && !$connection->hasPendingCalls())) {
|
||||
yield from $connection->sendMessage(['_' => 'http_wait', 'body' => ['max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 0], 'contentRelated' => true, 'unencrypted' => false, 'method' => false]);
|
||||
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!empty($connection->pendingOutgoing) || !empty($connection->new_outgoing) && !$connection->hasPendingCalls())) {
|
||||
yield from $connection->sendMessage(
|
||||
new OutgoingMessage(
|
||||
['max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 0],
|
||||
'http_wait',
|
||||
'',
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class PingLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$connection = $this->connection;
|
||||
$shared = $this->datacenterConnection;
|
||||
$timeout = $shared->getSettings()['timeout'];
|
||||
$timeout = $shared->getSettings()->getTimeout();
|
||||
$timeoutMs = $timeout * 1000;
|
||||
while (true) {
|
||||
while (!$shared->hasTempAuthKey()) {
|
||||
|
|
|
@ -25,6 +25,7 @@ use Amp\Loop;
|
|||
use Amp\Websocket\ClosedException;
|
||||
use danog\Loop\SignalLoop;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||
use danog\MadelineProto\MTProtoTools\Crypt;
|
||||
use danog\MadelineProto\NothingInTheSocketException;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
@ -70,8 +71,8 @@ class ReadLoop extends SignalLoop
|
|||
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", Logger::WARNING);
|
||||
$shared->setTempAuthKey(null);
|
||||
$shared->resetSession();
|
||||
foreach ($connection->new_outgoing as $message_id) {
|
||||
$connection->outgoing_messages[$message_id]['sent'] = 0;
|
||||
foreach ($connection->new_outgoing as $message) {
|
||||
$message->resetSent();
|
||||
}
|
||||
yield from $shared->reconnect();
|
||||
yield from $API->initAuthorization();
|
||||
|
@ -146,7 +147,6 @@ class ReadLoop extends SignalLoop
|
|||
}
|
||||
yield $buffer->bufferRead($left);
|
||||
}
|
||||
$connection->incoming_messages[$message_id] = [];
|
||||
} elseif ($auth_key_id === $shared->getTempAuthKey()->getID()) {
|
||||
$message_key = yield $buffer->bufferRead(16);
|
||||
list($aes_key, $aes_iv) = Crypt::aesCalculate($message_key, $shared->getTempAuthKey()->getAuthKey(), false);
|
||||
|
@ -191,18 +191,22 @@ class ReadLoop extends SignalLoop
|
|||
if ($message_key != \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 96, 32).$decrypted_data, true), 8, 16)) {
|
||||
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
|
||||
}
|
||||
$connection->incoming_messages[$message_id] = ['seq_no' => $seq_no];
|
||||
} else {
|
||||
$API->logger->logger('Got unknown auth_key id', Logger::ERROR);
|
||||
return -404;
|
||||
}
|
||||
$deserialized = yield from $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
|
||||
[$deserialized, $sideEffects] = $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
|
||||
if (isset($API->referenceDatabase)) {
|
||||
$API->referenceDatabase->reset();
|
||||
}
|
||||
$connection->incoming_messages[$message_id]['content'] = $deserialized;
|
||||
$connection->incoming_messages[$message_id]['response'] = -1;
|
||||
$connection->new_incoming[$message_id] = $message_id;
|
||||
$message = new IncomingMessage($deserialized, $message_id);
|
||||
if (isset($seq_no)) {
|
||||
$message->setSeqNo($seq_no);
|
||||
}
|
||||
if ($sideEffects) {
|
||||
$message->setSideEffects($sideEffects);
|
||||
}
|
||||
$connection->new_incoming[$message_id] = $connection->incoming_messages[$message_id] = $message;
|
||||
$API->logger->logger('Received payload from DC '.$datacenter, Logger::ULTRA_VERBOSE);
|
||||
} finally {
|
||||
$connection->reading(false);
|
||||
|
|
|
@ -23,6 +23,7 @@ use Amp\ByteStream\StreamException;
|
|||
use Amp\Loop;
|
||||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProto\Container;
|
||||
use danog\MadelineProto\MTProtoTools\Crypt;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
|
@ -34,7 +35,7 @@ use danog\MadelineProto\Tools;
|
|||
class WriteLoop extends ResumableSignalLoop
|
||||
{
|
||||
const MAX_COUNT = 1020;
|
||||
const MAX_SIZE = 1 << 15;
|
||||
private const MAX_SIZE = 1 << 15;
|
||||
const MAX_IDS = 8192;
|
||||
|
||||
use Common;
|
||||
|
@ -51,7 +52,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$please_wait = false;
|
||||
while (true) {
|
||||
while (empty($connection->pending_outgoing) || $please_wait) {
|
||||
while (empty($connection->pendingOutgoing) || $please_wait) {
|
||||
if ($connection->shouldReconnect()) {
|
||||
$API->logger->logger('Not writing because connection is old');
|
||||
return;
|
||||
|
@ -93,35 +94,35 @@ class WriteLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$connection = $this->connection;
|
||||
$shared = $this->datacenterConnection;
|
||||
while ($connection->pending_outgoing) {
|
||||
while ($connection->pendingOutgoing) {
|
||||
$skipped_all = true;
|
||||
foreach ($connection->pending_outgoing as $k => $message) {
|
||||
foreach ($connection->pendingOutgoing as $k => $message) {
|
||||
if ($shared->hasTempAuthKey()) {
|
||||
return;
|
||||
}
|
||||
if (!$message['unencrypted']) {
|
||||
if ($message->isEncrypted()) {
|
||||
continue;
|
||||
}
|
||||
$skipped_all = false;
|
||||
$API->logger->logger("Sending {$message['_']} as unencrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->msgIdHandler->generateMessageId();
|
||||
$length = \strlen($message['serialized_body']);
|
||||
$API->logger->logger("Sending $message as unencrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$message_id = $message->getMsgId() ?? $connection->msgIdHandler->generateMessageId();
|
||||
$length = \strlen($message->getSerializedBody());
|
||||
$pad_length = -$length & 15;
|
||||
$pad_length += 16 * \danog\MadelineProto\Tools::randomInt($modulus = 16);
|
||||
$pad = \danog\MadelineProto\Tools::random($pad_length);
|
||||
$buffer = yield $connection->stream->getWriteBuffer(8 + 8 + 4 + $pad_length + $length);
|
||||
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0".$message_id.\danog\MadelineProto\Tools::packUnsignedInt($length).$message['serialized_body'].$pad);
|
||||
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0".$message_id.\danog\MadelineProto\Tools::packUnsignedInt($length).$message->getSerializedBody().$pad);
|
||||
//var_dump("plain ".bin2hex($message_id));
|
||||
$connection->httpSent();
|
||||
|
||||
$API->logger->logger("Sent $message as unencrypted message to DC $datacenter!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
|
||||
unset($connection->pendingOutgoing[$k]);
|
||||
$message->setMsgId($message_id);
|
||||
$connection->outgoing_messages[$message_id] = $message;
|
||||
$connection->outgoing_messages[$message_id]['sent'] = \time();
|
||||
$connection->outgoing_messages[$message_id]['tries'] = 0;
|
||||
$connection->outgoing_messages[$message_id]['unencrypted'] = true;
|
||||
$connection->new_outgoing[$message_id] = $message_id;
|
||||
unset($connection->pending_outgoing[$k]);
|
||||
$API->logger->logger("Sent {$message['_']} as unencrypted message to DC {$datacenter}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$message['send_promise']->resolve(isset($message['promise']) ? $message['promise'] : true);
|
||||
unset($message['send_promise']);
|
||||
$connection->new_outgoing[$message_id] = $message;
|
||||
|
||||
$message->sent();
|
||||
}
|
||||
if ($skipped_all) {
|
||||
return true;
|
||||
|
@ -138,11 +139,11 @@ class WriteLoop extends ResumableSignalLoop
|
|||
if (!$shared->hasTempAuthKey()) {
|
||||
return;
|
||||
}
|
||||
if ($shared->isHttp() && empty($connection->pending_outgoing)) {
|
||||
if ($shared->isHttp() && empty($connection->pendingOutgoing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
\ksort($connection->pending_outgoing);
|
||||
\ksort($connection->pendingOutgoing);
|
||||
|
||||
$messages = [];
|
||||
$keys = [];
|
||||
|
@ -157,71 +158,72 @@ class WriteLoop extends ResumableSignalLoop
|
|||
$has_state = false;
|
||||
$has_resend = false;
|
||||
$has_http_wait = false;
|
||||
foreach ($connection->pending_outgoing as $k => $message) {
|
||||
if ($message['unencrypted']) {
|
||||
foreach ($connection->pendingOutgoing as $k => $message) {
|
||||
if ($message->isUnencrypted()) {
|
||||
continue;
|
||||
}
|
||||
if (isset($message['container'])) {
|
||||
unset($connection->pending_outgoing[$k]);
|
||||
if ($message instanceof Container) {
|
||||
unset($connection->pendingOutgoing[$k]);
|
||||
continue;
|
||||
}
|
||||
if ($shared->getSettings()['pfs'] && !$shared->isBound() && !$connection->isCDN() && !\in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) {
|
||||
$API->logger->logger("Skipping {$message['_']} due to unbound keys in DC {$datacenter}");
|
||||
$constructor = $message->getConstructor();
|
||||
if ($shared->getGenericSettings()->getAuth()->getPfs() && !$shared->isBound() && !$connection->isCDN() && $message->isMethod() && !\in_array($constructor, ['http_wait', 'auth.bindTempAuthKey'])) {
|
||||
$API->logger->logger("Skipping $message due to unbound keys in DC $datacenter");
|
||||
$skipped = true;
|
||||
continue;
|
||||
}
|
||||
if ($message['_'] === 'http_wait') {
|
||||
if ($constructor === 'http_wait') {
|
||||
$has_http_wait = true;
|
||||
}
|
||||
if ($message['_'] === 'msgs_state_req') {
|
||||
if ($constructor === 'msgs_state_req') {
|
||||
if ($has_state) {
|
||||
$API->logger->logger("Already have a state request queued for the current container in DC {$datacenter}");
|
||||
continue;
|
||||
}
|
||||
$has_state = true;
|
||||
}
|
||||
if ($message['_'] === 'msg_resend_req') {
|
||||
if ($constructor === 'msg_resend_req') {
|
||||
if ($has_resend) {
|
||||
$API->logger->logger("Already have a resend request queued for the current container in DC {$datacenter}");
|
||||
continue;
|
||||
}
|
||||
$has_resend = true;
|
||||
}
|
||||
|
||||
$body_length = \strlen($message['serialized_body']);
|
||||
$body_length = \strlen($message->getSerializedBody());
|
||||
$actual_length = $body_length + 32;
|
||||
if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) {
|
||||
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
break;
|
||||
}
|
||||
if (isset($message['seqno'])) {
|
||||
if ($message->hasSeqNo()) {
|
||||
$has_seq = true;
|
||||
}
|
||||
|
||||
$message_id = $message['msg_id'] ?? $connection->msgIdHandler->generateMessageId();
|
||||
$API->logger->logger("Sending {$message['_']} as encrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$message_id = $message->getMsgId() ?? $connection->msgIdHandler->generateMessageId();
|
||||
$API->logger->logger("Sending $message as encrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$MTmessage = [
|
||||
'_' => 'MTmessage',
|
||||
'msg_id' => $message_id,
|
||||
'body' => $message['serialized_body'],
|
||||
'seqno' => $message['seqno'] ?? $connection->generateOutSeqNo($message['contentRelated'])
|
||||
'body' => $message->getSerializedBody(),
|
||||
'seqno' => $message->getSeqNo() ?? $connection->generateOutSeqNo($message->isContentRelated())
|
||||
];
|
||||
if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') {
|
||||
if (!$shared->getTempAuthKey()->isInited() && $message['_'] !== 'auth.bindTempAuthKey' && !$inited) {
|
||||
if ($message->isMethod() && $constructor !== 'http_wait') {
|
||||
if (!$shared->getTempAuthKey()->isInited() && $constructor !== 'auth.bindTempAuthKey' && !$inited) {
|
||||
$inited = true;
|
||||
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE);
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeWithLayer', ['layer' => $API->settings['tl_schema']['layer'], 'query' => yield from $API->getTL()->serializeMethod('initConnection', ['api_id' => $API->settings['app_info']['api_id'], 'api_hash' => $API->settings['app_info']['api_hash'], 'device_model' => !$connection->isCDN() ? $API->settings['app_info']['device_model'] : 'n/a', 'system_version' => !$connection->isCDN() ? $API->settings['app_info']['system_version'] : 'n/a', 'app_version' => $API->settings['app_info']['app_version'], 'system_lang_code' => $API->settings['app_info']['lang_code'], 'lang_code' => $API->settings['app_info']['lang_code'], 'lang_pack' => $API->settings['app_info']['lang_pack'], 'proxy' => $connection->getCtx()->getInputClientProxy(), 'query' => $MTmessage['body']])]));
|
||||
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $constructor), \danog\MadelineProto\Logger::NOTICE);
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeWithLayer', ['layer' => $API->settings->getSchema()->getLayer(), 'query' => yield from $API->getTL()->serializeMethod('initConnection', ['api_id' => $API->settings->getAppInfo()->getApiId(), 'api_hash' => $API->settings->getAppInfo()->getApiHash(), 'device_model' => !$connection->isCDN() ? $API->settings->getAppInfo()->getDeviceModel() : 'n/a', 'system_version' => !$connection->isCDN() ? $API->settings->getAppInfo()->getSystemVersion() : 'n/a', 'app_version' => $API->settings->getAppInfo()->getAppVersion(), 'system_lang_code' => $API->settings->getAppInfo()->getLangCode(), 'lang_code' => $API->settings->getAppInfo()->getLangCode(), 'lang_pack' => $API->settings->getAppInfo()->getLangPack(), 'proxy' => $connection->getCtx()->getInputClientProxy(), 'query' => $MTmessage['body']])]));
|
||||
} else {
|
||||
if (isset($message['queue'])) {
|
||||
if (!isset($connection->call_queue[$message['queue']])) {
|
||||
$connection->call_queue[$message['queue']] = [];
|
||||
if ($message->hasQueue()) {
|
||||
$queueId = $message->getQueueId();
|
||||
if (!isset($connection->call_queue[$queueId])) {
|
||||
$connection->call_queue[$queueId] = [];
|
||||
}
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$message['queue']], 'query' => $MTmessage['body']]));
|
||||
$connection->call_queue[$message['queue']][$message_id] = $message_id;
|
||||
if (\count($connection->call_queue[$message['queue']]) > $API->settings['msg_array_limit']['call_queue']) {
|
||||
\reset($connection->call_queue[$message['queue']]);
|
||||
$key = \key($connection->call_queue[$message['queue']]);
|
||||
unset($connection->call_queue[$message['queue']][$key]);
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$queueId], 'query' => $MTmessage['body']]));
|
||||
$connection->call_queue[$queueId][$message_id] = $message_id;
|
||||
if (\count($connection->call_queue[$queueId]) > $API->settings->getRpc()->getLimitCallQueue()) {
|
||||
\reset($connection->call_queue[$queueId]);
|
||||
$key = \key($connection->call_queue[$queueId]);
|
||||
unset($connection->call_queue[$queueId][$key]);
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
|
@ -229,7 +231,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||
if ($API->settings['requests']['gzip_encode_if_gt'] !== -1 && ($l = strlen($MTmessage['body'])) > $API->settings['requests']['gzip_encode_if_gt']) {
|
||||
if (($g = strlen($gzipped = gzencode($MTmessage['body']))) < $l) {
|
||||
$MTmessage['body'] = yield $API->getTL()->serializeObject(['type' => ''], ['_' => 'gzip_packed', 'packed_data' => $gzipped], 'gzipped data');
|
||||
$API->logger->logger('Using GZIP compression for ' . $message['_'] . ', saved ' . ($l - $g) . ' bytes of data, reduced call size by ' . $g * 100 / $l . '%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$API->logger->logger('Using GZIP compression for ' . $constructor . ', saved ' . ($l - $g) . ' bytes of data, reduced call size by ' . $g * 100 / $l . '%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
}
|
||||
unset($gzipped);
|
||||
}*/
|
||||
|
@ -247,8 +249,8 @@ class WriteLoop extends ResumableSignalLoop
|
|||
$messages[] = $MTmessage;
|
||||
$keys[$k] = $message_id;
|
||||
|
||||
$connection->pending_outgoing[$k]['seqno'] = $MTmessage['seqno'];
|
||||
$connection->pending_outgoing[$k]['msg_id'] = $MTmessage['msg_id'];
|
||||
$message->setSeqNo($MTmessage['seqno'])
|
||||
->setMsgId($MTmessage['msg_id']);
|
||||
}
|
||||
$MTmessage = null;
|
||||
|
||||
|
@ -284,9 +286,9 @@ class WriteLoop extends ResumableSignalLoop
|
|||
if ($count > 1 || $has_seq) {
|
||||
$API->logger->logger("Wrapping in msg_container ({$count} messages of total size {$total_length}) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$message_id = $connection->msgIdHandler->generateMessageId();
|
||||
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => \array_values($keys), 'contentRelated' => false, 'method' => false, 'unencrypted' => false];
|
||||
$keys[$connection->pending_outgoing_key++] = $message_id;
|
||||
$message_data = (yield from $API->getTL()->serializeObject(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container'));
|
||||
$connection->pendingOutgoing[$connection->pendingOutgoingKey] = new Container(\array_values($keys));
|
||||
$keys[$connection->pendingOutgoingKey++] = $message_id;
|
||||
$message_data = yield from $API->getTL()->serializeObject(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container');
|
||||
$message_data_length = \strlen($message_data);
|
||||
$seq_no = $connection->generateOutSeqNo(false);
|
||||
} elseif ($count) {
|
||||
|
@ -320,20 +322,17 @@ class WriteLoop extends ResumableSignalLoop
|
|||
}
|
||||
|
||||
foreach ($keys as $key => $message_id) {
|
||||
$connection->outgoing_messages[$message_id] =& $connection->pending_outgoing[$key];
|
||||
if (isset($connection->outgoing_messages[$message_id]['promise'])) {
|
||||
$connection->new_outgoing[$message_id] = $message_id;
|
||||
$connection->outgoing_messages[$message_id]['sent'] = $sent;
|
||||
$message = $connection->pendingOutgoing[$key];
|
||||
unset($connection->pendingOutgoing[$key]);
|
||||
$connection->outgoing_messages[$message_id] = $message;
|
||||
if ($message->hasPromise()) {
|
||||
$connection->new_outgoing[$message_id] = $message;
|
||||
}
|
||||
if (isset($connection->outgoing_messages[$message_id]['send_promise'])) {
|
||||
$connection->outgoing_messages[$message_id]['send_promise']->resolve(isset($connection->outgoing_messages[$message_id]['promise']) ? $connection->outgoing_messages[$message_id]['promise'] : true);
|
||||
unset($connection->outgoing_messages[$message_id]['send_promise']);
|
||||
}
|
||||
unset($connection->pending_outgoing[$key]);
|
||||
$message->sent();
|
||||
}
|
||||
} while ($connection->pending_outgoing && !$skipped);
|
||||
if (empty($connection->pending_outgoing)) {
|
||||
$connection->pending_outgoing_key = 'a';
|
||||
} while ($connection->pendingOutgoing && !$skipped);
|
||||
if (empty($connection->pendingOutgoing)) {
|
||||
$connection->pendingOutgoingKey = 'a';
|
||||
}
|
||||
return $skipped;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace danog\MadelineProto\Loop\Update;
|
|||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\MTProtoTools\UpdatesState;
|
||||
|
||||
/**
|
||||
* Update feed loop.
|
||||
|
@ -55,6 +56,10 @@ class FeedLoop extends ResumableSignalLoop
|
|||
* @var UpdateLoop
|
||||
*/
|
||||
private ?UpdateLoop $updater = null;
|
||||
/**
|
||||
* Update state.
|
||||
*/
|
||||
private ?UpdatesState $state = null;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -75,17 +80,14 @@ class FeedLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$this->updater = $API->updaters[$this->channelId];
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return false;
|
||||
}
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
|
||||
while (true) {
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
|
@ -93,9 +95,6 @@ class FeedLoop extends ResumableSignalLoop
|
|||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
$API->logger->logger("Resumed {$this}");
|
||||
while ($this->incomingUpdates) {
|
||||
$updates = $this->incomingUpdates;
|
||||
|
@ -114,7 +113,7 @@ class FeedLoop extends ResumableSignalLoop
|
|||
}
|
||||
}
|
||||
}
|
||||
public function parse($updates): \Generator
|
||||
public function parse(array $updates): \Generator
|
||||
{
|
||||
\reset($updates);
|
||||
while ($updates) {
|
||||
|
@ -127,7 +126,7 @@ class FeedLoop extends ResumableSignalLoop
|
|||
continue;
|
||||
}
|
||||
if (isset($update['pts'], $update['pts_count'])) {
|
||||
$logger = function ($msg) use ($update) {
|
||||
$logger = function ($msg) use ($update): void {
|
||||
$pts_count = $update['pts_count'];
|
||||
$mid = isset($update['message']['id']) ? $update['message']['id'] : '-';
|
||||
$mypts = $this->state->pts();
|
||||
|
@ -147,7 +146,7 @@ class FeedLoop extends ResumableSignalLoop
|
|||
$this->incomingUpdates = [];
|
||||
continue;
|
||||
}
|
||||
if (isset($update['message']['id'], $update['message']['to_id']) && !\in_array($update['_'], ['updateEditMessage', 'updateEditChannelMessage', 'updateMessageID'])) {
|
||||
if (isset($update['message']['id'], $update['message']['peer_id']) && !\in_array($update['_'], ['updateEditMessage', 'updateEditChannelMessage', 'updateMessageID'])) {
|
||||
if (!$this->API->checkMsgId($update['message'])) {
|
||||
$logger('MSGID duplicate');
|
||||
continue;
|
||||
|
@ -177,7 +176,7 @@ class FeedLoop extends ResumableSignalLoop
|
|||
switch ($update['_']) {
|
||||
case 'updateNewChannelMessage':
|
||||
case 'updateEditChannelMessage':
|
||||
$channelId = isset($update['message']['to_id']['channel_id']) ? $update['message']['to_id']['channel_id'] : self::GENERIC;
|
||||
$channelId = isset($update['message']['peer_id']['channel_id']) ? $update['message']['peer_id']['channel_id'] : self::GENERIC;
|
||||
if (!$channelId) {
|
||||
return false;
|
||||
}
|
||||
|
@ -213,13 +212,29 @@ class FeedLoop extends ResumableSignalLoop
|
|||
$from = false;
|
||||
$via_bot = false;
|
||||
$entities = false;
|
||||
if ($update['message']['_'] !== 'messageEmpty' && (($from = isset($update['message']['from_id']) && !(yield from $this->API->peerIsset($update['message']['from_id']))) || ($to = !(yield from $this->API->peerIsset($update['message']['to_id']))) || ($via_bot = isset($update['message']['via_bot_id']) && !(yield from $this->API->peerIsset($update['message']['via_bot_id']))) || ($entities = isset($update['message']['entities']) && !(yield from $this->API->entitiesPeerIsset($update['message']['entities']))))) {
|
||||
if ($update['message']['_'] !== 'messageEmpty' && (
|
||||
(
|
||||
$from = isset($update['message']['from_id'])
|
||||
&& !(yield from $this->API->peerIsset($update['message']['from_id']))
|
||||
) || (
|
||||
$to = !(yield from $this->API->peerIsset($update['message']['peer_id']))
|
||||
)
|
||||
|| (
|
||||
$via_bot = isset($update['message']['via_bot_id'])
|
||||
&& !(yield from $this->API->peerIsset($update['message']['via_bot_id']))
|
||||
) || (
|
||||
$entities = isset($update['message']['entities'])
|
||||
&& !(yield from $this->API->entitiesPeerIsset($update['message']['entities']))
|
||||
)
|
||||
)
|
||||
) {
|
||||
$log = '';
|
||||
if ($from) {
|
||||
$log .= "from_id {$update['message']['from_id']}, ";
|
||||
$from_id = $this->API->getId($update['message']['from_id']);
|
||||
$log .= "from_id {$from_id}, ";
|
||||
}
|
||||
if ($to) {
|
||||
$log .= 'to_id '.\json_encode($update['message']['to_id']).', ';
|
||||
$log .= 'peer_id '.\json_encode($update['message']['peer_id']).', ';
|
||||
}
|
||||
if ($via_bot) {
|
||||
$log .= "via_bot {$update['message']['via_bot_id']}, ";
|
||||
|
@ -248,15 +263,15 @@ class FeedLoop extends ResumableSignalLoop
|
|||
return yield from $this->API->feeders[self::GENERIC]->feedSingle($update);
|
||||
}
|
||||
}
|
||||
$this->API->logger->logger('Was fed an update of type '.$update['_']." in {$this}...", \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->API->logger->logger('Was fed an update of type '.$update['_']." in {$this}...", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$this->incomingUpdates[] = $update;
|
||||
return $this->channelId;
|
||||
}
|
||||
public function save($update)
|
||||
public function save($update): void
|
||||
{
|
||||
$this->parsedUpdates[] = $update;
|
||||
}
|
||||
public function saveMessages($messages)
|
||||
public function saveMessages($messages): void
|
||||
{
|
||||
foreach ($messages as $message) {
|
||||
if (!$this->API->checkMsgId($message)) {
|
||||
|
|
113
src/danog/MadelineProto/Loop/Update/SecretFeedLoop.php
Normal file
113
src/danog/MadelineProto/Loop/Update/SecretFeedLoop.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Update feeder loop.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Loop\Update;
|
||||
|
||||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\SecurityException;
|
||||
|
||||
/**
|
||||
* Secret feed loop.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class SecretFeedLoop extends ResumableSignalLoop
|
||||
{
|
||||
use InternalLoop {
|
||||
__construct as private init;
|
||||
}
|
||||
/**
|
||||
* Incoming secret updates array.
|
||||
*/
|
||||
private array $incomingUpdates = [];
|
||||
/**
|
||||
* Secret chat ID.
|
||||
*/
|
||||
private int $secretId;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param MTProto $API API instance
|
||||
* @param integer $secretId Secret chat ID
|
||||
*/
|
||||
public function __construct(MTProto $API, int $secretId)
|
||||
{
|
||||
$this->init($API);
|
||||
$this->secretId = $secretId;
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function loop(): \Generator
|
||||
{
|
||||
$API = $this->API;
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
$API->logger->logger("Resumed {$this}");
|
||||
while ($this->incomingUpdates) {
|
||||
$updates = $this->incomingUpdates;
|
||||
$this->incomingUpdates = [];
|
||||
foreach ($updates as $update) {
|
||||
try {
|
||||
if (!yield from $API->handleEncryptedUpdate($update)) {
|
||||
$API->logger->logger("Secret chat deleted, exiting $this...");
|
||||
unset($API->secretFeeders[$this->secretId]);
|
||||
return;
|
||||
}
|
||||
} catch (SecurityException $e) {
|
||||
$API->logger->logger("Secret chat deleted, exiting $this...");
|
||||
unset($API->secretFeeders[$this->secretId]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$updates = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Feed incoming update to loop.
|
||||
*
|
||||
* @param array $update
|
||||
* @return void
|
||||
*/
|
||||
public function feed(array $update): void
|
||||
{
|
||||
$this->incomingUpdates []= $update;
|
||||
}
|
||||
public function __toString(): string
|
||||
{
|
||||
return "secret chat feed loop {$this->secretId}";
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ namespace danog\MadelineProto\Loop\Update;
|
|||
|
||||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\MTProtoTools\UpdatesState;
|
||||
|
||||
/**
|
||||
* update feed loop.
|
||||
|
@ -42,6 +43,10 @@ class SeqLoop extends ResumableSignalLoop
|
|||
* Pending updates.
|
||||
*/
|
||||
private array $pendingWakeups = [];
|
||||
/**
|
||||
* State.
|
||||
*/
|
||||
private ?UpdatesState $state = null;
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
|
@ -51,17 +56,14 @@ class SeqLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$this->feeder = $API->feeders[FeedLoop::GENERIC];
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return false;
|
||||
}
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = (yield from $API->loadUpdateState());
|
||||
while (true) {
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
|
@ -69,9 +71,6 @@ class SeqLoop extends ResumableSignalLoop
|
|||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
while ($this->incomingUpdates) {
|
||||
$updates = $this->incomingUpdates;
|
||||
$this->incomingUpdates = [];
|
||||
|
@ -88,7 +87,7 @@ class SeqLoop extends ResumableSignalLoop
|
|||
}
|
||||
}
|
||||
}
|
||||
public function parse($updates): \Generator
|
||||
public function parse(array $updates): \Generator
|
||||
{
|
||||
\reset($updates);
|
||||
while ($updates) {
|
||||
|
@ -120,7 +119,10 @@ class SeqLoop extends ResumableSignalLoop
|
|||
yield from $this->save($update);
|
||||
}
|
||||
}
|
||||
public function feed($updates)
|
||||
/**
|
||||
* @param (array|mixed)[] $updates
|
||||
*/
|
||||
public function feed(array $updates): void
|
||||
{
|
||||
$this->API->logger->logger('Was fed updates of type '.$updates['_'].'...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->incomingUpdates[] = $updates;
|
||||
|
@ -129,7 +131,10 @@ class SeqLoop extends ResumableSignalLoop
|
|||
{
|
||||
$this->pendingWakeups += (yield from $this->feeder->feed($updates['updates']));
|
||||
}
|
||||
public function addPendingWakeups($wakeups)
|
||||
/**
|
||||
* @param true[] $wakeups
|
||||
*/
|
||||
public function addPendingWakeups(array $wakeups): void
|
||||
{
|
||||
$this->pendingWakeups += $wakeups;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace danog\MadelineProto\Loop\Update;
|
|||
|
||||
use danog\Loop\ResumableSignalLoop;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\RPCErrorException;
|
||||
|
@ -69,17 +70,17 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$feeder = $this->feeder = $API->feeders[$this->channelId];
|
||||
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
$API->logger->logger("Exiting {$this} due to signal");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = $state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
|
||||
$timeout = $API->settings['updates']['getdifference_interval'] * 1000;
|
||||
$timeout = 30 * 1000;
|
||||
$first = true;
|
||||
while (true) {
|
||||
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
$API->logger->logger("Exiting {$this} due to signal");
|
||||
return;
|
||||
|
@ -123,7 +124,7 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
if (isset($difference['timeout'])) {
|
||||
$timeout = $difference['timeout'];
|
||||
}
|
||||
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::VERBOSE);
|
||||
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
switch ($difference['_']) {
|
||||
case 'updates.channelDifferenceEmpty':
|
||||
$state->update($difference);
|
||||
|
@ -160,7 +161,7 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
}
|
||||
} else {
|
||||
$API->logger->logger('Resumed and fetching normal difference...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$difference = yield from $API->methodCallAsyncRead('updates.getDifference', ['pts' => $state->pts(), 'date' => $state->date(), 'qts' => $state->qts()], ['datacenter' => $API->settings['connection_settings']['default_dc']]);
|
||||
$difference = yield from $API->methodCallAsyncRead('updates.getDifference', ['pts' => $state->pts(), 'date' => $state->date(), 'qts' => $state->qts()], $API->settings->getDefaultDcParams());
|
||||
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
switch ($difference['_']) {
|
||||
case 'updates.differenceEmpty':
|
||||
|
@ -202,13 +203,13 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
}
|
||||
}
|
||||
}
|
||||
$API->logger->logger("Finished parsing updates in {$this}, now resuming feeders");
|
||||
$API->logger->logger("Finished parsing updates in {$this}, now resuming feeders", Logger::ULTRA_VERBOSE);
|
||||
foreach ($result as $channelId => $boh) {
|
||||
$API->feeders[$channelId]->resumeDefer();
|
||||
}
|
||||
$API->logger->logger("Finished resuming feeders in {$this}, signaling updates");
|
||||
$API->logger->logger("Finished resuming feeders in {$this}, signaling updates", Logger::ULTRA_VERBOSE);
|
||||
$API->signalUpdate();
|
||||
$API->logger->logger("Finished signaling updates in {$this}, pausing");
|
||||
$API->logger->logger("Finished signaling updates in {$this}, pausing", Logger::ULTRA_VERBOSE);
|
||||
$first = false;
|
||||
if (yield $this->waitSignal($this->pause($timeout * 1000))) {
|
||||
$API->logger->logger("Exiting {$this} due to signal");
|
||||
|
@ -216,7 +217,7 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
}
|
||||
}
|
||||
}
|
||||
public function setLimit($toPts): void
|
||||
public function setLimit(int $toPts): void
|
||||
{
|
||||
$this->toPts = $toPts;
|
||||
}
|
||||
|
|
|
@ -19,20 +19,20 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
/**
|
||||
* Lua interface.
|
||||
*/
|
||||
class Lua
|
||||
{
|
||||
use \danog\Serializable;
|
||||
public $MadelineProto;
|
||||
public API $MadelineProto;
|
||||
protected $Lua;
|
||||
protected $script;
|
||||
public function __magic_construct($script, $MadelineProto)
|
||||
protected string $script;
|
||||
public function __construct(string $script, API $MadelineProto)
|
||||
{
|
||||
if (!\file_exists($script)) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['script_not_exist']);
|
||||
}
|
||||
$this->MadelineProto = $MadelineProto;
|
||||
$this->MadelineProto->settings['updates']['handle_updates'] = true;
|
||||
$this->MadelineProto->API->datacenter->sockets[$this->MadelineProto->settings['connection_settings']['default_dc']]->startUpdateLoop();
|
||||
$this->script = $script;
|
||||
$this->__wakeup();
|
||||
}
|
||||
|
@ -76,13 +76,16 @@ class Lua
|
|||
$this->MadelineProto->{$namespace}->lua = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return \Generator|int
|
||||
*/
|
||||
public function tdcliFunction($params, $cb = null, $cb_extra = null)
|
||||
{
|
||||
$params = $this->MadelineProto->td_to_mtproto($this->MadelineProto->tdcliToTd($params));
|
||||
if ($params === 0) {
|
||||
return 0;
|
||||
}
|
||||
$result = $this->MadelineProto->API->methodCall($params['_'], $params, ['datacenter' => $this->MadelineProto->API->datacenter->curdc]);
|
||||
$result = $this->MadelineProto->API->methodCall($params['_'], $params);
|
||||
if (\is_callable($cb)) {
|
||||
$cb($this->MadelineProto->mtproto_to_td($result), $cb_extra);
|
||||
}
|
||||
|
@ -90,14 +93,14 @@ class Lua
|
|||
}
|
||||
public function madelineFunction($params, $cb = null, $cb_extra = null)
|
||||
{
|
||||
$result = $this->MadelineProto->API->methodCall($params['_'], $params, ['datacenter' => $this->MadelineProto->API->datacenter->curdc]);
|
||||
$result = $this->MadelineProto->API->methodCall($params['_'], $params);
|
||||
if (\is_callable($cb)) {
|
||||
$cb($result, $cb_extra);
|
||||
}
|
||||
self::convertObjects($result);
|
||||
return $result;
|
||||
}
|
||||
public function tdcliUpdateCallback($update)
|
||||
public function tdcliUpdateCallback($update): void
|
||||
{
|
||||
$this->Lua->tdcliUpdateCallback($this->MadelineProto->mtproto_to_tdcli($update));
|
||||
}
|
||||
|
@ -112,7 +115,7 @@ class Lua
|
|||
}, \array_flip($array)));
|
||||
}
|
||||
}
|
||||
private function isSequential(array $arr)
|
||||
private function isSequential(array $arr): bool
|
||||
{
|
||||
if ([] === $arr) {
|
||||
return false;
|
||||
|
@ -151,7 +154,7 @@ class Lua
|
|||
{
|
||||
return $this->Lua->{$name} = $value;
|
||||
}
|
||||
public static function convertObjects(&$data)
|
||||
public static function convertObjects(&$data): void
|
||||
{
|
||||
\array_walk_recursive($data, function (&$value, $key) {
|
||||
if (\is_object($value) && !$value instanceof \tgseclib\Math\BigInteger) {
|
||||
|
|
File diff suppressed because one or more lines are too long
54
src/danog/MadelineProto/MTProto/Container.php
Normal file
54
src/danog/MadelineProto/MTProto/Container.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Container message.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\MTProto;
|
||||
|
||||
/**
|
||||
* Outgoing container message.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Container extends OutgoingMessage
|
||||
{
|
||||
/**
|
||||
* Message IDs.
|
||||
*
|
||||
*/
|
||||
private array $ids = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $ids
|
||||
*/
|
||||
public function __construct(array $ids)
|
||||
{
|
||||
$this->ids = $ids;
|
||||
parent::__construct([], 'msg_container', '', false, false);
|
||||
}
|
||||
/**
|
||||
* Get message IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIds(): array
|
||||
{
|
||||
return $this->ids;
|
||||
}
|
||||
}
|
237
src/danog/MadelineProto/MTProto/IncomingMessage.php
Normal file
237
src/danog/MadelineProto/MTProto/IncomingMessage.php
Normal file
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Outgoing message.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\MTProto;
|
||||
|
||||
use Amp\Promise;
|
||||
|
||||
/**
|
||||
* Incoming message.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IncomingMessage extends Message
|
||||
{
|
||||
/**
|
||||
* We have received this message.
|
||||
*/
|
||||
const STATE_RECEIVED = 4;
|
||||
/**
|
||||
* We have acknowledged this message.
|
||||
*/
|
||||
const STATE_ACKED = 8;
|
||||
/**
|
||||
* We have read the contents of this message.
|
||||
*/
|
||||
const STATE_READ = 128;
|
||||
|
||||
/**
|
||||
* Response field map.
|
||||
*/
|
||||
private const RESPONSE_ID_MAP = [
|
||||
'rpc_result' => 'req_msg_id',
|
||||
'future_salts' => 'req_msg_id',
|
||||
'msgs_state_info' => 'req_msg_id',
|
||||
'bad_server_salt' => 'bad_msg_id',
|
||||
'bad_msg_notification' => 'bad_msg_id',
|
||||
'pong' => 'msg_id',
|
||||
];
|
||||
/**
|
||||
* State.
|
||||
*/
|
||||
private int $state = self::STATE_RECEIVED;
|
||||
/**
|
||||
* Receive date.
|
||||
*/
|
||||
private int $received;
|
||||
/**
|
||||
* Deserialized response content.
|
||||
*/
|
||||
private array $content;
|
||||
/**
|
||||
* Was present in container.
|
||||
*/
|
||||
private bool $fromContainer;
|
||||
|
||||
/**
|
||||
* DB side effects to be resolved before using the content.
|
||||
*/
|
||||
private ?Promise $sideEffects = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $content Content
|
||||
* @param boolean $fromContainer Whether this message was in a container
|
||||
*/
|
||||
public function __construct(array $content, string $msgId, bool $fromContainer = false)
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->fromContainer = $fromContainer;
|
||||
$this->msgId = $msgId;
|
||||
|
||||
$this->received = \time();
|
||||
|
||||
$this->contentRelated = !isset(Message::NOT_CONTENT_RELATED[$content['_']]);
|
||||
if (!$this->contentRelated) {
|
||||
$this->state |= 16; // message not requiring acknowledgment
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get deserialized response content.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getContent(): array
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get was present in container.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFromContainer(): bool
|
||||
{
|
||||
return $this->fromContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log line.
|
||||
*
|
||||
* @param int|string $dc DC ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function log($dc): string
|
||||
{
|
||||
if ($this->fromContainer) {
|
||||
return "Inside of container, received {$this->content['_']} from DC $dc";
|
||||
}
|
||||
return "Received {$this->content['_']} from DC $dc";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->content['_'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->content['_'];
|
||||
}
|
||||
|
||||
/**
|
||||
* We have acked this message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ack(): void
|
||||
{
|
||||
$this->state |= self::STATE_ACKED;
|
||||
}
|
||||
/**
|
||||
* Read this message, clearing its contents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function read(): array
|
||||
{
|
||||
$this->state |= self::STATE_READ;
|
||||
$content = $this->content;
|
||||
$this->content = ['_' => $content['_']];
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this message can be garbage collected.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canGarbageCollect(): bool
|
||||
{
|
||||
return (bool) ($this->state & self::STATE_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of message to which this message replies.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestId(): string
|
||||
{
|
||||
return $this->content[self::RESPONSE_ID_MAP[$this->content['_']]];
|
||||
}
|
||||
/**
|
||||
* Get state.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getState(): int
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set DB side effects to be resolved before using the content.
|
||||
*
|
||||
* @param ?Promise $sideEffects DB side effects to be resolved before using the content
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSideEffects(?Promise $sideEffects): self
|
||||
{
|
||||
$this->sideEffects = $sideEffects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* yield DB side effects to be resolved before using the content.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function yieldSideEffects(): \Generator
|
||||
{
|
||||
if ($this->sideEffects) {
|
||||
yield $this->sideEffects;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get receive date.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getReceived(): int
|
||||
{
|
||||
return $this->received;
|
||||
}
|
||||
}
|
160
src/danog/MadelineProto/MTProto/Message.php
Normal file
160
src/danog/MadelineProto/MTProto/Message.php
Normal file
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Message.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\MTProto;
|
||||
|
||||
/**
|
||||
* MTProto message.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class Message
|
||||
{
|
||||
protected const NOT_CONTENT_RELATED = [
|
||||
//'rpc_result' => true,
|
||||
//'rpc_error' => true,
|
||||
'rpc_drop_answer' => true,
|
||||
'rpc_answer_unknown' => true,
|
||||
'rpc_answer_dropped_running' => true,
|
||||
'rpc_answer_dropped' => true,
|
||||
'get_future_salts' => true,
|
||||
'future_salt' => true,
|
||||
'future_salts' => true,
|
||||
'ping' => true,
|
||||
'pong' => true,
|
||||
'ping_delay_disconnect' => true,
|
||||
'destroy_session' => true,
|
||||
'destroy_session_ok' => true,
|
||||
'destroy_session_none' => true,
|
||||
//'new_session_created' => true,
|
||||
'msg_container' => true,
|
||||
'msg_copy' => true,
|
||||
'gzip_packed' => true,
|
||||
'http_wait' => true,
|
||||
'msgs_ack' => true,
|
||||
'bad_msg_notification' => true,
|
||||
'bad_server_salt' => true,
|
||||
'msgs_state_req' => true,
|
||||
'msgs_state_info' => true,
|
||||
'msgs_all_info' => true,
|
||||
'msg_detailed_info' => true,
|
||||
'msg_new_detailed_info' => true,
|
||||
'msg_resend_req' => true,
|
||||
'msg_resend_ans_req' => true,
|
||||
];
|
||||
/**
|
||||
* My message ID.
|
||||
*/
|
||||
protected $msgId = null;
|
||||
|
||||
/**
|
||||
* Sequence number.
|
||||
*/
|
||||
protected ?int $seqNo = null;
|
||||
|
||||
/**
|
||||
* Whether constructor is content related.
|
||||
*/
|
||||
protected bool $contentRelated;
|
||||
|
||||
/**
|
||||
* Get whether constructor is content related.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isContentRelated(): bool
|
||||
{
|
||||
return $this->contentRelated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get my message ID.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMsgId()
|
||||
{
|
||||
return $this->msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set my message ID.
|
||||
*
|
||||
* @param mixed $msgId My message ID
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMsgId($msgId): self
|
||||
{
|
||||
$this->msgId = $msgId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have a message ID.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasMsgId(): bool
|
||||
{
|
||||
return $this->msgId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sequence number.
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function getSeqNo(): ?int
|
||||
{
|
||||
return $this->seqNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has sequence number.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSeqNo(): bool
|
||||
{
|
||||
return isset($this->seqNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sequence number.
|
||||
*
|
||||
* @param ?int $seqNo Sequence number
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSeqNo(?int $seqNo): self
|
||||
{
|
||||
$this->seqNo = $seqNo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this message can be garbage collected.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function canGarbageCollect(): bool;
|
||||
}
|
617
src/danog/MadelineProto/MTProto/OutgoingMessage.php
Normal file
617
src/danog/MadelineProto/MTProto/OutgoingMessage.php
Normal file
|
@ -0,0 +1,617 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Outgoing message.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\MTProto;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\MTProtoSession\MsgIdHandler;
|
||||
|
||||
/**
|
||||
* Outgoing message.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OutgoingMessage extends Message
|
||||
{
|
||||
/**
|
||||
* The message was created.
|
||||
*/
|
||||
const STATE_PENDING = 0;
|
||||
/**
|
||||
* The message was sent.
|
||||
*/
|
||||
const STATE_SENT = 1;
|
||||
/**
|
||||
* The message was acked.
|
||||
*/
|
||||
const STATE_ACKED = 2;
|
||||
/**
|
||||
* We got a reply to the message.
|
||||
*/
|
||||
const STATE_REPLIED = self::STATE_ACKED | 4;
|
||||
|
||||
/**
|
||||
* State of message.
|
||||
*
|
||||
* @var int
|
||||
* @psalm-var self::STATE_*
|
||||
*/
|
||||
private int $state = self::STATE_PENDING;
|
||||
/**
|
||||
* Constructor name.
|
||||
*/
|
||||
private string $constructor;
|
||||
/**
|
||||
* Constructor type.
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
/**
|
||||
* Whether this is a method.
|
||||
*/
|
||||
private bool $method;
|
||||
/**
|
||||
* Resolution deferred.
|
||||
*/
|
||||
private ?Deferred $promise = null;
|
||||
/**
|
||||
* Send deferred.
|
||||
*/
|
||||
private ?Deferred $sendPromise = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether this is an unencrypted message.
|
||||
*/
|
||||
private bool $unencrypted;
|
||||
|
||||
/**
|
||||
* Message body.
|
||||
*
|
||||
* @var \Generator|array|null
|
||||
*/
|
||||
private $body;
|
||||
|
||||
/**
|
||||
* Serialized body.
|
||||
*/
|
||||
private ?string $serializedBody = null;
|
||||
|
||||
/**
|
||||
* Whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||
*/
|
||||
private bool $userRelated = false;
|
||||
/**
|
||||
* Whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||
*/
|
||||
private bool $fileRelated = false;
|
||||
|
||||
/**
|
||||
* Custom flood wait limit for this bot.
|
||||
*/
|
||||
private ?int $floodWaitLimit = null;
|
||||
|
||||
/**
|
||||
* Whether we should try converting the result to a bot API object.
|
||||
*/
|
||||
private bool $botAPI = false;
|
||||
|
||||
/**
|
||||
* Whether we should refresh references upon serialization of this message.
|
||||
*/
|
||||
private bool $refreshReferences = false;
|
||||
|
||||
/**
|
||||
* Queue ID.
|
||||
*/
|
||||
private ?string $queueId = null;
|
||||
|
||||
/**
|
||||
* When was this message sent.
|
||||
*/
|
||||
private int $sent = 0;
|
||||
|
||||
/**
|
||||
* Number of times this message was sent.
|
||||
*/
|
||||
private int $tries = 0;
|
||||
|
||||
/**
|
||||
* Create outgoing message.
|
||||
*
|
||||
* @param \Generator|array $body Body
|
||||
* @param string $constructor Constructor name
|
||||
* @param string $type Constructor type
|
||||
* @param boolean $method Is this a method?
|
||||
* @param boolean $unencrypted Is this an unencrypted message?
|
||||
*/
|
||||
public function __construct($body, string $constructor, string $type, bool $method, bool $unencrypted)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->constructor = $constructor;
|
||||
$this->type = $type;
|
||||
$this->method = $method;
|
||||
$this->unencrypted = $unencrypted;
|
||||
if ($method) {
|
||||
$this->promise = new Deferred;
|
||||
}
|
||||
|
||||
$this->contentRelated = !isset(Message::NOT_CONTENT_RELATED[$constructor]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that we're trying to send the message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function trySend(): void
|
||||
{
|
||||
if (!isset($this->sendPromise)) {
|
||||
$this->sendPromise = new Deferred;
|
||||
}
|
||||
$this->tries++;
|
||||
}
|
||||
/**
|
||||
* Signal that the message was sent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sent(): void
|
||||
{
|
||||
if ($this->state & self::STATE_REPLIED) {
|
||||
throw new Exception("Trying to resend already replied message $this!");
|
||||
}
|
||||
$this->state |= self::STATE_SENT;
|
||||
$this->sent = \time();
|
||||
if (isset($this->sendPromise)) {
|
||||
$sendPromise = $this->sendPromise;
|
||||
$this->sendPromise = null;
|
||||
$sendPromise->resolve($this->promise ?? true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set reply to message.
|
||||
*
|
||||
* @param mixed $result
|
||||
* @return void
|
||||
*/
|
||||
public function reply($result): void
|
||||
{
|
||||
if ($this->state & self::STATE_REPLIED) {
|
||||
throw new Exception("Trying to double reply to message $this!");
|
||||
}
|
||||
$this->serializedBody = null;
|
||||
$this->body = null;
|
||||
|
||||
$this->state |= self::STATE_REPLIED;
|
||||
if ($this->promise) { // Sometimes can get an RPC error for constructors
|
||||
$promise = $this->promise;
|
||||
$this->promise = null;
|
||||
$promise->resolve($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ACK message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ack(): void
|
||||
{
|
||||
$this->state |= self::STATE_ACKED;
|
||||
}
|
||||
/**
|
||||
* Get state of message.
|
||||
*
|
||||
* @return int
|
||||
* @psalm-return self::STATE_*
|
||||
*/
|
||||
public function getState(): int
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get message body.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function getBody(): \Generator
|
||||
{
|
||||
return $this->body instanceof \Generator ? yield from $this->body : $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message body or empty array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBodyOrEmpty(): array
|
||||
{
|
||||
return \is_array($this->body) ? $this->body : [];
|
||||
}
|
||||
/**
|
||||
* Check if we have a body.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasBody(): bool
|
||||
{
|
||||
return $this->body !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get serialized body.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getSerializedBody(): ?string
|
||||
{
|
||||
return $this->serializedBody;
|
||||
}
|
||||
/**
|
||||
* Check if we have a serialized body.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasSerializedBody(): bool
|
||||
{
|
||||
return $this->serializedBody !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of times this message was sent.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTries(): int
|
||||
{
|
||||
return $this->tries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get constructor name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConstructor(): string
|
||||
{
|
||||
return $this->constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get constructor type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this is a method.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMethod(): bool
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this is an unencrypted message.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUnencrypted(): bool
|
||||
{
|
||||
return $this->unencrypted;
|
||||
}
|
||||
/**
|
||||
* Get whether this is an encrypted message.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted(): bool
|
||||
{
|
||||
return !$this->unencrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserRelated(): bool
|
||||
{
|
||||
return $this->userRelated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether we should refresh references upon serialization of this message.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldRefreshReferences(): bool
|
||||
{
|
||||
return $this->refreshReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue ID.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getQueueId(): ?string
|
||||
{
|
||||
return $this->queueId;
|
||||
}
|
||||
/**
|
||||
* Get whether we have a queue ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasQueue(): bool
|
||||
{
|
||||
return $this->queueId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set serialized body.
|
||||
*
|
||||
* @param string $serializedBody Serialized body.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSerializedBody(string $serializedBody): self
|
||||
{
|
||||
$this->serializedBody = $serializedBody;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||
*
|
||||
* @param bool $userRelated Whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setUserRelated(bool $userRelated): self
|
||||
{
|
||||
$this->userRelated = $userRelated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we should refresh references upon serialization of this message.
|
||||
*
|
||||
* @param bool $refreshReferences Whether we should refresh references upon serialization of this message.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRefreshReferences(bool $refreshReferences): self
|
||||
{
|
||||
$this->refreshReferences = $refreshReferences;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set queue ID.
|
||||
*
|
||||
* @param ?string $queueId Queue ID.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setQueueId(?string $queueId): self
|
||||
{
|
||||
$this->queueId = $queueId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when was this message sent.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSent(): int
|
||||
{
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the message was sent.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function wasSent(): bool
|
||||
{
|
||||
return (bool) ($this->state & self::STATE_SENT);
|
||||
}
|
||||
/**
|
||||
* Check if can garbage collect this message.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canGarbageCollect(): bool
|
||||
{
|
||||
if ($this->state & self::STATE_REPLIED) {
|
||||
return true;
|
||||
}
|
||||
if (!$this->hasPromise()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* For logging.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->msgId) {
|
||||
$msgId = MsgIdHandler::toString($this->msgId);
|
||||
return "{$this->constructor} with message ID $msgId";
|
||||
}
|
||||
return $this->constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set resolution deferred.
|
||||
*
|
||||
* @param Deferred $promise Resolution deferred.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPromise(Deferred $promise): self
|
||||
{
|
||||
$this->promise = $promise;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for message to be sent.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function getSendPromise(): Promise
|
||||
{
|
||||
if (!$this->sendPromise) {
|
||||
throw new Exception("Message was already sent, can't get send promise!");
|
||||
}
|
||||
return $this->sendPromise->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have a promise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPromise(): bool
|
||||
{
|
||||
return $this->promise !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sent time to trigger resending.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetSent(): self
|
||||
{
|
||||
$this->sent = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether we should try converting the result to a bot API object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getBotAPI(): bool
|
||||
{
|
||||
return $this->botAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we should try converting the result to a bot API object.
|
||||
*
|
||||
* @param bool $botAPI Whether we should try converting the result to a bot API object
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setBotAPI(bool $botAPI): self
|
||||
{
|
||||
$this->botAPI = $botAPI;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFileRelated(): bool
|
||||
{
|
||||
return $this->fileRelated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||
*
|
||||
* @param bool $fileRelated Whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFileRelated(bool $fileRelated): self
|
||||
{
|
||||
$this->fileRelated = $fileRelated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom flood wait limit for this bot.
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function getFloodWaitLimit(): ?int
|
||||
{
|
||||
return $this->floodWaitLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom flood wait limit for this bot.
|
||||
*
|
||||
* @param ?int $floodWaitLimit Custom flood wait limit for this bot
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFloodWaitLimit(?int $floodWaitLimit): self
|
||||
{
|
||||
$this->floodWaitLimit = $floodWaitLimit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when was this message sent.
|
||||
*
|
||||
* @param int $sent When was this message sent.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSent(int $sent): self
|
||||
{
|
||||
$this->sent = $sent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -166,9 +166,9 @@ class TempAuthKey extends AuthKey implements JsonSerializable
|
|||
/**
|
||||
* Wakeup function.
|
||||
*
|
||||
* @return array
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
public function __wakeup(): void
|
||||
{
|
||||
$this->inited = (bool) $this->inited;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,15 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\DataCenterConnection;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
|
||||
/**
|
||||
* Manages acknowledgement of messages.
|
||||
*
|
||||
* @property DataCenterConnection $shared
|
||||
*/
|
||||
trait AckHandler
|
||||
{
|
||||
|
@ -41,45 +48,35 @@ trait AckHandler
|
|||
return true;
|
||||
}
|
||||
/**
|
||||
* We have gotten response for outgoing message ID.
|
||||
* We have gotten a response for an outgoing message.
|
||||
*
|
||||
* @param string|int $message_id Message ID
|
||||
* @param OutgoingMessage $message Message
|
||||
*
|
||||
* @return boolean
|
||||
* @return void
|
||||
*/
|
||||
public function gotResponseForOutgoingMessageId($message_id): bool
|
||||
public function gotResponseForOutgoingMessage(OutgoingMessage $outgoingMessage): void
|
||||
{
|
||||
// The server acknowledges that it received my message
|
||||
if (isset($this->new_outgoing[$message_id])) {
|
||||
unset($this->new_outgoing[$message_id]);
|
||||
if (isset($this->new_outgoing[$outgoingMessage->getMsgId()])) {
|
||||
unset($this->new_outgoing[$outgoingMessage->getMsgId()]);
|
||||
} else {
|
||||
$this->logger->logger("Could not find $outgoingMessage in new_outgoing!", Logger::FATAL_ERROR);
|
||||
}
|
||||
if (!isset($this->outgoing_messages[$message_id])) {
|
||||
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
|
||||
return false;
|
||||
}
|
||||
if (isset($this->outgoing_messages[$message_id]['body'])) {
|
||||
unset($this->outgoing_messages[$message_id]['body']);
|
||||
}
|
||||
if (isset($this->outgoing_messages[$message_id]['serialized_body'])) {
|
||||
unset($this->outgoing_messages[$message_id]['serialized_body']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Acknowledge incoming message ID.
|
||||
*
|
||||
* @param string|int $message_id Message ID
|
||||
* @param IncomingMessage $message Message
|
||||
*
|
||||
* @return boolean
|
||||
* @return void
|
||||
*/
|
||||
public function ackIncomingMessageId($message_id): bool
|
||||
public function ackIncomingMessage(IncomingMessage $message): void
|
||||
{
|
||||
// Not exactly true, but we don't care
|
||||
$message->ack();
|
||||
$message_id = $message->getMsgId();
|
||||
// I let the server know that I received its message
|
||||
if (!isset($this->incoming_messages[$message_id])) {
|
||||
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of incoming messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
|
||||
}
|
||||
$this->ack_queue[$message_id] = $message_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,15 +86,18 @@ trait AckHandler
|
|||
*/
|
||||
public function hasPendingCalls(): bool
|
||||
{
|
||||
$settings = $this->shared->getSettings();
|
||||
$timeout = $settings['timeout'];
|
||||
$pfs = $settings['pfs'];
|
||||
$timeout = $this->shared->getSettings()->getTimeout();
|
||||
$pfs = $this->shared->getGenericSettings()->getAuth()->getPfs();
|
||||
$unencrypted = !$this->shared->hasTempAuthKey();
|
||||
$notBound = !$this->shared->isBound();
|
||||
$pfsNotBound = $pfs && $notBound;
|
||||
foreach ($this->new_outgoing as $message_id) {
|
||||
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted'] && $this->outgoing_messages[$message_id]['_'] !== 'msgs_state_req') {
|
||||
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
|
||||
/** @var OutgoingMessage */
|
||||
foreach ($this->new_outgoing as $message) {
|
||||
if ($message->wasSent()
|
||||
&& $message->getSent() + $timeout < \time()
|
||||
&& $message->isUnencrypted() === $unencrypted
|
||||
&& $message->getConstructor() !== 'msgs_state_req') {
|
||||
if ($pfsNotBound && $message->getConstructor() !== 'auth.bindTempAuthKey') {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
|
@ -113,25 +113,34 @@ trait AckHandler
|
|||
public function getPendingCalls(): array
|
||||
{
|
||||
$settings = $this->shared->getSettings();
|
||||
$dropTimeout = $settings['drop_timeout'];
|
||||
$timeout = $settings['timeout'];
|
||||
$pfs = $settings['pfs'];
|
||||
$global = $this->shared->getGenericSettings();
|
||||
$dropTimeout = $global->getRpc()->getRpcTimeout();
|
||||
$timeout = $settings->getTimeout();
|
||||
$pfs = $global->getAuth()->getPfs();
|
||||
$unencrypted = !$this->shared->hasTempAuthKey();
|
||||
$notBound = !$this->shared->isBound();
|
||||
$pfsNotBound = $pfs && $notBound;
|
||||
$result = [];
|
||||
foreach ($this->new_outgoing as $k => $message_id) {
|
||||
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted']) {
|
||||
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
|
||||
/** @var OutgoingMessage $message */
|
||||
foreach ($this->new_outgoing as $message_id => $message) {
|
||||
if ($message->wasSent()
|
||||
&& $message->getSent() + $timeout < \time()
|
||||
&& $message->isUnencrypted() === $unencrypted
|
||||
) {
|
||||
if ($pfsNotBound && $message->getConstructor() !== 'auth.bindTempAuthKey') {
|
||||
continue;
|
||||
}
|
||||
if ($this->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
|
||||
unset($this->new_outgoing[$k], $this->outgoing_messages[$message_id]);
|
||||
if ($message->getConstructor() === 'msgs_state_req') {
|
||||
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
|
||||
continue;
|
||||
}
|
||||
if ($this->outgoing_messages[$message_id]['sent'] + $dropTimeout < \time()) {
|
||||
$this->gotResponseForOutgoingMessageId($message_id);
|
||||
$this->handleReject($this->outgoing_messages[$message_id], new \danog\MadelineProto\Exception("Request timeout"));
|
||||
if ($message->getSent() + $dropTimeout < \time()) {
|
||||
$this->handleReject($message, new \danog\MadelineProto\Exception("Request timeout"));
|
||||
continue;
|
||||
}
|
||||
if ($message->getState() & OutgoingMessage::STATE_REPLIED) {
|
||||
$this->logger->logger("Already replied to message $message, but still in new_outgoing");
|
||||
unset($this->new_outgoing[$message_id]);
|
||||
continue;
|
||||
}
|
||||
$result[] = $message_id;
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Async\AsyncParameters;
|
||||
use danog\MadelineProto\MTProto\Container;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
use danog\MadelineProto\TL\Exception;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
|
@ -46,25 +46,31 @@ trait CallHandler
|
|||
if ($datacenter === $this->datacenter) {
|
||||
$datacenter = false;
|
||||
}
|
||||
$message_ids = $this->outgoing_messages[$message_id]['container'] ?? [$message_id];
|
||||
$message_ids = ($this->outgoing_messages[$message_id] ?? null) instanceof Container
|
||||
? $this->outgoing_messages[$message_id]->getIds()
|
||||
: [$message_id];
|
||||
foreach ($message_ids as $message_id) {
|
||||
if (isset($this->outgoing_messages[$message_id]['body'])) {
|
||||
if (isset($this->outgoing_messages[$message_id])
|
||||
&& !$this->outgoing_messages[$message_id]->canGarbageCollect()) {
|
||||
if ($datacenter) {
|
||||
unset($this->outgoing_messages[$message_id]['msg_id'], $this->outgoing_messages[$message_id]['seqno']);
|
||||
Tools::call($this->API->datacenter->waitGetConnection($datacenter))->onResolve(function ($e, $r) use ($message_id) {
|
||||
Tools::callFork($r->sendMessage($this->outgoing_messages[$message_id], false));
|
||||
/** @var OutgoingMessage */
|
||||
$message = $this->outgoing_messages[$message_id];
|
||||
$this->gotResponseForOutgoingMessage($message);
|
||||
$message->setMsgId(null);
|
||||
$message->setSeqNo(null);
|
||||
Tools::call($this->API->datacenter->waitGetConnection($datacenter))->onResolve(function ($e, $r) use ($message) {
|
||||
Tools::callFork($r->sendMessage($message, false));
|
||||
});
|
||||
$this->ackOutgoingMessageId($message_id);
|
||||
$this->gotResponseForOutgoingMessageId($message_id);
|
||||
} else {
|
||||
Tools::callFork($this->sendMessage($this->outgoing_messages[$message_id], false));
|
||||
if (!isset($this->outgoing_messages[$message_id]['seqno'])) {
|
||||
$this->ackOutgoingMessageId($message_id);
|
||||
$this->gotResponseForOutgoingMessageId($message_id);
|
||||
/** @var OutgoingMessage */
|
||||
$message = $this->outgoing_messages[$message_id];
|
||||
if (!$message->hasSeqNo()) {
|
||||
$this->gotResponseForOutgoingMessage($message);
|
||||
}
|
||||
Tools::callFork($this->sendMessage($message, false));
|
||||
}
|
||||
} else {
|
||||
$this->logger->logger('Could not resend '.(isset($this->outgoing_messages[$message_id]['_']) ? $this->outgoing_messages[$message_id]['_'] : $message_id));
|
||||
$this->logger->logger('Could not resend '.($this->outgoing_messages[$message_id] ?? $message_id));
|
||||
}
|
||||
}
|
||||
if (!$postpone) {
|
||||
|
@ -80,9 +86,11 @@ trait CallHandler
|
|||
*
|
||||
* If the $aargs['noResponse'] is true, will not wait for a response.
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
* @param string $method Method name
|
||||
* @param array|\Generator $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @psalm-param array|\Generator<mixed, mixed, mixed, array> $args
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
|
@ -101,9 +109,11 @@ trait CallHandler
|
|||
/**
|
||||
* Call method and make sure it is asynchronously sent (generator).
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
* @param string $method Method name
|
||||
* @param array|\Generator $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @psalm-param array|\Generator<mixed, mixed, mixed, array> $args
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
|
@ -152,32 +162,31 @@ trait CallHandler
|
|||
$args['ping_id'] = Tools::packSignedLong($args['ping_id']);
|
||||
}
|
||||
}
|
||||
$deferred = new Deferred();
|
||||
$methodInfo = $this->API->getTL()->getMethods()->findByMethod($method);
|
||||
if (!$methodInfo) {
|
||||
throw new Exception("Could not find method $method!");
|
||||
}
|
||||
$message = \array_merge(
|
||||
$aargs,
|
||||
[
|
||||
'_' => $method,
|
||||
'type' => $methodInfo['type'],
|
||||
'contentRelated' => $this->contentRelated($method),
|
||||
'promise' => $deferred,
|
||||
'method' => true,
|
||||
'unencrypted' => !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
|
||||
]
|
||||
$message = new OutgoingMessage(
|
||||
$args,
|
||||
$method,
|
||||
$methodInfo['type'],
|
||||
true,
|
||||
!$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
|
||||
);
|
||||
if (\is_object($args) && $args instanceof AsyncParameters) {
|
||||
$message['body'] = yield $args->fetchParameters();
|
||||
} else {
|
||||
$message['body'] = $args;
|
||||
}
|
||||
if ($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]] || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
|
||||
$message['user_related'] = true;
|
||||
$message->setUserRelated(true);
|
||||
}
|
||||
if (isset($aargs['msg_id'])) {
|
||||
$message->setMsgId($aargs['msg_id']);
|
||||
}
|
||||
if ($aargs['file'] ?? false) {
|
||||
$message->setFileRelated(true);
|
||||
}
|
||||
if (isset($aargs['FloodWaitLimit'])) {
|
||||
$message->setFloodWaitLimit($aargs['FloodWaitLimit']);
|
||||
}
|
||||
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
||||
$deferred = (yield from $this->sendMessage($message, !$aargs['postpone']));
|
||||
$deferred = yield from $this->sendMessage($message, !$aargs['postpone']);
|
||||
$this->checker->resume();
|
||||
return $deferred;
|
||||
}
|
||||
|
@ -188,15 +197,21 @@ trait CallHandler
|
|||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @return Promise
|
||||
* @return \Generator
|
||||
*/
|
||||
public function objectCall(string $object, $args = [], array $aargs = ['msg_id' => null]): \Generator
|
||||
{
|
||||
$message = ['_' => $object, 'body' => $args, 'contentRelated' => $this->contentRelated($object), 'unencrypted' => !$this->shared->hasTempAuthKey(), 'method' => false];
|
||||
$message = new OutgoingMessage(
|
||||
$args,
|
||||
$object,
|
||||
'',
|
||||
false,
|
||||
!$this->shared->hasTempAuthKey()
|
||||
);
|
||||
if (isset($aargs['promise'])) {
|
||||
$message['promise'] = $aargs['promise'];
|
||||
$message->setPromise($aargs['promise']);
|
||||
}
|
||||
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
||||
return $this->sendMessage($message, !$aargs['postpone']);
|
||||
return yield from $this->sendMessage($message, !$aargs['postpone']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\Connection;
|
||||
use danog\MadelineProto\MTProtoSession\MsgIdHandler\MsgIdHandler32;
|
||||
use danog\MadelineProto\MTProtoSession\MsgIdHandler\MsgIdHandler64;
|
||||
|
||||
|
@ -29,16 +30,14 @@ abstract class MsgIdHandler
|
|||
{
|
||||
/**
|
||||
* Session instance.
|
||||
*
|
||||
* @var Session
|
||||
*/
|
||||
protected $session;
|
||||
protected Connection $session;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Session $session Session
|
||||
* @param Connection $session Session
|
||||
*/
|
||||
private function __construct(Session $session)
|
||||
private function __construct(Connection $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
@ -46,11 +45,11 @@ abstract class MsgIdHandler
|
|||
/**
|
||||
* Create MsgIdHandler instance.
|
||||
*
|
||||
* @param Session $session Session
|
||||
* @param Connection $session Session
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function createInstance(Session $session): self
|
||||
public static function createInstance(Connection $session): self
|
||||
{
|
||||
if (PHP_INT_SIZE === 8) {
|
||||
return new MsgIdHandler64($session);
|
||||
|
@ -81,4 +80,50 @@ abstract class MsgIdHandler
|
|||
* @return mixed
|
||||
*/
|
||||
abstract public function getMaxId(bool $incoming);
|
||||
|
||||
/**
|
||||
* Get readable representation of message ID.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return string
|
||||
*/
|
||||
abstract protected static function toStringInternal(string $messageId): string;
|
||||
|
||||
/**
|
||||
* Cleanup incoming or outgoing messages.
|
||||
*
|
||||
* @param boolean $incoming
|
||||
* @return void
|
||||
*/
|
||||
protected function cleanup(bool $incoming): void
|
||||
{
|
||||
if ($incoming) {
|
||||
$array = &$this->session->incoming_messages;
|
||||
} else {
|
||||
$array = &$this->session->outgoing_messages;
|
||||
}
|
||||
if (\count($array) > $this->session->API->settings->getRpc()->getLimitOutgoing()) {
|
||||
\reset($array);
|
||||
$key = \key($array);
|
||||
foreach ($array as $key => $message) {
|
||||
if ($message->canGarbageCollect()) {
|
||||
unset($array[$key]);
|
||||
break;
|
||||
}
|
||||
$this->session->API->logger->logger("Can't garbage collect $message", \danog\MadelineProto\Logger::VERBOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get readable representation of message ID.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return string
|
||||
*/
|
||||
public static function toString(string $messageId): string
|
||||
{
|
||||
return PHP_INT_SIZE === 8
|
||||
? MsgIdHandler64::toStringInternal($messageId)
|
||||
: MsgIdHandler32::toStringInternal($messageId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,15 +30,15 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
/**
|
||||
* Maximum incoming ID.
|
||||
*
|
||||
* @var BigInteger
|
||||
* @var ?BigInteger
|
||||
*/
|
||||
private $maxIncomingId;
|
||||
private $maxIncomingId = null;
|
||||
/**
|
||||
* Maximum outgoing ID.
|
||||
*
|
||||
* @var BigInteger
|
||||
* @var ?BigInteger
|
||||
*/
|
||||
private $maxOutgoingId;
|
||||
private $maxOutgoingId = null;
|
||||
/**
|
||||
* Check validity of given message ID.
|
||||
*
|
||||
|
@ -66,15 +66,8 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
if ($newMessageId->compare($key = $this->getMaxId($incoming = false)) <= 0) {
|
||||
throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', 1);
|
||||
}
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) {
|
||||
\reset($this->session->outgoing_messages);
|
||||
$key = \key($this->session->outgoing_messages);
|
||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
||||
unset($this->session->outgoing_messages[$key]);
|
||||
}
|
||||
}
|
||||
$this->cleanup(false);
|
||||
$this->maxOutgoingId = $newMessageId;
|
||||
$this->session->outgoing_messages[\strrev($newMessageId->toBytes())] = [];
|
||||
} else {
|
||||
if (!$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) {
|
||||
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
||||
|
@ -89,15 +82,8 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
$this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
|
||||
}
|
||||
}
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) {
|
||||
\reset($this->session->incoming_messages);
|
||||
$key = \key($this->session->incoming_messages);
|
||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
||||
unset($this->session->incoming_messages[$key]);
|
||||
}
|
||||
}
|
||||
$this->cleanup(true);
|
||||
$this->maxIncomingId = $newMessageId;
|
||||
$this->session->incoming_messages[\strrev($newMessageId->toBytes())] = [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -140,4 +126,15 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
$this->maxIncomingId = null;
|
||||
$this->maxOutgoingId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get readable representation of message ID.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return string
|
||||
*/
|
||||
protected static function toStringInternal(string $messageId): string
|
||||
{
|
||||
return new BigInteger(\strrev($messageId), 256);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,15 +65,8 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||
if ($newMessageId <= $this->maxOutgoingId) {
|
||||
throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$this->maxOutgoingId.'). Consider syncing your date.');
|
||||
}
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) {
|
||||
\reset($this->session->outgoing_messages);
|
||||
$key = \key($this->session->outgoing_messages);
|
||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
||||
unset($this->session->outgoing_messages[$key]);
|
||||
}
|
||||
}
|
||||
$this->cleanup(false);
|
||||
$this->maxOutgoingId = $newMessageId;
|
||||
$this->session->outgoing_messages[Tools::packSignedLong($newMessageId)] = [];
|
||||
} else {
|
||||
if (!($newMessageId % 2)) {
|
||||
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
||||
|
@ -88,15 +81,8 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||
$this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
|
||||
}
|
||||
}
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) {
|
||||
\reset($this->session->incoming_messages);
|
||||
$key = \key($this->session->incoming_messages);
|
||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
||||
unset($this->session->incoming_messages[$key]);
|
||||
}
|
||||
}
|
||||
$this->cleanup(true);
|
||||
$this->maxIncomingId = $newMessageId;
|
||||
$this->session->incoming_messages[Tools::packSignedLong($newMessageId)] = [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -124,4 +110,15 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||
{
|
||||
return $this->{$incoming ? 'maxIncomingId' : 'maxOutgoingId'};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get readable representation of message ID.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return string
|
||||
*/
|
||||
protected static function toStringInternal(string $messageId): string
|
||||
{
|
||||
return (string) Tools::unpackSignedLong($messageId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,20 +19,108 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* Manages responses.
|
||||
*/
|
||||
trait Reliable
|
||||
{
|
||||
/**
|
||||
* Called when receiving a new_msg_detailed_info.
|
||||
*
|
||||
* @param array $content
|
||||
* @return void
|
||||
*/
|
||||
public function onNewMsgDetailedInfo(array $content): void
|
||||
{
|
||||
if (isset($this->incoming_messages[$content['answer_msg_id']])) {
|
||||
$this->ackIncomingMessage($this->incoming_messages[$content['answer_msg_id']]);
|
||||
} else {
|
||||
Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$content['answer_msg_id']]], ['postpone' => true]));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when receiving a msg_detailed_info.
|
||||
*
|
||||
* @param array $content
|
||||
* @return void
|
||||
*/
|
||||
public function onMsgDetailedInfo(array $content): void
|
||||
{
|
||||
if (isset($this->outgoing_messages[$content['msg_id']])) {
|
||||
$this->onNewMsgDetailedInfo($content);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when receiving a msg_resend_req.
|
||||
*
|
||||
* @param array $content
|
||||
* @param string $current_msg_id
|
||||
* @return void
|
||||
*/
|
||||
public function onMsgResendReq(array $content, $current_msg_id): void
|
||||
{
|
||||
$ok = true;
|
||||
foreach ($content['msg_ids'] as $msg_id) {
|
||||
if (!isset($this->outgoing_messages[$msg_id]) || isset($this->incoming_messages[$msg_id])) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
foreach ($content['msg_ids'] as $msg_id) {
|
||||
$this->methodRecall('', ['message_id' => $msg_id, 'postpone' => true]);
|
||||
}
|
||||
} else {
|
||||
$this->sendMsgsStateInfo($content['msg_ids'], $current_msg_id);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when receiving a msg_resend_ans_req.
|
||||
*
|
||||
* @param array $content
|
||||
* @param string $current_msg_id
|
||||
* @return void
|
||||
*/
|
||||
public function onMsgResendAnsReq(array $content, $current_msg_id): void
|
||||
{
|
||||
$this->sendMsgsStateInfo($content['msg_ids'], $current_msg_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when receiving a msgs_all_info.
|
||||
*
|
||||
* @param array $content
|
||||
* @return void
|
||||
*/
|
||||
public function onMsgsAllInfo(array $content): void
|
||||
{
|
||||
foreach ($content['msg_ids'] as $key => $msg_id) {
|
||||
$info = \ord($content['info'][$key]);
|
||||
$msg_id = MsgIdHandler::toString($msg_id);
|
||||
$status = 'Status for message id '.$msg_id.': ';
|
||||
/*if ($info & 4) {
|
||||
*$this->gotResponseForOutgoingMessageId($msg_id);
|
||||
*}
|
||||
*/
|
||||
foreach (MTProto::MSGS_INFO_FLAGS as $flag => $description) {
|
||||
if (($info & $flag) !== 0) {
|
||||
$status .= $description;
|
||||
}
|
||||
}
|
||||
$this->logger->logger($status, \danog\MadelineProto\Logger::NOTICE);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send state info for message IDs.
|
||||
*
|
||||
* @param string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
||||
* @param array $msg_ids Message IDs to send info about
|
||||
* @param string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
||||
*
|
||||
* @return \Generator
|
||||
* @return void
|
||||
*/
|
||||
public function sendMsgsStateInfo($req_msg_id, array $msg_ids): \Generator
|
||||
public function sendMsgsStateInfo(array $msg_ids, $req_msg_id): void
|
||||
{
|
||||
$this->logger->logger('Sending state info for '.\count($msg_ids).' message IDs');
|
||||
$info = '';
|
||||
|
@ -52,10 +140,10 @@ trait Reliable
|
|||
}
|
||||
} else {
|
||||
$this->logger->logger("Know about {$msg_id}");
|
||||
$cur_info |= 4;
|
||||
$cur_info = $this->incoming_messages[$msg_id]->getState();
|
||||
}
|
||||
$info .= \chr($cur_info);
|
||||
}
|
||||
$this->outgoing_messages[yield from $this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true])]['response'] = $req_msg_id;
|
||||
Tools::callFork($this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,496 +19,385 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use Amp\Failure;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\Update\UpdateLoop;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* Manages responses.
|
||||
*
|
||||
* @extend Session
|
||||
*/
|
||||
trait ResponseHandler
|
||||
{
|
||||
public $n = 0;
|
||||
public function handleMessages()
|
||||
public function handleMessages(): void
|
||||
{
|
||||
$only_updates = true;
|
||||
while ($this->new_incoming) {
|
||||
\reset($this->new_incoming);
|
||||
$current_msg_id = \key($this->new_incoming);
|
||||
if (!isset($this->incoming_messages[$current_msg_id])) {
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
continue;
|
||||
|
||||
/** @var IncomingMessage */
|
||||
$message = $this->new_incoming[$current_msg_id];
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
|
||||
|
||||
$this->logger->logger($message->log($this->datacenter), Logger::ULTRA_VERBOSE);
|
||||
|
||||
$type = $message->getType();
|
||||
if ($type !== 'msg_container') {
|
||||
$this->checkInSeqNo($message);
|
||||
}
|
||||
$this->logger->logger((isset($this->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ').$this->incoming_messages[$current_msg_id]['content']['_'].' from DC '.$this->datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
switch ($this->incoming_messages[$current_msg_id]['content']['_']) {
|
||||
switch ($type) {
|
||||
case 'msgs_ack':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
||||
$this->ackOutgoingMessageId($msg_id);
|
||||
foreach ($message->read()['msg_ids'] as $msg_id) {
|
||||
// Acknowledge that the server received my message
|
||||
$this->ackOutgoingMessageId($msg_id);
|
||||
}
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
break;
|
||||
case 'rpc_result':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->ackIncomingMessageId($current_msg_id);
|
||||
$only_updates = false;
|
||||
// Acknowledge that the server received my request
|
||||
$req_msg_id = $this->incoming_messages[$current_msg_id]['content']['req_msg_id'];
|
||||
$this->incoming_messages[$current_msg_id]['content'] = $this->incoming_messages[$current_msg_id]['content']['result'];
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$this->handleResponse($req_msg_id, $current_msg_id);
|
||||
break;
|
||||
$this->ackIncomingMessage($message);
|
||||
// no break
|
||||
case 'future_salts':
|
||||
case 'msgs_state_info':
|
||||
$msg_id_type = 'req_msg_id';
|
||||
// no break
|
||||
case 'bad_server_salt':
|
||||
case 'bad_msg_notification':
|
||||
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'bad_msg_id';
|
||||
// no break
|
||||
case 'pong':
|
||||
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'msg_id';
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
$this->handleResponse($this->incoming_messages[$current_msg_id]['content'][$msg_id_type], $current_msg_id);
|
||||
unset($msg_id_type);
|
||||
$this->handleResponse($message);
|
||||
break;
|
||||
case 'new_session_created':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
$this->shared->getTempAuthKey()->setServerSalt($this->incoming_messages[$current_msg_id]['content']['server_salt']);
|
||||
$this->ackIncomingMessageId($current_msg_id);
|
||||
// Acknowledge that I received the server's response
|
||||
$this->ackIncomingMessage($message);
|
||||
$this->shared->getTempAuthKey()->setServerSalt($message->read()['server_salt']);
|
||||
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[UpdateLoop::GENERIC])) {
|
||||
$this->API->updaters[UpdateLoop::GENERIC]->resumeDefer();
|
||||
}
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
break;
|
||||
case 'msg_container':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$only_updates = false;
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['messages'] as $message) {
|
||||
foreach ($message->read()['messages'] as $message) {
|
||||
$this->msgIdHandler->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]);
|
||||
$this->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body'], 'from_container' => true];
|
||||
$this->new_incoming[$message['msg_id']] = $message['msg_id'];
|
||||
$newMessage = new IncomingMessage($message['body'], $message['msg_id'], true);
|
||||
$newMessage->setSeqNo($message['seqno']);
|
||||
$this->new_incoming[$message['msg_id']] = $this->incoming_messages[$message['msg_id']] = $newMessage;
|
||||
}
|
||||
unset($newMessage, $message);
|
||||
\ksort($this->new_incoming);
|
||||
//$this->handleMessages();
|
||||
//$this->checkInSeqNo($current_msg_id);
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
break;
|
||||
case 'msg_copy':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
$this->ackIncomingMessageId($current_msg_id);
|
||||
// Acknowledge that I received the server's response
|
||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']])) {
|
||||
$this->ackIncomingMessageId($this->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']);
|
||||
// Acknowledge that I received the server's response
|
||||
$this->ackIncomingMessage($message);
|
||||
$content = $message->read();
|
||||
$referencedMsgId = $content['msg_id'];
|
||||
if (isset($this->incoming_messages[$referencedMsgId])) {
|
||||
$this->ackIncomingMessage($this->incoming_messages[$referencedMsgId]);
|
||||
} else {
|
||||
$message = $this->incoming_messages[$current_msg_id]['content'];
|
||||
$this->msgIdHandler->checkMessageId($message['orig_message']['msg_id'], ['outgoing' => false, 'container' => true]);
|
||||
$this->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $this->incoming_messages[$current_msg_id]['content']['orig_message']];
|
||||
$this->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id'];
|
||||
$this->msgIdHandler->checkMessageId($referencedMsgId, ['outgoing' => false, 'container' => true]);
|
||||
$message = new IncomingMessage($content['orig_message'], $referencedMsgId);
|
||||
$this->new_incoming[$referencedMsgId] = $this->incoming_messages[$referencedMsgId] = $message;
|
||||
unset($message);
|
||||
}
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
unset($content, $referencedMsgId);
|
||||
break;
|
||||
case 'http_wait':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
$this->logger->logger($this->incoming_messages[$current_msg_id]['content'], \danog\MadelineProto\Logger::NOTICE);
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
$this->logger->logger($message->read(), Logger::NOTICE);
|
||||
break;
|
||||
case 'msgs_state_req':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
$this->sendMsgsStateInfo($message->read()['msg_ids'], $current_msg_id);
|
||||
break;
|
||||
case 'msgs_all_info':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $key => $msg_id) {
|
||||
$info = \ord($this->incoming_messages[$current_msg_id]['content']['info'][$key]);
|
||||
$msg_id = new \tgseclib\Math\BigInteger(\strrev($msg_id), 256);
|
||||
$status = 'Status for message id '.$msg_id.': ';
|
||||
/*if ($info & 4) {
|
||||
*$this->gotResponseForOutgoingMessageId($msg_id);
|
||||
*}
|
||||
*/
|
||||
foreach (MTProto::MSGS_INFO_FLAGS as $flag => $description) {
|
||||
if (($info & $flag) !== 0) {
|
||||
$status .= $description;
|
||||
}
|
||||
}
|
||||
$this->logger->logger($status, \danog\MadelineProto\Logger::NOTICE);
|
||||
}
|
||||
$this->onMsgsAllInfo($message->read());
|
||||
break;
|
||||
case 'msg_detailed_info':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$only_updates = false;
|
||||
if (isset($this->outgoing_messages[$this->incoming_messages[$current_msg_id]['content']['msg_id']])) {
|
||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
|
||||
$this->handleResponse($this->incoming_messages[$current_msg_id]['content']['msg_id'], $this->incoming_messages[$current_msg_id]['content']['answer_msg_id']);
|
||||
} else {
|
||||
\danog\MadelineProto\Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']]], ['postpone' => true]));
|
||||
}
|
||||
}
|
||||
$this->onMsgDetailedInfo($message->read());
|
||||
break;
|
||||
case 'msg_new_detailed_info':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
|
||||
$this->ackIncomingMessageId($this->incoming_messages[$current_msg_id]['content']['answer_msg_id']);
|
||||
} else {
|
||||
\danog\MadelineProto\Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']]], ['postpone' => true]));
|
||||
}
|
||||
$this->onNewMsgDetailedInfo($message->read());
|
||||
break;
|
||||
case 'msg_resend_req':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$ok = true;
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
||||
if (!isset($this->outgoing_messages[$msg_id]) || isset($this->incoming_messages[$msg_id])) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
||||
$this->methodRecall('', ['message_id' => $msg_id, 'postpone' => true]);
|
||||
}
|
||||
} else {
|
||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
||||
}
|
||||
$this->onMsgResendReq($message->read(), $current_msg_id);
|
||||
break;
|
||||
case 'msg_resend_ans_req':
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$only_updates = false;
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
||||
if (isset($this->incoming_messages[$msg_id]['response']) && isset($this->outgoing_messages[$this->incoming_messages[$msg_id]['response']])) {
|
||||
\danog\MadelineProto\Tools::callFork($this->objectCall($this->outgoing_messages[$this->incoming_messages[$msg_id]['response']]['_'], $this->outgoing_messages[$this->incoming_messages[$msg_id]['response']]['body'], ['postpone' => true]));
|
||||
}
|
||||
}
|
||||
$this->onMsgResendAnsReq($message->read(), $current_msg_id);
|
||||
break;
|
||||
default:
|
||||
$this->checkInSeqNo($current_msg_id);
|
||||
$this->ackIncomingMessageId($current_msg_id);
|
||||
// Acknowledge that I received the server's response
|
||||
$response_type = $this->API->getTL()->getConstructors()->findByPredicate($this->incoming_messages[$current_msg_id]['content']['_'])['type'];
|
||||
switch ($response_type) {
|
||||
case 'Updates':
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
if (!$this->isCdn()) {
|
||||
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($this->incoming_messages[$current_msg_id]['content']));
|
||||
}
|
||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
||||
$only_updates = true && $only_updates;
|
||||
break;
|
||||
default:
|
||||
$only_updates = false;
|
||||
$this->logger->logger('Trying to assign a response of type '.$response_type.' to its request...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
foreach ($this->new_outgoing as $key => $expecting_msg_id) {
|
||||
$expecting = $this->outgoing_messages[$expecting_msg_id];
|
||||
if (!isset($expecting['type'])) {
|
||||
continue;
|
||||
}
|
||||
$this->logger->logger('Does the request of return type '.$expecting['type'].' match?', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if ($response_type === $expecting['type']) {
|
||||
$this->logger->logger('Yes', \danog\MadelineProto\Logger::VERBOSE);
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
$this->handleResponse($expecting_msg_id, $current_msg_id);
|
||||
break 2;
|
||||
}
|
||||
$this->logger->logger('No', \danog\MadelineProto\Logger::VERBOSE);
|
||||
}
|
||||
$this->logger->logger('Dunno how to handle '.PHP_EOL.\var_export($this->incoming_messages[$current_msg_id]['content'], true), \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
unset($this->new_incoming[$current_msg_id]);
|
||||
break;
|
||||
$this->ackIncomingMessage($message);
|
||||
$response_type = $this->API->getTL()->getConstructors()->findByPredicate($message->getContent()['_'])['type'];
|
||||
if ($response_type == 'Updates') {
|
||||
if (!$this->isCdn()) {
|
||||
Tools::callForkDefer($this->API->handleUpdates($message->read()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$this->logger->logger('Trying to assign a response of type '.$response_type.' to its request...', Logger::VERBOSE);
|
||||
foreach ($this->new_outgoing as $expecting_msg_id => $expecting) {
|
||||
if (!$type = $expecting->getType()) {
|
||||
continue;
|
||||
}
|
||||
$this->logger->logger("Does the request of return type $type match?", Logger::VERBOSE);
|
||||
if ($response_type === $type) {
|
||||
$this->logger->logger('Yes', Logger::VERBOSE);
|
||||
$this->handleResponse($message, $expecting_msg_id);
|
||||
break 2;
|
||||
}
|
||||
$this->logger->logger('No', Logger::VERBOSE);
|
||||
}
|
||||
$this->logger->logger('Dunno how to handle '.PHP_EOL.\var_export($message->read(), true), Logger::FATAL_ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($this->pending_outgoing) {
|
||||
if ($this->pendingOutgoing) {
|
||||
$this->writer->resume();
|
||||
}
|
||||
return $only_updates;
|
||||
}
|
||||
public function handleReject(OutgoingMessage $message, \Throwable $data): void
|
||||
{
|
||||
$this->gotResponseForOutgoingMessage($message);
|
||||
Loop::defer(fn () => $message->reply(new Failure($data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject request with exception.
|
||||
* Handle RPC response.
|
||||
*
|
||||
* @param array $request Request
|
||||
* @param \Throwable $data Exception
|
||||
* @param IncomingMessage $message Incoming message
|
||||
* @param string $requestId Request ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleReject(&$request, \Throwable $data)
|
||||
private function handleResponse(IncomingMessage $message, $requestId = null): void
|
||||
{
|
||||
if (isset($request['promise']) && \is_object($request['promise'])) {
|
||||
Loop::defer(function () use (&$request, $data) {
|
||||
if (isset($request['promise'])) {
|
||||
$this->logger->logger('Rejecting: '.(isset($request['_']) ? $request['_'] : '-'));
|
||||
$this->logger->logger("Rejecting: {$data}");
|
||||
$promise = $request['promise'];
|
||||
unset($request['promise']);
|
||||
try {
|
||||
$promise->fail($data);
|
||||
} catch (\Error $e) {
|
||||
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
|
||||
throw $e;
|
||||
}
|
||||
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
}
|
||||
} else {
|
||||
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
|
||||
$this->logger->logger("Rejecting: {$data}");
|
||||
}
|
||||
});
|
||||
} elseif (isset($request['container'])) {
|
||||
foreach ($request['container'] as $message_id) {
|
||||
$this->handleReject($this->outgoing_messages[$message_id], $data);
|
||||
}
|
||||
} else {
|
||||
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
|
||||
$this->logger->logger("Rejecting: {$data}");
|
||||
}
|
||||
}
|
||||
public function handleResponse($request_id, $response_id)
|
||||
{
|
||||
$response =& $this->incoming_messages[$response_id]['content'];
|
||||
unset($this->incoming_messages[$response_id]['content']);
|
||||
$request =& $this->outgoing_messages[$request_id];
|
||||
if (isset($response['_'])) {
|
||||
switch ($response['_']) {
|
||||
case 'rpc_error':
|
||||
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||
$this->shared->getTempAuthKey()->init(true);
|
||||
}
|
||||
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) {
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->handleReject($request, new \danog\MadelineProto\PTSException($response['error_message']));
|
||||
return;
|
||||
}
|
||||
if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') {
|
||||
$response['error_code'] = 500;
|
||||
}
|
||||
if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
|
||||
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
|
||||
$request['refreshReferences'] = true;
|
||||
if (isset($request['serialized_body'])) {
|
||||
unset($request['serialized_body']);
|
||||
}
|
||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
||||
return;
|
||||
}
|
||||
switch ($response['error_code']) {
|
||||
case 500:
|
||||
case -500:
|
||||
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
|
||||
$this->call_queue[$request['queue']] = [];
|
||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
||||
return;
|
||||
}
|
||||
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
|
||||
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
|
||||
return;
|
||||
}
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
return;
|
||||
case 303:
|
||||
$this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||
if (isset($request['file']) && $request['file'] && $this->API->datacenter->has($datacenter.'_media')) {
|
||||
$datacenter .= '_media';
|
||||
}
|
||||
if (isset($request['user_related']) && $request['user_related']) {
|
||||
$this->API->settings['connection_settings']['default_dc'] = $this->API->authorized_dc = $this->API->datacenter->curdc;
|
||||
}
|
||||
Loop::defer([$this, 'methodRecall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
|
||||
//$this->API->methodRecall('', ['message_id' => $request_id, 'datacenter' => $datacenter, 'postpone' => true]);
|
||||
return;
|
||||
case 401:
|
||||
switch ($response['error_message']) {
|
||||
case 'USER_DEACTIVATED':
|
||||
case 'SESSION_REVOKED':
|
||||
case 'SESSION_EXPIRED':
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->logger->logger($response['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setPermAuthKey(null);
|
||||
$socket->resetSession();
|
||||
}
|
||||
if ($response['error_message'] === 'USER_DEACTIVATED') {
|
||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
|
||||
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$phone = isset($this->authorization['user']['phone']) ? '+'.$this->authorization['user']['phone'] : 'you are currently using';
|
||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and shortly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
}
|
||||
$this->API->resetSession();
|
||||
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
})());
|
||||
return;
|
||||
case 'AUTH_KEY_UNREGISTERED':
|
||||
case 'AUTH_KEY_INVALID':
|
||||
if ($this->API->authorized !== MTProto::LOGGED_IN) {
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
})());
|
||||
return;
|
||||
}
|
||||
$this->session_id = null;
|
||||
$this->shared->setTempAuthKey(null);
|
||||
$this->shared->setPermAuthKey(null);
|
||||
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
|
||||
if ($this->API->authorized_dc === $this->datacenter && $this->API->authorized === MTProto::LOGGED_IN) {
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setPermAuthKey(null);
|
||||
}
|
||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
|
||||
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$phone = isset($this->authorization['user']['phone']) ? '+'.$this->authorization['user']['phone'] : 'you are currently using';
|
||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
$this->API->resetSession();
|
||||
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
})());
|
||||
return;
|
||||
}
|
||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $request_id]);
|
||||
})());
|
||||
return;
|
||||
case 'AUTH_KEY_PERM_EMPTY':
|
||||
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
|
||||
$this->shared->setTempAuthKey(null);
|
||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $request_id]);
|
||||
})());
|
||||
return;
|
||||
}
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
return;
|
||||
case 420:
|
||||
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||
$limit = $request['FloodWaitLimit'] ?? $this->API->settings['flood_timeout']['wait_if_lt'];
|
||||
if (\is_numeric($seconds) && $seconds < $limit) {
|
||||
//$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->logger->logger('Flood, waiting '.$seconds.' seconds before repeating async call of '.($request['_'] ?? '').'...', \danog\MadelineProto\Logger::NOTICE);
|
||||
$request['sent'] = ($request['sent'] ?? \time()) + $seconds;
|
||||
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
|
||||
return;
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
case 'boolTrue':
|
||||
case 'boolFalse':
|
||||
$response = $response['_'] === 'boolTrue';
|
||||
break;
|
||||
case 'bad_server_salt':
|
||||
case 'bad_msg_notification':
|
||||
$this->logger->logger('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
|
||||
switch ($response['error_code']) {
|
||||
case 48:
|
||||
$this->shared->getTempAuthKey()->setServerSalt($response['new_server_salt']);
|
||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
||||
return;
|
||||
case 16:
|
||||
case 17:
|
||||
$this->time_delta = (int) (new \tgseclib\Math\BigInteger(\strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \tgseclib\Math\BigInteger(\time()))->toString();
|
||||
$this->logger->logger('Set time delta to '.$this->time_delta, \danog\MadelineProto\Logger::WARNING);
|
||||
$this->API->resetMTProtoSession();
|
||||
$this->shared->setTempAuthKey(null);
|
||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $request_id]);
|
||||
})());
|
||||
return;
|
||||
}
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request['_'] ?? ''));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||
$this->shared->getTempAuthKey()->init(true);
|
||||
}
|
||||
if (!isset($request['promise'])) {
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->logger->logger('Response: already got response for '.(isset($request['_']) ? $request['_'] : '-').' with message ID '.$request_id);
|
||||
$requestId ??= $message->getRequestId();
|
||||
$response = $message->read();
|
||||
if (!isset($this->outgoing_messages[$requestId])) {
|
||||
$requestId = MsgIdHandler::toString($requestId);
|
||||
$this->logger->logger("Got a reponse $message with message ID $requestId, but there is no request!", Logger::FATAL_ERROR);
|
||||
return;
|
||||
}
|
||||
$botAPI = isset($request['botAPI']) && $request['botAPI'];
|
||||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
||||
$body = [];
|
||||
if (isset($request['body']['peer'])) {
|
||||
$body['peer'] = $this->API->getID($request['body']['peer']);
|
||||
}
|
||||
if (isset($request['body']['message'])) {
|
||||
$body['message'] = (string) $request['body']['message'];
|
||||
}
|
||||
$response['request'] = ['_' => $request['_'], 'body' => $body];
|
||||
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($response));
|
||||
/** @var OutgoingMessage */
|
||||
$request = $this->outgoing_messages[$requestId];
|
||||
if ($request->getState() & OutgoingMessage::STATE_REPLIED) {
|
||||
$this->logger->logger("Already got a response to $request, but there is another reply $message with message ID $requestId!", Logger::FATAL_ERROR);
|
||||
return;
|
||||
}
|
||||
unset($request);
|
||||
$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
|
||||
if ($response['_'] === 'rpc_result') {
|
||||
$response = $response['result'];
|
||||
}
|
||||
$constructor = $response['_'] ?? '';
|
||||
if ($constructor === 'rpc_error') {
|
||||
try {
|
||||
$exception = $this->handleRpcError($request, $response);
|
||||
} catch (\Throwable $e) {
|
||||
$exception = $e;
|
||||
}
|
||||
if ($exception) {
|
||||
$this->handleReject($request, $exception);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ($constructor === 'bad_server_salt' || $constructor === 'bad_msg_notification') {
|
||||
$this->logger->logger('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], Logger::WARNING);
|
||||
switch ($response['error_code']) {
|
||||
case 48:
|
||||
$this->shared->getTempAuthKey()->setServerSalt($response['new_server_salt']);
|
||||
$this->methodRecall('', ['message_id' => $requestId, 'postpone' => true]);
|
||||
return;
|
||||
case 20:
|
||||
$this->methodRecall('', ['message_id' => $requestId, 'postpone' => true]);
|
||||
return;
|
||||
case 16:
|
||||
case 17:
|
||||
$this->time_delta = (int) (new \tgseclib\Math\BigInteger(\strrev($message->getMsgId()), 256))->bitwise_rightShift(32)->subtract(new \tgseclib\Math\BigInteger(\time()))->toString();
|
||||
$this->logger->logger('Set time delta to '.$this->time_delta, Logger::WARNING);
|
||||
$this->API->resetMTProtoSession();
|
||||
$this->shared->setTempAuthKey(null);
|
||||
Tools::callFork((function () use ($requestId): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $requestId]);
|
||||
})());
|
||||
return;
|
||||
}
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request->getConstructor()));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request->isMethod() && $request->getConstructor() !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||
$this->shared->getTempAuthKey()->init(true);
|
||||
}
|
||||
$botAPI = $request->getBotAPI();
|
||||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
||||
$body = $request->getBodyOrEmpty();
|
||||
$trimmed = [];
|
||||
if (isset($body['peer'])) {
|
||||
$trimmed['peer'] = \is_string($body['peer']) ? $body['peer'] : $this->API->getId($body['peer']);
|
||||
}
|
||||
if (isset($body['message'])) {
|
||||
$trimmed['message'] = (string) $body['message'];
|
||||
}
|
||||
$response['request'] = ['_' => $request->getConstructor(), 'body' => $trimmed];
|
||||
unset($body);
|
||||
Tools::callForkDefer($this->API->handleUpdates($response));
|
||||
}
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
|
||||
$r = $response['_'] ?? \json_encode($response);
|
||||
$this->logger->logger("Defer sending {$r} to deferred", Logger::ULTRA_VERBOSE);
|
||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id, $response, $botAPI): \Generator {
|
||||
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
|
||||
$this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE);
|
||||
if ($botAPI) {
|
||||
$response = (yield from $this->API->MTProtoToBotAPI($response));
|
||||
}
|
||||
if (isset($this->outgoing_messages[$request_id]['promise'])) {
|
||||
// This should not happen but happens, should debug
|
||||
$promise = $this->outgoing_messages[$request_id]['promise'];
|
||||
unset($this->outgoing_messages[$request_id]['promise']);
|
||||
try {
|
||||
$promise->resolve($response);
|
||||
} catch (\Error $e) {
|
||||
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
|
||||
throw $e;
|
||||
}
|
||||
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
|
||||
if (!$botAPI) {
|
||||
Tools::callForkDefer((function () use ($request, $message, $response): \Generator {
|
||||
yield from $message->yieldSideEffects();
|
||||
$request->reply($response);
|
||||
})());
|
||||
} else {
|
||||
Tools::callForkDefer((function () use ($request, $message, $response): \Generator {
|
||||
yield from $message->yieldSideEffects();
|
||||
$response = yield from $this->API->MTProtoToBotAPI($response);
|
||||
$request->reply($response);
|
||||
})());
|
||||
}
|
||||
}
|
||||
public function handleRpcError(OutgoingMessage $request, array $response): ?\Throwable
|
||||
{
|
||||
if ($request->isMethod() && $request->getConstructor() !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||
$this->shared->getTempAuthKey()->init(true);
|
||||
}
|
||||
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) {
|
||||
return new \danog\MadelineProto\PTSException($response['error_message']);
|
||||
}
|
||||
if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') {
|
||||
$response['error_code'] = 500;
|
||||
}
|
||||
if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
|
||||
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
$msgId = $request->getMsgId();
|
||||
$request->setRefreshReferences(true);
|
||||
$request->setMsgId(null);
|
||||
$request->setSeqNo(null);
|
||||
$this->methodRecall('', ['message_id' => $msgId, 'postpone' => true]);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($response['error_code']) {
|
||||
case 500:
|
||||
case -500:
|
||||
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
|
||||
$this->call_queue[$request->getQueueId()] = [];
|
||||
$this->methodRecall('', ['message_id' => $request->getMsgId(), 'postpone' => true]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})());
|
||||
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
|
||||
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request->getMsgId()]);
|
||||
return null;
|
||||
}
|
||||
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||
case 303:
|
||||
$this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||
if ($request->isFileRelated() && $this->API->datacenter->has($datacenter.'_media')) {
|
||||
$datacenter .= '_media';
|
||||
}
|
||||
if ($request->isUserRelated()) {
|
||||
$this->API->settings->setDefaultDc($this->API->authorized_dc = $this->API->datacenter->curdc);
|
||||
}
|
||||
Loop::defer([$this, 'methodRecall'], ['message_id' => $request->getMsgId(), 'datacenter' => $datacenter]);
|
||||
//$this->API->methodRecall('', ['message_id' => $requestId, 'datacenter' => $datacenter, 'postpone' => true]);
|
||||
return null;
|
||||
case 401:
|
||||
switch ($response['error_message']) {
|
||||
case 'USER_DEACTIVATED':
|
||||
case 'USER_DEACTIVATED_BAN':
|
||||
case 'SESSION_REVOKED':
|
||||
case 'SESSION_EXPIRED':
|
||||
$this->logger->logger($response['error_message'], Logger::FATAL_ERROR);
|
||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setPermAuthKey(null);
|
||||
$socket->resetSession();
|
||||
}
|
||||
if (\in_array($response['error_message'], ['USER_DEACTIVATED', 'USER_DEACTIVATED_BAN'], true)) {
|
||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', Logger::FATAL_ERROR);
|
||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", Logger::ERROR);
|
||||
$this->logger->logger('To continue, manual verification is required.', Logger::FATAL_ERROR);
|
||||
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and shortly describe what will you do with this phone number.', Logger::FATAL_ERROR);
|
||||
$this->logger->logger('Then login again.', Logger::FATAL_ERROR);
|
||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', Logger::FATAL_ERROR);
|
||||
}
|
||||
$this->API->resetSession();
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
Tools::callFork((function () use ($request, $response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||
})());
|
||||
return null;
|
||||
case 'AUTH_KEY_UNREGISTERED':
|
||||
case 'AUTH_KEY_INVALID':
|
||||
if ($this->API->authorized !== MTProto::LOGGED_IN) {
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
Tools::callFork((function () use ($request, $response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||
})());
|
||||
return null;
|
||||
}
|
||||
$this->session_id = null;
|
||||
$this->shared->setTempAuthKey(null);
|
||||
$this->shared->setPermAuthKey(null);
|
||||
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', Logger::ERROR);
|
||||
if ($this->API->authorized_dc === $this->datacenter && $this->API->authorized === MTProto::LOGGED_IN) {
|
||||
$this->logger->logger('Permanent auth key was main authorized key, logging out...', Logger::FATAL_ERROR);
|
||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setPermAuthKey(null);
|
||||
}
|
||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', Logger::FATAL_ERROR);
|
||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", Logger::ERROR);
|
||||
$this->logger->logger('To continue, manual verification is required.', Logger::FATAL_ERROR);
|
||||
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', Logger::FATAL_ERROR);
|
||||
$this->logger->logger('Then login again.', Logger::FATAL_ERROR);
|
||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', Logger::FATAL_ERROR);
|
||||
$this->API->resetSession();
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
Tools::callFork((function () use ($request, &$response): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||
})());
|
||||
return null;
|
||||
}
|
||||
Tools::callFork((function () use ($request): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $request->getMsgId()]);
|
||||
})());
|
||||
return null;
|
||||
case 'AUTH_KEY_PERM_EMPTY':
|
||||
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', Logger::ERROR);
|
||||
$this->shared->setTempAuthKey(null);
|
||||
Tools::callFork((function () use ($request): \Generator {
|
||||
yield from $this->API->initAuthorization();
|
||||
$this->methodRecall('', ['message_id' => $request->getMsgId()]);
|
||||
})());
|
||||
return null;
|
||||
}
|
||||
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||
case 420:
|
||||
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||
$limit = $request->getFloodWaitLimit() ?? $this->API->settings->getRPC()->getFloodTimeout();
|
||||
if (\is_numeric($seconds) && $seconds < $limit) {
|
||||
$this->logger->logger("Flood, waiting $seconds seconds before repeating async call of $request...", Logger::NOTICE);
|
||||
$this->gotResponseForOutgoingMessage($request);
|
||||
$msgId = $request->getMsgId();
|
||||
$request->setSent(($request->getSent() ?? \time()) + $seconds);
|
||||
$request->setMsgId(null);
|
||||
$request->setSeqNo(null);
|
||||
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $msgId]);
|
||||
return null;
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||
|
||||
/**
|
||||
* Manages sequence number.
|
||||
|
@ -37,11 +37,16 @@ trait SeqNoHandler
|
|||
//$this->API->logger->logger("OUT: $value + $in = ".$this->session_out_seq_no);
|
||||
return $value * 2 + $in;
|
||||
}
|
||||
public function checkInSeqNo($current_msg_id)
|
||||
public function checkInSeqNo(IncomingMessage $message): void
|
||||
{
|
||||
$type = isset($this->incoming_messages[$current_msg_id]['content']['_']) ? $this->incoming_messages[$current_msg_id]['content']['_'] : '-';
|
||||
if (isset($this->incoming_messages[$current_msg_id]['seq_no']) && ($seq_no = $this->generateInSeqNo($this->contentRelated($this->incoming_messages[$current_msg_id]['content']))) !== $this->incoming_messages[$current_msg_id]['seq_no']) {
|
||||
$this->API->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$this->incoming_messages[$current_msg_id]['seq_no'].', '.$type.')', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
if ($message->hasSeqNo()) {
|
||||
$seq_no = $this->generateInSeqNo($message->isContentRelated());
|
||||
if ($seq_no !== $message->getSeqNo()) {
|
||||
if ($message->isContentRelated()) {
|
||||
$this->session_in_seq_no -= 1;
|
||||
}
|
||||
$this->API->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$message->getSeqNo().", $message)", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
public function generateInSeqNo($contentRelated)
|
||||
|
@ -52,9 +57,40 @@ trait SeqNoHandler
|
|||
//$this->API->logger->logger("IN: $value + $in = ".$this->session_in_seq_no);
|
||||
return $value * 2 + $in;
|
||||
}
|
||||
public function contentRelated($method)
|
||||
public function contentRelated($method): bool
|
||||
{
|
||||
$method = \is_array($method) && isset($method['_']) ? $method['_'] : $method;
|
||||
return \is_string($method) ? !\in_array($method, MTProto::NOT_CONTENT_RELATED) : true;
|
||||
return \is_string($method) ? !\in_array($method, [
|
||||
//'rpc_result',
|
||||
//'rpc_error',
|
||||
'rpc_drop_answer',
|
||||
'rpc_answer_unknown',
|
||||
'rpc_answer_dropped_running',
|
||||
'rpc_answer_dropped',
|
||||
'get_future_salts',
|
||||
'future_salt',
|
||||
'future_salts',
|
||||
'ping',
|
||||
'pong',
|
||||
'ping_delay_disconnect',
|
||||
'destroy_session',
|
||||
'destroy_session_ok',
|
||||
'destroy_session_none',
|
||||
//'new_session_created',
|
||||
'msg_container',
|
||||
'msg_copy',
|
||||
'gzip_packed',
|
||||
'http_wait',
|
||||
'msgs_ack',
|
||||
'bad_msg_notification',
|
||||
'bad_server_salt',
|
||||
'msgs_state_req',
|
||||
'msgs_state_info',
|
||||
'msgs_all_info',
|
||||
'msg_detailed_info',
|
||||
'msg_new_detailed_info',
|
||||
'msg_resend_req',
|
||||
'msg_resend_ans_req',
|
||||
]) : true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,17 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\Connection;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||
|
||||
/**
|
||||
* Manages MTProto session-specific data.
|
||||
*
|
||||
* @extends Connection
|
||||
*/
|
||||
abstract class Session
|
||||
trait Session
|
||||
{
|
||||
use AckHandler;
|
||||
use ResponseHandler;
|
||||
|
@ -32,39 +39,39 @@ abstract class Session
|
|||
/**
|
||||
* Incoming message array.
|
||||
*
|
||||
* @var array
|
||||
* @var IncomingMessage[]
|
||||
*/
|
||||
public $incoming_messages = [];
|
||||
/**
|
||||
* Outgoing message array.
|
||||
*
|
||||
* @var array
|
||||
* @var OutgoingMessage[]
|
||||
*/
|
||||
public $outgoing_messages = [];
|
||||
/**
|
||||
* New incoming message ID array.
|
||||
*
|
||||
* @var array
|
||||
* @var IncomingMessage[]
|
||||
*/
|
||||
public $new_incoming = [];
|
||||
/**
|
||||
* New outgoing message ID array.
|
||||
* New outgoing message array.
|
||||
*
|
||||
* @var array
|
||||
* @var OutgoingMessage[]
|
||||
*/
|
||||
public $new_outgoing = [];
|
||||
/**
|
||||
* Pending outgoing messages.
|
||||
*
|
||||
* @var array
|
||||
* @var OutgoingMessage[]
|
||||
*/
|
||||
public $pending_outgoing = [];
|
||||
public $pendingOutgoing = [];
|
||||
/**
|
||||
* Pending outgoing key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pending_outgoing_key = 'a';
|
||||
public $pendingOutgoingKey = 'a';
|
||||
/**
|
||||
* Time delta with server.
|
||||
*
|
||||
|
@ -108,16 +115,19 @@ abstract class Session
|
|||
*/
|
||||
public function resetSession(): void
|
||||
{
|
||||
$this->API->logger->logger("Resetting session in DC {$this->datacenterId}...", \danog\MadelineProto\Logger::WARNING);
|
||||
$this->session_id = \danog\MadelineProto\Tools::random(8);
|
||||
$this->session_in_seq_no = 0;
|
||||
$this->session_out_seq_no = 0;
|
||||
$this->msgIdHandler = MsgIdHandler::createInstance($this);
|
||||
if (!isset($this->msgIdHandler)) {
|
||||
$this->msgIdHandler = MsgIdHandler::createInstance($this);
|
||||
}
|
||||
foreach ($this->outgoing_messages as &$msg) {
|
||||
if (isset($msg['msg_id'])) {
|
||||
unset($msg['msg_id']);
|
||||
if ($msg->hasMsgId()) {
|
||||
$msg->setMsgId(null);
|
||||
}
|
||||
if (isset($msg['seqno'])) {
|
||||
unset($msg['seqno']);
|
||||
if ($msg->hasSeqNo()) {
|
||||
$msg->setSeqNo(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,10 +149,7 @@ abstract class Session
|
|||
*/
|
||||
public function backupSession(): array
|
||||
{
|
||||
$pending = \array_values($this->pending_outgoing);
|
||||
foreach ($this->new_outgoing as $id) {
|
||||
$pending[] = $this->outgoing_messages[$id];
|
||||
}
|
||||
return $pending;
|
||||
$pending = \array_values($this->pendingOutgoing);
|
||||
return \array_merge($pending, $this->new_outgoing);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ namespace danog\MadelineProto\MTProtoTools;
|
|||
use Amp\Http\Client\Request;
|
||||
use danog\MadelineProto\DataCenterConnection;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\MTProto\AuthKey;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\PrimeModule;
|
||||
|
||||
use tgseclib\Math\BigInteger;
|
||||
|
||||
/**
|
||||
|
@ -33,6 +34,8 @@ use tgseclib\Math\BigInteger;
|
|||
*
|
||||
* https://core.telegram.org/mtproto/auth_key
|
||||
* https://core.telegram.org/mtproto/samples-auth_key
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait AuthKeyHandler
|
||||
{
|
||||
|
@ -56,16 +59,18 @@ trait AuthKeyHandler
|
|||
*
|
||||
* @internal
|
||||
*
|
||||
* @return \Generator<AuthKey>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<mixed, mixed|string, mixed, \danog\MadelineProto\MTProto\PermAuthKey|\danog\MadelineProto\MTProto\TempAuthKey|null>
|
||||
*/
|
||||
public function createAuthKey(int $expires_in, string $datacenter): \Generator
|
||||
{
|
||||
$connection = $this->datacenter->getAuthConnection($datacenter);
|
||||
$cdn = $connection->isCDN();
|
||||
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id_total++) {
|
||||
try {
|
||||
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->logger->logger("Requesting pq...", \danog\MadelineProto\Logger::VERBOSE);
|
||||
/**
|
||||
* ***********************************************************************
|
||||
* Make pq request, DH exchange initiation.
|
||||
|
@ -224,6 +229,7 @@ trait AuthKeyHandler
|
|||
* Separate answer and hash
|
||||
*/
|
||||
$answer_hash = \substr($answer_with_hash, 0, 20);
|
||||
/** @var string */
|
||||
$answer = \substr($answer_with_hash, 20);
|
||||
/*
|
||||
* ***********************************************************************
|
||||
|
@ -237,7 +243,7 @@ trait AuthKeyHandler
|
|||
* int $server_time
|
||||
* ]
|
||||
*/
|
||||
$server_DH_inner_data = yield from $this->TL->deserialize($answer, ['type' => '']);
|
||||
[$server_DH_inner_data] = $this->TL->deserialize($answer, ['type' => '']);
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Do some checks
|
||||
|
@ -262,14 +268,14 @@ trait AuthKeyHandler
|
|||
$server_time = $server_DH_inner_data['server_time'];
|
||||
$connection->time_delta = $server_time - \time();
|
||||
$this->logger->logger(\sprintf('Server-client time delta = %.1f s', $connection->time_delta), \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->checkPG($dh_prime, $g);
|
||||
$this->checkG($g_a, $dh_prime);
|
||||
for ($retry_id = 0; $retry_id <= $this->settings['max_tries']['authorization']; $retry_id++) {
|
||||
Crypt::checkPG($dh_prime, $g);
|
||||
Crypt::checkG($g_a, $dh_prime);
|
||||
for ($retry_id = 0; $retry_id <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id++) {
|
||||
$this->logger->logger('Generating b...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$b = new BigInteger(\danog\MadelineProto\Tools::random(256), 256);
|
||||
$this->logger->logger('Generating g_b...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$g_b = $g->powMod($b, $dh_prime);
|
||||
$this->checkG($g_b, $dh_prime);
|
||||
Crypt::checkG($g_b, $dh_prime);
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of g_b
|
||||
|
@ -396,87 +402,6 @@ trait AuthKeyHandler
|
|||
throw new \danog\MadelineProto\SecurityException('Auth Failed');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check validity of g_a parameters.
|
||||
*
|
||||
* @param BigInteger $g_a
|
||||
* @param BigInteger $p
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkG(BigInteger $g_a, BigInteger $p): bool
|
||||
{
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of g_a
|
||||
* 1 < g_a < p - 1
|
||||
*/
|
||||
$this->logger->logger('Executing g_a check (1/2)...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if ($g_a->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
|
||||
throw new \danog\MadelineProto\SecurityException('g_a is invalid (1 < g_a < p - 1 is false).');
|
||||
}
|
||||
$this->logger->logger('Executing g_a check (2/2)...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if ($g_a->compare(\danog\MadelineProto\Magic::$twoe1984) < 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$twoe1984)) >= 0) {
|
||||
throw new \danog\MadelineProto\SecurityException('g_a is invalid (2^1984 < g_a < p - 2^1984 is false).');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Check validity of p and g parameters.
|
||||
*
|
||||
* @param BigInteger $p
|
||||
* @param BigInteger $g
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function checkPG(BigInteger $p, BigInteger $g): bool
|
||||
{
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of dh_prime
|
||||
* Is it a prime?
|
||||
*/
|
||||
$this->logger->logger('Executing p/g checks (1/2)...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if (!$p->isPrime()) {
|
||||
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime (p isn't a prime).");
|
||||
}
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of p
|
||||
* Is (p - 1) / 2 a prime?
|
||||
*
|
||||
* Almost always fails
|
||||
*/
|
||||
/*
|
||||
$this->logger->logger('Executing p/g checks (2/3)...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if (!$p->subtract(\danog\MadelineProto\Magic::$one)->divide(\danog\MadelineProto\Magic::$two)[0]->isPrime()) {
|
||||
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime ((p - 1) / 2 isn't a prime).");
|
||||
}
|
||||
*/
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of p
|
||||
* 2^2047 < p < 2^2048
|
||||
*/
|
||||
$this->logger->logger('Executing p/g checks (2/2)...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if ($p->compare(\danog\MadelineProto\Magic::$twoe2047) <= 0 || $p->compare(\danog\MadelineProto\Magic::$twoe2048) >= 0) {
|
||||
throw new \danog\MadelineProto\SecurityException("g isn't a safe 2048-bit prime (2^2047 < p < 2^2048 is false).");
|
||||
}
|
||||
/*
|
||||
* ***********************************************************************
|
||||
* Check validity of g
|
||||
* 1 < g < p - 1
|
||||
*/
|
||||
$this->logger->logger('Executing g check...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
if ($g->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
|
||||
throw new \danog\MadelineProto\SecurityException('g is invalid (1 < g < p - 1 is false).');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Get diffie-hellman configuration.
|
||||
*
|
||||
|
@ -486,14 +411,14 @@ trait AuthKeyHandler
|
|||
*/
|
||||
public function getDhConfig(): \Generator
|
||||
{
|
||||
$dh_config = yield from $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
|
||||
$dh_config = yield from $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0]);
|
||||
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
|
||||
$this->logger->logger('DH configuration not modified', \danog\MadelineProto\Logger::VERBOSE);
|
||||
return $this->dh_config;
|
||||
}
|
||||
$dh_config['p'] = new BigInteger((string) $dh_config['p'], 256);
|
||||
$dh_config['g'] = new BigInteger($dh_config['g']);
|
||||
$this->checkPG($dh_config['p'], $dh_config['g']);
|
||||
Crypt::checkPG($dh_config['p'], $dh_config['g']);
|
||||
return $this->dh_config = $dh_config;
|
||||
}
|
||||
/**
|
||||
|
@ -504,13 +429,15 @@ trait AuthKeyHandler
|
|||
*
|
||||
* @internal
|
||||
*
|
||||
* @return \Generator<bool>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int|mixed, array|mixed, mixed, true>
|
||||
*/
|
||||
public function bindTempAuthKey(int $expires_in, string $datacenter): \Generator
|
||||
{
|
||||
$datacenterConnection = $this->datacenter->getDataCenterConnection($datacenter);
|
||||
$connection = $datacenterConnection->getAuthConnection();
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id_total++) {
|
||||
try {
|
||||
$this->logger->logger('Binding authorization keys...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$nonce = \danog\MadelineProto\Tools::random(8);
|
||||
|
@ -548,7 +475,9 @@ trait AuthKeyHandler
|
|||
*
|
||||
* @param string|integer $what Number to factorize
|
||||
*
|
||||
* @return \Generator<string|bool>
|
||||
* @return \Generator
|
||||
*
|
||||
* @psalm-return \Generator<int, \Amp\Promise<string>, mixed, false|int|string>
|
||||
*/
|
||||
private function wolframSingle($what): \Generator
|
||||
{
|
||||
|
@ -614,7 +543,7 @@ trait AuthKeyHandler
|
|||
$this->pending_auth = true;
|
||||
continue;
|
||||
}
|
||||
$dcs[$id] = function () use ($id, $socket) {
|
||||
$dcs[$id] = function () use ($id, $socket): \Generator {
|
||||
return $this->initAuthorizationSocket($id, $socket);
|
||||
};
|
||||
}
|
||||
|
@ -625,6 +554,7 @@ trait AuthKeyHandler
|
|||
foreach ($dcs as $id => &$dc) {
|
||||
$dc = $dc();
|
||||
}
|
||||
/** @var \Generator[] $dcs */
|
||||
yield \danog\MadelineProto\Tools::all($dcs);
|
||||
foreach ($postpone as $id => $socket) {
|
||||
yield from $this->initAuthorizationSocket($id, $socket);
|
||||
|
@ -669,19 +599,19 @@ trait AuthKeyHandler
|
|||
return;
|
||||
}
|
||||
}
|
||||
if ($this->datacenter->getDataCenterConnection($id)->getSettings()['pfs']) {
|
||||
if ($this->getSettings()->getAuth()->getPfs()) {
|
||||
if (!$cdn) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
//$authorized = $socket->authorized;
|
||||
//$socket->authorized = false;
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
yield from $this->bindTempAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
yield from $this->bindTempAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id);
|
||||
$this->config = yield from $connection->methodCallAsyncRead('help.getConfig', []);
|
||||
yield from $this->syncAuthorization($id);
|
||||
} elseif (!$socket->hasTempAuthKey()) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
}
|
||||
} else {
|
||||
if (!$cdn) {
|
||||
|
@ -690,7 +620,7 @@ trait AuthKeyHandler
|
|||
yield from $this->syncAuthorization($id);
|
||||
} elseif (!$socket->hasTempAuthKey()) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
}
|
||||
}
|
||||
} elseif (!$cdn) {
|
||||
|
|
|
@ -19,17 +19,23 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use danog\MadelineProto\Settings;
|
||||
|
||||
/**
|
||||
* Manages method and object calls.
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait CallHandler
|
||||
{
|
||||
/**
|
||||
* Synchronous wrapper for methodCall.
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
* @param string $method Method name
|
||||
* @param array|\Generator $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @psalm-param array|\Generator<mixed, mixed, mixed, array> $args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -42,9 +48,11 @@ trait CallHandler
|
|||
*
|
||||
* If the $aargs['noResponse'] is true, will not wait for a response.
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
* @param string $method Method name
|
||||
* @param array|\Generator $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @psalm-param array|\Generator<mixed, mixed, mixed, array> $args
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
|
@ -55,9 +63,11 @@ trait CallHandler
|
|||
/**
|
||||
* Call method and make sure it is asynchronously sent.
|
||||
*
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
* @param string $method Method name
|
||||
* @param array|\Generator $args Arguments
|
||||
* @param array $aargs Additional arguments
|
||||
*
|
||||
* @psalm-param array|\Generator<mixed, mixed, mixed, array> $args
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user