diff --git a/src/API.php b/src/API.php index 7e755e7cb..e5b351423 100644 --- a/src/API.php +++ b/src/API.php @@ -52,7 +52,7 @@ final class API extends AbstractAPI * * @var string */ - public const RELEASE = '8.0.0-beta99'; + public const RELEASE = '8.0.0-beta100'; /** * Secret chat was not found. * diff --git a/src/AnnotationsBuilder.php b/src/AnnotationsBuilder.php index 781e0450a..e6f63552d 100644 --- a/src/AnnotationsBuilder.php +++ b/src/AnnotationsBuilder.php @@ -317,8 +317,8 @@ final class Blacklist { $contents .= " * @param {$psalmType} \${$name} {$description}\n"; if ($name === 'entities') { - $contents .= " * @param ''|'HTML'|'html'|'Markdown'|'markdown' \$parse_mode Whether to parse HTML or Markdown markup in the message\n"; - $signature []= "string \$parse_mode = ''"; + $contents .= " * @param \\danog\\MadelineProto\\ParseMode \$parse_mode Whether to parse HTML or Markdown markup in the message\n"; + $signature []= "\\danog\\MadelineProto\\ParseMode \$parse_mode = \\danog\\MadelineProto\\ParseMode::TEXT"; } } return [$contents, $signature]; diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 2fc3d086a..a0aa25f0e 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -1229,9 +1229,9 @@ abstract class InternalDoc * * @see https://docs.madelineproto.xyz/API_docs/methods/messages.sendMessage.html#usage-of-parse_mode * - * @return \danog\MadelineProto\TL\Conversion\DOMEntities Object containing message and entities + * @return \danog\MadelineProto\TL\Conversion\MarkdownEntities Object containing message and entities */ - public static function markdownToMessageEntities(string $markdown): \danog\MadelineProto\TL\Conversion\DOMEntities + public static function markdownToMessageEntities(string $markdown): \danog\MadelineProto\TL\Conversion\MarkdownEntities { return \danog\MadelineProto\StrTools::markdownToMessageEntities($markdown); } diff --git a/src/Namespace/Help.php b/src/Namespace/Help.php index a5c98b39a..439f85838 100644 --- a/src/Namespace/Help.php +++ b/src/Namespace/Help.php @@ -144,10 +144,10 @@ interface Help * @param array|int|string $user_id User @see https://docs.madelineproto.xyz/API_docs/types/InputUser.html * @param string $message Message * @param list $entities Array of [Message entities for styled text](https://core.telegram.org/api/entities) @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message * @return array{_: 'help.userInfoEmpty'}|array{_: 'help.userInfo', message: string, entities: list, author: string, date: int} @see https://docs.madelineproto.xyz/API_docs/types/help.UserInfo.html */ - public function editUserInfo(array|int|string $user_id, string $message = '', array $entities = [], string $parse_mode = ''): array; + public function editUserInfo(array|int|string $user_id, string $message = '', array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT): array; /** * Get MTProxy/Public Service Announcement information. diff --git a/src/Namespace/Messages.php b/src/Namespace/Messages.php index c0de023f4..1d7acd2b6 100644 --- a/src/Namespace/Messages.php +++ b/src/Namespace/Messages.php @@ -106,12 +106,12 @@ interface Messages * @param string $message The message * @param array $reply_markup Reply markup for sending bot buttons @see https://docs.madelineproto.xyz/API_docs/types/ReplyMarkup.html * @param list $entities Array of Message [entities](https://core.telegram.org/api/entities) for sending styled text @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message * @param int $schedule_date Scheduled message date for [scheduled messages](https://core.telegram.org/api/scheduled-messages) * @param array|int|string $send_as Send this message as the specified peer @see https://docs.madelineproto.xyz/API_docs/types/InputPeer.html * @return array @see https://docs.madelineproto.xyz/API_docs/types/Updates.html */ - public function sendMessage(array|int|string $peer, bool $no_webpage = false, bool $silent = false, bool $background = false, bool $clear_draft = false, bool $noforwards = false, bool $update_stickersets_order = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $reply_markup = [], array $entities = [], string $parse_mode = '', int $schedule_date = 0, array|int|string $send_as = []): array; + public function sendMessage(array|int|string $peer, bool $no_webpage = false, bool $silent = false, bool $background = false, bool $clear_draft = false, bool $noforwards = false, bool $update_stickersets_order = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $reply_markup = [], array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT, int $schedule_date = 0, array|int|string $send_as = []): array; /** * Send a media. @@ -128,12 +128,12 @@ interface Messages * @param string $message Caption * @param array $reply_markup Reply markup for bot keyboards @see https://docs.madelineproto.xyz/API_docs/types/ReplyMarkup.html * @param list $entities Array of Message [entities](https://core.telegram.org/api/entities) for styled text @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message * @param int $schedule_date Scheduled message date for [scheduled messages](https://core.telegram.org/api/scheduled-messages) * @param array|int|string $send_as Send this message as the specified peer @see https://docs.madelineproto.xyz/API_docs/types/InputPeer.html * @return array @see https://docs.madelineproto.xyz/API_docs/types/Updates.html */ - public function sendMedia(array|int|string $peer, array|string $media, bool $silent = false, bool $background = false, bool $clear_draft = false, bool $noforwards = false, bool $update_stickersets_order = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $reply_markup = [], array $entities = [], string $parse_mode = '', int $schedule_date = 0, array|int|string $send_as = []): array; + public function sendMedia(array|int|string $peer, array|string $media, bool $silent = false, bool $background = false, bool $clear_draft = false, bool $noforwards = false, bool $update_stickersets_order = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $reply_markup = [], array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT, int $schedule_date = 0, array|int|string $send_as = []): array; /** * Forwards messages by their IDs. @@ -322,10 +322,10 @@ interface Messages * * @param string $message Message from which to extract the preview * @param list $entities Array of [Message entities for styled text](https://core.telegram.org/api/entities) @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message * @return array{_: 'messageMediaEmpty'}|array{_: 'messageMediaPhoto', spoiler: bool, photo?: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, ttl_seconds: int}|array{_: 'messageMediaGeo', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}}|array{_: 'messageMediaContact', phone_number: string, first_name: string, last_name: string, vcard: string, user_id: int}|array{_: 'messageMediaUnsupported'}|array{_: 'messageMediaDocument', nopremium: bool, spoiler: bool, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}, ttl_seconds: int}|array{_: 'messageMediaWebPage', webpage: array{_: 'webPageEmpty', id: array}|array{_: 'webPagePending', id: array, date: array}|array{_: 'webPage', id: array, url: array, display_url: array, hash: array, type: array, site_name: array, title: array, description: array, photo?: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, embed_url: array, embed_type: array, embed_width: array, embed_height: array, duration: array, author: array, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}, cached_page?: array, attributes: list}|array{_: 'webPageNotModified', cached_page_views: array}}|array{_: 'messageMediaVenue', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}, title: string, address: string, provider: string, venue_id: string, venue_type: string}|array{_: 'messageMediaGame', game: array{_: 'game', photo: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, id: array, access_hash: array, short_name: array, title: array, description: array, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}}}|array{_: 'messageMediaInvoice', shipping_address_requested: bool, test: bool, title: string, description: string, photo?: array{_: 'webDocument', url: array, access_hash: array, size: array, mime_type: array, attributes: list}|array{_: 'webDocumentNoProxy', url: array, size: array, mime_type: array, attributes: list}, receipt_msg_id: int, currency: string, total_amount: int, start_param: string, extended_media?: array{_: 'messageExtendedMediaPreview', w: array, h: array, thumb?: array, video_duration: array}|array{_: 'messageExtendedMedia', media: array{_: 'messageMediaEmpty'}|array{_: 'messageMediaPhoto', spoiler: bool, photo?: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, ttl_seconds: int}|array{_: 'messageMediaGeo', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}}|array{_: 'messageMediaContact', phone_number: string, first_name: string, last_name: string, vcard: string, user_id: int}|array{_: 'messageMediaUnsupported'}|array{_: 'messageMediaDocument', nopremium: bool, spoiler: bool, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}, ttl_seconds: int}|array{_: 'messageMediaWebPage', webpage: array{_: 'webPageEmpty', id: array}|array{_: 'webPagePending', id: array, date: array}|array{_: 'webPage', id: array, url: array, display_url: array, hash: array, type: array, site_name: array, title: array, description: array, photo?: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, embed_url: array, embed_type: array, embed_width: array, embed_height: array, duration: array, author: array, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}, cached_page?: array, attributes: list}|array{_: 'webPageNotModified', cached_page_views: array}}|array{_: 'messageMediaVenue', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}, title: string, address: string, provider: string, venue_id: string, venue_type: string}|array{_: 'messageMediaGame', game: array{_: 'game', photo: array{_: 'photoEmpty', id: array}|array{_: 'photo', has_stickers: array, id: array, access_hash: array, file_reference: array, date: array, sizes: list, video_sizes: list, dc_id: array}, id: array, access_hash: array, short_name: array, title: array, description: array, document?: array{_: 'documentEmpty', id: array}|array{_: 'document', id: array, access_hash: array, file_reference: array, date: array, mime_type: array, size: array, thumbs: list, video_thumbs: list, dc_id: array, attributes: list}}}|array{_: 'messageMediaGeoLive', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}, heading: int, period: int, proximity_notification_radius: int}|array{_: 'messageMediaPoll', poll: array{_: 'poll', id: array, closed: array, public_voters: array, multiple_choice: array, quiz: array, question: array, answers: list, close_period: array, close_date: array}, results: array{_: 'pollResults', min: array, results: list, total_voters: array, recent_voters: list, solution: array, solution_entities: list}}|array{_: 'messageMediaDice', value: int, emoticon: string}}}|array{_: 'messageMediaGeoLive', geo: array{_: 'geoPointEmpty'}|array{_: 'geoPoint', long: array, lat: array, access_hash: array, accuracy_radius: array}, heading: int, period: int, proximity_notification_radius: int}|array{_: 'messageMediaPoll', poll: array{_: 'poll', id: array, closed: array, public_voters: array, multiple_choice: array, quiz: array, question: array, answers: list, close_period: array, close_date: array}, results: array{_: 'pollResults', min: array, results: list, total_voters: array, recent_voters: list, solution: array, solution_entities: list}}|array{_: 'messageMediaDice', value: int, emoticon: string} @see https://docs.madelineproto.xyz/API_docs/types/MessageMedia.html */ - public function getWebPagePreview(string $message = '', array $entities = [], string $parse_mode = ''): array; + public function getWebPagePreview(string $message = '', array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT): array; /** * Export an invite link for a chat. @@ -531,11 +531,11 @@ interface Messages * @param string|array $media New attached media @see https://docs.madelineproto.xyz/API_docs/types/InputMedia.html * @param array $reply_markup Reply markup for inline keyboards @see https://docs.madelineproto.xyz/API_docs/types/ReplyMarkup.html * @param list $entities Array of [Message entities for styled text](https://core.telegram.org/api/entities) @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message * @param int $schedule_date Scheduled message date for [scheduled messages](https://core.telegram.org/api/scheduled-messages) * @return array @see https://docs.madelineproto.xyz/API_docs/types/Updates.html */ - public function editMessage(array|int|string $peer, bool $no_webpage = false, int $id = 0, string $message = '', array|string $media = [], array $reply_markup = [], array $entities = [], string $parse_mode = '', int $schedule_date = 0): array; + public function editMessage(array|int|string $peer, bool $no_webpage = false, int $id = 0, string $message = '', array|string $media = [], array $reply_markup = [], array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT, int $schedule_date = 0): array; /** * Edit an inline bot message. @@ -546,9 +546,9 @@ interface Messages * @param string|array $media Media @see https://docs.madelineproto.xyz/API_docs/types/InputMedia.html * @param array $reply_markup Reply markup for inline keyboards @see https://docs.madelineproto.xyz/API_docs/types/ReplyMarkup.html * @param list $entities Array of [Message entities for styled text](https://core.telegram.org/api/entities) @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message */ - public function editInlineBotMessage(array $id, bool $no_webpage = false, string $message = '', array|string $media = [], array $reply_markup = [], array $entities = [], string $parse_mode = ''): bool; + public function editInlineBotMessage(array $id, bool $no_webpage = false, string $message = '', array|string $media = [], array $reply_markup = [], array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT): bool; /** * Press an inline callback button and get a callback answer from the bot. @@ -590,9 +590,9 @@ interface Messages * @param int $top_msg_id [Forum topic](https://core.telegram.org/api/forum#forum-topics) where the message will be sent * @param string $message The draft * @param list $entities Array of Message [entities](https://core.telegram.org/api/entities) for styled text @see https://docs.madelineproto.xyz/API_docs/types/MessageEntity.html - * @param ''|'HTML'|'html'|'Markdown'|'markdown' $parse_mode Whether to parse HTML or Markdown markup in the message + * @param \danog\MadelineProto\ParseMode $parse_mode Whether to parse HTML or Markdown markup in the message */ - public function saveDraft(array|int|string $peer, bool $no_webpage = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $entities = [], string $parse_mode = ''): bool; + public function saveDraft(array|int|string $peer, bool $no_webpage = false, int $reply_to_msg_id = 0, int $top_msg_id = 0, string $message = '', array $entities = [], \danog\MadelineProto\ParseMode $parse_mode = \danog\MadelineProto\ParseMode::TEXT): bool; /** * Return all message [drafts](https://core.telegram.org/api/drafts). diff --git a/src/StrTools.php b/src/StrTools.php index 9e03a3b23..864c99329 100644 --- a/src/StrTools.php +++ b/src/StrTools.php @@ -23,7 +23,7 @@ namespace danog\MadelineProto; use danog\MadelineProto\TL\Conversion\DOMEntities; use danog\MadelineProto\TL\Conversion\Extension; use danog\MadelineProto\TL\Conversion\MarkdownEntities; -use Parsedown; +use Throwable; use Webmozart\Assert\Assert; /** @@ -236,6 +236,10 @@ abstract class StrTools extends Extension if ($markdown === '') { return $markdown; } - return (new DOMEntities(Parsedown::instance()->text($markdown)))->message; + try { + return (new MarkdownEntities($markdown))->message; + } catch (Throwable) { + return (new MarkdownEntities(\str_replace('_', '\\_', $markdown)))->message; + } } } diff --git a/src/TL/Conversion/BotAPI.php b/src/TL/Conversion/BotAPI.php index 6afde55f8..a1d36caa9 100644 --- a/src/TL/Conversion/BotAPI.php +++ b/src/TL/Conversion/BotAPI.php @@ -26,7 +26,6 @@ use danog\MadelineProto\Logger; use danog\MadelineProto\MTProto; use danog\MadelineProto\StrTools; use danog\MadelineProto\Tools; -use Parsedown; use Throwable; use const danog\Decoder\TYPES_IDS; @@ -503,7 +502,7 @@ trait BotAPI $offset += $entity['length']; $newentity['offset'] = $offset; $orig = $multiple_args[$i]['message']; - $trimmed = rtrim($orig); + $trimmed = \rtrim($orig); $diff = StrTools::mbStrlen($orig) - StrTools::mbStrlen($trimmed); $entity['length'] -= $diff; $multiple_args[$i]['message'] = $trimmed; diff --git a/src/TL/Conversion/Entities.php b/src/TL/Conversion/Entities.php index 051a4eb03..78f65944d 100644 --- a/src/TL/Conversion/Entities.php +++ b/src/TL/Conversion/Entities.php @@ -4,17 +4,9 @@ declare(strict_types=1); namespace danog\MadelineProto\TL\Conversion; -use danog\MadelineProto\Exception; -use danog\MadelineProto\StrTools; -use DOMDocument; -use DOMElement; -use DOMNode; -use DOMText; -use Throwable; - /** * Class that converts HTML or markdown to a message + set of entities. - * + * * @internal */ abstract class Entities diff --git a/src/TL/Conversion/MarkdownEntities.php b/src/TL/Conversion/MarkdownEntities.php index 6d573e2aa..c1fec7def 100644 --- a/src/TL/Conversion/MarkdownEntities.php +++ b/src/TL/Conversion/MarkdownEntities.php @@ -7,10 +7,6 @@ namespace danog\MadelineProto\TL\Conversion; use AssertionError; use danog\MadelineProto\Exception; use danog\MadelineProto\StrTools; -use DOMDocument; -use DOMElement; -use DOMNode; -use DOMText; use Throwable; /** @@ -28,18 +24,18 @@ final class MarkdownEntities extends Entities */ public function __construct(string $markdown) { - $markdown = str_replace("\r\n", "\n", $markdown); + $markdown = \str_replace("\r\n", "\n", $markdown); try { $message = ''; $messageLen = 0; $entities = []; $offset = 0; $stack = []; - while ($offset < strlen($markdown)) { - $len = strcspn($markdown, '*_~`[]|\\', $offset); - $piece = substr($markdown, $offset, $len); + while ($offset < \strlen($markdown)) { + $len = \strcspn($markdown, '*_~`[]|!\\', $offset); + $piece = \substr($markdown, $offset, $len); $offset += $len; - if ($offset === strlen($markdown)) { + if ($offset === \strlen($markdown)) { $message .= $piece; break; } @@ -65,10 +61,19 @@ final class MarkdownEntities extends Entities $messageLen += StrTools::mbStrlen($piece)+1; continue; } + } elseif ($char === '!') { + if ($next === '[') { + $offset++; + $char = ']('; + } else { + $message .= $piece.$char; + $messageLen += StrTools::mbStrlen($piece)+1; + continue; + } } elseif ($char === '[') { $char = ']('; } elseif ($char === ']') { - if (!$stack || end($stack)[0] !== '](') { + if (!$stack || \end($stack)[0] !== '](') { $message .= $piece.$char; $messageLen += StrTools::mbStrlen($piece)+1; continue; @@ -83,15 +88,15 @@ final class MarkdownEntities extends Entities $messageLen += StrTools::mbStrlen($piece); $offset += 2; - $langLen = strcspn($markdown, "\n ", $offset); - $language = substr($markdown, $offset, $langLen); + $langLen = \strcspn($markdown, "\n ", $offset); + $language = \substr($markdown, $offset, $langLen); $offset += $langLen; if ($markdown[$offset] === "\n") { $offset++; } $posClose = $offset; - while (($posClose = strpos($markdown, '```', $posClose)) !== false) { + while (($posClose = \strpos($markdown, '```', $posClose)) !== false) { if ($markdown[$posClose-1] === '\\') { $posClose++; continue; @@ -104,7 +109,7 @@ final class MarkdownEntities extends Entities $start = $messageLen; - $message .= $piece = substr($markdown, $offset, $posClose-$offset); + $message .= $piece = \substr($markdown, $offset, $posClose-$offset); $pieceLen = StrTools::mbStrlen($piece); $messageLen += $pieceLen; @@ -131,11 +136,11 @@ final class MarkdownEntities extends Entities continue; } - if ($stack && end($stack)[0] === $char) { - [, $start] = array_pop($stack); + if ($stack && \end($stack)[0] === $char) { + [, $start] = \array_pop($stack); if ($char === '](') { $posClose = $offset; - while (($posClose = strpos($markdown, ')', $posClose)) !== false) { + while (($posClose = \strpos($markdown, ')', $posClose)) !== false) { if ($markdown[$posClose-1] === '\\') { $posClose++; continue; @@ -145,7 +150,7 @@ final class MarkdownEntities extends Entities if ($posClose === false) { throw new AssertionError("Unclosed ) opened @ pos $offset!"); } - $entity = self::handleLink(substr($markdown, $offset, $posClose-$offset)); + $entity = self::handleLink(\substr($markdown, $offset, $posClose-$offset)); $offset = $posClose+1; } else { $entity = match ($char) { @@ -160,9 +165,9 @@ final class MarkdownEntities extends Entities } $message .= $piece; $messageLen += StrTools::mbStrlen($piece); - + $lengthReal = $messageLen-$start; - for ($x = strlen($message)-1; $x >= 0; $x--) { + for ($x = \strlen($message)-1; $x >= 0; $x--) { if (!( $message[$x] === ' ' || $message[$x] === "\r" @@ -181,6 +186,9 @@ final class MarkdownEntities extends Entities $stack []= [$char, $messageLen]; } } + if ($stack) { + throw new AssertionError("Found unclosed markdown elements ".\implode(', ', \array_column($stack, 0))); + } $this->message = $message; $this->entities = $entities; diff --git a/src/Tools.php b/src/Tools.php index 71888274f..59209ff07 100644 --- a/src/Tools.php +++ b/src/Tools.php @@ -27,6 +27,7 @@ use Closure; use Countable; use Exception; use Fiber; +use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Include_; use PhpParser\Node\Expr\New_; @@ -600,7 +601,7 @@ abstract class Tools extends AsyncTools private const BANNED_FUNCTIONS = [ 'file_get_contents' => 'please use https://github.com/amphp/file or https://github.com/amphp/http-client, instead', 'file_put_contents' => 'please use https://github.com/amphp/file, instead', - 'unlink' => 'please use https://github.com/amphp/http-client, instead', + 'unlink' => 'plugins may not access the filesystem', 'curl_exec' => 'please use https://github.com/amphp/http-client, instead', 'mysqli_query' => 'please use https://github.com/amphp/mysql, instead', 'mysqli_connect' => 'please use https://github.com/amphp/mysql, instead', @@ -662,6 +663,16 @@ abstract class Tools extends AsyncTools $name = $call->name->toLowerString(); if (isset(self::BANNED_FUNCTIONS[$name])) { + if (!$plugin && $name === 'unlink') { + if ($call->args + && $call->args[0] instanceof Arg + && $call->args[0]->value instanceof String_ + && $call->args[0]->value->value === 'MadelineProto.log' + ) { + throw new AssertionError("An error occurred while analyzing $class: the MadelineProto.log must never be deleted, please set a custom max size in the settings, instead!"); + } + continue; + } $explanation = self::BANNED_FUNCTIONS[$name]; throw new AssertionError("An error occurred while analyzing $class: for performance reasons, plugins may not use the non-async blocking function $name, $explanation!"); }