diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a52b3720..3b0c96721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,92 @@ -MadelineProto was updated (8.0.0-beta100)! +Introducing MadelineProto's biggest update yet, 8.0.0-beta100! -Features: +This version introduces [plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html), [bound methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods), [filters](https://docs.madelineproto.xyz/docs/FILTERS.html), [a built-in cron system](https://docs.madelineproto.xyz/docs/UPDATES.html#cron), [IPC support for the event handler](https://docs.madelineproto.xyz/docs/UPDATES.html#persisting-data-and-ipc) and automatic static analysis for event handler code. + +To create a plugin, simply create an event handler that extends PluginEventHandler. + +For example, create a `plugins/Danogentili/PingPlugin.php` file: +``` +commandArgs; + + $message->reply($args[0] ?? ''); + } + + #[FilterRegex('/.*(mt?proto).*/i')] + public function testRegex(Incoming & Message $message): void + { + $message->reply("Did you mean to write MadelineProto instead of ".$message->matches[1].'?'); + } + + #[FilterText('ping')] + public function pingCommand(Incoming&Message $message): void + { + $message->reply("Pong"); + } +} +``` + +And use a [plugin base](https://raw.githubusercontent.com/danog/MadelineProto/v8/examples/PluginBase.php) to run all plugins included in the `plugins` folder. + +See the [documentation](https://docs.madelineproto.xyz/docs/PLUGINS.html) for more info on how to create MadelineProto plugins! + +Both plugins and normal bots can make use of [bound update methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) like `reply()`, `delete()`, `getReply()`, `getHTML()` and simplified properties like `chatId`, `senderId`, `command`, `commandArgs` and many more, see the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) for more info! + +Plugins and bots can now use three different filtering systems, to easily receive only updates satisfying certain conditions (incoming/outgoing, from group, channel, private, from an admin or a specific peer, with an audio/sticker/..., satisfying a certain regex or a certain /command, and much more!), [see the documentation](https://docs.madelineproto.xyz/docs/FILTERS.html) for more info! + +All event handler methods marked by the `Cron` attribute are now automatically invoked by MadelineProto every `period` seconds: + +``` +use danog\MadelineProto\EventHandler\Attributes\Cron; + +class MyEventHandler extends SimpleEventHandler +{ + /** + * This cron function will be executed forever, every 60 seconds. + */ + #[Cron(period: 60.0)] + public function cron1(): void + { + $this->sendMessageToAdmins("The bot is online, current time ".date(DATE_RFC850)."!"); + } +} +``` + +See the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#cron) for more info! + +Finally, all new bots and plugins will be automatically analyzed by MadelineProto, blocking execution if performance or security issues are detected! + +Other features: - Thanks to the many translation contributors @ https://weblate.madelineproto.xyz/, MadelineProto is now localized in Hebrew, Persian, Kurdish, Uzbek, Russian, French and Italian! - You can now use `Tools::callFork` to fork a new green thread! - You can now automatically pin messages broadcasted using `broadcastMessages`, `broadcastForwardMessages` by using the new `pin: true` parameter! - You can now use `sendMessageToAdmins` to send messages to the bot's admin (the peers returned by `getReportPeers`). -- Added `wrapUpdate`, `wrapMessage`, `wrapMedia` -- Added `Cron` -- Added plugins, filters, simple filters +- Added `wrapUpdate`, `wrapMessage`, `wrapMedia` methods to wrap low-level MTProto updates into an abstracted Message object with bound methods! - The `waveform` attribute of `Voice` objects is now automatically encoded and decoded to an array of 100 integer values! -- Added a custom PeerNotInDbException class for "This peer is not present in the internal peer database" errors +- Added a custom `PeerNotInDbException` class for "This peer is not present in the internal peer database" errors - Added a `label` property to the Button class, directly indicating the button label (instead of manually fetching it as an array key). - Added `isForum` method to check whether a given supergroup is a forum -- Added `entitiesToHtml` method to convert a message and a set of Telegram entities to an HTML string! +- Added an `entitiesToHtml` method to convert a message and a set of Telegram entities to an HTML string! - You can now use `reportMemoryProfile()` to generate and send a `pprof` memory profile to all report peers to debug the causes of high memory usage. - 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! +- Added a `getAdminIds` function that returns the IDs of the admin of the bot (equal to the peers returned by getReportPeers in the event handler). +- getEventHandler can now be used from IPC clients! +- Added `Cron` +- Added plugins, filters, simple filters - `getReply`, `sendMessage`, `sendDocument`, `sendPhoto`, `reply`, `delete` Fixes: @@ -26,8 +96,8 @@ Fixes: - 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! +- Reduced memory usage during flood waits by tweaking config defaults. +- Reduced 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. +- Fixed some issues with pure phar installs. +- And many other performance improvements and bugfixes! diff --git a/README.md b/README.md index 28ecfdc3c..661b4e5c8 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,24 @@ Want to add your own open-source project to this list? [Click here!](https://doc * [Broadcasting messages to all users](https://docs.madelineproto.xyz/docs/BROADCAST.html) * [Handling updates (new messages & other events)](https://docs.madelineproto.xyz/docs/UPDATES.html) * [Async Event driven](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven) + * [Full example](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven) + * [Bound methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) + * [Filters](https://docs.madelineproto.xyz/docs/FILTERS.html) + * [Simple filters](https://docs.madelineproto.xyz/docs/FILTERS.html#simple-filters) + * [Attribute filters](https://docs.madelineproto.xyz/docs/FILTERS.html#attribute-filters) + * [MTProto filters](https://docs.madelineproto.xyz/docs/FILTERS.html#mtproto-filters) + * [Plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html) + * [Cron](https://docs.madelineproto.xyz/docs/UPDATES.html#cron) + * [Persisting data and IPC](https://docs.madelineproto.xyz/docs/UPDATES.html#persisting-data-and-ipc) + * [Restarting](https://docs.madelineproto.xyz/docs/UPDATES.html#restarting) * [Self-restart on webhosts](https://docs.madelineproto.xyz/docs/UPDATES.html#self-restart-on-webhosts) - * [Async Event driven multi-account](https://docs.madelineproto.xyz/docs/UPDATES.html#async-event-driven-multiaccount) + * [Multi-account](https://docs.madelineproto.xyz/docs/UPDATES.html#multiaccount) * [Webhook (for HTTP APIs)](https://docs.madelineproto.xyz/docs/UPDATES.html#webhook) * [getUpdates (only for Javascript APIs)](https://docs.madelineproto.xyz/docs/UPDATES.html#getUpdates) * [Noop (default)](https://docs.madelineproto.xyz/docs/UPDATES.html#noop) + * [Simple filters »](https://docs.madelineproto.xyz/docs/FILTERS.html#simple-filters) + * [Attribute filters »](https://docs.madelineproto.xyz/docs/FILTERS.html#attribute-filters) + * [MTProto filters »](https://docs.madelineproto.xyz/docs/FILTERS.html#mtproto-filters) * [Filters](https://docs.madelineproto.xyz/docs/FILTERS.html) * [Simple filters](https://docs.madelineproto.xyz/docs/FILTERS.html#simple-filters) * [Attribute filters](https://docs.madelineproto.xyz/docs/FILTERS.html#attribute-filters) @@ -104,10 +117,10 @@ Want to add your own open-source project to this list? [Click here!](https://doc * [Simple installation](https://docs.madelineproto.xyz/docs/PLUGINS.html#simple-installation) * [Composer installation](https://docs.madelineproto.xyz/docs/PLUGINS.html#composer-installation) * [Creating plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html#creating-plugins) + * [Full plugin example](https://docs.madelineproto.xyz/docs/PLUGINS.html#full-plugin-example) * [Limitations](https://docs.madelineproto.xyz/docs/PLUGINS.html#limitations) * [Namespace requirements](https://docs.madelineproto.xyz/docs/PLUGINS.html#namespace-requirements) * [Distribution](https://docs.madelineproto.xyz/docs/PLUGINS.html#distribution) - * [Full plugin example](https://docs.madelineproto.xyz/docs/PLUGINS.html#full-plugin-example) * [Database](https://docs.madelineproto.xyz/docs/DATABASE.html) * [\danog\MadelineProto\Settings\Database\Memory: Memory backend settings.](https://docs.madelineproto.xyz/PHP/danog/MadelineProto/Settings/Database/Memory.html) * [\danog\MadelineProto\Settings\Database\Mysql: MySQL backend settings.](https://docs.madelineproto.xyz/PHP/danog/MadelineProto/Settings/Database/Mysql.html) @@ -438,6 +451,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc * Get a payment form: payments.getPaymentForm * Get a set of suggested custom emoji stickers that can be used as group picture: account.getDefaultGroupPhotoEmojis * Get a set of suggested custom emoji stickers that can be used as profile picture: account.getDefaultProfilePhotoEmojis + * Get admin IDs (equal to the report peers): getAdminIds * Get all archived stickers: messages.getArchivedStickers * Get all available chat themes: account.getChatThemes * Get all contacts: contacts.getSaved @@ -649,7 +663,6 @@ Want to add your own open-source project to this list? [Click here!](https://doc * Obtain information about a chat folder deep link ยป: chatlists.checkChatlistInvite * Obtain information about a named bot web app: messages.getBotApp * Obtain the API ID UI template: getWebAPITemplate - * Obtain the ID of the admin of the bot (equal to the first user ID returned by getReportPeers): getAdmin * Obtain user info from a temporary profile link: contacts.importContactToken * Obtains a list of messages, indicating to which other public channels was a channel message forwarded. : stats.getMessagePublicForwards * Obtains a list of peers that can be used to send messages in a specific group: channels.getSendAs diff --git a/docs b/docs index 9a73a4a7e..d3eb81be0 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 9a73a4a7e348f9b1b641fdc48198130c62967db9 +Subproject commit d3eb81be0cad837e7fd3f7f6d6e2f7b518b78b55 diff --git a/examples/bot.php b/examples/bot.php index e05c9e626..dd3071ee0 100755 --- a/examples/bot.php +++ b/examples/bot.php @@ -25,8 +25,10 @@ use danog\MadelineProto\Broadcast\Status; use danog\MadelineProto\EventHandler\Attributes\Cron; use danog\MadelineProto\EventHandler\Attributes\Handler; use danog\MadelineProto\EventHandler\Filter\FilterCommand; +use danog\MadelineProto\EventHandler\Filter\FilterRegex; use danog\MadelineProto\EventHandler\Filter\FilterText; use danog\MadelineProto\EventHandler\Message; +use danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin; use danog\MadelineProto\EventHandler\SimpleFilter\Incoming; use danog\MadelineProto\Logger; use danog\MadelineProto\Settings; @@ -57,9 +59,7 @@ class MyEventHandler extends SimpleEventHandler /** * @var int|string Username or ID of bot admin */ - const ADMIN = "@me"; // !!! Change this to your username !!! - - private int $adminId; + const ADMIN = "@danogentili"; // !!! Change this to your username !!! /** * @var array @@ -90,7 +90,6 @@ class MyEventHandler extends SimpleEventHandler { $this->logger("The bot was started!"); $this->logger($this->getFullInfo('MadelineProto')); - $this->adminId = $this->getId(self::ADMIN); $this->sendMessageToAdmins("The bot was started!"); } @@ -118,10 +117,6 @@ class MyEventHandler extends SimpleEventHandler /** * Handle incoming updates from users, chats and channels. - * - * 100+ other types of onUpdate... method types are available, see https://docs.madelineproto.xyz/API_docs/types/Update.html for the full list. - * You can also use onAny to catch all update types (only for debugging) - * A special onUpdateCustomEvent method can also be defined, to send messages to the event handler from an API instance, using the sendCustomEvent method. */ #[Handler] public function handleMessage(Incoming&Message $message): void @@ -139,32 +134,43 @@ class MyEventHandler extends SimpleEventHandler } #[FilterCommand('restart')] - public function restartCommand(Incoming&Message $message): void + public function restartCommand(Incoming & Message & FromAdmin $message): void { // If the message is a /restart command from an admin, restart to reload changes to the event handler code. - if ($message->senderId === $this->adminId) { - // Make sure to run in a bash while loop when running via CLI to allow self-restarts. - $this->restart(); - } + + // Make sure to run in a bash while loop when running via CLI to allow self-restarts. + $this->restart(); } #[FilterCommand('broadcast')] - public function broadcastCommand(Incoming&Message $message): void + public function broadcastCommand(Incoming & Message & FromAdmin $message): void { // We can broadcast messages to all users. - if ($message->senderId === $this->adminId) { - if (!$message->replyToMsgId) { - $message->reply("You should reply to the message you want to broadcast."); - return; - } - $this->broadcastForwardMessages( - from_peer: $message->senderId, - message_ids: [$message->replyToMsgId], - drop_author: true, - pin: true, - ); + if (!$message->replyToMsgId) { + $message->reply("You should reply to the message you want to broadcast."); return; } + $this->broadcastForwardMessages( + from_peer: $message->senderId, + message_ids: [$message->replyToMsgId], + drop_author: true, + pin: true, + ); + } + + #[FilterCommand('echo')] + public function echoCmd(Incoming & Message $message): void + { + // Contains the arguments of the command + $args = $message->commandArgs; + + $message->reply($args[0] ?? ''); + } + + #[FilterRegex('/.*(mt?proto).*/i')] + public function testRegex(Incoming & Message $message): void + { + $message->reply("Did you mean to write MadelineProto instead of ".$message->matches[1].'?'); } #[FilterText('hi')] diff --git a/examples/plugins/Danogentili/OnlinePlugin.php b/examples/plugins/Danogentili/OnlinePlugin.php index 020459f6d..909bfbec4 100644 --- a/examples/plugins/Danogentili/OnlinePlugin.php +++ b/examples/plugins/Danogentili/OnlinePlugin.php @@ -1,5 +1,7 @@ getPlugin(PingPlugin::class); diff --git a/src/EventHandler.php b/src/EventHandler.php index 4bde41fe4..dfb61e104 100644 --- a/src/EventHandler.php +++ b/src/EventHandler.php @@ -29,7 +29,6 @@ use danog\Loop\PeriodicLoop; use danog\MadelineProto\Db\DbPropertiesTrait; use danog\MadelineProto\EventHandler\Attributes\Cron; use danog\MadelineProto\EventHandler\Attributes\Handler; -use danog\MadelineProto\EventHandler\Attributes\Periodic; use danog\MadelineProto\EventHandler\Filter\Combinator\FiltersAnd; use danog\MadelineProto\EventHandler\Filter\Filter; use danog\MadelineProto\EventHandler\Filter\FilterAllowAll; @@ -249,7 +248,7 @@ abstract class EventHandler extends AbstractAPI } } /** - * Obtain a PeriodicLoop instance created by the Periodic attribute. + * Obtain a PeriodicLoop instance created by the Cron attribute. * * @param string $name Method name */ diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 077b419a4..ba966cd8b 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -640,13 +640,6 @@ abstract class InternalDoc { return \danog\MadelineProto\Tools::genVectorHash($ints); } - /** - * Obtain the ID of the admin of the bot (equal to the first user ID returned by getReportPeers). - */ - public function getAdmin(): int - { - return $this->wrapper->getAPI()->getAdmin(); - } /** * Get admin IDs (equal to the report peers). */ diff --git a/src/MTProto.php b/src/MTProto.php index 66da66f08..4e8b3ab0b 100644 --- a/src/MTProto.php +++ b/src/MTProto.php @@ -1575,10 +1575,6 @@ final class MTProto implements TLCallback, LoggerGetter * @var list */ private array $reportDest = []; - /** - * Admin ID. - */ - private ?int $adminId = null; /** * Check if has report peers. */ @@ -1659,16 +1655,6 @@ final class MTProto implements TLCallback, LoggerGetter /** @var array $userOrId */ return \array_values($userOrId); } - /** - * Obtain the ID of the admin of the bot (equal to the first user ID returned by getReportPeers). - */ - public function getAdmin(): int - { - if (!$this->adminId) { - throw new Exception("No admin ID was set!"); - } - return $this->adminId; - } /** * Set peer(s) where to send errors occurred in the event loop. * @@ -1677,13 +1663,6 @@ final class MTProto implements TLCallback, LoggerGetter public function setReportPeers(int|string|array $userOrId): void { $this->reportDest = $this->sanitizeReportPeers($userOrId); - $this->adminId = null; - foreach ($this->reportDest as $id) { - if ($id > 0) { - $this->adminId = $id; - break; - } - } } private ?LocalMutex $reportMutex = null; /** diff --git a/src/MTProtoTools/PeerHandler.php b/src/MTProtoTools/PeerHandler.php index 84ed08c29..648ba6b53 100644 --- a/src/MTProtoTools/PeerHandler.php +++ b/src/MTProtoTools/PeerHandler.php @@ -700,12 +700,6 @@ trait PeerHandler if ($id === 'me') { return $this->getInfo($this->authorization['user']['id'], $type); } - if ($id === 'admin') { - if (!isset($this->reportDest[0])) { - throw new Exception("No report peers were configured, can't send a message to the bot admin!"); - } - return $this->getInfo($this->reportDest[0], $type); - } if ($id === 'support') { if (!$this->supportUser) { $this->methodCallAsyncRead('help.getSupport', []); diff --git a/tools/build_docs.php b/tools/build_docs.php index 07ccb588e..cd7324a51 100755 --- a/tools/build_docs.php +++ b/tools/build_docs.php @@ -140,6 +140,9 @@ function printTypes(array $types, string $type): string $phpdoc = PhpDoc::fromNamespace(); $data = ''; foreach ($types as $class) { + if ($type === 'concretefilters' && $class === Update::class) { + continue; + } $refl = new ReflectionClass($class); $link = "https://docs.madelineproto.xyz/PHP/".str_replace('\\', '/', $class).'.html'; $f = $b->create($refl->getDocComment())->getSummary(); @@ -156,7 +159,7 @@ function printTypes(array $types, string $type): string continue; } $data .= " * [Full property list »]($link#properties)\n"; - $data .= " * [Full method list »]($link#method-list)\n"; + $data .= " * [Full bound method list »]($link#method-list)\n"; } return $data; } @@ -186,7 +189,9 @@ foreach ($orderedfiles as $key => $filename) { $result = array_filter($result, fn (string $class) => (new ReflectionClass($class))->getAttributes()); $data = printTypes($result, $match); } elseif ($match === "mtprotofilters") { - $data = ''; + $data = " * onUpdateCustomEvent: Receives messages sent to the event handler from an API instance using the [`sendCustomEvent` »](https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#sendcustomevent-mixed-payload-void) method.\n"; + $data .= " * onAny: Catch-all filter, if defined catches all updates that aren't catched by any other filter.\n"; + $data .= " * [onUpdateBroadcastProgress »](https://docs.madelineproto.xyz/docs/BROADCAST.html#get-progress): Used to receive updates to an in-progress [message broadcast »](https://docs.madelineproto.xyz/docs/BROADCAST.html)"; $TL = new TL(null); $TL->init(new TLSchema); foreach ($TL->getConstructors()->by_id as $cons) { @@ -261,6 +266,9 @@ foreach ($orderedfiles as $key => $filename) { continue; } } + if (basename($filename) === 'UPDATES.md' && str_starts_with($url, 'https://docs.madelineproto.xyz/PHP/danog/MadelineProto/EventHandler')) { + continue; + } $index .= "$spaces* [$name]($url)\n"; if ($name === 'FULL API Documentation with descriptions') { $spaces .= ' ';