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));