From 2b30be0d2c8bcc3ce48557b1b43ecab935588bfe Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 9 Jul 2023 15:42:08 +0200 Subject: [PATCH] Allow automatically pinning broadcasted messages --- CHANGELOG.md | 2 +- README.md | 4 +- examples/bot.php | 3 +- src/Broadcast/Action/ActionForward.php | 18 +++++++- src/Broadcast/Action/ActionSend.php | 11 ++++- src/Broadcast/Broadcast.php | 10 ++-- src/InternalDoc.php | 10 ++-- src/Loop/Connection/ReadLoop.php | 2 +- src/MTProtoSession/CallHandler.php | 2 +- src/MTProtoSession/ResponseHandler.php | 2 +- src/MTProtoTools/MinDatabase.php | 3 +- src/MTProtoTools/UpdateHandler.php | 7 +-- src/Stream/Common/HashedBufferedStream.php | 2 +- src/TL/TL.php | 6 +-- tools/asyncify.php | 53 ---------------------- 15 files changed, 55 insertions(+), 80 deletions(-) delete mode 100644 tools/asyncify.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0225dc7..3484739d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ MadelineProto was updated (8.0.0-beta100)! Features: - Thanks to the many translation contributors @ https://weblate.madelineproto.xyz/, MadelineProto is now fully localized in Hebrew, Persian, Kurdish, Uzbek and Italian, with WIP translations in Russian and French! - 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! - 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 `label` property to the Button class, directly indicating the button label (instead of manually fetching it as an array key). @@ -11,7 +12,6 @@ Features: - Added `wrapUpdate`, `wrapMessage`, `wrapMedia` - 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. - Fixes: - Fixed file uploads with ext-uv! - Many performance improvements and bugfixes! diff --git a/README.md b/README.md index f5d9cc104..7ae78b9a9 100644 --- a/README.md +++ b/README.md @@ -390,7 +390,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc * Find out if a media message's caption can be edited: messages.getMessageEditData * Finish account takeout session: account.finishTakeoutSession * Fork a new green thread and execute the passed function in the background: callFork - * Forwards a list of messages to all peers (users, chats, channels) of the bot: broadcastForwardMessages + * Forwards a list of messages to all peers (users, chats, channels) of the bot: broadcastForwardMessages * Forwards messages by their IDs: messages.forwardMessages * Generate MTProto vector hash: genVectorHash * Generate a login token, for login via QR code. : auth.exportLoginToken @@ -750,7 +750,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc * Sends a Telegram Passport authorization form, effectively sharing data with the service: account.acceptAuthorization * Sends a current user typing event (see SendMessageAction for all event types) to a conversation partner or group: messages.setTyping * Sends a custom request; for bots only: bots.sendCustomRequest - * Sends a list of messages to all peers (users, chats, channels) of the bot: broadcastMessages + * Sends a list of messages to all peers (users, chats, channels) of the bot: broadcastMessages * Sends a message to a chat: messages.sendMessage * Sends a message with a file attachment to a secret chat: messages.sendEncryptedFile * Sends a service message to a secret chat: messages.sendEncryptedService diff --git a/examples/bot.php b/examples/bot.php index 35ec7d43e..26bcd3cc7 100755 --- a/examples/bot.php +++ b/examples/bot.php @@ -164,7 +164,7 @@ class MyEventHandler extends EventHandler } // We can broadcast messages to all users. - if ($update['message']['message'] === '/broadcast' + if (($update['message']['message'] ?? '') === '/broadcast' && $from_id === $this->adminId ) { if (!isset($update['message']['reply_to']['reply_to_msg_id'])) { @@ -179,6 +179,7 @@ class MyEventHandler extends EventHandler from_peer: $update, message_ids: [$update['message']['reply_to']['reply_to_msg_id']], drop_author: true, + pin: true, ); return; } diff --git a/src/Broadcast/Action/ActionForward.php b/src/Broadcast/Action/ActionForward.php index 04964342b..c00948bf8 100644 --- a/src/Broadcast/Action/ActionForward.php +++ b/src/Broadcast/Action/ActionForward.php @@ -29,13 +29,13 @@ use danog\MadelineProto\RPCErrorException; /** @internal */ final class ActionForward implements Action { - public function __construct(private readonly MTProto $API, private readonly int $from_peer, private readonly array $ids, private readonly bool $drop_author) + public function __construct(private readonly MTProto $API, private readonly int $from_peer, private readonly array $ids, private readonly bool $drop_author, private readonly bool $pin) { } public function act(int $broadcastId, int $peer, Cancellation $cancellation): void { try { - $this->API->methodCallAsyncRead( + $updates = $this->API->methodCallAsyncRead( 'messages.forwardMessages', [ 'from_peer' => $this->from_peer, @@ -45,6 +45,20 @@ final class ActionForward implements Action ], ['FloodWaitLimit' => 2*86400] ); + if ($this->pin) { + $updates = $this->API->extractUpdates($updates); + $id = 0; + foreach ($updates as $update) { + if (\in_array($update['_'], ['updateNewMessage', 'updateNewChannelMessage'], true)) { + $id = max($id, $update['message']['id']); + } + } + $this->API->methodCallAsyncRead( + 'messages.updatePinnedMessage', + ['peer' => $peer, 'id' => $id, 'unpin' => false, 'pm_oneside' => false], + ['FloodWaitLimit' => 2*86400] + ); + } } catch (RPCErrorException $e) { if ($e->rpc === 'INPUT_USER_DEACTIVATED') { return; diff --git a/src/Broadcast/Action/ActionSend.php b/src/Broadcast/Action/ActionSend.php index 0cc3c61a3..931bae549 100644 --- a/src/Broadcast/Action/ActionSend.php +++ b/src/Broadcast/Action/ActionSend.php @@ -29,7 +29,7 @@ use danog\MadelineProto\RPCErrorException; /** @internal */ final class ActionSend implements Action { - public function __construct(private readonly MTProto $API, public readonly array $messages) + public function __construct(private readonly MTProto $API, private readonly array $messages, private readonly bool $pin) { } public function act(int $broadcastId, int $peer, Cancellation $cancellation): void @@ -39,13 +39,20 @@ final class ActionSend implements Action if ($cancellation->isRequested()) { return; } - $this->API->methodCallAsyncRead( + $id = $this->API->extractMessageId($this->API->methodCallAsyncRead( isset($message['media']['_']) && $message['media']['_'] !== 'messageMediaWebPage' ? 'messages.sendMedia' : 'messages.sendMessage', \array_merge($message, ['peer' => $peer]), ['FloodWaitLimit' => 2*86400] + )); + } + if ($this->pin) { + $this->API->methodCallAsyncRead( + 'messages.updatePinnedMessage', + ['peer' => $peer, 'id' => $id, 'unpin' => false, 'pm_oneside' => false], + ['FloodWaitLimit' => 2*86400] ); } } catch (RPCErrorException $e) { diff --git a/src/Broadcast/Broadcast.php b/src/Broadcast/Broadcast.php index a3746fb8a..2730b0ace 100644 --- a/src/Broadcast/Broadcast.php +++ b/src/Broadcast/Broadcast.php @@ -50,10 +50,11 @@ trait Broadcast * containing a Progress object for all broadcasts currently in-progress. * * @param array $messages The messages to send: an array of arrays, containing parameters to pass to messages.sendMessage. + * @param bool $pin Whether to also pin the last sent message. */ - public function broadcastMessages(array $messages, ?Filter $filter = null): int + public function broadcastMessages(array $messages, ?Filter $filter = null, bool $pin = false): int { - return $this->broadcastCustom(new ActionSend($this, $messages), $filter); + return $this->broadcastCustom(new ActionSend($this, $messages, $pin), $filter); } /** * Forwards a list of messages to all peers (users, chats, channels) of the bot. @@ -70,10 +71,11 @@ trait Broadcast * @param mixed $from_peer Bot API ID or Update, from where to forward the messages. * @param list $message_ids IDs of the messages to forward. * @param bool $drop_author If true, will forward messages without quoting the original author. + * @param bool $pin Whether to also pin the last sent message. */ - public function broadcastForwardMessages(mixed $from_peer, array $message_ids, bool $drop_author = false, ?Filter $filter = null): int + public function broadcastForwardMessages(mixed $from_peer, array $message_ids, bool $drop_author = false, ?Filter $filter = null, bool $pin = false): int { - return $this->broadcastCustom(new ActionForward($this, $this->getID($from_peer), $message_ids, $drop_author), $filter); + return $this->broadcastCustom(new ActionForward($this, $this->getID($from_peer), $message_ids, $drop_author, $pin), $filter); } /** diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 2cb7ee924..74999e180 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -253,10 +253,11 @@ abstract class InternalDoc * @param mixed $from_peer Bot API ID or Update, from where to forward the messages. * @param list $message_ids IDs of the messages to forward. * @param bool $drop_author If true, will forward messages without quoting the original author. + * @param bool $pin Whether to also pin the last sent message. */ - public function broadcastForwardMessages(mixed $from_peer, array $message_ids, bool $drop_author = false, ?\danog\MadelineProto\Broadcast\Filter $filter = null): int + public function broadcastForwardMessages(mixed $from_peer, array $message_ids, bool $drop_author = false, ?\danog\MadelineProto\Broadcast\Filter $filter = null, bool $pin = false): int { - return $this->wrapper->getAPI()->broadcastForwardMessages($from_peer, $message_ids, $drop_author, $filter); + return $this->wrapper->getAPI()->broadcastForwardMessages($from_peer, $message_ids, $drop_author, $filter, $pin); } /** * Sends a list of messages to all peers (users, chats, channels) of the bot. @@ -273,10 +274,11 @@ abstract class InternalDoc * containing a Progress object for all broadcasts currently in-progress. * * @param array $messages The messages to send: an array of arrays, containing parameters to pass to messages.sendMessage. + * @param bool $pin Whether to also pin the last sent message. */ - public function broadcastMessages(array $messages, ?\danog\MadelineProto\Broadcast\Filter $filter = null): int + public function broadcastMessages(array $messages, ?\danog\MadelineProto\Broadcast\Filter $filter = null, bool $pin = false): int { - return $this->wrapper->getAPI()->broadcastMessages($messages, $filter); + return $this->wrapper->getAPI()->broadcastMessages($messages, $filter, $pin); } /** * Convert generator, promise or any other value to a promise. diff --git a/src/Loop/Connection/ReadLoop.php b/src/Loop/Connection/ReadLoop.php index 2459be857..2e2c4372b 100644 --- a/src/Loop/Connection/ReadLoop.php +++ b/src/Loop/Connection/ReadLoop.php @@ -118,7 +118,7 @@ final class ReadLoop extends Loop $buffer = $this->connection->stream->getReadBuffer($payload_length); } catch (ClosedException $e) { $this->logger->logger($e->getReason()); - if (\strpos($e->getReason(), ' ') === 0) { + if (\str_starts_with($e->getReason(), ' ')) { $payload = -((int) \substr($e->getReason(), 7)); $this->logger->logger("Received {$payload} from DC ".$this->datacenter, Logger::ERROR); return $payload; diff --git a/src/MTProtoSession/CallHandler.php b/src/MTProtoSession/CallHandler.php index da1b5913e..8382a5cc5 100644 --- a/src/MTProtoSession/CallHandler.php +++ b/src/MTProtoSession/CallHandler.php @@ -165,7 +165,7 @@ trait CallHandler $method, $methodInfo['type'], true, - !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false && $method !== 'ping_delay_disconnect', + !$this->shared->hasTempAuthKey() && !\str_contains($method, '.') && $method !== 'ping_delay_disconnect', $response, $aargs['cancellation'] ?? null ); diff --git a/src/MTProtoSession/ResponseHandler.php b/src/MTProtoSession/ResponseHandler.php index 9681e7b3d..85dafbacb 100644 --- a/src/MTProtoSession/ResponseHandler.php +++ b/src/MTProtoSession/ResponseHandler.php @@ -283,7 +283,7 @@ trait ResponseHandler if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') { $response['error_code'] = 500; } - if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) { + if (\str_starts_with($response['error_message'], 'FILE_REFERENCE_')) { $this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call..."); $this->gotResponseForOutgoingMessage($request); $msgId = $request->getMsgId(); diff --git a/src/MTProtoTools/MinDatabase.php b/src/MTProtoTools/MinDatabase.php index 9dba44b6c..62520f7b4 100644 --- a/src/MTProtoTools/MinDatabase.php +++ b/src/MTProtoTools/MinDatabase.php @@ -228,7 +228,8 @@ final class MinDatabase implements TLCallback /** * Remove location info for peer. */ - public function clearPeer(int $id): void { + public function clearPeer(int $id): void + { unset($this->db[$id]); } public function __debugInfo() diff --git a/src/MTProtoTools/UpdateHandler.php b/src/MTProtoTools/UpdateHandler.php index f9a7e773c..41427558b 100644 --- a/src/MTProtoTools/UpdateHandler.php +++ b/src/MTProtoTools/UpdateHandler.php @@ -120,6 +120,7 @@ trait UpdateHandler */ private function eventUpdateHandler(array $update): void { + $updateType = $update['_']; if ($update['_'] === 'updateBroadcastProgress') { $update = $update['progress']; } @@ -133,7 +134,7 @@ trait UpdateHandler }); return; } - if (\count($this->eventHandlerHandlers) !== 0) { + if (\count($this->eventHandlerHandlers) !== 0 && \is_array($update)) { $update = $this->wrapUpdate($update); if ($update !== null) { foreach ($this->eventHandlerHandlers as $closure) { @@ -141,8 +142,8 @@ trait UpdateHandler } } } - if (isset($this->eventHandlerMethods[$update['_']])) { - foreach ($this->eventHandlerMethods[$update['_']] as $closure) { + if (isset($this->eventHandlerMethods[$updateType])) { + foreach ($this->eventHandlerMethods[$updateType] as $closure) { $closure($update); } } diff --git a/src/Stream/Common/HashedBufferedStream.php b/src/Stream/Common/HashedBufferedStream.php index 683248c80..a96c98ae5 100644 --- a/src/Stream/Common/HashedBufferedStream.php +++ b/src/Stream/Common/HashedBufferedStream.php @@ -128,7 +128,7 @@ final class HashedBufferedStream implements BufferedProxyStreamInterface, Buffer */ public function setExtra($hash): void { - $rev = \strpos($hash, '_rev'); + $rev = \str_contains($hash, '_rev'); $this->rev = false; if ($rev !== false) { $hash = \substr($hash, 0, $rev); diff --git a/src/TL/TL.php b/src/TL/TL.php index bc1fa198b..c75ba25e2 100644 --- a/src/TL/TL.php +++ b/src/TL/TL.php @@ -227,14 +227,14 @@ final class TL implements TLInterface $layer = (int) $matches[1]; continue; } - if (\strpos($line, 'vector#') === 0) { + if (\str_starts_with($line, 'vector#')) { continue; } - if (\strpos($line, ' ?= ') !== false) { + if (\str_contains($line, ' ?= ')) { continue; } $line = \preg_replace(['/[(]([\\w\\.]+) ([\\w\\.]+)[)]/', '/\\s+/'], ['$1<$2>', ' '], $line); - if (\strpos($line, ';') === false) { + if (!\str_contains($line, ';')) { $lineBuf .= $line; continue; } elseif ($lineBuf) { diff --git a/tools/asyncify.php b/tools/asyncify.php deleted file mode 100644 index a1f0069e8..000000000 --- a/tools/asyncify.php +++ /dev/null @@ -1,53 +0,0 @@ - $line) { - if (preg_match("/public function (\w*)[(]/", $line, $matches)) { - $last_match = stripos($matches[1], 'async') === false ? $matches[1] : null; - } - if (preg_match('/function [(]/', $line) && stripos($line, 'public function') === false) { - $last_match = 0; - } - if (strpos($line, 'yield') !== false) { - if ($last_match) { - echo "subbing $last_match for $line at $number in $file".PHP_EOL; - $to_sub[] = $last_match; - } elseif ($last_match === 0) { - echo "============\nNOT SUBBING $last_match for $line at $number in $file\n============".PHP_EOL; - $not_subbing[$file] = $file; - } - } - } - $input = []; - $output = []; - foreach ($to_sub as $func) { - $input[] = "public function $func("; - $output[] = "public function $func".'_async('; - } - if ($input) { - file_put_contents($file, str_replace($input, $output, $filec)); - } -} -var_dump(array_values($not_subbing));