diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index a52308778..1e0b7a9c8 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -310,7 +310,7 @@ contact phone_number:string first_name:string last_name:string vcard:string user //@horizontal_accuracy The estimated horizontal accuracy of the location, in meters; as defined by the sender. 0 if unknown location latitude:double longitude:double horizontal_accuracy:double = Location; -//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently only "foursquare" and "gplaces" (Google Places) need to be supported +//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently, only "foursquare" and "gplaces" (Google Places) need to be supported //@id Identifier of the venue in the provider database; as defined by the sender @type Type of the venue in the provider database; as defined by the sender venue location:location title:string address:string provider:string id:string type:string = Venue; @@ -655,7 +655,7 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@username Username of the supergroup or channel; empty for private supergroups or channels //@date Point in time (Unix timestamp) when the current user joined, or the point in time when the supergroup or channel was created, in case the user is not a member //@status Status of the current user in the supergroup or channel; custom title will be always empty -//@member_count Number of members in the supergroup or channel; 0 if unknown. Currently it is guaranteed to be known only if the supergroup or channel was received through searchPublicChats, searchChatsNearby, getInactiveSupergroupChats, getSuitableDiscussionChats, getGroupsInCommon, or getUserPrivacySettingRules +//@member_count Number of members in the supergroup or channel; 0 if unknown. Currently, it is guaranteed to be known only if the supergroup or channel was received through searchPublicChats, searchChatsNearby, getInactiveSupergroupChats, getSuitableDiscussionChats, getGroupsInCommon, or getUserPrivacySettingRules //@has_linked_chat True, if the channel has a discussion group, or the supergroup is the designated discussion group for a channel //@has_location True, if the supergroup is connected to a location, i.e. the supergroup is a location-based supergroup //@sign_messages True, if messages sent to the channel need to contain information about the sender. This field is only applicable to channels @@ -844,7 +844,7 @@ messageCalendar total_count:int32 days:vector = MessageCalen //@description Describes a sponsored message @id Unique sponsored message identifier @sponsor_chat_id Chat identifier -//@link An internal link to be opened when the sponsored message is clicked; may be null. If null, the sponsor chat needs to be opened instead @content Content of the message +//@link An internal link to be opened when the sponsored message is clicked; may be null. If null, the sponsor chat needs to be opened instead @content Content of the message. Currently, can be only of the type messageText sponsoredMessage id:int32 sponsor_chat_id:int53 link:InternalLinkType content:MessageContent = SponsoredMessage; //@description Contains a list of sponsored messages @messages List of sponsored messages @@ -1095,7 +1095,7 @@ inlineKeyboardButtonTypeSwitchInline query:string in_current_chat:Bool = InlineK //@description A button to buy something. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageInvoice inlineKeyboardButtonTypeBuy = InlineKeyboardButtonType; -//@description A button to open a chat with a user @user_id User identifier +//@description A button with a user reference to be handled in the same way as textEntityTypeMentionName entities @user_id User identifier inlineKeyboardButtonTypeUser user_id:int53 = InlineKeyboardButtonType; @@ -1334,7 +1334,7 @@ pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageB //@description Describes an instant view page for a web page //@page_blocks Content of the web page //@view_count Number of the instant view views; 0 if unknown -//@version Version of the instant view, currently can be 1 or 2 +//@version Version of the instant view; currently, can be 1 or 2 //@is_rtl True, if the instant view must be shown from right to left //@is_full True, if the instant view contains the full page. A network request might be needed to get the full web page instant view //@feedback_link An internal link to be opened to leave feedback about the instant view @@ -1357,12 +1357,12 @@ webPageInstantView page_blocks:vector view_count:int32 version:int32 //@author Author of the content //@animation Preview of the content as an animation, if available; may be null //@audio Preview of the content as an audio file, if available; may be null -//@document Preview of the content as a document, if available (currently only available for small PDF files and ZIP archives); may be null +//@document Preview of the content as a document, if available; may be null //@sticker Preview of the content as a sticker for small WEBP files, if available; may be null //@video Preview of the content as a video, if available; may be null //@video_note Preview of the content as a video note, if available; may be null //@voice_note Preview of the content as a voice note, if available; may be null -//@instant_view_version Version of instant view, available for the web page (currently can be 1 or 2), 0 if none +//@instant_view_version Version of instant view, available for the web page (currently, can be 1 or 2), 0 if none webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote instant_view_version:int32 = WebPage; @@ -4222,10 +4222,10 @@ loadChats chat_list:ChatList limit:int32 = Ok; //@chat_list The chat list in which to return chats; pass null to get chats from the main chat list @limit The maximum number of chats to be returned getChats chat_list:ChatList limit:int32 = Chats; -//@description Searches a public chat by its username. Currently only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved +//@description Searches a public chat by its username. Currently, only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved searchPublicChat username:string = Chat; -//@description Searches public chats by looking for specified query in their username and title. Currently only private chats, supergroups and channels can be public. Returns a meaningful number of results. +//@description Searches public chats by looking for specified query in their username and title. Currently, only private chats, supergroups and channels can be public. Returns a meaningful number of results. //-Excludes private chats with contacts and chats from the chat list from the results @query Query to search for searchPublicChats query:string = Chats; @@ -4425,7 +4425,7 @@ setChatDefaultMessageSender chat_id:int53 default_message_sender_id:MessageSende //@input_message_content The content of the message to be sent sendMessage chat_id:int53 message_thread_id:int53 reply_to_message_id:int53 options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; -//@description Sends 2-10 messages grouped together into an album. Currently only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages +//@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages //@chat_id Target chat //@message_thread_id If not 0, a message thread identifier in which the messages will be sent //@reply_to_message_id Identifier of a message to reply to or 0 @@ -4854,7 +4854,7 @@ leaveChat chat_id:int53 = Ok; //@chat_id Chat identifier @user_id Identifier of the user @forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 100. Ignored for supergroups and channels, or if the added user is a bot addChatMember chat_id:int53 user_id:int53 forward_limit:int32 = Ok; -//@description Adds multiple new members to a chat. Currently this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members +//@description Adds multiple new members to a chat. Currently, this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members //@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat. The maximum number of added users is 20 for supergroups and 100 for channels addChatMembers chat_id:int53 user_ids:vector = Ok; @@ -5260,7 +5260,7 @@ getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = S //@limit The maximum number of sticker sets to be returned; up to 100. For optimal performance, the number of returned sticker sets is chosen by TDLib and can be smaller than the specified limit, even if the end of the list has not been reached getTrendingStickerSets offset:int32 limit:int32 = StickerSets; -//@description Returns a list of sticker sets attached to a file. Currently only photos and videos can have attached sticker sets @file_id File identifier +//@description Returns a list of sticker sets attached to a file. Currently, only photos and videos can have attached sticker sets @file_id File identifier getAttachedStickerSets file_id:int32 = StickerSets; //@description Returns information about a sticker set by its identifier @set_id Identifier of the sticker set @@ -5586,15 +5586,15 @@ deleteAccount reason:string = Ok; //@description Removes a chat action bar without any other action @chat_id Chat identifier removeChatActionBar chat_id:int53 = Ok; -//@description Reports a chat to the Telegram moderators. A chat can be reported only from the chat action bar, or if this is a private chat with a bot, a private chat with a user sharing their location, a supergroup, or a channel, since other chats can't be checked by moderators +//@description Reports a chat to the Telegram moderators. A chat can be reported only from the chat action bar, or if chat.can_be_reported //@chat_id Chat identifier @message_ids Identifiers of reported messages, if any @reason The reason for reporting the chat @text Additional report details; 0-1024 characters reportChat chat_id:int53 message_ids:vector reason:ChatReportReason text:string = Ok; -//@description Reports a chat photo to the Telegram moderators. A chat photo can be reported only if this is a private chat with a bot, a private chat with a user sharing their location, a supergroup, or a channel, since other chats can't be checked by moderators +//@description Reports a chat photo to the Telegram moderators. A chat photo can be reported only if chat.can_be_reported //@chat_id Chat identifier @file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported @reason The reason for reporting the chat photo @text Additional report details; 0-1024 characters reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok; -//@description Returns detailed statistics about a chat. Currently this method can be used only for supergroups and channels. Can be used only if supergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application +//@description Returns detailed statistics about a chat. Currently, this method can be used only for supergroups and channels. Can be used only if supergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics; //@description Returns detailed statistics about a message. Can be used only if message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index cec3b32c0..a9f81bd26 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -15676,9 +15676,9 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char c->is_changed = true; invalidate_channel_full(channel_id, !c->is_slow_mode_enabled); } - if (c->is_verified != is_verified || c->sign_messages != sign_messages) { + // sign_messages isn't known for min-channels + if (c->is_verified != is_verified) { c->is_verified = is_verified; - c->sign_messages = sign_messages; c->is_changed = true; } diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index c0e75f264..6eeb8c7e4 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -883,10 +883,10 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que } } } else if (path.size() == 1 && path[0] == "privatepost") { - // privatepost?channel=123456789&msg_id=12345&single&thread=&comment=&t= - if (has_arg("channel") && has_arg("msg_id")) { + // privatepost?channel=123456789&post=12345&single&thread=&comment=&t= + if (has_arg("channel") && has_arg("post")) { return td::make_unique( - PSTRING() << "tg:privatepost" << copy_arg("channel") << copy_arg("msg_id") << copy_arg("single") + PSTRING() << "tg:privatepost" << copy_arg("channel") << copy_arg("post") << copy_arg("single") << copy_arg("thread") << copy_arg("comment") << copy_arg("t")); } } else if (path.size() == 1 && path[0] == "bg") { @@ -941,10 +941,9 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q if (path.size() >= 3 && to_integer(path[1]) > 0 && to_integer(path[2]) > 0) { // /c/123456789/12345?single&thread=&comment=&t= is_first_arg = false; - return td::make_unique(PSTRING() - << "tg:privatepost?channel=" << to_integer(path[1]) - << "&msg_id=" << to_integer(path[2]) << copy_arg("single") - << copy_arg("thread") << copy_arg("comment") << copy_arg("t")); + return td::make_unique( + PSTRING() << "tg:privatepost?channel=" << to_integer(path[1]) << "&post=" << to_integer(path[2]) + << copy_arg("single") << copy_arg("thread") << copy_arg("comment") << copy_arg("t")); } } else if (path[0] == "login") { if (path.size() >= 2 && !path[1].empty()) { @@ -1296,7 +1295,7 @@ Result LinkManager::get_message_link_info(Slice url) { bool for_comment = false; if (link_info.is_tg_) { // resolve?domain=username&post=12345&single&t=123&comment=12&thread=21 - // privatepost?channel=123456789&msg_id=12345&single&t=123&comment=12&thread=21 + // privatepost?channel=123456789&post=12345&single&t=123&comment=12&thread=21 bool is_resolve = false; if (begins_with(url, "resolve")) { @@ -1323,16 +1322,13 @@ Result LinkManager::get_message_link_info(Slice url) { if (key_value.first == "domain") { username = key_value.second; } - if (key_value.first == "post") { - message_id_slice = key_value.second; - } } else { if (key_value.first == "channel") { channel_id_slice = key_value.second; } - if (key_value.first == "msg_id") { - message_id_slice = key_value.second; - } + } + if (key_value.first == "post") { + message_id_slice = key_value.second; } if (key_value.first == "t") { media_timestamp_slice = key_value.second; diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 3ea6fd9d7..87a4821dd 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -448,7 +448,7 @@ class MessageChatSetTtl final : public MessageContent { class MessageUnsupported final : public MessageContent { public: - static constexpr int32 CURRENT_VERSION = 7; + static constexpr int32 CURRENT_VERSION = 8; int32 version = CURRENT_VERSION; MessageUnsupported() = default; diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index a8d1da6c1..b333c0f8f 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -10920,22 +10920,28 @@ void MessagesManager::find_newer_messages(const Message *m, MessageId min_messag } void MessagesManager::find_unloadable_messages(const Dialog *d, int32 unload_before_date, const Message *m, - vector &message_ids, int32 &left_to_unload) const { + vector &message_ids, + bool &has_left_to_unload_messages) const { if (m == nullptr) { return; } - find_unloadable_messages(d, unload_before_date, m->left.get(), message_ids, left_to_unload); + find_unloadable_messages(d, unload_before_date, m->left.get(), message_ids, has_left_to_unload_messages); if (can_unload_message(d, m)) { if (m->last_access_date <= unload_before_date) { message_ids.push_back(m->message_id); } else { - left_to_unload++; + has_left_to_unload_messages = true; } } - find_unloadable_messages(d, unload_before_date, m->right.get(), message_ids, left_to_unload); + if (has_left_to_unload_messages && m->date > unload_before_date) { + // we aren't interested in unloading too new messages + return; + } + + find_unloadable_messages(d, unload_before_date, m->right.get(), message_ids, has_left_to_unload_messages); } void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, @@ -10995,7 +11001,7 @@ void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, Dialo } vector message_ids; - find_messages(d->messages.get(), message_ids, [this, sender_dialog_id](const Message *m) { + find_messages(d->messages.get(), message_ids, [sender_dialog_id](const Message *m) { return sender_dialog_id == MessagesManager::get_message_sender(m); }); @@ -11202,6 +11208,11 @@ int32 MessagesManager::get_unload_dialog_delay() const { return narrow_cast(G()->shared_config().get_option_integer("message_unload_delay", default_unload_delay)); } +int32 MessagesManager::get_next_unload_dialog_delay() const { + auto delay = get_unload_dialog_delay(); + return Random::fast(delay / 4, delay / 2); +} + void MessagesManager::unload_dialog(DialogId dialog_id) { if (G()->close_flag()) { return; @@ -11224,9 +11235,9 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { } vector to_unload_message_ids; - int32 left_to_unload = 0; + bool has_left_to_unload_messages = false; find_unloadable_messages(d, G()->unix_time_cached() - get_unload_dialog_delay() + 2, d->messages.get(), - to_unload_message_ids, left_to_unload); + to_unload_message_ids, has_left_to_unload_messages); vector unloaded_message_ids; for (auto message_id : to_unload_message_ids) { @@ -11244,9 +11255,9 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { make_tl_object(dialog_id.get(), std::move(unloaded_message_ids), false, true)); } - if (left_to_unload > 0) { - LOG(DEBUG) << "Need to unload " << left_to_unload << " messages more in " << dialog_id; - pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), get_unload_dialog_delay()); + if (has_left_to_unload_messages) { + LOG(DEBUG) << "Need to unload more messages in " << dialog_id; + pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), get_next_unload_dialog_delay()); } else { d->has_unload_timeout = false; } @@ -20274,8 +20285,8 @@ void MessagesManager::close_dialog(Dialog *d) { if (is_message_unload_enabled()) { CHECK(!d->has_unload_timeout); + pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay()); d->has_unload_timeout = true; - pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_unload_dialog_delay()); } for (auto &it : d->pending_viewed_live_locations) { @@ -24212,8 +24223,14 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( } else { add_sender(get_my_dialog_id()); } - for (auto channel_id : created_public_broadcasts_) { - add_sender(DialogId(channel_id)); + auto sorted_channel_ids = transform(created_public_broadcasts_, [&](ChannelId channel_id) { + auto participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id); + return std::make_pair(-participant_count, channel_id.get()); + }); + std::sort(sorted_channel_ids.begin(), sorted_channel_ids.end()); + + for (auto channel_id : sorted_channel_ids) { + add_sender(DialogId(ChannelId(channel_id.second))); } } return promise.set_value(std::move(senders)); @@ -24265,7 +24282,7 @@ void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dial break; } if (!is_broadcast_channel(message_sender_dialog_id) || - td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()).empty()) { + td_->contacts_manager_->get_channel_username(message_sender_dialog_id.get_channel_id()).empty()) { return promise.set_error(Status::Error(400, "Message sender chat must be a public channel")); } break; @@ -33200,7 +33217,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled() && !d->has_unload_timeout) { LOG(INFO) << "Schedule unload of " << dialog_id; - pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_unload_dialog_delay()); + pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay()); d->has_unload_timeout = true; } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 3eb943ba4..944e3c2dc 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -2005,6 +2005,8 @@ class MessagesManager final : public Actor { int32 get_unload_dialog_delay() const; + int32 get_next_unload_dialog_delay() const; + void unload_dialog(DialogId dialog_id); void delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanently_deleted); @@ -2061,7 +2063,7 @@ class MessagesManager final : public Actor { static void find_newer_messages(const Message *m, MessageId min_message_id, vector &message_ids); void find_unloadable_messages(const Dialog *d, int32 unload_before_date, const Message *m, - vector &message_ids, int32 &left_to_unload) const; + vector &message_ids, bool &has_left_to_unload_messages) const; void on_pending_message_views_timeout(DialogId dialog_id); diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 0f28b717f..6fc453181 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -1652,16 +1652,16 @@ void UpdatesManager::on_pending_updates(vector 0 && updates.size() == 1 && updates[0] != nullptr && - updates[0]->get_id() == telegram_api::updateReadHistoryOutbox::ID) { - auto update = static_cast(updates[0].get()); - DialogId dialog_id(update->peer_); - if (dialog_id.get_type() == DialogType::User) { - auto user_id = dialog_id.get_user_id(); - if (user_id.is_valid()) { - td_->contacts_manager_->on_update_user_local_was_online(user_id, date); - } + if (date > 0 && updates.size() == 1 && updates[0] != nullptr && + updates[0]->get_id() == telegram_api::updateReadHistoryOutbox::ID) { + auto update = static_cast(updates[0].get()); + DialogId dialog_id(update->peer_); + if (dialog_id.get_type() == DialogType::User) { + auto user_id = dialog_id.get_user_id(); + if (user_id.is_valid()) { + td_->contacts_manager_->on_update_user_local_was_online(user_id, date); } } } @@ -2029,6 +2029,14 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr return; } + // is_acceptable_update check was skipped for postponed pts updates + if (Slice(source) == "after get difference" && !is_acceptable_update(update.get())) { + LOG(INFO) << "Postpone again unacceptable pending update"; + postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise)); + set_pts_gap_timeout(0.001); + return; + } + if (old_pts > new_pts - pts_count) { LOG(WARNING) << "Have old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts << "). Logged in " << G()->shared_config().get_option_integer("authorization_date") << ". Update from " diff --git a/test/link.cpp b/test/link.cpp index 52d0f6533..19bb4f58a 100644 --- a/test/link.cpp +++ b/test/link.cpp @@ -233,18 +233,18 @@ TEST(Link, parse_internal_link) { unknown_deep_link("tg://privatepost?domain=username/12345&single")); parse_internal_link("tg:privatepost?channel=username/12345&single", unknown_deep_link("tg://privatepost?channel=username/12345&single")); - parse_internal_link("tg:privatepost?channel=username&msg_id=12345", - message("tg:privatepost?channel=username&msg_id=12345")); + parse_internal_link("tg:privatepost?channel=username&post=12345", + message("tg:privatepost?channel=username&post=12345")); parse_internal_link("t.me/c/12345?single", nullptr); parse_internal_link("t.me/c/1/c?single", nullptr); parse_internal_link("t.me/c/c/1?single", nullptr); parse_internal_link("t.me/c//1?single", nullptr); - parse_internal_link("t.me/c/12345/123", message("tg:privatepost?channel=12345&msg_id=123")); - parse_internal_link("t.me/c/12345/123?single", message("tg:privatepost?channel=12345&msg_id=123&single")); - parse_internal_link("t.me/c/12345/123/asd/asd////?single", message("tg:privatepost?channel=12345&msg_id=123&single")); + parse_internal_link("t.me/c/12345/123", message("tg:privatepost?channel=12345&post=123")); + parse_internal_link("t.me/c/12345/123?single", message("tg:privatepost?channel=12345&post=123&single")); + parse_internal_link("t.me/c/12345/123/asd/asd////?single", message("tg:privatepost?channel=12345&post=123&single")); parse_internal_link("t.me/c/%312345/%3123?comment=456&t=789&single&thread=123%20%31", - message("tg:privatepost?channel=12345&msg_id=123&single&thread=123%201&comment=456&t=789")); + message("tg:privatepost?channel=12345&post=123&single&thread=123%201&comment=456&t=789")); parse_internal_link("tg:bg?color=111111#asdasd", background("111111")); parse_internal_link("tg:bg?color=11111%31", background("111111"));