diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e54d6a6..f3ea32d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,15 +17,17 @@ Features: - Added support for `pay`, `login_url`, `web_app` and `tg://user?id=` buttons in bot API syntax! - Added a `getAdmin` function that returns the ID of the admin of the bot (which is equal to the first peer returned by getReportPeers in the event handler). - getPlugin can now be used from IPC clients! -- `getReply`, `sendMessage`, `reply` +- `getReply`, `sendMessage`, `sendDocument`, `sendPhoto`, `reply`, `delete` Fixes: - Fixed file uploads with ext-uv! -- Many performance improvements and bugfixes! +- Fixed file re-uploads! - Improve background broadcasting with the broadcast API using a pre-defined list of `whitelist` IDs! +- Fixed a bug that caused updates to get paused if an exception is thrown during onStart. - Broadcast IDs are now unique across multiple broadcasts, even if previous broadcasts already completed their ID will never be re-used. - Now uploadMedia, sendMedia and upload can upload files from string buffers created using `ReadableBuffer`. - Reduce memory usage during flood waits by tweaking config defaults. - Reduce memory usage by clearing the min database automatically as needed. - Automatically try caching all dialogs if a peer not found error is about to be thrown - Fix some issues with pure phar installs +- And many performance improvements and bugfixes! diff --git a/examples/bot.php b/examples/bot.php index 224ea0549..24ea85d1c 100755 --- a/examples/bot.php +++ b/examples/bot.php @@ -22,7 +22,6 @@ use danog\MadelineProto\API; use danog\MadelineProto\Broadcast\Progress; use danog\MadelineProto\Broadcast\Status; -use danog\MadelineProto\EventHandler; use danog\MadelineProto\EventHandler\Attributes\Cron; use danog\MadelineProto\EventHandler\Attributes\Handler; use danog\MadelineProto\EventHandler\Filter\FilterCommand; @@ -34,6 +33,7 @@ use danog\MadelineProto\Settings; use danog\MadelineProto\Settings\Database\Mysql; use danog\MadelineProto\Settings\Database\Postgres; use danog\MadelineProto\Settings\Database\Redis; +use danog\MadelineProto\SimpleEventHandler; // MadelineProto is already loaded if (class_exists(API::class)) { @@ -52,7 +52,7 @@ if (class_exists(API::class)) { * * All properties returned by __sleep are automatically stored in the database. */ -class MyEventHandler extends EventHandler +class MyEventHandler extends SimpleEventHandler { /** * @var int|string Username or ID of bot admin @@ -145,11 +145,9 @@ class MyEventHandler extends EventHandler if (!isset($this->notifiedChats[$message->chatId])) { $this->notifiedChats[$message->chatId] = true; - $this->messages->sendMessage( - peer: $message->chatId, + $message->reply( message: "This userbot is powered by [MadelineProto](https://t.me/MadelineProto)!", - reply_to_msg_id: $message->id, - parse_mode: 'Markdown' + parseMode: 'Markdown' ); } } @@ -170,11 +168,7 @@ class MyEventHandler extends EventHandler // We can broadcast messages to all users. if ($message->senderId === $this->adminId) { if (!$message->replyToMsgId) { - $this->messages->sendMessage( - peer: $message->senderId, - message: "You should reply to the message you want to broadcast.", - reply_to_msg_id: $message->id, - ); + $message->reply("You should reply to the message you want to broadcast."); return; } $this->broadcastForwardMessages( @@ -190,7 +184,7 @@ class MyEventHandler extends EventHandler #[FilterText('ping')] public function pingCommand(Incoming&Message $message): void { - $this->messages->sendMessage(['message' => 'pong', 'peer' => $message->chatId]); + $message->reply('pong'); } } diff --git a/src/EventHandler.php b/src/EventHandler.php index 6a8115277..c43a715e2 100644 --- a/src/EventHandler.php +++ b/src/EventHandler.php @@ -42,6 +42,7 @@ use PhpParser\Node\Expr\Include_; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name; use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\DeclareDeclare; use PhpParser\NodeFinder; use PhpParser\NodeVisitor\NameResolver; @@ -470,7 +471,11 @@ abstract class EventHandler extends AbstractAPI } } - if ($finder->findFirstInstanceOf($file, Include_::class)) { + /** @var Include_ $include */ + $include = $finder->findFirstInstanceOf($file, Include_::class); + if ($include + && !($include->expr instanceof String_ && \in_array($include->expr->value, ['vendor/autoload.php', 'madeline.php', 'madeline.phar'], true)) + ) { throw new AssertionError("An error occurred while analyzing plugin $class: for performance reasons, plugins can only automatically include or require other files present in the plugins folder by triggering the PSR-4 autoloader (not by manually require()'ing them)."); } } diff --git a/src/MTProto.php b/src/MTProto.php index 2d55e6bd6..da7306947 100644 --- a/src/MTProto.php +++ b/src/MTProto.php @@ -487,8 +487,11 @@ final class MTProto implements TLCallback, LoggerGetter $initDeferred = new DeferredFuture; $this->initPromise = $initDeferred->getFuture(); - $this->initialize($settings); - $initDeferred->complete(); + try { + $this->initialize($settings); + } finally { + $initDeferred->complete(); + } } /** @@ -976,80 +979,82 @@ final class MTProto implements TLCallback, LoggerGetter $deferred = new DeferredFuture; $this->initPromise = $deferred->getFuture(); - // Cleanup old properties, init new stuffs - $this->cleanupProperties(); + try { + // Cleanup old properties, init new stuffs + $this->cleanupProperties(); - // Re-set TL closures - $callbacks = [$this]; - if ($this->settings->getDb()->getEnableFileReferenceDb()) { - $callbacks []= $this->referenceDatabase; - } - if (!($this->authorization['user']['bot'] ?? false) && $this->settings->getDb()->getEnableMinDb()) { - $callbacks[] = $this->minDatabase; - } - - $this->TL->updateCallbacks($callbacks); - - // Clean up phone call array - foreach ($this->calls as $id => $controller) { - if (!\is_object($controller)) { - unset($this->calls[$id]); - } elseif ($controller->getCallState() === VoIP::CALL_STATE_ENDED) { - $controller->setMadeline($this); - $controller->discard(); - } else { - $controller->setMadeline($this); + // Re-set TL closures + $callbacks = [$this]; + if ($this->settings->getDb()->getEnableFileReferenceDb()) { + $callbacks []= $this->referenceDatabase; + } + if (!($this->authorization['user']['bot'] ?? false) && $this->settings->getDb()->getEnableMinDb()) { + $callbacks[] = $this->minDatabase; } - } - $this->settings->getConnection()->init(); - // Setup logger - $this->setupLogger(); - // Setup language - Lang::$current_lang =& Lang::$lang['en']; - if (Lang::$lang[$this->settings->getAppInfo()->getLangCode()] ?? false) { - Lang::$current_lang =& Lang::$lang[$this->settings->getAppInfo()->getLangCode()]; - } - // Reset MTProto session (not related to user session) - $this->resetMTProtoSession(); - // Update settings from constructor - $this->updateSettings($settings); - // Update TL callbacks - $callbacks = [$this]; - if ($this->settings->getDb()->getEnableFileReferenceDb()) { - $callbacks[] = $this->referenceDatabase; - } - if ($this->settings->getDb()->getEnableMinDb() && !($this->authorization['user']['bot'] ?? false)) { - $callbacks[] = $this->minDatabase; - } - // Connect to all DCs, start internal loops - $this->connectToAllDcs(); - if ($this->fullGetSelf()) { - $this->authorized = API::LOGGED_IN; + $this->TL->updateCallbacks($callbacks); + + // Clean up phone call array + foreach ($this->calls as $id => $controller) { + if (!\is_object($controller)) { + unset($this->calls[$id]); + } elseif ($controller->getCallState() === VoIP::CALL_STATE_ENDED) { + $controller->setMadeline($this); + $controller->discard(); + } else { + $controller->setMadeline($this); + } + } + + $this->settings->getConnection()->init(); + // Setup logger $this->setupLogger(); - $this->startLoops(); - $this->getCdnConfig(); - $this->initAuthorization(); - } else { - $this->startLoops(); - } - // onStart event handler - if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) { - $this->setEventHandler($this->event_handler); - } - $this->startUpdateSystem(true); - $this->cacheFullDialogs(); - if ($this->authorized === API::LOGGED_IN) { - $this->logger->logger("Obtaining updates after deserialization...", Logger::NOTICE); - $this->updaters[UpdateLoop::GENERIC]->resume(); - } - $this->updaters[UpdateLoop::GENERIC]->start(); + // Setup language + Lang::$current_lang =& Lang::$lang['en']; + if (Lang::$lang[$this->settings->getAppInfo()->getLangCode()] ?? false) { + Lang::$current_lang =& Lang::$lang[$this->settings->getAppInfo()->getLangCode()]; + } + // Reset MTProto session (not related to user session) + $this->resetMTProtoSession(); + // Update settings from constructor + $this->updateSettings($settings); + // Update TL callbacks + $callbacks = [$this]; + if ($this->settings->getDb()->getEnableFileReferenceDb()) { + $callbacks[] = $this->referenceDatabase; + } + if ($this->settings->getDb()->getEnableMinDb() && !($this->authorization['user']['bot'] ?? false)) { + $callbacks[] = $this->minDatabase; + } + // Connect to all DCs, start internal loops + $this->connectToAllDcs(); + if ($this->fullGetSelf()) { + $this->authorized = API::LOGGED_IN; + $this->setupLogger(); + $this->startLoops(); + $this->getCdnConfig(); + $this->initAuthorization(); + } else { + $this->startLoops(); + } + // onStart event handler + if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) { + $this->setEventHandler($this->event_handler); + } + $this->startUpdateSystem(true); + $this->cacheFullDialogs(); + if ($this->authorized === API::LOGGED_IN) { + $this->logger->logger("Obtaining updates after deserialization...", Logger::NOTICE); + $this->updaters[UpdateLoop::GENERIC]->resume(); + } + $this->updaters[UpdateLoop::GENERIC]->start(); - foreach ($this->broadcasts as $broadcast) { - $broadcast->resume(); + foreach ($this->broadcasts as $broadcast) { + $broadcast->resume(); + } + } finally { + $deferred->complete(); } - - $deferred->complete(); } /** * Unreference instance, allowing destruction.