diff --git a/examples/bot.php b/examples/bot.php
index 53e5f753..d3be484e 100755
--- a/examples/bot.php
+++ b/examples/bot.php
@@ -19,7 +19,12 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
- /*
+use danog\MadelineProto\API;
+use danog\MadelineProto\EventHandler;
+use danog\MadelineProto\Exception;
+use danog\MadelineProto\RPCErrorException;
+
+/*
* Various ways to load MadelineProto
*/
if (\file_exists('vendor/autoload.php')) {
@@ -34,13 +39,40 @@ if (\file_exists('vendor/autoload.php')) {
/**
* Event handler class.
*/
-class EventHandler extends \danog\MadelineProto\EventHandler
+class MyEventHandler extends EventHandler
{
- public function onUpdateNewChannelMessage($update)
+ /**
+ * @var int|string Username or ID of bot admin
+ */
+ const ADMIN = "danogentili"; // Change this
+ /**
+ * Get peer(s) where to report errors.
+ *
+ * @return int|string|array
+ */
+ public function getReportPeers()
{
- yield $this->onUpdateNewMessage($update);
+ return [self::ADMIN];
}
- public function onUpdateNewMessage($update)
+ /**
+ * Handle updates from supergroups and channels.
+ *
+ * @param array $update Update
+ *
+ * @return void
+ */
+ public function onUpdateNewChannelMessage(array $update): \Generator
+ {
+ return $this->onUpdateNewMessage($update);
+ }
+ /**
+ * Handle updates from users.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewMessage(array $update): \Generator
{
if ($update['message']['_'] === 'messageEmpty' || $update['message']['out'] ?? false) {
return;
@@ -51,21 +83,13 @@ class EventHandler extends \danog\MadelineProto\EventHandler
yield $this->messages->sendMessage(['peer' => $update, 'message' => "$res
", '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]);
- /* '_' => 'inputMediaUploadedDocument',
- 'file' => $update,
- 'attributes' => [
- ['_' => 'documentAttributeFilename', 'file_name' => 'document.txt']
- ]
- ],]);*/
- //yield $this->downloadToDir($update, '/tmp');
}
- } catch (\danog\MadelineProto\RPCErrorException $e) {
- $this->logger((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
- } catch (\danog\MadelineProto\Exception $e) {
+ } catch (RPCErrorException $e) {
+ $this->report("Surfaced: $e");
+ } catch (Exception $e) {
if (\stripos($e->getMessage(), 'invalid constructor given') === false) {
- $this->logger((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
+ $this->report("Surfaced: $e");
}
- //$this->messages->sendMessage(['peer' => '@danogentili', 'message' => $e->getCode().': '.$e->getMessage().PHP_EOL.$e->getTraceAsString()]);
}
}
}
@@ -78,11 +102,8 @@ $settings = [
],
];
-$MadelineProto = new \danog\MadelineProto\API('bot.madeline', $settings);
-$MadelineProto->async(true);
-$MadelineProto->loop(function () use ($MadelineProto) {
- yield $MadelineProto->start();
- yield $MadelineProto->setEventHandler('\EventHandler');
-});
+$MadelineProto = new API('bot.madeline', $settings);
-$MadelineProto->loop();
+// Reduce boilerplate with new wrapper method.
+// Also initializes error reporting, catching and reporting all errors surfacing from the event loop.
+$MadelineProto->startAndLoop(MyEventHandler::class);
diff --git a/examples/combined_bot.php b/examples/combined_bot.php
index 6bb132b7..9b23fcb0 100755
--- a/examples/combined_bot.php
+++ b/examples/combined_bot.php
@@ -19,7 +19,12 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
-\set_include_path(\get_include_path().':'.\realpath(\dirname(__FILE__).'/MadelineProto/'));
+use danog\MadelineProto\API;
+use danog\MadelineProto\EventHandler;
+use danog\MadelineProto\Exception;
+use danog\MadelineProto\Logger;
+use danog\MadelineProto\RPCErrorException;
+use danog\MadelineProto\Tools;
/*
* Various ways to load MadelineProto
@@ -34,54 +39,89 @@ if (\file_exists(__DIR__.'/vendor/autoload.php')) {
}
/**
- * Combined event handler class.
+ * Event handler class.
*/
-class EventHandler extends \danog\MadelineProto\CombinedEventHandler
+class MyEventHandler extends EventHandler
{
- public function onUpdateNewChannelMessage($update, $path)
+ /**
+ * @var int|string Username or ID of bot admin
+ */
+ const ADMIN = "danogentili"; // Change this
+ /**
+ * Get peer(s) where to report errors.
+ *
+ * @return int|string|array
+ */
+ public function getReportPeers()
{
- yield $this->onUpdateNewMessage($update, $path);
+ return [self::ADMIN];
}
- public function onUpdateNewMessage($update, $path)
+ /**
+ * Handle updates from supergroups and channels.
+ *
+ * @param array $update Update
+ *
+ * @return void
+ */
+ public function onUpdateNewChannelMessage(array $update): \Generator
{
- if (isset($update['message']['out']) && $update['message']['out']) {
+ return $this->onUpdateNewMessage($update);
+ }
+ /**
+ * Handle updates from users.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewMessage(array $update): \Generator
+ {
+ if ($update['message']['_'] === 'messageEmpty' || $update['message']['out'] ?? false) {
return;
}
- $MadelineProto = $this->{$path};
-
- if (isset($update['message']['media'])) {
- yield $MadelineProto->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
- }
-
$res = \json_encode($update, JSON_PRETTY_PRINT);
- if ($res == '') {
- $res = \var_export($update, true);
- }
- yield $MadelineProto->sleep(3);
try {
- yield $MadelineProto->messages->sendMessage(['peer' => $update, 'message' => "$res
\n\nDopo 3 secondi, in modo asincrono", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']); //'entities' => [['_' => 'messageEntityPre', 'offset' => 0, 'length' => strlen($res), 'language' => 'json']]]);
- } catch (\danog\MadelineProto\RPCErrorException $e) {
- \danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
- } catch (\danog\MadelineProto\Exception $e) {
- \danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
- //$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => $e->getCode().': '.$e->getMessage().PHP_EOL.$e->getTraceAsString()]);
+ yield $this->messages->sendMessage(['peer' => $update, 'message' => "$res
", '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");
+ }
}
}
}
-$settings = ['logger' => ['logger_level' => 5]];
-$CombinedMadelineProto = new \danog\MadelineProto\CombinedAPI('combined_session.madeline', ['bot.madeline' => $settings, 'user.madeline' => $settings]);
+$MadelineProtos = [];
+foreach ([
+ 'bot.madeline' => 'Bot Login',
+ 'user.madeline' => 'Userbot login',
+ 'user2.madeline' => 'Userbot login (2)'
+] as $session => $message) {
+ Logger::log($message, Logger::WARNING);
+ $MadelineProto = new API($session);
+ $MadelineProto->async(true);
+ $MadelineProto->loop(function () use ($MadelineProto) {
+ yield $MadelineProto->start();
+ yield $MadelineProto->setEventHandler(MyEventHandler::class);
+ });
+ $MadelineProtos []= $MadelineProto->loopFork();
+}
-\danog\MadelineProto\Logger::log('Bot login', \danog\MadelineProto\Logger::WARNING);
-$CombinedMadelineProto->instances['bot.madeline']->start();
-
-\danog\MadelineProto\Logger::log('Userbot login');
-$CombinedMadelineProto->instances['user.madeline']->start();
-
-$CombinedMadelineProto->setEventHandler('\EventHandler');
-$CombinedMadelineProto->loop();
-
-$CombinedMadelineProto->async(true);
-$CombinedMadelineProto->setEventHandler('\EventHandler');
-$CombinedMadelineProto->loop();
+do {
+ $thrown = false;
+ try {
+ Tools::wait(Tools::all($MadelineProtos));
+ } catch (\Throwable $e) {
+ $thrown = true;
+ try {
+ $MadelineProto->report("Surfaced: $e");
+ } catch (\Throwable $e) {
+ $MadelineProto->logger((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
+ }
+ }
+} while ($thrown);
diff --git a/examples/downloadRenameBot.php b/examples/downloadRenameBot.php
index e3de5930..6d5da954 100755
--- a/examples/downloadRenameBot.php
+++ b/examples/downloadRenameBot.php
@@ -21,7 +21,6 @@
use Amp\Http\Server\HttpServer;
use danog\MadelineProto\API;
-use danog\MadelineProto\Logger;
use danog\MadelineProto\MTProtoTools\Files;
use danog\MadelineProto\RPCErrorException;
use danog\MadelineProto\Tools;
@@ -49,7 +48,21 @@ class EventHandler extends \danog\MadelineProto\EventHandler
"Usage: `https://example.com file name.ext`\n\n".
"I can also rename Telegram files, just send me any file and I will rename it!\n\n".
"Max 1.5GB, parallel upload and download powered by @MadelineProto.";
- const ADMIN = 'danogentili';
+
+ /**
+ * @var int|string Username or ID of bot admin
+ */
+ const ADMIN = 'danogentili'; // Change this
+
+ /**
+ * Get peer(s) where to report errors.
+ *
+ * @return int|string|array
+ */
+ public function getReportPeers()
+ {
+ return [self::ADMIN];
+ }
/**
* Whether to allow uploads.
@@ -65,26 +78,32 @@ class EventHandler extends \danog\MadelineProto\EventHandler
/**
* Constructor.
*
- * @param API $API API
+ * @param ?API $API API
*/
- public function __construct($API)
+ public function __construct(?API $API)
{
$this->UPLOAD = \class_exists(HttpServer::class);
parent::__construct($API);
}
- public function onUpdateNewChannelMessage($update)
+ /**
+ * Handle updates from channels and supergroups.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewChannelMessage(array $update)
{
//yield $this->onUpdateNewMessage($update);
}
- public function report(string $message)
- {
- try {
- $this->messages->sendMessage(['peer' => self::ADMIN, 'message' => $message]);
- } catch (\Throwable $e) {
- $this->logger("While reporting: $e", Logger::FATAL_ERROR);
- }
- }
- public function onUpdateNewMessage($update)
+ /**
+ * Handle updates from users.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewMessage(array $update): \Generator
{
if ($update['message']['out'] ?? false) {
return;
@@ -111,7 +130,7 @@ class EventHandler extends \danog\MadelineProto\EventHandler
unset($this->states[$peerId]);
$update = Files::extractBotAPIFile(yield $this->MTProtoToBotAPI($update));
$file = [$update['file_size'], $update['mime_type']];
- \var_dump($update['file_id'].'.'.Tools::base64urlEncode(json_encode($file))."/".$update['file_name']);
+ \var_dump($update['file_id'].'.'.Tools::base64urlEncode(\json_encode($file))."/".$update['file_name']);
return;
}
yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a new name for this file: ', 'reply_to_msg_id' => $messageId]);
@@ -220,19 +239,7 @@ $settings = [
];
$MadelineProto = new \danog\MadelineProto\API(($argv[1] ?? 'bot').'.madeline', $settings);
-$MadelineProto->async(true);
-while (true) {
- try {
- $MadelineProto->loop(function () use ($MadelineProto) {
- yield $MadelineProto->start();
- yield $MadelineProto->setEventHandler('\EventHandler');
- });
- $MadelineProto->loop();
- } catch (\Throwable $e) {
- try {
- $MadelineProto->logger("Surfaced: $e");
- $MadelineProto->getEventHandler(['async' => false])->report("Surfaced: $e");
- } catch (\Throwable $e) {
- }
- }
-}
+
+// Reduce boilerplate with new wrapper method.
+// Also initializes error reporting, catching and reporting all errors surfacing from the event loop.
+$MadelineProto->startAndLoop(MyEventHandler::class);
diff --git a/examples/secret_bot.php b/examples/secret_bot.php
index e4c3712e..6eafdc26 100755
--- a/examples/secret_bot.php
+++ b/examples/secret_bot.php
@@ -33,11 +33,48 @@ if (\file_exists(__DIR__.'/../vendor/autoload.php')) {
include 'madeline.php';
}
-class EventHandler extends \danog\MadelineProto\EventHandler
+class SecretHandler extends \danog\MadelineProto\EventHandler
{
private $sent = [-440592694 => true];
-
- public function onUpdateNewEncryptedMessage($update)
+ public function __construct($API)
+ {
+ parent::__construct($API);
+ $this->sent = [];
+ }
+ /**
+ * @var int|string Username or ID of bot admin
+ */
+ const ADMIN = "danogentili"; // Change this
+ /**
+ * Get peer(s) where to report errors.
+ *
+ * @return int|string|array
+ */
+ public function getReportPeers()
+ {
+ return [self::ADMIN];
+ }
+ /**
+ * Handle updates from users.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewMessage(array $update): \Generator
+ {
+ if ($update['message']['message'] === 'request') {
+ yield $this->requestSecretChat($update);
+ }
+ }
+ /**
+ * Handle secret chat messages.
+ *
+ * @param array $update Update
+ *
+ * @return \Generator
+ */
+ public function onUpdateNewEncryptedMessage(array $update): \Generator
{
try {
if (isset($update['message']['decrypted_message']['media'])) {
@@ -72,14 +109,14 @@ class EventHandler extends \danog\MadelineProto\EventHandler
$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) {
- yield $this->messages->sendEncryptedFile($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->sendEncrypted(['peer' => $update, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => (string) ($i++)]]);
yield $this->messages->sendMessage(['peer' => $update, 'message' => (string) ($i++)]);
}
$this->sent[$update['message']['chat_id']] = true;
@@ -102,7 +139,5 @@ $settings = \json_decode(\getenv('MTPROTO_SETTINGS'), true) ?: [];
$MadelineProto = new \danog\MadelineProto\API('s.madeline', $settings);
-$MadelineProto->start();
-$MadelineProto->setEventHandler('\EventHandler');
-$MadelineProto->async(true);
-$MadelineProto->loop();
+// Reduce boilerplate with new wrapper method
+$MadelineProto->startAndLoop(MyEventHandler::class);
diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php
index 6d1ec363..918209c3 100644
--- a/src/danog/MadelineProto/API.php
+++ b/src/danog/MadelineProto/API.php
@@ -405,4 +405,35 @@ class API extends InternalDoc
return $wrote;
})());
}
+
+ /**
+ * 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
+ {
+ $this->async(true);
+ do {
+ $thrown = false;
+ try {
+ $this->loop(function () use ($eventHandler) {
+ yield $this->start();
+ yield $this->setEventHandler($eventHandler);
+ });
+ $this->loop();
+ } catch (\Throwable $e) {
+ $thrown = true;
+ try {
+ $this->report("Surfaced: $e");
+ } catch (\Throwable $e) {
+ $this->logger((string) $e, Logger::FATAL_ERROR);
+ }
+ }
+ } while ($thrown);
+ }
}
diff --git a/src/danog/MadelineProto/AbstractAPIFactory.php b/src/danog/MadelineProto/AbstractAPIFactory.php
index a5405dd0..765122ea 100644
--- a/src/danog/MadelineProto/AbstractAPIFactory.php
+++ b/src/danog/MadelineProto/AbstractAPIFactory.php
@@ -97,15 +97,15 @@ abstract class AbstractAPIFactory extends AsyncConstruct
public function __call(string $name, array $arguments)
{
$yielded = Tools::call($this->__call_async($name, $arguments));
- $async = !$this->lua && ((is_array(\end($arguments)) ? \end($arguments) : [])['async'] ?? ($this->async && $name !== 'loop'));
+ $async = !$this->lua && ((\is_array(\end($arguments)) ? \end($arguments) : [])['async'] ?? ($this->async && $name !== 'loop'));
if ($async) {
return $yielded;
}
+ $yielded = Tools::wait($yielded);
if (!$this->lua) {
- return Tools::wait($yielded);
+ return $yielded;
}
try {
- $yielded = Tools::wait($yielded);
Lua::convertObjects($yielded);
return $yielded;
} catch (\Throwable $e) {
@@ -128,9 +128,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
yield from $this->initAsynchronously();
$this->API->logger->logger('Finished init asynchronously');
}
- if (Magic::isFork() && !Magic::$processed_fork) {
- throw new Exception('Forking not supported, use async logic, instead: https://docs.madelineproto.xyz/docs/ASYNC.html');
- }
if (!$this->API) {
throw new Exception('API did not init!');
}
@@ -160,7 +157,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
}
- $res = yield $this->methods[$lower_name](...$arguments);
+ $res = $this->methods[$lower_name](...$arguments);
return $res instanceof \Generator ? yield from $res : yield $res;
}
/**
diff --git a/src/danog/MadelineProto/EventHandler.php b/src/danog/MadelineProto/EventHandler.php
index e84c5f1f..be46b709 100644
--- a/src/danog/MadelineProto/EventHandler.php
+++ b/src/danog/MadelineProto/EventHandler.php
@@ -41,4 +41,13 @@ class EventHandler extends InternalDoc
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async);
}
}
+ /**
+ * Get peers where to send error reports.
+ *
+ * @return array|string|int
+ */
+ public function getReportPeers()
+ {
+ return [];
+ }
}
diff --git a/src/danog/MadelineProto/InternalDoc.php b/src/danog/MadelineProto/InternalDoc.php
index 5a042df8..446c2827 100644
--- a/src/danog/MadelineProto/InternalDoc.php
+++ b/src/danog/MadelineProto/InternalDoc.php
@@ -4215,6 +4215,37 @@ class InternalDoc extends APIFactory
{
return $this->__call(__FUNCTION__, [$extra]);
}
+ /**
+ * Check if has report peers.
+ *
+ * @return boolean
+ */
+ public function hasReportPeers(): bool
+ {
+ return $this->API->hasReportPeers();
+ }
+ /**
+ * Set peer(s) where to send errors occurred in the event loop.
+ *
+ * @param int|string $userOrId Username(s) or peer ID(s)
+ *
+ * @return \Generator
+ */
+ public function setReportPeers($userOrId, array $extra = [])
+ {
+ return $this->__call(__FUNCTION__, [$userOrId, $extra]);
+ }
+ /**
+ * Report an error to the previously set peer.
+ *
+ * @param string $message Error to report
+ *
+ * @return \Generator
+ */
+ public function report(string $message, array $extra = [])
+ {
+ return $this->__call(__FUNCTION__, [$message, $extra]);
+ }
/**
* Call method and wait asynchronously for response.
*
@@ -5293,6 +5324,17 @@ class InternalDoc extends APIFactory
{
return \danog\MadelineProto\MTProto::rleEncode($string);
}
+ /**
+ * Inflate stripped photosize to full JPG payload.
+ *
+ * @param string $stripped Stripped photosize
+ *
+ * @return string JPG payload
+ */
+ public function inflateStripped(string $stripped): string
+ {
+ return \danog\MadelineProto\MTProto::inflateStripped($stripped);
+ }
/**
* Get final element of array.
*
@@ -5460,6 +5502,17 @@ class InternalDoc extends APIFactory
{
$this->API->setEventHandler($event_handler);
}
+ /**
+ * Unset event handler.
+ *
+ * @param bool $disableUpdateHandling Whether to also disable internal update handling (will cause errors, otherwise will simply use the NOOP handler)
+ *
+ * @return void
+ */
+ public function unsetEventHandler(bool $disableUpdateHandling = false): void
+ {
+ $this->API->unsetEventHandler($disableUpdateHandling);
+ }
/**
* Get event handler.
*
@@ -5469,6 +5522,15 @@ class InternalDoc extends APIFactory
{
return $this->API->getEventHandler();
}
+ /**
+ * Check if an event handler instance is present.
+ *
+ * @return boolean
+ */
+ public function hasEventHandler(): bool
+ {
+ return $this->API->hasEventHandler();
+ }
/**
* Set webhook update handler.
*
@@ -5591,17 +5653,6 @@ class InternalDoc extends APIFactory
{
return $this->__call(__FUNCTION__, [$params, $extra]);
}
- /**
- * Set loop callback (DEPRECATED).
- *
- * @param callable $callback Callback
- *
- * @return void
- */
- public function setLoopCallback($callback): void
- {
- $this->API->setLoopCallback($callback);
- }
/**
* Start MadelineProto's update handling loop, or run the provided async callable.
*
@@ -5623,15 +5674,22 @@ class InternalDoc extends APIFactory
$this->API->stop();
}
/**
- * Start MadelineProto's update handling loop in background, or run the provided async callable.
+ * Restart update loop.
*
- * @param callable $callback Async callable to run
- *
- * @return mixed
+ * @return void
*/
- public function loopFork($callback = null): void
+ public function restart(): void
{
- $this->API->loopFork($callback);
+ $this->API->restart();
+ }
+ /**
+ * Start MadelineProto's update handling loop in background.
+ *
+ * @return Promise
+ */
+ public function loopFork(array $extra = [])
+ {
+ return $this->__call(__FUNCTION__, [$extra]);
}
/**
* Close connection with client, connected via web.
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index a25e720e..c42c49e6 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -493,12 +493,14 @@ class MTProto extends AsyncConstruct implements TLCallback
'referenceDatabase',
'minDatabase',
'channel_participants',
+
// Misc caching
'dialog_params',
'last_stored',
'qres',
'supportUser',
'tos',
+
// Event handler
'event_handler',
'event_handler_instance',
@@ -506,34 +508,47 @@ class MTProto extends AsyncConstruct implements TLCallback
'updates',
'updates_key',
'hook_url',
+
// Web login template
'web_template',
+
// Settings
'settings',
'config',
+
// Authorization keys
'datacenter',
+
// Authorization state
'authorization',
'authorized',
'authorized_dc',
+
// Authorization cache
'rsa_keys',
'dh_config',
+
// Update state
'got_state',
'channels_state',
'msg_ids',
+
// Version
'v',
+
// TL
'TL',
+
// Secret chats
'secret_chats',
'temp_requested_secret_chats',
'temp_rekeyed_secret_chats',
+
// Object storage
'storage',
+
+ // Report URI
+ 'reportDest'
];
}
/**
@@ -874,7 +889,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->parseSettings(\array_replace_recursive($this->settings, $backtrace[2]['args'][1]));
}
}
- if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__ . '/TL_botAPI.tl') {
+ if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__.'/TL_botAPI.tl') {
unset($this->v);
}
if (!\file_exists($this->settings['tl_schema']['src']['telegram'])) {
@@ -1050,7 +1065,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$lang_pack = 'android';
}
// Detect app version
- $app_version = self::RELEASE . ' (' . self::V . ', ' . Magic::$revision . ')';
+ $app_version = self::RELEASE.' ('.self::V.', '.Magic::$revision.')';
if (($settings['app_info']['api_id'] ?? 0) === 6) {
// TG DEV NOTICE: these app info spoofing measures were implemented for NON-MALICIOUS purposes.
// All accounts registered with a custom API ID require manual verification through recover@telegram.org, to avoid instant permabans.
@@ -1169,16 +1184,16 @@ class MTProto extends AsyncConstruct implements TLCallback
'layer' => 109,
// layer version
'src' => [
- 'mtproto' => __DIR__ . '/TL_mtproto_v1.tl',
+ 'mtproto' => __DIR__.'/TL_mtproto_v1.tl',
// mtproto TL scheme
- 'telegram' => __DIR__ . '/TL_telegram_v109.tl',
+ 'telegram' => __DIR__.'/TL_telegram_v109.tl',
// telegram TL scheme
- 'secret' => __DIR__ . '/TL_secret.tl',
+ 'secret' => __DIR__.'/TL_secret.tl',
// secret chats TL scheme
- 'calls' => __DIR__ . '/TL_calls.tl',
+ 'calls' => __DIR__.'/TL_calls.tl',
// calls TL scheme
//'td' => __DIR__.'/TL_td.tl', // telegram-cli TL scheme
- 'botAPI' => __DIR__ . '/TL_botAPI.tl',
+ 'botAPI' => __DIR__.'/TL_botAPI.tl',
],
], 'logger' => [
// Logger settings
@@ -1192,7 +1207,7 @@ class MTProto extends AsyncConstruct implements TLCallback
* $message is an array containing the messages the log, $level, is the logging level
*/
// write to
- 'logger_param' => Magic::$script_cwd . '/MadelineProto.log',
+ 'logger_param' => Magic::$script_cwd.'/MadelineProto.log',
'logger' => PHP_SAPI === 'cli' ? 3 : 2,
// overwrite previous setting and echo logs
'logger_level' => Logger::VERBOSE,
@@ -1651,6 +1666,58 @@ class MTProto extends AsyncConstruct implements TLCallback
}
return $this->authorization['user'];
}
+ /**
+ * IDs of peers where to report errors.
+ *
+ * @var int[]
+ */
+ private $reportDest = [];
+ /**
+ * Check if has report peers.
+ *
+ * @return boolean
+ */
+ public function hasReportPeers(): bool
+ {
+ return (bool) $this->reportDest;
+ }
+ /**
+ * Set peer(s) where to send errors occurred in the event loop.
+ *
+ * @param int|string $userOrId Username(s) or peer ID(s)
+ *
+ * @return \Generator
+ */
+ public function setReportPeers($userOrId): \Generator
+ {
+ if (!(\is_array($userOrId) && !isset($userOrId['_']) && !isset($userOrId['id']))) {
+ $userOrId = [$userOrId];
+ }
+ foreach ($userOrId as &$peer) {
+ $peer = yield from $this->getInfo($userOrId)['bot_api_id'];
+ }
+ $this->reportDest = $userOrId;
+ }
+ /**
+ * Report an error to the previously set peer.
+ *
+ * @param string $message Error to report
+ *
+ * @return \Generator
+ */
+ public function report(string $message): \Generator
+ {
+ if (!$this->reportDest) {
+ return;
+ }
+ foreach ($this->reportDest as $id) {
+ try {
+ yield from $this->methodCallAsyncRead('messages.sendMessage', ['peer' => $id, 'message' => $message]);
+ } catch (\Throwable $e) {
+ $this->logger("While reporting to $id: $e", Logger::FATAL_ERROR);
+ }
+ }
+ }
/**
* Called right before serialization of method starts.
*
@@ -1723,7 +1790,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*/
public function __debugInfo(): array
{
- return ['MadelineProto instance ' . \spl_object_hash($this)];
+ return ['MadelineProto instance '.\spl_object_hash($this)];
}
const ALL_MIMES = ['webp' => [0 => 'image/webp'], 'png' => [0 => 'image/png', 1 => 'image/x-png'], 'bmp' => [0 => 'image/bmp', 1 => 'image/x-bmp', 2 => 'image/x-bitmap', 3 => 'image/x-xbitmap', 4 => 'image/x-win-bitmap', 5 => 'image/x-windows-bmp', 6 => 'image/ms-bmp', 7 => 'image/x-ms-bmp', 8 => 'application/bmp', 9 => 'application/x-bmp', 10 => 'application/x-win-bitmap'], 'gif' => [0 => 'image/gif'], 'jpeg' => [0 => 'image/jpeg', 1 => 'image/pjpeg'], 'xspf' => [0 => 'application/xspf+xml'], 'vlc' => [0 => 'application/videolan'], 'wmv' => [0 => 'video/x-ms-wmv', 1 => 'video/x-ms-asf'], 'au' => [0 => 'audio/x-au'], 'ac3' => [0 => 'audio/ac3'], 'flac' => [0 => 'audio/x-flac'], 'ogg' => [0 => 'audio/ogg', 1 => 'video/ogg', 2 => 'application/ogg'], 'kmz' => [0 => 'application/vnd.google-earth.kmz'], 'kml' => [0 => 'application/vnd.google-earth.kml+xml'], 'rtx' => [0 => 'text/richtext'], 'rtf' => [0 => 'text/rtf'], 'jar' => [0 => 'application/java-archive', 1 => 'application/x-java-application', 2 => 'application/x-jar'], 'zip' => [0 => 'application/x-zip', 1 => 'application/zip', 2 => 'application/x-zip-compressed', 3 => 'application/s-compressed', 4 => 'multipart/x-zip'], '7zip' => [0 => 'application/x-compressed'], 'xml' => [0 => 'application/xml', 1 => 'text/xml'], 'svg' => [0 => 'image/svg+xml'], '3g2' => [0 => 'video/3gpp2'], '3gp' => [0 => 'video/3gp', 1 => 'video/3gpp'], 'mp4' => [0 => 'video/mp4'], 'm4a' => [0 => 'audio/x-m4a'], 'f4v' => [0 => 'video/x-f4v'], 'flv' => [0 => 'video/x-flv'], 'webm' => [0 => 'video/webm'], 'aac' => [0 => 'audio/x-acc'], 'm4u' => [0 => 'application/vnd.mpegurl'], 'pdf' => [0 => 'application/pdf', 1 => 'application/octet-stream'], 'pptx' => [0 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], 'ppt' => [0 => 'application/powerpoint', 1 => 'application/vnd.ms-powerpoint', 2 => 'application/vnd.ms-office', 3 => 'application/msword'], 'docx' => [0 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], 'xlsx' => [0 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 1 => 'application/vnd.ms-excel'], 'xl' => [0 => 'application/excel'], 'xls' => [0 => 'application/msexcel', 1 => 'application/x-msexcel', 2 => 'application/x-ms-excel', 3 => 'application/x-excel', 4 => 'application/x-dos_ms_excel', 5 => 'application/xls', 6 => 'application/x-xls'], 'xsl' => [0 => 'text/xsl'], 'mpeg' => [0 => 'video/mpeg'], 'mov' => [0 => 'video/quicktime'], 'avi' => [0 => 'video/x-msvideo', 1 => 'video/msvideo', 2 => 'video/avi', 3 => 'application/x-troff-msvideo'], 'movie' => [0 => 'video/x-sgi-movie'], 'log' => [0 => 'text/x-log'], 'txt' => [0 => 'text/plain'], 'css' => [0 => 'text/css'], 'html' => [0 => 'text/html'], 'wav' => [0 => 'audio/x-wav', 1 => 'audio/wave', 2 => 'audio/wav'], 'xhtml' => [0 => 'application/xhtml+xml'], 'tar' => [0 => 'application/x-tar'], 'tgz' => [0 => 'application/x-gzip-compressed'], 'psd' => [0 => 'application/x-photoshop', 1 => 'image/vnd.adobe.photoshop'], 'exe' => [0 => 'application/x-msdownload'], 'js' => [0 => 'application/x-javascript'], 'mp3' => [0 => 'audio/mpeg', 1 => 'audio/mpg', 2 => 'audio/mpeg3', 3 => 'audio/mp3'], 'rar' => [0 => 'application/x-rar', 1 => 'application/rar', 2 => 'application/x-rar-compressed'], 'gzip' => [0 => 'application/x-gzip'], 'hqx' => [0 => 'application/mac-binhex40', 1 => 'application/mac-binhex', 2 => 'application/x-binhex40', 3 => 'application/x-mac-binhex40'], 'cpt' => [0 => 'application/mac-compactpro'], 'bin' => [0 => 'application/macbinary', 1 => 'application/mac-binary', 2 => 'application/x-binary', 3 => 'application/x-macbinary'], 'oda' => [0 => 'application/oda'], 'ai' => [0 => 'application/postscript'], 'smil' => [0 => 'application/smil'], 'mif' => [0 => 'application/vnd.mif'], 'wbxml' => [0 => 'application/wbxml'], 'wmlc' => [0 => 'application/wmlc'], 'dcr' => [0 => 'application/x-director'], 'dvi' => [0 => 'application/x-dvi'], 'gtar' => [0 => 'application/x-gtar'], 'php' => [0 => 'application/x-httpd-php', 1 => 'application/php', 2 => 'application/x-php', 3 => 'text/php', 4 => 'text/x-php', 5 => 'application/x-httpd-php-source'], 'swf' => [0 => 'application/x-shockwave-flash'], 'sit' => [0 => 'application/x-stuffit'], 'z' => [0 => 'application/x-compress'], 'mid' => [0 => 'audio/midi'], 'aif' => [0 => 'audio/x-aiff', 1 => 'audio/aiff'], 'ram' => [0 => 'audio/x-pn-realaudio'], 'rpm' => [0 => 'audio/x-pn-realaudio-plugin'], 'ra' => [0 => 'audio/x-realaudio'], 'rv' => [0 => 'video/vnd.rn-realvideo'], 'jp2' => [0 => 'image/jp2', 1 => 'video/mj2', 2 => 'image/jpx', 3 => 'image/jpm'], 'tiff' => [0 => 'image/tiff'], 'eml' => [0 => 'message/rfc822'], 'pem' => [0 => 'application/x-x509-user-cert', 1 => 'application/x-pem-file'], 'p10' => [0 => 'application/x-pkcs10', 1 => 'application/pkcs10'], 'p12' => [0 => 'application/x-pkcs12'], 'p7a' => [0 => 'application/x-pkcs7-signature'], 'p7c' => [0 => 'application/pkcs7-mime', 1 => 'application/x-pkcs7-mime'], 'p7r' => [0 => 'application/x-pkcs7-certreqresp'], 'p7s' => [0 => 'application/pkcs7-signature'], 'crt' => [0 => 'application/x-x509-ca-cert', 1 => 'application/pkix-cert'], 'crl' => [0 => 'application/pkix-crl', 1 => 'application/pkcs-crl'], 'pgp' => [0 => 'application/pgp'], 'gpg' => [0 => 'application/gpg-keys'], 'rsa' => [0 => 'application/x-pkcs7'], 'ics' => [0 => 'text/calendar'], 'zsh' => [0 => 'text/x-scriptzsh'], 'cdr' => [0 => 'application/cdr', 1 => 'application/coreldraw', 2 => 'application/x-cdr', 3 => 'application/x-coreldraw', 4 => 'image/cdr', 5 => 'image/x-cdr', 6 => 'zz-application/zz-winassoc-cdr'], 'wma' => [0 => 'audio/x-ms-wma'], 'vcf' => [0 => 'text/x-vcard'], 'srt' => [0 => 'text/srt'], 'vtt' => [0 => 'text/vtt'], 'ico' => [0 => 'image/x-icon', 1 => 'image/x-ico', 2 => 'image/vnd.microsoft.icon'], 'csv' => [0 => 'text/x-comma-separated-values', 1 => 'text/comma-separated-values', 2 => 'application/vnd.msexcel'], 'json' => [0 => 'application/json', 1 => 'text/json']];
}
diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php
index 9b6380de..d21edc3f 100644
--- a/src/danog/MadelineProto/MTProtoTools/Files.php
+++ b/src/danog/MadelineProto/MTProtoTools/Files.php
@@ -79,7 +79,7 @@ trait Files
} elseif (\is_array($file)) {
return yield from $this->uploadFromTgfile($file, $cb, $encrypted);
}
- if (\is_resource($file) || (is_object($file) && $file instanceof InputStream)) {
+ if (\is_resource($file) || (\is_object($file) && $file instanceof InputStream)) {
return yield from $this->uploadFromStream($file, 0, '', $fileName, $cb, $encrypted);
}
if (!$this->settings['upload']['allow_automatic_upload']) {
@@ -212,7 +212,7 @@ trait Files
yield $stream->seek(0, \SEEK_END);
$size = yield $stream->tell();
yield $stream->seek(0);
- } else if (!$size) {
+ } elseif (!$size) {
$this->logger->logger("No content length for stream, caching first");
$body = $stream;
$stream = new BlockingFile(\fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
diff --git a/src/danog/MadelineProto/TL/Conversion/BotAPI.php b/src/danog/MadelineProto/TL/Conversion/BotAPI.php
index ab95135c..b02b79ed 100644
--- a/src/danog/MadelineProto/TL/Conversion/BotAPI.php
+++ b/src/danog/MadelineProto/TL/Conversion/BotAPI.php
@@ -240,7 +240,8 @@ trait BotAPI
if (isset($data['fwd_from']['channel_id'])) {
try {
$newd['forward_from_chat'] = yield from $this->getPwrChat(PeerHandler::toSupergroup($data['fwd_from']['channel_id']));
- } catch (\Throwable $e) {}
+ } catch (\Throwable $e) {
+ }
}
if (isset($data['fwd_from']['date'])) {
$newd['forward_date'] = $data['fwd_from']['date'];
diff --git a/src/danog/MadelineProto/Tools.php b/src/danog/MadelineProto/Tools.php
index ff7f8d22..308bca26 100644
--- a/src/danog/MadelineProto/Tools.php
+++ b/src/danog/MadelineProto/Tools.php
@@ -739,10 +739,10 @@ trait Tools
return $new;
}
/**
- * Inflate stripped photosize to full JPG payload
+ * Inflate stripped photosize to full JPG payload.
*
* @param string $stripped Stripped photosize
- *
+ *
* @return string JPG payload
*/
public static function inflateStripped(string $stripped): string
diff --git a/src/danog/MadelineProto/Wrappers/Events.php b/src/danog/MadelineProto/Wrappers/Events.php
index bd69a452..a5fc07c4 100644
--- a/src/danog/MadelineProto/Wrappers/Events.php
+++ b/src/danog/MadelineProto/Wrappers/Events.php
@@ -19,7 +19,7 @@
namespace danog\MadelineProto\Wrappers;
-use EventHandler;
+use danog\MadelineProto\EventHandler;
/**
* Event handler.
@@ -35,7 +35,7 @@ trait Events
/**
* Event handler instance.
*
- * @var \danog\MadelineProto\EventHandler
+ * @var EventHandler
*/
private $event_handler_instance;
/**
@@ -78,6 +78,7 @@ trait Events
$this->event_handler_methods[$method_name] = [$this->event_handler_instance, $method];
}
}
+ $this->setReportPeers($this->event_handler_instance->getReportPeers());
$this->settings['updates']['callback'] = [$this, 'eventUpdateHandler'];
$this->settings['updates']['handle_updates'] = true;
$this->settings['updates']['run_callback'] = true;
@@ -86,12 +87,12 @@ trait Events
}
}
/**
- * Unset event handler.
- *
- * @param bool $disableUpdateHandling Whether to also disable internal update handling (will cause errors, otherwise will simply use the NOOP handler)
- *
- * @return void
- */
+ * Unset event handler.
+ *
+ * @param bool $disableUpdateHandling Whether to also disable internal update handling (will cause errors, otherwise will simply use the NOOP handler)
+ *
+ * @return void
+ */
public function unsetEventHandler(bool $disableUpdateHandling = false): void
{
$this->event_handler = null;
@@ -107,10 +108,19 @@ trait Events
*
* @return EventHandler
*/
- public function getEventHandler(): \danog\MadelineProto\EventHandler
+ public function getEventHandler(): EventHandler
{
return $this->event_handler_instance;
}
+ /**
+ * Check if an event handler instance is present.
+ *
+ * @return boolean
+ */
+ public function hasEventHandler(): bool
+ {
+ return isset($this->event_handler_instance);
+ }
/**
* Event update handler.
*
diff --git a/src/danog/MadelineProto/Wrappers/Loop.php b/src/danog/MadelineProto/Wrappers/Loop.php
index f92ab2c0..ca955873 100644
--- a/src/danog/MadelineProto/Wrappers/Loop.php
+++ b/src/danog/MadelineProto/Wrappers/Loop.php
@@ -35,17 +35,7 @@ trait Loop
* @var boolean
*/
private $stopLoop = false;
- /**
- * Set loop callback (DEPRECATED).
- *
- * @param callable $callback Callback
- *
- * @return void
- */
- public function setLoopCallback($callback): void
- {
- $this->loop_callback = $callback;
- }
+
/**
* Start MadelineProto's update handling loop, or run the provided async callable.
*
@@ -75,7 +65,9 @@ trait Loop
if (!\is_callable($this->loop_callback) || \is_array($this->loop_callback) && $this->loop_callback[1] === 'onLoop' && !\method_exists(...$this->loop_callback)) {
$this->loop_callback = null;
}
- if (PHP_SAPI !== 'cli') {
+
+ static $inited = false;
+ if (PHP_SAPI !== 'cli' && !$inited) {
$needs_restart = true;
try {
\set_time_limit(-1);
@@ -83,11 +75,11 @@ trait Loop
$needs_restart = true;
}
if (isset($_REQUEST['MadelineSelfRestart'])) {
- $this->logger->logger("Self-restarted, restart token " . $_REQUEST['MadelineSelfRestart']);
+ $this->logger->logger("Self-restarted, restart token ".$_REQUEST['MadelineSelfRestart']);
}
$this->logger->logger($needs_restart ? 'Will self-restart' : 'Will not self-restart');
$backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $lockfile = \dirname(\end($backtrace)['file']) . '/bot' . $this->authorization['user']['id'] . '.lock';
+ $lockfile = \dirname(\end($backtrace)['file']).'/bot'.$this->authorization['user']['id'].'.lock';
unset($backtrace);
$try_locking = true;
if (!\file_exists($lockfile)) {
@@ -120,7 +112,7 @@ trait Loop
if ($needs_restart) {
$logger =& $this->logger;
Shutdown::addCallback(static function () use (&$logger) {
- $address = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 'tls' : 'tcp') . '://' . $_SERVER['SERVER_NAME'];
+ $address = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 'tls' : 'tcp').'://'.$_SERVER['SERVER_NAME'];
$port = $_SERVER['SERVER_PORT'];
$uri = $_SERVER['REQUEST_URI'];
$params = $_GET;
@@ -128,7 +120,7 @@ trait Loop
$url = \explode('?', $uri, 2)[0] ?? '';
$query = \http_build_query($params);
$uri = \implode('?', [$url, $query]);
- $payload = $_SERVER['REQUEST_METHOD'] . ' ' . $uri . ' ' . $_SERVER['SERVER_PROTOCOL'] . "\r\n" . 'Host: ' . $_SERVER['SERVER_NAME'] . "\r\n\r\n";
+ $payload = $_SERVER['REQUEST_METHOD'].' '.$uri.' '.$_SERVER['SERVER_PROTOCOL']."\r\n".'Host: '.$_SERVER['SERVER_NAME']."\r\n\r\n";
$logger->logger("Connecting to {$address}:{$port}");
$a = \fsockopen($address, $port);
$logger->logger("Sending self-restart payload");
@@ -140,6 +132,7 @@ trait Loop
}, 'restarter');
}
$this->closeConnection('Bot was started');
+ $inited = true;
}
if (!$this->settings['updates']['handle_updates']) {
$this->settings['updates']['handle_updates'] = true;
@@ -179,15 +172,22 @@ trait Loop
$this->signalUpdate();
}
/**
- * Start MadelineProto's update handling loop in background, or run the provided async callable.
+ * Restart update loop.
*
- * @param callable $callback Async callable to run
- *
- * @return mixed
+ * @return void
*/
- public function loopFork($callback = null): void
+ public function restart(): void
{
- Tools::callFork($this->loop($callback));
+ $this->stop();
+ }
+ /**
+ * Start MadelineProto's update handling loop in background.
+ *
+ * @return Promise
+ */
+ public function loopFork(): Promise
+ {
+ return Tools::callFork($this->loop());
}
/**
* Close connection with client, connected via web.
@@ -203,14 +203,14 @@ trait Loop
}
$this->logger->logger($message);
$buffer = @\ob_get_clean() ?: '';
- $buffer .= '