Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2021-08-23 02:22:28 +02:00
commit 232ce6e4d3
47 changed files with 1979 additions and 907 deletions

View File

@ -544,6 +544,7 @@ set(TDLIB_SOURCE
td/telegram/MessageId.h
td/telegram/MessageLinkInfo.h
td/telegram/MessageReplyInfo.h
td/telegram/MessageThreadInfo.h
td/telegram/MessagesDb.h
td/telegram/MessageSearchFilter.h
td/telegram/MessagesManager.h

View File

@ -203,11 +203,9 @@ For C++ projects that use CMake, the best approach is to build `TDLib` as part o
There are several libraries that you could use in your CMake project:
* Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for more information.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for more information.
* Td::TdStatic — static library with C++ interface for general usage.
See [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information.
* Td::TdCoreStatic — static library with low-level C++ interface intended mostly for internal usage.
See [ClientActor](https://core.telegram.org/tdlib/docs/classtd_1_1_client_actor.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information.
See [ClientManager](https://core.telegram.org/tdlib/docs/classtd_1_1_client_manager.html) and [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) documentation for more information.
For example, part of your CMakeLists.txt may look like this:
```
@ -245,7 +243,7 @@ git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
## Using from other programming languages
`TDLib` provides efficient native C++, Java, and .NET interfaces.
But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for detailed JSON interface description,
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for detailed JSON interface description,
the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of
all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).

View File

@ -16,6 +16,7 @@
#include "td/utils/port/thread.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/ThreadSafeCounter.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/telegram_api.hpp"
@ -32,34 +33,32 @@
#include <atomic>
#include <cstdint>
namespace td {
class F {
uint32 &sum;
td::uint32 &sum;
public:
explicit F(uint32 &sum) : sum(sum) {
explicit F(td::uint32 &sum) : sum(sum) {
}
template <class T>
void operator()(const T &x) const {
sum += static_cast<uint32>(x.get_id());
sum += static_cast<td::uint32>(x.get_id());
}
};
BENCH(Call, "TL Call") {
tl_object_ptr<telegram_api::Function> x = make_tl_object<telegram_api::account_getWallPapers>(0);
uint32 res = 0;
td::tl_object_ptr<td::telegram_api::Function> x = td::make_tl_object<td::telegram_api::account_getWallPapers>(0);
td::uint32 res = 0;
F f(res);
for (int i = 0; i < n; i++) {
downcast_call(*x, f);
}
do_not_optimize_away(res);
td::do_not_optimize_away(res);
}
#if !TD_EVENTFD_UNSUPPORTED
BENCH(EventFd, "EventFd") {
EventFd fd;
td::EventFd fd;
fd.init();
for (int i = 0; i < n; i++) {
fd.release();
@ -76,15 +75,15 @@ BENCH(NewInt, "new int + delete") {
res += reinterpret_cast<std::uintptr_t>(x);
delete x;
}
do_not_optimize_away(res);
td::do_not_optimize_away(res);
}
BENCH(NewObj, "new struct, then delete") {
struct A {
int32 a = 0;
int32 b = 0;
int32 c = 0;
int32 d = 0;
td::int32 a = 0;
td::int32 b = 0;
td::int32 c = 0;
td::int32 d = 0;
};
std::uintptr_t res = 0;
A **ptr = new A *[n];
@ -96,14 +95,14 @@ BENCH(NewObj, "new struct, then delete") {
delete ptr[i];
}
delete[] ptr;
do_not_optimize_away(res);
td::do_not_optimize_away(res);
}
#if !TD_THREAD_UNSUPPORTED
BENCH(ThreadNew, "new struct, then delete in several threads") {
td::NewObjBench a, b;
thread ta([&] { a.run(n / 2); });
thread tb([&] { b.run(n - n / 2); });
NewObjBench a, b;
td::thread ta([&] { a.run(n / 2); });
td::thread tb([&] { b.run(n - n / 2); });
ta.join();
tb.join();
}
@ -113,17 +112,17 @@ BENCH(ThreadNew, "new struct, then delete in several threads") {
BENCH(Time, "Clocks::monotonic") {
double res = 0;
for (int i = 0; i < n; i++) {
res += Clocks::monotonic();
res += td::Clocks::monotonic();
}
do_not_optimize_away(res);
td::do_not_optimize_away(res);
}
*/
#if !TD_WINDOWS
class PipeBench final : public Benchmark {
class PipeBench final : public td::Benchmark {
public:
int p[2];
string get_description() const final {
td::string get_description() const final {
return "pipe write + read int32";
}
@ -142,7 +141,7 @@ class PipeBench final : public Benchmark {
CHECK(read_len == sizeof(val));
res += val;
}
do_not_optimize_away(res);
td::do_not_optimize_away(res);
}
void tear_down() final {
@ -153,11 +152,11 @@ class PipeBench final : public Benchmark {
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
class SemBench final : public Benchmark {
class SemBench final : public td::Benchmark {
sem_t sem;
public:
string get_description() const final {
td::string get_description() const final {
return "sem post + wait";
}
@ -180,12 +179,12 @@ class SemBench final : public Benchmark {
#endif
#if !TD_WINDOWS
class UtimeBench final : public Benchmark {
class UtimeBench final : public td::Benchmark {
public:
void start_up() final {
FileFd::open("test", FileFd::Flags::Create | FileFd::Flags::Write).move_as_ok().close();
td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok().close();
}
string get_description() const final {
td::string get_description() const final {
return "utime";
}
void run(int n) final {
@ -203,62 +202,62 @@ class UtimeBench final : public Benchmark {
#endif
BENCH(Pwrite, "pwrite") {
auto fd = FileFd::open("test", FileFd::Flags::Create | FileFd::Flags::Write).move_as_ok();
auto fd = td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok();
for (int i = 0; i < n; i++) {
fd.pwrite("a", 0).ok();
}
fd.close();
}
class CreateFileBench final : public Benchmark {
string get_description() const final {
class CreateFileBench final : public td::Benchmark {
td::string get_description() const final {
return "create_file";
}
void start_up() final {
mkdir("A").ensure();
td::mkdir("A").ensure();
}
void run(int n) final {
for (int i = 0; i < n; i++) {
FileFd::open(PSLICE() << "A/" << i, FileFd::Flags::Write | FileFd::Flags::Create).move_as_ok().close();
td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
void tear_down() final {
td::walk_path("A/", [&](CSlice path, auto type) {
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::ExitDir) {
rmdir(path).ignore();
td::rmdir(path).ignore();
} else if (type == td::WalkPath::Type::NotDir) {
unlink(path).ignore();
td::unlink(path).ignore();
}
}).ignore();
}
};
class WalkPathBench final : public Benchmark {
string get_description() const final {
class WalkPathBench final : public td::Benchmark {
td::string get_description() const final {
return "walk_path";
}
void start_up_n(int n) final {
mkdir("A").ensure();
td::mkdir("A").ensure();
for (int i = 0; i < n; i++) {
FileFd::open(PSLICE() << "A/" << i, FileFd::Flags::Write | FileFd::Flags::Create).move_as_ok().close();
td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
void run(int n) final {
int cnt = 0;
td::walk_path("A/", [&](CSlice path, auto type) {
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::EnterDir) {
return;
}
stat(path).ok();
td::stat(path).ok();
cnt++;
}).ignore();
}
void tear_down() final {
td::walk_path("A/", [&](CSlice path, auto type) {
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::ExitDir) {
rmdir(path).ignore();
td::rmdir(path).ignore();
} else if (type == td::WalkPath::Type::NotDir) {
unlink(path).ignore();
td::unlink(path).ignore();
}
}).ignore();
}
@ -266,14 +265,14 @@ class WalkPathBench final : public Benchmark {
#if !TD_THREAD_UNSUPPORTED
template <int ThreadN = 2>
class AtomicReleaseIncBench final : public Benchmark {
string get_description() const final {
class AtomicReleaseIncBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "AtomicReleaseInc" << ThreadN;
}
static std::atomic<uint64> a_;
static std::atomic<td::uint64> a_;
void run(int n) final {
std::vector<thread> threads;
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@ -287,17 +286,17 @@ class AtomicReleaseIncBench final : public Benchmark {
}
};
template <int ThreadN>
std::atomic<uint64> AtomicReleaseIncBench<ThreadN>::a_;
std::atomic<td::uint64> AtomicReleaseIncBench<ThreadN>::a_;
template <int ThreadN = 2>
class AtomicReleaseCasIncBench final : public Benchmark {
string get_description() const final {
class AtomicReleaseCasIncBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "AtomicReleaseCasInc" << ThreadN;
}
static std::atomic<uint64> a_;
static std::atomic<td::uint64> a_;
void run(int n) final {
std::vector<thread> threads;
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@ -313,16 +312,16 @@ class AtomicReleaseCasIncBench final : public Benchmark {
}
};
template <int ThreadN>
std::atomic<uint64> AtomicReleaseCasIncBench<ThreadN>::a_;
std::atomic<td::uint64> AtomicReleaseCasIncBench<ThreadN>::a_;
template <int ThreadN = 2>
class RwMutexReadBench final : public Benchmark {
string get_description() const final {
template <int ThreadN>
class RwMutexReadBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "RwMutexRead" << ThreadN;
}
RwMutex mutex_;
td::RwMutex mutex_;
void run(int n) final {
std::vector<thread> threads;
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@ -335,14 +334,15 @@ class RwMutexReadBench final : public Benchmark {
}
}
};
template <int ThreadN = 2>
class RwMutexWriteBench final : public Benchmark {
string get_description() const final {
template <int ThreadN>
class RwMutexWriteBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "RwMutexWrite" << ThreadN;
}
RwMutex mutex_;
td::RwMutex mutex_;
void run(int n) final {
std::vector<thread> threads;
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@ -355,41 +355,107 @@ class RwMutexWriteBench final : public Benchmark {
}
}
};
class ThreadSafeCounterBench final : public td::Benchmark {
static td::ThreadSafeCounter counter_;
int thread_count_;
td::string get_description() const final {
return PSTRING() << "ThreadSafeCounter" << thread_count_;
}
void run(int n) final {
counter_.clear();
td::vector<td::thread> threads;
for (int i = 0; i < thread_count_; i++) {
threads.emplace_back([n] {
for (int i = 0; i < n; i++) {
counter_.add(1);
}
});
}
for (auto &thread : threads) {
thread.join();
}
CHECK(counter_.sum() == n * thread_count_);
}
public:
explicit ThreadSafeCounterBench(int thread_count) : thread_count_(thread_count) {
}
};
td::ThreadSafeCounter ThreadSafeCounterBench::counter_;
template <bool StrictOrder>
class AtomicCounterBench final : public td::Benchmark {
static std::atomic<td::int64> counter_;
int thread_count_;
td::string get_description() const final {
return PSTRING() << "AtomicCounter" << thread_count_;
}
void run(int n) final {
counter_.store(0);
td::vector<td::thread> threads;
for (int i = 0; i < thread_count_; i++) {
threads.emplace_back([n] {
for (int i = 0; i < n; i++) {
counter_.fetch_add(1, StrictOrder ? std::memory_order_seq_cst : std::memory_order_relaxed);
}
});
}
for (auto &thread : threads) {
thread.join();
}
CHECK(counter_.load() == n * thread_count_);
}
public:
explicit AtomicCounterBench(int thread_count) : thread_count_(thread_count) {
}
};
template <bool StrictOrder>
std::atomic<td::int64> AtomicCounterBench<StrictOrder>::counter_;
#endif
} // namespace td
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
#if !TD_THREAD_UNSUPPORTED
td::bench(td::AtomicReleaseIncBench<1>());
td::bench(td::AtomicReleaseIncBench<2>());
td::bench(td::AtomicReleaseCasIncBench<1>());
td::bench(td::AtomicReleaseCasIncBench<2>());
td::bench(td::RwMutexWriteBench<1>());
td::bench(td::RwMutexReadBench<1>());
td::bench(td::RwMutexWriteBench<>());
td::bench(td::RwMutexReadBench<>());
for (int i = 1; i <= 16; i *= 2) {
td::bench(ThreadSafeCounterBench(i));
td::bench(AtomicCounterBench<false>(i));
td::bench(AtomicCounterBench<true>(i));
}
td::bench(AtomicReleaseIncBench<1>());
td::bench(AtomicReleaseIncBench<2>());
td::bench(AtomicReleaseCasIncBench<1>());
td::bench(AtomicReleaseCasIncBench<2>());
td::bench(RwMutexWriteBench<1>());
td::bench(RwMutexReadBench<1>());
td::bench(RwMutexWriteBench<2>());
td::bench(RwMutexReadBench<2>());
#endif
#if !TD_WINDOWS
td::bench(td::UtimeBench());
td::bench(UtimeBench());
#endif
td::bench(td::WalkPathBench());
td::bench(td::CreateFileBench());
td::bench(td::PwriteBench());
td::bench(WalkPathBench());
td::bench(CreateFileBench());
td::bench(PwriteBench());
td::bench(td::CallBench());
td::bench(CallBench());
#if !TD_THREAD_UNSUPPORTED
td::bench(td::ThreadNewBench());
td::bench(ThreadNewBench());
#endif
#if !TD_EVENTFD_UNSUPPORTED
td::bench(td::EventFdBench());
td::bench(EventFdBench());
#endif
td::bench(td::NewObjBench());
td::bench(td::NewIntBench());
td::bench(NewObjBench());
td::bench(NewIntBench());
#if !TD_WINDOWS
td::bench(td::PipeBench());
td::bench(PipeBench());
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
td::bench(td::SemBench());
td::bench(SemBench());
#endif
}

View File

@ -626,7 +626,7 @@ function onOptionsChanged() {
cmake = 'cmake3';
packages += ' gperf';
} else {
commands.push(sudo + 'dnf --enablerepo=PowerTools install gperf');
commands.push(sudo + 'dnf --enablerepo=powertools install gperf');
}
packages += ' ' + cmake;
if (target === 'JNI') {

View File

@ -7,5 +7,5 @@ Then you can run the example:
python tdjson_example.py
```
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html),
[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@ -11,5 +11,5 @@ cmake --build . --target install
Then you can open and build the example with the latest Xcode.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html),
[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@ -759,6 +759,8 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r
//@can_be_deleted_for_all_users True, if the message can be deleted for all users
//@can_get_statistics True, if the message statistics are available
//@can_get_message_thread True, if the message thread info is available
//@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or web page description
//@has_timestamped_media True, if media timestamp entities refers to a media in this message as opposed to a media in the replied message
//@is_channel_post True, if the message is a channel post. All messages to channels are channel posts, all other messages are not channel posts
//@contains_unread_mention True, if the message contains an unread mention for the current user
//@date Point in time (Unix timestamp) when the message was sent
@ -776,7 +778,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r
//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted
//@content Content of the message
//@reply_markup Reply markup for the message; may be null
message id:int53 sender:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_statistics:Bool can_get_message_thread:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int32 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message;
message id:int53 sender:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_media_timestamp_links:Bool has_timestamped_media:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int32 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message;
//@description Contains a list of messages @total_count Approximate total count of messages found @messages List of messages; messages may be null
messages total_count:int32 messages:vector<message> = Messages;
@ -1257,7 +1259,8 @@ pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageB
//@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
webPageInstantView page_blocks:vector<PageBlock> view_count:int32 version:int32 is_rtl:Bool is_full:Bool = WebPageInstantView;
//@feedback_link An internal link to be opened to leave feedback about the instant view
webPageInstantView page_blocks:vector<PageBlock> view_count:int32 version:int32 is_rtl:Bool is_full:Bool feedback_link:InternalLinkType = WebPageInstantView;
//@description Describes a web page preview
@ -1852,6 +1855,9 @@ textEntityTypeTextUrl url:string = TextEntityType;
//@description A text shows instead of a raw mention of the user (e.g., when the user has no username) @user_id Identifier of the mentioned user
textEntityTypeMentionName user_id:int32 = TextEntityType;
//@description A media timestamp @media_timestamp Timestamp from which a video/audio/video note/voice note playing should start, in seconds. The media can be in the content or the web page preview of the current message, or in the same places in the replied message
textEntityTypeMediaTimestamp media_timestamp:int32 = TextEntityType;
//@description A thumbnail to be sent along with a file; must be in JPEG or WEBP format for stickers, and less than 200 KB in size @thumbnail Thumbnail file to send. Sending thumbnails by file_id is currently not supported
//@width Thumbnail width, usually shouldn't exceed 320. Use 0 if unknown @height Thumbnail height, usually shouldn't exceed 320. Use 0 if unknown
@ -3052,27 +3058,27 @@ internalLinkTypeBotStartInGroup bot_username:string start_parameter:string = Int
//@description The link is a link to the change phone number section of the app
internalLinkTypeChangePhoneNumber = InternalLinkType;
//@description The link is a chat invite link. Call checkChatInviteLink to process the link
internalLinkTypeChatInvite = InternalLinkType;
//@description The link is a chat invite link. Call checkChatInviteLink with the given invite link to process the link @invite_link Internal representation of the invite link
internalLinkTypeChatInvite invite_link:string = InternalLinkType;
//@description The link is a link to the filter settings section of the app
internalLinkTypeFilterSettings = InternalLinkType;
//@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a group to send the game, and then call sendMessage with inputMessageGame
//@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a chat to send the game, and then call sendMessage with inputMessageGame
//@bot_username Username of the bot that owns the game @game_short_name Short name of the game
internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType;
//@description The link is a link to a language pack. Call getLanguagePackInfo with the given language pack identifier to process the link @language_pack_id Language pack identifier
internalLinkTypeLanguagePack language_pack_id:string = InternalLinkType;
//@description The link is a link to a Telegram message. Call getMessageLinkInfo to process the link
internalLinkTypeMessage = InternalLinkType;
//@description The link is a link to a Telegram message. Call getMessageLinkInfo with the given URL to process the link @url URL to be passed to getMessageLinkInfo
internalLinkTypeMessage url:string = InternalLinkType;
//@description The link contains a message draft text. A share screen needs to be shown to the user, then the chosen chat should be open and the text should be added to the input field
//@text Message draft text @contains_link True, if the first line of the text contains a link. If true, the input field needs to be focused and the text after the link should be selected
internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType;
//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm to process the link if the link was received outside of the app, otherwise ignore it
//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the app, otherwise ignore it
//@bot_user_id User identifier of the service's bot @scope Telegram Passport element types requested by the service @public_key Service's public key @nonce Unique request identifier provided by the service
//@callback_url An HTTP URL to open once the request is finished or canceled with the parameter tg_passport=success or tg_passport=cancel respectively. If empty, then the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel needs to be opened instead
internalLinkTypePassportDataRequest bot_user_id:int32 scope:string public_key:string nonce:string callback_url:string = InternalLinkType;
@ -3081,7 +3087,7 @@ internalLinkTypePassportDataRequest bot_user_id:int32 scope:string public_key:st
//@hash Hash value from the link @phone_number Phone number value from the link
internalLinkTypePhoneNumberConfirmation hash:string phone_number:string = InternalLinkType;
//@description The link is a link to a proxy. Call addProxy to process the link and add the proxy
//@description The link is a link to a proxy. Call addProxy with the given parameters to process the link and add the proxy
//@server Proxy server IP address @port Proxy server port @type Type of the proxy
internalLinkTypeProxy server:string port:int32 type:ProxyType = InternalLinkType;
@ -3104,8 +3110,8 @@ internalLinkTypeTheme theme_name:string = InternalLinkType;
//@description The link is a link to the theme settings section of the app
internalLinkTypeThemeSettings = InternalLinkType;
//@description The link is an unknown tg: link. Call getDeepLinkInfo to process the link
internalLinkTypeUnknownDeepLink = InternalLinkType;
//@description The link is an unknown tg: link. Call getDeepLinkInfo to process the link @link Link to be passed to getDeepLinkInfo
internalLinkTypeUnknownDeepLink link:string = InternalLinkType;
//@description The link is a link to a voice chat. Call searchPublicChat with the given chat username, and then joinGoupCall with the given invite hash to process the link
//@chat_username Username of the chat with the voice chat @invite_hash If non-empty, invite hash to be used to join the voice chat without being muted by administrators
@ -3119,7 +3125,7 @@ messageLink link:string is_public:Bool = MessageLink;
//@is_public True, if the link is a public link for a message in a chat
//@chat_id If found, identifier of the chat to which the message belongs, 0 otherwise
//@message If found, the linked message; may be null
//@media_timestamp Timestamp from which the video/audio/video note/voice note playing should start, in seconds; 0 if not specified. The media can be in the message content or in its link preview
//@media_timestamp Timestamp from which the video/audio/video note/voice note playing should start, in seconds; 0 if not specified. The media can be in the message content or in its web page preview
//@for_album True, if the whole media album to which the message belongs is linked
//@for_comment True, if the message is linked as a channel post comment or from a message thread
messageLinkInfo is_public:Bool chat_id:int53 message:message media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLinkInfo;
@ -3245,7 +3251,7 @@ networkStatistics since_date:int32 entries:vector<NetworkStatisticsEntry> = Netw
//@use_less_data_for_calls True, if "use less data for calls" option needs to be enabled
autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int32 max_other_file_size:int32 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings;
//@description Contains auto-download settings presets for the user
//@description Contains auto-download settings presets for the current user
//@low Preset with lowest settings; supposed to be used by default when roaming
//@medium Preset with medium settings; supposed to be used by default when using mobile data
//@high Preset with highest settings; supposed to be used by default when connected on Wi-Fi
@ -3926,7 +3932,7 @@ setDatabaseEncryptionKey new_encryption_key:bytes = Ok;
//@description Returns the current state of 2-step verification
getPasswordState = PasswordState;
//@description Changes the password for the user. If a new recovery email address is specified, then the change will not be applied until the new recovery email address is confirmed
//@description Changes the password for the current user. If a new recovery email address is specified, then the change will not be applied until the new recovery email address is confirmed
//@old_password Previous password of the user @new_password New password of the user; may be empty to remove the password @new_hint New password hint; may be empty @set_recovery_email_address Pass true if the recovery email address should be changed @new_recovery_email_address New recovery email address; may be empty
setPassword old_password:string new_password:string new_hint:string set_recovery_email_address:Bool new_recovery_email_address:string = PasswordState;
@ -4027,10 +4033,10 @@ getChannelDifference channel_difference_id:int64 = Ok;
getRemoteFile remote_file_id:string file_type:FileType = File;
//@description Returns an ordered list of chats in a chat list. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. (For example, to get a list of chats from the beginning, the offset_order should be equal to a biggest signed 64-bit number 9223372036854775807 == 2^63 - 1).
//-For optimal performance the number of returned chats is chosen by the library
//-For optimal performance, the number of returned chats is chosen by TDLib
//@chat_list The chat list in which to return chats
//@offset_order Chat order to return chats from @offset_chat_id Chat identifier to return chats from
//@limit The maximum number of chats to be returned. It is possible that fewer chats than the limit are returned even if the end of the list is not reached
//@limit The maximum number of chats to be returned. For optimal performance, the number of returned chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached
getChats chat_list:ChatList offset_order:int64 offset_chat_id:int53 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
@ -4039,7 +4045,7 @@ 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. Returns nothing if the length of the searched username prefix is less than 5. Excludes private chats with contacts and chats from the chat list from the results @query Query to search for
searchPublicChats query:string = Chats;
//@description Searches for the specified query in the title and username of already known chats, this is an offline request. Returns chats in the order seen in the main chat list @query Query to search for. If the query is empty, returns up to 20 recently found chats @limit The maximum number of chats to be returned
//@description Searches for the specified query in the title and username of already known chats, this is an offline request. Returns chats in the order seen in the main chat list @query Query to search for. If the query is empty, returns up to 50 recently found chats @limit The maximum number of chats to be returned
searchChats query:string limit:int32 = Chats;
//@description Searches for the specified query in the title and username of already known chats via request to the server. Returns chats in the order seen in the main chat list @query Query to search for @limit The maximum number of chats to be returned
@ -4084,21 +4090,21 @@ getGroupsInCommon user_id:int32 offset_chat_id:int53 limit:int32 = Chats;
//@description Returns messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id).
//-For optimal performance the number of returned messages is chosen by the library. This is an offline request if only_local is true
//-For optimal performance, the number of returned messages is chosen by TDLib. This is an offline request if only_local is true
//@chat_id Chat identifier
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@only_local If true, returns only messages that are available locally without sending network requests
getChatHistory chat_id:int53 from_message_id:int53 offset:int32 limit:int32 only_local:Bool = Messages;
//@description Returns messages in a message thread of a message. Can be used only if message.can_get_message_thread == true. Message thread of a channel message is in the channel's linked supergroup.
//-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library
//-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Chat identifier
//@message_id Message identifier, which thread history needs to be returned
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message thread history has not been reached
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 offset:int32 limit:int32 = Messages;
//@description Deletes all messages in the chat. Use Chat.can_be_deleted_only_for_self and Chat.can_be_deleted_for_all_users fields to find whether and how the method can be applied to the chat
@ -4109,41 +4115,41 @@ deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok;
deleteChat chat_id:int53 = Ok;
//@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query
//-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance the number of returned messages is chosen by the library
//-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@chat_id Identifier of the chat in which to search messages
//@query Query to search for
//@sender If not null, only messages sent by the specified sender will be returned. Not supported in secret chats
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset to get the specified message and some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Filter for message content in the search results
//@message_thread_id If not 0, only messages in the specified thread will be returned; supergroups only
searchChatMessages chat_id:int53 query:string sender:MessageSender from_message_id:int53 offset:int32 limit:int32 filter:SearchMessagesFilter message_thread_id:int53 = Messages;
//@description Searches for messages in all chats except secret chats. Returns the results in reverse chronological order (i.e., in order of decreasing (date, chat_id, message_id)).
//-For optimal performance the number of returned messages is chosen by the library
//-For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@chat_list Chat list in which to search messages; pass null to search in all chats regardless of their chat list. Only Main and Archive chat lists are supported
//@query Query to search for
//@offset_date The date of the message starting from which the results should be fetched. Use 0 or any date in the future to get results from the last message
//@offset_chat_id The chat identifier of the last found message, or 0 for the first request
//@offset_message_id The message identifier of the last found message, or 0 for the first request
//@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Filter for message content in the search results; searchMessagesFilterCall, searchMessagesFilterMissedCall, searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterFailedToSend and searchMessagesFilterPinned are unsupported in this function
//@min_date If not 0, the minimum date of the messages to return
//@max_date If not 0, the maximum date of the messages to return
searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = Messages;
//@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance the number of returned messages is chosen by the library
//@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats
//@query Query to search for. If empty, searchChatMessages should be used instead
//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results
//@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter A filter for message content in the search results
searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter:SearchMessagesFilter = FoundMessages;
//@description Searches for call messages. Returns the results in reverse chronological order (i. e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library
//@description Searches for call messages. Returns the results in reverse chronological order (i. e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib
//@from_message_id Identifier of the message from which to search; use 0 to get results from the last message
//@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached @only_missed If true, returns only messages with missed calls
//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit @only_missed If true, returns only messages with missed calls
searchCallMessages from_message_id:int53 limit:int32 only_missed:Bool = Messages;
//@description Deletes all call messages @revoke Pass true to delete the messages for all users
@ -4164,11 +4170,11 @@ getChatMessageCount chat_id:int53 filter:SearchMessagesFilter return_local:Bool
//@description Returns all scheduled messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id) @chat_id Chat identifier
getChatScheduledMessages chat_id:int53 = Messages;
//@description Returns forwarded copies of a channel message to different public channels. For optimal performance the number of returned messages is chosen by the library
//@description Returns forwarded copies of a channel message to different public channels. For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Chat identifier of the message
//@message_id Message identifier
//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. Fewer messages may be returned than specified by the limit, even if the end of the list has not been reached
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages;
@ -4179,10 +4185,10 @@ removeNotification notification_group_id:int32 notification_id:int32 = Ok;
removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = Ok;
//@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels. This is an offline request
//@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels, or if message.can_get_media_timestamp_links and a media timestamp link is generated. This is an offline request
//@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message
//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing should start, in seconds. The media can be in the message content or in its link preview
//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing should start, in seconds. The media can be in the message content or in its web page preview
//@for_album Pass true to create a link for the whole media album
//@for_comment Pass true to create a link to the message as a channel post comment, or from a message thread
getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLink;
@ -4193,7 +4199,7 @@ getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bo
//@for_album Pass true to return an HTML code for embedding of the whole media album
getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text;
//@description Returns information about a public or private message link @url The message link
//@description Returns information about a public or private message link. Can be called for any internal link of the type internalLinkTypeMessage @url The message link
getMessageLinkInfo url:string = MessageLinkInfo;
@ -4341,11 +4347,11 @@ getJsonString json_value:JsonValue = Text;
//@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers
setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@description Returns users voted for the specified option in a non-anonymous polls. For the optimal performance the number of returned users is chosen by the library
//@description Returns users voted for the specified option in a non-anonymous polls. For optimal performance, the number of returned users is chosen by TDLib
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_id 0-based identifier of the answer option
//@offset Number of users to skip in the result; must be non-negative
//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. Fewer users may be returned than specified by the limit, even if the end of the voter list has not been reached
//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned users is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached
getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set
@ -4435,7 +4441,7 @@ openMessageContent chat_id:int53 message_id:int53 = Ok;
//@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link
getInternalLinkType link:string = InternalLinkType;
//@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if link preview is disabled in secret chats @link The link
//@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if web page preview is disabled in secret chats @link The link
getExternalLinkInfo link:string = LoginUrlInfo;
//@description Returns an HTTP URL which can be used to automatically authorize the current user on a website after clicking an HTTP link. Use the method getExternalLinkInfo to find whether a prior user confirmation is needed
@ -4588,7 +4594,7 @@ setChatMemberStatus chat_id:int53 member_id:MessageSender status:ChatMemberStatu
//@chat_id Chat identifier
//@member_id Member identifier
//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever. Ignored in basic groups
//@revoke_messages Pass true to delete all messages in the chat for the user. Always true for supergroups and channels
//@revoke_messages Pass true to delete all messages in the chat for the user that is being removed. Always true for supergroups and channels
banChatMember chat_id:int53 member_id:MessageSender banned_until_date:int32 revoke_messages:Bool = Ok;
//@description Checks whether the current session can be used to transfer a chat ownership to another user
@ -4945,9 +4951,9 @@ getInstalledStickerSets is_masks:Bool = StickerSets;
//@description Returns a list of archived sticker sets @is_masks Pass true to return mask stickers sets; pass false to return ordinary sticker sets @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit The maximum number of sticker sets to return
getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = StickerSets;
//@description Returns a list of trending sticker sets. For the optimal performance the number of returned sticker sets is chosen by the library
//@description Returns a list of trending sticker sets. For optimal performance, the number of returned sticker sets is chosen by TDLib
//@offset The offset from which to return the sticker sets; must be non-negative
//@limit The maximum number of sticker sets to be returned; must be non-negative. Fewer sticker sets may be returned than specified by the limit, even if the end of the list has not been reached
//@limit The maximum number of sticker sets to be returned; must be non-negative. 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
@ -5042,7 +5048,7 @@ setProfilePhoto photo:InputChatPhoto = Ok;
//@description Deletes a profile photo @profile_photo_id Identifier of the profile photo to delete
deleteProfilePhoto profile_photo_id:int64 = Ok;
//@description Changes the first and last name of the current user @first_name The new value of the first name for the user; 1-64 characters @last_name The new value of the optional last name for the user; 0-64 characters
//@description Changes the first and last name of the current user @first_name The new value of the first name for the current user; 1-64 characters @last_name The new value of the optional last name for the current user; 0-64 characters
setName first_name:string last_name:string = Ok;
//@description Changes the bio of the current user @bio The new value of the user bio; 0-70 characters without line feeds
@ -5058,7 +5064,7 @@ setLocation location:location = Ok;
//@phone_number The new phone number of the user in international format @settings Settings for the authentication of the user's phone number
changePhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo;
//@description Re-sends the authentication code sent to confirm a new phone number for the user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed
//@description Re-sends the authentication code sent to confirm a new phone number for the current user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed
resendChangePhoneNumberCode = AuthenticationCodeInfo;
//@description Checks the authentication code sent to confirm a new phone number of the user @code Verification code received by SMS, phone call or flash call

View File

@ -1485,7 +1485,8 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
for (auto &key_value : static_cast<telegram_api::jsonObject *>(config.get())->value_) {
Slice key = key_value->key_;
telegram_api::JSONValue *value = key_value->value_.get();
if (key == "test" || key == "wallet_enabled" || key == "wallet_blockchain_name" || key == "wallet_config") {
if (key == "test" || key == "wallet_enabled" || key == "wallet_blockchain_name" || key == "wallet_config" ||
key == "stickers_emoji_cache_time") {
continue;
}
if (key == "ignore_restriction_reasons") {

View File

@ -9540,7 +9540,9 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s
Dependencies dependencies;
dependencies.channel_ids.insert(channel_id);
add_dialog_and_dependencies(dependencies, DialogId(channel_full->linked_channel_id));
// must not depend on the linked_dialog_id itself, because message database can be disabled
// the Dialog will be forcely created in update_channel_full
add_dialog_dependencies(dependencies, DialogId(channel_full->linked_channel_id));
dependencies.chat_ids.insert(channel_full->migrated_from_chat_id);
dependencies.user_ids.insert(channel_full->bot_user_ids.begin(), channel_full->bot_user_ids.end());
dependencies.user_ids.insert(channel_full->invite_link.get_creator_user_id());

View File

@ -240,8 +240,9 @@ void CountryInfoManager::do_get_phone_number_info(string phone_number_prefix, st
result += pattern[current_pattern_pos++];
}
if (current_pattern_pos == pattern.size()) {
result += c;
} else if (pattern[current_pattern_pos] == 'X') {
result += ' ';
}
if (current_pattern_pos >= pattern.size() || pattern[current_pattern_pos] == 'X') {
result += c;
current_pattern_pos++;
} else {

View File

@ -45,13 +45,13 @@ unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
}
auto entities = get_message_entities(contacts_manager, std::move(draft->entities_), "draftMessage");
auto status = fix_formatted_text(draft->message_, entities, true, true, true, true);
auto status = fix_formatted_text(draft->message_, entities, true, true, true, true, true);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_;
if (!clean_input_string(draft->message_)) {
draft->message_.clear();
}
entities = find_entities(draft->message_, false);
entities = find_entities(draft->message_, false, true);
}
result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)};
result->input_message_text.disable_web_page_preview = (flags & telegram_api::draftMessage::NO_WEBPAGE_MASK) != 0;

View File

@ -93,7 +93,7 @@ const FormattedText &Game::get_text() const {
tl_object_ptr<td_api::game> Game::get_game_object(Td *td, bool skip_bot_commands) const {
return make_tl_object<td_api::game>(
id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands), description_,
id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands, -1), description_,
get_photo_object(td->file_manager_.get(), photo_),
td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object"));
}

View File

@ -39,22 +39,24 @@ Result<InputMessageText> process_input_message_text(const ContactsManager *conta
}
TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(input_message_text->text_->entities_)));
auto need_skip_commands = need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot);
auto need_skip_bot_commands = need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot);
bool parse_markdown = G()->shared_config().get_option_boolean("always_parse_markdown");
TRY_STATUS(fix_formatted_text(input_message_text->text_->text_, entities, for_draft, parse_markdown,
need_skip_commands, for_draft));
need_skip_bot_commands, is_bot || for_draft || parse_markdown, for_draft));
InputMessageText result{FormattedText{std::move(input_message_text->text_->text_), std::move(entities)},
input_message_text->disable_web_page_preview_, input_message_text->clear_draft_};
if (G()->shared_config().get_option_boolean("always_parse_markdown")) {
if (parse_markdown) {
result.text = parse_markdown_v3(std::move(result.text));
fix_formatted_text(result.text.text, result.text.entities, for_draft, false, need_skip_commands, for_draft)
fix_formatted_text(result.text.text, result.text.entities, for_draft, false, need_skip_bot_commands,
is_bot || for_draft, for_draft)
.ensure();
}
return std::move(result);
}
// used only for draft
td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text) {
return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false),
return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false, -1),
input_message_text.disable_web_page_preview,
input_message_text.clear_draft);
}

View File

@ -16,6 +16,7 @@
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
#include "td/telegram/ServerMessageId.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
@ -64,6 +65,26 @@ static bool is_valid_username(Slice username) {
return true;
}
static string get_url_query_hash(bool is_tg, const HttpUrlQuery &url_query) {
const auto &path = url_query.path_;
if (is_tg) {
if (path.size() == 1 && path[0] == "join" && !url_query.get_arg("invite").empty()) {
// join?invite=abcdef
return url_query.get_arg("invite").str();
}
} else {
if (path.size() >= 2 && path[0] == "joinchat" && !path[1].empty()) {
// /joinchat/<link>
return path[1];
}
if (path.size() >= 1 && path[0].size() >= 2 && (path[0][0] == ' ' || path[0][0] == '+')) {
// /+<link>
return path[0].substr(1);
}
}
return string();
}
class LinkManager::InternalLinkActiveSessions final : public InternalLink {
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeActiveSessions>();
@ -143,8 +164,14 @@ class LinkManager::InternalLinkConfirmPhone final : public InternalLink {
};
class LinkManager::InternalLinkDialogInvite final : public InternalLink {
string url_;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeChatInvite>();
return td_api::make_object<td_api::internalLinkTypeChatInvite>(url_);
}
public:
explicit InternalLinkDialogInvite(string url) : url_(std::move(url)) {
}
};
@ -181,8 +208,14 @@ class LinkManager::InternalLinkLanguage final : public InternalLink {
};
class LinkManager::InternalLinkMessage final : public InternalLink {
string url_;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeMessage>();
return td_api::make_object<td_api::internalLinkTypeMessage>(url_);
}
public:
explicit InternalLinkMessage(string url) : url_(std::move(url)) {
}
};
@ -191,7 +224,7 @@ class LinkManager::InternalLinkMessageDraft final : public InternalLink {
bool contains_link_ = false;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true),
return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true, -1),
contains_link_);
}
@ -310,8 +343,14 @@ class LinkManager::InternalLinkThemeSettings final : public InternalLink {
};
class LinkManager::InternalLinkUnknownDeepLink final : public InternalLink {
string link_;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeUnknownDeepLink>();
return td_api::make_object<td_api::internalLinkTypeUnknownDeepLink>(link_);
}
public:
explicit InternalLinkUnknownDeepLink(string link) : link_(std::move(link)) {
}
};
@ -329,6 +368,55 @@ class LinkManager::InternalLinkVoiceChat final : public InternalLink {
}
};
class GetDeepLinkInfoQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
public:
explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
: promise_(std::move(promise)) {
}
void send(Slice link) {
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
}
void on_result(uint64 id, BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
switch (result->get_id()) {
case telegram_api::help_deepLinkInfoEmpty::ID:
return promise_.set_value(nullptr);
case telegram_api::help_deepLinkInfo::ID: {
auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
bool need_update = (info->flags_ & telegram_api::help_deepLinkInfo::UPDATE_APP_MASK) != 0;
auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
auto status = fix_formatted_text(info->message_, entities, true, true, true, true, true);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
if (!clean_input_string(info->message_)) {
info->message_.clear();
}
entities = find_entities(info->message_, true, true);
}
FormattedText text{std::move(info->message_), std::move(entities)};
return promise_.set_value(
td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true, -1), need_update));
}
default:
UNREACHABLE();
}
}
void on_error(uint64 id, Status status) final {
promise_.set_error(std::move(status));
}
};
class RequestUrlAuthQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_;
string url_;
@ -637,6 +725,13 @@ struct CopyArg {
StringBuilder &operator<<(StringBuilder &string_builder, const CopyArg &copy_arg) {
auto arg = copy_arg.url_query_->get_arg(copy_arg.name_);
if (arg.empty()) {
for (const auto &query_arg : copy_arg.url_query_->args_) {
if (query_arg.first == copy_arg.name_) {
char c = *copy_arg.is_first_ ? '?' : '&';
*copy_arg.is_first_ = false;
return string_builder << c << copy_arg.name_;
}
}
return string_builder;
}
char c = *copy_arg.is_first_ ? '?' : '&';
@ -666,8 +761,10 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
if (path.size() == 1 && path[0] == "resolve") {
if (is_valid_username(get_arg("domain"))) {
if (has_arg("post")) {
// resolve?domain=<username>&post=12345&single
return td::make_unique<InternalLinkMessage>();
// resolve?domain=<username>&post=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
return td::make_unique<InternalLinkMessage>(PSTRING() << "tg:resolve" << copy_arg("domain") << copy_arg("post")
<< copy_arg("single") << copy_arg("thread")
<< copy_arg("comment") << copy_arg("t"));
}
auto username = get_arg("domain");
for (auto &arg : url_query.args_) {
@ -694,7 +791,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
}
if (username == "telegrampassport") {
// resolve?domain=telegrampassport&bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
return get_internal_link_passport(url_query.args_);
return get_internal_link_passport(query, url_query.args_);
}
// resolve?domain=<username>
return td::make_unique<InternalLinkPublicDialog>(std::move(username));
@ -710,7 +807,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
}
} else if (path.size() == 1 && path[0] == "passport") {
// passport?bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
return get_internal_link_passport(url_query.args_);
return get_internal_link_passport(query, url_query.args_);
} else if (path.size() >= 1 && path[0] == "settings") {
if (path.size() == 2 && path[1] == "change_number") {
// settings/change_number
@ -733,7 +830,8 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
} else if (path.size() == 1 && path[0] == "join") {
// join?invite=<hash>
if (has_arg("invite")) {
return td::make_unique<InternalLinkDialogInvite>();
return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
<< url_encode(get_url_query_hash(true, url_query)));
}
} else if (path.size() == 1 && path[0] == "addstickers") {
// addstickers?set=<name>
@ -774,9 +872,11 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
}
}
} else if (path.size() == 1 && path[0] == "privatepost") {
// privatepost?channel=123456789&msg_id=12345
// privatepost?channel=123456789&msg_id=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
if (has_arg("channel") && has_arg("msg_id")) {
return td::make_unique<InternalLinkMessage>();
return td::make_unique<InternalLinkMessage>(
PSTRING() << "tg:privatepost" << copy_arg("channel") << copy_arg("msg_id") << copy_arg("single")
<< copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
}
} else if (path.size() == 1 && path[0] == "bg") {
// bg?color=<color>
@ -801,7 +901,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice que
return get_internal_link_message_draft(get_arg("url"), get_arg("text"));
}
if (!path.empty() && !path[0].empty()) {
return td::make_unique<InternalLinkUnknownDeepLink>();
return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
}
return nullptr;
}
@ -828,8 +928,12 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_t_me_link_query(Slice q
if (path[0] == "c") {
if (path.size() >= 3 && to_integer<int64>(path[1]) > 0 && to_integer<int64>(path[2]) > 0) {
// /c/123456789/12345
return td::make_unique<InternalLinkMessage>();
// /c/123456789/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
is_first_arg = false;
return td::make_unique<InternalLinkMessage>(PSTRING()
<< "tg:privatepost?channel=" << to_integer<int64>(path[1])
<< "&msg_id=" << to_integer<int64>(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()) {
@ -839,12 +943,14 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_t_me_link_query(Slice q
} else if (path[0] == "joinchat") {
if (path.size() >= 2 && !path[1].empty()) {
// /joinchat/<link>
return td::make_unique<InternalLinkDialogInvite>();
return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
<< url_encode(get_url_query_hash(false, url_query)));
}
} else if (path[0][0] == ' ' || path[0][0] == '+') {
if (path[0].size() >= 2) {
// /+<link>
return td::make_unique<InternalLinkDialogInvite>();
return td::make_unique<InternalLinkDialogInvite>(
PSTRING() << "tg:join?invite=" + url_encode(get_url_query_hash(false, url_query)));
}
} else if (path[0] == "addstickers") {
if (path.size() >= 2 && !path[1].empty()) {
@ -903,8 +1009,11 @@ unique_ptr<LinkManager::InternalLink> LinkManager::parse_t_me_link_query(Slice q
}
} else if (is_valid_username(path[0])) {
if (path.size() >= 2 && to_integer<int64>(path[1]) > 0) {
// /<username>/12345?single&thread=<thread_id>&comment=<message_id>
return td::make_unique<InternalLinkMessage>();
// /<username>/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
is_first_arg = false;
return td::make_unique<InternalLinkMessage>(
PSTRING() << "tg:resolve?domain=" << url_encode(path[0]) << "&post=" << to_integer<int64>(path[1])
<< copy_arg("single") << copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
}
auto username = path[0];
for (auto &arg : url_query.args_) {
@ -955,7 +1064,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_message_dra
} else {
full_text.text = url.str();
}
if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true).is_error()) {
if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true, true).is_error()) {
return nullptr;
}
if (full_text.text[0] == '@') {
@ -968,7 +1077,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_message_dra
}
unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_passport(
const vector<std::pair<string, string>> &args) {
Slice query, const vector<std::pair<string, string>> &args) {
auto get_arg = [&args](Slice key) {
for (auto &arg : args) {
if (arg.first == key) {
@ -988,7 +1097,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_passport(
auto callback_url = get_arg("callback_url");
if (!bot_user_id.is_valid() || scope.empty() || public_key.empty() || nonce.empty()) {
return td::make_unique<InternalLinkUnknownDeepLink>();
return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
}
return td::make_unique<InternalLinkPassportDataRequest>(bot_user_id, scope.str(), public_key.str(), nonce.str(),
callback_url.str());
@ -1008,6 +1117,22 @@ void LinkManager::update_autologin_domains(string autologin_token, vector<string
}
}
void LinkManager::get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise) {
Slice link_scheme("tg:");
if (begins_with(link, link_scheme)) {
link.remove_prefix(link_scheme.size());
if (begins_with(link, "//")) {
link.remove_prefix(2);
}
}
size_t pos = 0;
while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
pos++;
}
link.truncate(pos);
td_->create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(link);
}
void LinkManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false);
if (G()->close_flag()) {
@ -1091,24 +1216,7 @@ string LinkManager::get_dialog_invite_link_hash(Slice invite_link) {
return string();
}
const auto url_query = parse_url_query(link_info.query_);
const auto &path = url_query.path_;
if (link_info.is_tg_) {
if (path.size() == 1 && path[0] == "join" && !url_query.get_arg("invite").empty()) {
// join?invite=abcdef
return url_query.get_arg("invite").str();
}
} else {
if (path.size() >= 2 && path[0] == "joinchat" && !path[1].empty()) {
// /joinchat/<link>
return path[1];
}
if (path.size() >= 1 && path[0].size() >= 2 && (path[0][0] == ' ' || path[0][0] == '+')) {
// /+<link>
return path[0].substr(1);
}
}
return string();
return get_url_query_hash(link_info.is_tg_, url_query);
}
Result<MessageLinkInfo> LinkManager::get_message_link_info(Slice url) {
@ -1129,8 +1237,8 @@ Result<MessageLinkInfo> LinkManager::get_message_link_info(Slice url) {
bool is_single = false;
bool for_comment = false;
if (link_info.is_tg_) {
// resolve?domain=username&post=12345&single
// privatepost?channel=123456789&msg_id=12345
// 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
bool is_resolve = false;
if (begins_with(url, "resolve")) {

View File

@ -54,6 +54,8 @@ class LinkManager final : public Actor {
void update_autologin_domains(string autologin_token, vector<string> autologin_domains,
vector<string> url_auth_domains);
void get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise);
void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_login_url_info(FullMessageId full_message_id, int32 button_id,
@ -110,7 +112,8 @@ class LinkManager final : public Actor {
static unique_ptr<InternalLink> parse_t_me_link_query(Slice query);
static unique_ptr<InternalLink> get_internal_link_passport(const vector<std::pair<string, string>> &args);
static unique_ptr<InternalLink> get_internal_link_passport(Slice query,
const vector<std::pair<string, string>> &args);
static unique_ptr<InternalLink> get_internal_link_message_draft(Slice url, Slice text);

View File

@ -1030,7 +1030,7 @@ static void parse_caption(FormattedText &caption, ParserT &parser) {
if (!check_utf8(caption.text)) {
caption.text.clear();
}
caption.entities = find_entities(caption.text, false);
caption.entities = find_entities(caption.text, false, true);
}
}
@ -1461,7 +1461,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
auto inline_message = move_tl_object_as<telegram_api::botInlineMessageText>(bot_inline_message);
auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_),
"botInlineMessageText");
auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false);
auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false, false);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message->message_;
break;
@ -1525,7 +1525,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaAuto>(bot_inline_message);
auto caption =
get_message_text(td->contacts_manager_.get(), inline_message->message_, std::move(inline_message->entities_),
true, 0, false, "create_inline_message_content");
true, false, 0, false, "create_inline_message_content");
if (allowed_media_content_id == td_api::inputMessageAnimation::ID) {
result.message_content = make_unique<MessageAnimation>(file_id, std::move(caption));
} else if (allowed_media_content_id == td_api::inputMessageAudio::ID) {
@ -2911,12 +2911,14 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
case MessageContentType::Text: {
auto old_ = static_cast<const MessageText *>(old_content);
auto new_ = static_cast<const MessageText *>(new_content);
auto get_content_object = [td, dialog_id](const MessageContent *content) {
return to_string(
get_message_content_object(content, td, dialog_id, -1, false, false, std::numeric_limits<int32>::max()));
};
if (old_->text.text != new_->text.text) {
if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) {
LOG(ERROR) << "Message text has changed from "
<< to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false))
<< ". New content is "
<< to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false));
LOG(ERROR) << "Message text has changed in " << get_content_object(old_content) << ". New content is "
<< get_content_object(new_content);
}
need_update = true;
}
@ -2925,10 +2927,8 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
if (need_message_changed_warning && need_message_text_changed_warning(old_, new_) &&
old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT &&
need_message_entities_changed_warning(old_->text.entities, new_->text.entities)) {
LOG(WARNING) << "Entities has changed from "
<< to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false))
<< ". New content is "
<< to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false));
LOG(WARNING) << "Entities has changed in " << get_content_object(old_content) << ". New content is "
<< get_content_object(new_content);
}
need_update = true;
}
@ -3825,14 +3825,14 @@ unique_ptr<MessageContent> get_secret_message_content(
}
auto entities = get_message_entities(std::move(secret_entities));
auto status = fix_formatted_text(message_text, entities, true, false, true, false);
auto status = fix_formatted_text(message_text, entities, true, false, true, td->auth_manager_->is_bot(), false);
if (status.is_error()) {
LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text
<< "\" with entities " << format::as_array(entities);
if (!clean_input_string(message_text)) {
message_text.clear();
}
entities = find_entities(message_text, true);
entities = find_entities(message_text, true, td->auth_manager_->is_bot());
}
// support of old layer and old constructions
@ -4649,19 +4649,21 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
DialogId dialog_id, int32 message_date,
bool is_content_secret, bool skip_bot_commands) {
bool is_content_secret, bool skip_bot_commands,
int32 max_media_timestamp) {
CHECK(content != nullptr);
switch (content->get_type()) {
case MessageContentType::Animation: {
const MessageAnimation *m = static_cast<const MessageAnimation *>(content);
return make_tl_object<td_api::messageAnimation>(
td->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"),
get_formatted_text_object(m->caption, skip_bot_commands), is_content_secret);
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
}
case MessageContentType::Audio: {
const MessageAudio *m = static_cast<const MessageAudio *>(content);
return make_tl_object<td_api::messageAudio>(td->audios_manager_->get_audio_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands));
return make_tl_object<td_api::messageAudio>(
td->audios_manager_->get_audio_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
}
case MessageContentType::Contact: {
const MessageContact *m = static_cast<const MessageContact *>(content);
@ -4671,7 +4673,7 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
const MessageDocument *m = static_cast<const MessageDocument *>(content);
return make_tl_object<td_api::messageDocument>(
td->documents_manager_->get_document_object(m->file_id, PhotoFormat::Jpeg),
get_formatted_text_object(m->caption, skip_bot_commands));
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
}
case MessageContentType::Game: {
const MessageGame *m = static_cast<const MessageGame *>(content);
@ -4696,9 +4698,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::Photo: {
const MessagePhoto *m = static_cast<const MessagePhoto *>(content);
return make_tl_object<td_api::messagePhoto>(get_photo_object(td->file_manager_.get(), m->photo),
get_formatted_text_object(m->caption, skip_bot_commands),
is_content_secret);
return make_tl_object<td_api::messagePhoto>(
get_photo_object(td->file_manager_.get(), m->photo),
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
}
case MessageContentType::Sticker: {
const MessageSticker *m = static_cast<const MessageSticker *>(content);
@ -4706,7 +4708,8 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::Text: {
const MessageText *m = static_cast<const MessageText *>(content);
return make_tl_object<td_api::messageText>(get_formatted_text_object(m->text, skip_bot_commands),
return make_tl_object<td_api::messageText>(
get_formatted_text_object(m->text, skip_bot_commands, max_media_timestamp),
td->web_pages_manager_->get_web_page_object(m->web_page_id));
}
case MessageContentType::Unsupported:
@ -4717,9 +4720,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::Video: {
const MessageVideo *m = static_cast<const MessageVideo *>(content);
return make_tl_object<td_api::messageVideo>(td->videos_manager_->get_video_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands),
is_content_secret);
return make_tl_object<td_api::messageVideo>(
td->videos_manager_->get_video_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
}
case MessageContentType::VideoNote: {
const MessageVideoNote *m = static_cast<const MessageVideoNote *>(content);
@ -4728,9 +4731,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::VoiceNote: {
const MessageVoiceNote *m = static_cast<const MessageVoiceNote *>(content);
return make_tl_object<td_api::messageVoiceNote>(td->voice_notes_manager_->get_voice_note_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands),
m->is_listened);
return make_tl_object<td_api::messageVoiceNote>(
td->voice_notes_manager_->get_voice_note_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), m->is_listened);
}
case MessageContentType::ChatCreate: {
const MessageChatCreate *m = static_cast<const MessageChatCreate *>(content);
@ -4879,6 +4882,10 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
return nullptr;
}
FormattedText *get_message_content_text_mutable(MessageContent *content) {
return const_cast<FormattedText *>(get_message_content_text(content));
}
const FormattedText *get_message_content_text(const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Text:
@ -4920,10 +4927,6 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td)
auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
return td->audios_manager_->get_audio_duration(audio_file_id);
}
case MessageContentType::Text: {
auto web_page_id = static_cast<const MessageText *>(content)->web_page_id;
return td->web_pages_manager_->get_web_page_duration(web_page_id);
}
case MessageContentType::Video: {
auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
return td->videos_manager_->get_video_duration(video_file_id);
@ -4941,6 +4944,34 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td)
}
}
int32 get_message_content_media_duration(const MessageContent *content, const Td *td) {
CHECK(content != nullptr);
switch (content->get_type()) {
case MessageContentType::Audio: {
auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
return td->audios_manager_->get_audio_duration(audio_file_id);
}
case MessageContentType::Text: {
auto web_page_id = static_cast<const MessageText *>(content)->web_page_id;
return td->web_pages_manager_->get_web_page_media_duration(web_page_id);
}
case MessageContentType::Video: {
auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
return td->videos_manager_->get_video_duration(video_file_id);
}
case MessageContentType::VideoNote: {
auto video_note_file_id = static_cast<const MessageVideoNote *>(content)->file_id;
return td->video_notes_manager_->get_video_note_duration(video_note_file_id);
}
case MessageContentType::VoiceNote: {
auto voice_file_id = static_cast<const MessageVoiceNote *>(content)->file_id;
return td->voice_notes_manager_->get_voice_note_duration(voice_file_id);
}
default:
return -1;
}
}
FileId get_message_content_upload_file_id(const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Animation:

View File

@ -198,7 +198,10 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
DialogId dialog_id, int32 message_date,
bool is_content_secret, bool skip_bot_commands);
bool is_content_secret, bool skip_bot_commands,
int32 max_media_timestamp);
FormattedText *get_message_content_text_mutable(MessageContent *content);
const FormattedText *get_message_content_text(const MessageContent *content);
@ -206,6 +209,8 @@ const FormattedText *get_message_content_caption(const MessageContent *content);
int32 get_message_content_duration(const MessageContent *content, const Td *td);
int32 get_message_content_media_duration(const MessageContent *content, const Td *td);
FileId get_message_content_upload_file_id(const MessageContent *content);
FileId get_message_content_any_file_id(const MessageContent *content);

View File

@ -29,7 +29,7 @@
namespace td {
int MessageEntity::get_type_priority(Type type) {
static const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50};
static const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50, 50};
static_assert(sizeof(types) / sizeof(types[0]) == static_cast<size_t>(MessageEntity::Type::Size), "");
return types[static_cast<int32>(type)];
}
@ -72,6 +72,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty
return string_builder << "PhoneNumber";
case MessageEntity::Type::BankCardNumber:
return string_builder << "BankCardNumber";
case MessageEntity::Type::MediaTimestamp:
return string_builder << "MediaTimestamp";
default:
UNREACHABLE();
return string_builder << "Impossible";
@ -81,6 +83,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty
StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity) {
string_builder << '[' << message_entity.type << ", offset = " << message_entity.offset
<< ", length = " << message_entity.length;
if (message_entity.media_timestamp >= 0) {
string_builder << ", media_timestamp = \"" << message_entity.media_timestamp << "\"";
}
if (!message_entity.argument.empty()) {
string_builder << ", argument = \"" << message_entity.argument << "\"";
}
@ -130,6 +135,8 @@ tl_object_ptr<td_api::TextEntityType> MessageEntity::get_text_entity_type_object
return make_tl_object<td_api::textEntityTypePhoneNumber>();
case MessageEntity::Type::BankCardNumber:
return make_tl_object<td_api::textEntityTypeBankCardNumber>();
case MessageEntity::Type::MediaTimestamp:
return make_tl_object<td_api::textEntityTypeMediaTimestamp>(media_timestamp);
default:
UNREACHABLE();
return nullptr;
@ -141,7 +148,7 @@ tl_object_ptr<td_api::textEntity> MessageEntity::get_text_entity_object() const
}
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands) {
bool skip_bot_commands, int32 max_media_timestamp) {
vector<tl_object_ptr<td_api::textEntity>> result;
result.reserve(entities.size());
@ -149,6 +156,9 @@ vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<
if (skip_bot_commands && entity.type == MessageEntity::Type::BotCommand) {
continue;
}
if (entity.type == MessageEntity::Type::MediaTimestamp && max_media_timestamp < entity.media_timestamp) {
continue;
}
auto entity_object = entity.get_text_entity_object();
if (entity_object->type_ != nullptr) {
result.push_back(std::move(entity_object));
@ -162,9 +172,10 @@ StringBuilder &operator<<(StringBuilder &string_builder, const FormattedText &te
return string_builder << '"' << text.text << "\" with entities " << text.entities;
}
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands) {
return td_api::make_object<td_api::formattedText>(text.text,
get_text_entities_object(text.entities, skip_bot_commands));
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
int32 max_media_timestamp) {
return td_api::make_object<td_api::formattedText>(
text.text, get_text_entities_object(text.entities, skip_bot_commands, max_media_timestamp));
}
static bool is_word_character(uint32 code) {
@ -430,6 +441,57 @@ static vector<Slice> match_cashtags(Slice str) {
return result;
}
static vector<Slice> match_media_timestamps(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
const unsigned char *end = str.uend();
const unsigned char *ptr = begin;
while (true) {
ptr = static_cast<const unsigned char *>(std::memchr(ptr, ':', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
auto media_timestamp_begin = ptr;
while (media_timestamp_begin != begin &&
(media_timestamp_begin[-1] == ':' || is_digit(media_timestamp_begin[-1]))) {
media_timestamp_begin--;
}
auto media_timestamp_end = ptr;
while (media_timestamp_end + 1 != end && (media_timestamp_end[1] == ':' || is_digit(media_timestamp_end[1]))) {
media_timestamp_end++;
}
media_timestamp_end++;
if (media_timestamp_begin != ptr && media_timestamp_end != ptr + 1 && is_digit(ptr[1])) {
ptr = media_timestamp_end;
if (media_timestamp_begin != begin) {
uint32 prev;
next_utf8_unsafe(prev_utf8_unsafe(media_timestamp_begin), &prev, "match_media_timestamps 1");
if (is_word_character(prev)) {
continue;
}
}
if (media_timestamp_end != end) {
uint32 next;
next_utf8_unsafe(media_timestamp_end, &next, "match_media_timestamps 2");
if (is_word_character(next)) {
continue;
}
}
result.emplace_back(media_timestamp_begin, media_timestamp_end);
} else {
ptr = media_timestamp_end;
}
}
return result;
}
static vector<Slice> match_bank_card_numbers(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
@ -1247,6 +1309,42 @@ vector<std::pair<Slice, bool>> find_urls(Slice str) {
return result;
}
vector<std::pair<Slice, int32>> find_media_timestamps(Slice str) {
vector<std::pair<Slice, int32>> result;
for (auto media_timestamp : match_media_timestamps(str)) {
vector<Slice> parts = full_split(media_timestamp, ':');
CHECK(parts.size() >= 2);
if (parts.size() > 3 || parts.back().size() != 2) {
continue;
}
auto seconds = to_integer<int32>(parts.back());
if (seconds >= 60) {
continue;
}
if (parts.size() == 2) {
if (parts[0].size() > 4 || parts[0].empty()) {
continue;
}
auto minutes = to_integer<int32>(parts[0]);
result.emplace_back(media_timestamp, minutes * 60 + seconds);
continue;
} else {
if (parts[0].size() > 2 || parts[1].size() > 2 || parts[0].empty() || parts[1].empty()) {
continue;
}
auto minutes = to_integer<int32>(parts[1]);
if (minutes >= 60) {
continue;
}
auto hours = to_integer<int32>(parts[0]);
result.emplace_back(media_timestamp, hours * 3600 + minutes * 60 + seconds);
}
}
return result;
}
static int32 text_length(Slice text) {
return narrow_cast<int32>(utf8_utf16_length(text));
}
@ -1291,7 +1389,8 @@ static constexpr int32 get_continuous_entities_mask() {
get_entity_type_mask(MessageEntity::Type::EmailAddress) | get_entity_type_mask(MessageEntity::Type::TextUrl) |
get_entity_type_mask(MessageEntity::Type::MentionName) | get_entity_type_mask(MessageEntity::Type::Cashtag) |
get_entity_type_mask(MessageEntity::Type::PhoneNumber) |
get_entity_type_mask(MessageEntity::Type::BankCardNumber);
get_entity_type_mask(MessageEntity::Type::BankCardNumber) |
get_entity_type_mask(MessageEntity::Type::MediaTimestamp);
}
static constexpr int32 get_pre_entities_mask() {
@ -1440,7 +1539,7 @@ static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entit
while (blockquote_it != blockquote_entities.end() &&
(blockquote_it->type != MessageEntity::Type::BlockQuote ||
blockquote_it->offset + blockquote_it->length <= entities[i].offset)) {
blockquote_it++;
++blockquote_it;
}
if (blockquote_it != blockquote_entities.end() &&
(blockquote_it->offset + blockquote_it->length < entities[i].offset + entities[i].length ||
@ -1456,7 +1555,52 @@ static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entit
entities.erase(entities.begin() + left_entities, entities.end());
}
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands) {
// keeps only non-intersecting entities
// fixes entity offsets from UTF-8 to UTF-16 offsets
static void fix_entity_offsets(Slice text, vector<MessageEntity> &entities) {
if (entities.empty()) {
return;
}
sort_entities(entities);
remove_intersecting_entities(entities);
const unsigned char *begin = text.ubegin();
const unsigned char *ptr = begin;
const unsigned char *end = text.uend();
int32 utf16_pos = 0;
for (auto &entity : entities) {
int cnt = 2;
auto entity_begin = entity.offset;
auto entity_end = entity.offset + entity.length;
int32 pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
}
while (ptr != end && cnt > 0) {
unsigned char c = ptr[0];
utf16_pos += 1 + (c >= 0xf0);
ptr = next_utf8_unsafe(ptr, nullptr, "fix_entity_offsets");
pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
} else if (entity_end == pos) {
cnt--;
entity.length = utf16_pos - entity.offset;
}
}
CHECK(cnt == 0);
}
}
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps) {
vector<MessageEntity> entities;
auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable {
@ -1485,47 +1629,31 @@ vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands) {
entities.emplace_back(type, offset, length);
}
if (entities.empty()) {
if (!skip_media_timestamps) {
auto media_timestamps = find_media_timestamps(text);
for (auto &entity : media_timestamps) {
auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
auto length = narrow_cast<int32>(entity.first.size());
entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
}
}
fix_entity_offsets(text, entities);
return entities;
}
sort_entities(entities);
static vector<MessageEntity> find_media_timestamp_entities(Slice text) {
vector<MessageEntity> entities;
remove_intersecting_entities(entities);
// fix offsets to UTF-16 offsets
const unsigned char *begin = text.ubegin();
const unsigned char *ptr = begin;
const unsigned char *end = text.uend();
int32 utf16_pos = 0;
for (auto &entity : entities) {
int cnt = 2;
auto entity_begin = entity.offset;
auto entity_end = entity.offset + entity.length;
int32 pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
auto media_timestamps = find_media_timestamps(text);
for (auto &entity : media_timestamps) {
auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
auto length = narrow_cast<int32>(entity.first.size());
entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
}
while (ptr != end && cnt > 0) {
unsigned char c = ptr[0];
utf16_pos += 1 + (c >= 0xf0);
ptr = next_utf8_unsafe(ptr, nullptr, "find_entities");
pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
} else if (entity_end == pos) {
cnt--;
entity.length = utf16_pos - entity.offset;
}
}
CHECK(cnt == 0);
}
fix_entity_offsets(text, entities);
return entities;
}
@ -1546,17 +1674,17 @@ static vector<MessageEntity> merge_entities(vector<MessageEntity> old_entities,
for (auto &old_entity : old_entities) {
while (new_it != new_end && new_it->offset + new_it->length <= old_entity.offset) {
result.push_back(std::move(*new_it));
new_it++;
++new_it;
}
auto old_entity_end = old_entity.offset + old_entity.length;
result.push_back(std::move(old_entity));
while (new_it != new_end && new_it->offset < old_entity_end) {
new_it++;
++new_it;
}
}
while (new_it != new_end) {
result.push_back(std::move(*new_it));
new_it++;
++new_it;
}
return result;
@ -1616,6 +1744,8 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break;
case MessageEntity::Type::BankCardNumber:
break;
case MessageEntity::Type::MediaTimestamp:
break;
default:
UNREACHABLE();
}
@ -2239,7 +2369,7 @@ static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vecto
}
}
auto found_entities = find_entities(text, false);
auto found_entities = find_entities(text, false, true);
td::remove_if(found_entities, [](const auto &entity) {
return entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::Url;
});
@ -3030,6 +3160,8 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
break;
case MessageEntity::Type::MentionName:
break;
case MessageEntity::Type::MediaTimestamp:
break;
default:
UNREACHABLE();
}
@ -3121,6 +3253,15 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
entities.emplace_back(entity->offset_, entity->length_, user_id);
break;
}
case td_api::textEntityTypeMediaTimestamp::ID: {
auto entity_media_timestamp = static_cast<td_api::textEntityTypeMediaTimestamp *>(entity->type_.get());
if (entity_media_timestamp->media_timestamp_ < 0) {
return Status::Error(400, "Invalid media timestamp specified");
}
entities.emplace_back(MessageEntity::Type::MediaTimestamp, entity->offset_, entity->length_,
entity_media_timestamp->media_timestamp_);
break;
}
default:
UNREACHABLE();
}
@ -3516,6 +3657,19 @@ static Result<string> clean_input_string_with_entities(const string &text, vecto
// entities must be sorted by offset and length, but not necessary by type
// returns {last_non_whitespace_pos, last_non_whitespace_utf16_offset}
static std::pair<size_t, int32> remove_invalid_entities(const string &text, vector<MessageEntity> &entities) {
if (entities.empty()) {
// fast path
for (size_t pos = 0; pos < text.size(); pos++) {
auto back_pos = text.size() - pos - 1;
auto c = text[back_pos];
if (c != '\n' && c != ' ') {
return {back_pos, 0 /*unused*/};
}
}
return {text.size(), -1};
}
// check_is_sorted(entities);
vector<MessageEntity *> nested_entities_stack;
size_t current_entity = 0;
@ -3759,7 +3913,7 @@ static void merge_new_entities(vector<MessageEntity> &entities, vector<MessageEn
}
Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
bool skip_bot_commands, bool for_draft) {
bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) {
string result;
if (entities.empty()) {
// fast path
@ -3867,7 +4021,9 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
}
if (!skip_new_entities) {
merge_new_entities(entities, find_entities(text, skip_bot_commands));
merge_new_entities(entities, find_entities(text, skip_bot_commands, skip_media_timestamps));
} else if (!skip_media_timestamps) {
merge_new_entities(entities, find_media_timestamp_entities(text));
}
// new whitespace-only entities could be added after splitting of entities
@ -3880,11 +4036,12 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
bool skip_new_entities, int32 send_date, bool from_album, const char *source) {
bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
const char *source) {
auto entities = get_message_entities(contacts_manager, std::move(server_entities), source);
auto debug_message_text = message_text;
auto debug_entities = entities;
auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, false);
auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, skip_media_timestamps, false);
if (status.is_error()) {
// message entities in media albums can be wrong because of a long time ago fixed server-side bug
if (!from_album && (send_date == 0 || send_date > 1600340000)) { // approximate fix date
@ -3895,7 +4052,7 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m
if (!clean_input_string(message_text)) {
message_text.clear();
}
entities = find_entities(message_text, false);
entities = find_entities(message_text, false, skip_media_timestamps);
}
return FormattedText{std::move(message_text), std::move(entities)};
}
@ -3939,7 +4096,7 @@ Result<FormattedText> process_input_caption(const ContactsManager *contacts_mana
}
TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(caption->entities_)));
TRY_STATUS(fix_formatted_text(caption->text_, entities, true, false,
need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot), false));
need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot), is_bot, false));
return FormattedText{std::move(caption->text_), std::move(entities)};
}
@ -3954,6 +4111,19 @@ void add_formatted_text_dependencies(Dependencies &dependencies, const Formatted
}
}
bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp) {
if (text == nullptr) {
return false;
}
for (auto &entity : text->entities) {
if (entity.type == MessageEntity::Type::MediaTimestamp && min_media_timestamp <= entity.media_timestamp &&
entity.media_timestamp <= max_media_timestamp) {
return true;
}
}
return false;
}
bool has_bot_commands(const FormattedText *text) {
if (text == nullptr) {
return false;

View File

@ -48,28 +48,34 @@ class MessageEntity {
Strikethrough,
BlockQuote,
BankCardNumber,
MediaTimestamp,
Size
};
Type type;
int32 offset;
int32 length;
Type type = Type::Size;
int32 offset = -1;
int32 length = -1;
int32 media_timestamp = -1;
string argument;
UserId user_id;
MessageEntity() = default;
MessageEntity(Type type, int32 offset, int32 length, string argument = "")
: type(type), offset(offset), length(length), argument(std::move(argument)), user_id() {
: type(type), offset(offset), length(length), argument(std::move(argument)) {
}
MessageEntity(int32 offset, int32 length, UserId user_id)
: type(Type::MentionName), offset(offset), length(length), argument(), user_id(user_id) {
: type(Type::MentionName), offset(offset), length(length), user_id(user_id) {
}
MessageEntity(Type type, int32 offset, int32 length, int32 media_timestamp)
: type(type), offset(offset), length(length), media_timestamp(media_timestamp) {
CHECK(type == Type::MediaTimestamp);
}
tl_object_ptr<td_api::textEntity> get_text_entity_object() const;
bool operator==(const MessageEntity &other) const {
return offset == other.offset && length == other.length && type == other.type && argument == other.argument &&
user_id == other.user_id;
return offset == other.offset && length == other.length && type == other.type &&
media_timestamp == other.media_timestamp && argument == other.argument && user_id == other.user_id;
}
bool operator<(const MessageEntity &other) const {
@ -132,11 +138,12 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
bool allow_all = false);
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands);
bool skip_bot_commands, int32 max_media_timestamp);
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands);
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
int32 max_media_timestamp);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps);
vector<Slice> find_mentions(Slice str);
vector<Slice> find_bot_commands(Slice str);
@ -146,6 +153,7 @@ vector<Slice> find_bank_card_numbers(Slice str);
vector<Slice> find_tg_urls(Slice str);
bool is_email_address(Slice str);
vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address
vector<std::pair<Slice, int32>> find_media_timestamps(Slice str); // slice + media_timestamp
string get_first_url(Slice text, const vector<MessageEntity> &entities);
@ -178,11 +186,12 @@ vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::Mess
// like clean_input_string but also validates entities
Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
bool skip_bot_commands, bool for_draft) TD_WARN_UNUSED_RESULT;
bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) TD_WARN_UNUSED_RESULT;
FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
bool skip_new_entities, int32 send_date, bool from_album, const char *source);
bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
const char *source);
td_api::object_ptr<td_api::formattedText> extract_input_caption(
tl_object_ptr<td_api::InputMessageContent> &input_message_content);
@ -192,6 +201,8 @@ Result<FormattedText> process_input_caption(const ContactsManager *contacts_mana
void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text);
bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp);
bool has_bot_commands(const FormattedText *text);
bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot);

View File

@ -24,6 +24,9 @@ void MessageEntity::store(StorerT &storer) const {
if (type == Type::MentionName) {
store(user_id, storer);
}
if (type == Type::MediaTimestamp) {
store(media_timestamp, storer);
}
}
template <class ParserT>
@ -38,6 +41,9 @@ void MessageEntity::parse(ParserT &parser) {
if (type == Type::MentionName) {
parse(user_id, parser);
}
if (type == Type::MediaTimestamp) {
parse(media_timestamp, parser);
}
}
template <class StorerT>

View File

@ -0,0 +1,21 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/DialogId.h"
#include "td/telegram/MessageId.h"
#include "td/utils/common.h"
namespace td {
struct MessageThreadInfo {
DialogId dialog_id;
vector<MessageId> message_ids;
};
} // namespace td

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
#include "td/telegram/MessageId.h"
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageReplyInfo.h"
#include "td/telegram/MessageThreadInfo.h"
#include "td/telegram/MessagesDb.h"
#include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessageTtlSetting.h"
@ -573,17 +574,13 @@ class MessagesManager final : public Actor {
void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise, const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);
struct MessageThreadInfo {
DialogId dialog_id;
vector<MessageId> message_ids;
};
void get_message_thread(DialogId dialog_id, MessageId message_id, Promise<MessageThreadInfo> &&promise);
td_api::object_ptr<td_api::messageThreadInfo> get_message_thread_info_object(const MessageThreadInfo &info);
void process_discussion_message(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
MessageId expected_message_id, Promise<vector<FullMessageId>> promise);
MessageId expected_message_id, Promise<MessageThreadInfo> promise);
bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);
@ -1044,6 +1041,7 @@ class MessagesManager final : public Actor {
bool is_mention_notification_disabled = false;
bool is_from_scheduled = false;
bool is_pinned = false;
bool are_media_timestamp_entities_found = false;
bool is_copy = false; // for send_message
bool from_background = false; // for send_message
@ -1065,6 +1063,9 @@ class MessagesManager final : public Actor {
NotificationId notification_id;
NotificationId removed_notification_id;
int32 max_reply_media_timestamp = -1;
int32 max_own_media_timestamp = -2; // to update replied messages on the first load
int32 view_count = 0;
int32 forward_count = 0;
MessageReplyInfo reply_info;
@ -1101,6 +1102,7 @@ class MessagesManager final : public Actor {
unique_ptr<Message> right;
mutable int32 last_access_date = 0;
mutable bool is_update_sent = false; // whether the message is known to the app
mutable uint64 send_message_log_event_id = 0;
@ -1648,9 +1650,9 @@ class MessagesManager final : public Actor {
static constexpr int32 MAX_SEARCH_MESSAGES = 100; // server side limit
static constexpr int32 MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN = 4; // server side limit
static constexpr int32 MIN_CHANNEL_DIFFERENCE = 1;
static constexpr int32 MAX_CHANNEL_DIFFERENCE = 1000; // server side limit
static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 1000000; // server side limit
static constexpr int32 MAX_RECENTLY_FOUND_DIALOGS = 30; // some reasonable value
static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100;
static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit
static constexpr int32 MAX_RECENTLY_FOUND_DIALOGS = 50; // some reasonable value
static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title
static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description
static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title
@ -1784,6 +1786,8 @@ class MessagesManager final : public Actor {
Status can_pin_messages(DialogId dialog_id) const;
static Status can_get_media_timestamp_link(DialogId dialog_id, const Message *m);
void cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message);
void on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id,
@ -2142,13 +2146,26 @@ class MessagesManager final : public Actor {
void attach_message_to_next(Dialog *d, MessageId message_id, const char *source);
bool update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message, bool *need_update_dialog_pos);
bool update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message, bool *need_update_dialog_pos,
bool is_message_in_dialog);
static bool need_message_changed_warning(const Message *old_message);
bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr<MessageContent> new_content,
bool need_send_update_message_content, bool need_merge_files, bool is_message_in_dialog);
void update_message_max_reply_media_timestamp(const Dialog *d, Message *m, bool need_send_update_message_content);
void update_message_max_own_media_timestamp(const Dialog *d, Message *m);
void update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id, MessageId reply_to_message_id);
void register_message_reply(const Dialog *d, const Message *m);
void reregister_message_reply(const Dialog *d, const Message *m);
void unregister_message_reply(const Dialog *d, const Message *m);
void send_update_new_message(const Dialog *d, const Message *m);
static bool is_from_mention_notification_group(const Dialog *d, const Message *m);
@ -2205,7 +2222,9 @@ class MessagesManager final : public Actor {
void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const;
void send_update_message_content(DialogId dialog_id, const Message *m, const char *source);
void send_update_message_content(DialogId dialog_id, Message *m, bool is_message_in_dialog, const char *source);
void send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source);
void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const;
@ -2608,6 +2627,10 @@ class MessagesManager final : public Actor {
bool has_message_sender_user_id(DialogId dialog_id, const Message *m) const;
int32 get_message_own_max_media_timestamp(const Message *m) const;
static int32 get_message_max_media_timestamp(const Message *m);
static bool get_message_disable_web_page_preview(const Message *m);
static int32 get_message_flags(const Message *m);
@ -2656,9 +2679,9 @@ class MessagesManager final : public Actor {
void process_discussion_message_impl(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
MessageId expected_message_id, Promise<vector<FullMessageId>> promise);
MessageId expected_message_id, Promise<MessageThreadInfo> promise);
void on_get_discussion_message(DialogId dialog_id, MessageId message_id, vector<FullMessageId> full_message_ids,
void on_get_discussion_message(DialogId dialog_id, MessageId message_id, MessageThreadInfo &&message_thread_info,
Promise<MessageThreadInfo> &&promise);
static MessageId get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter);
@ -3207,10 +3230,10 @@ class MessagesManager final : public Actor {
std::unordered_map<int64, FoundMessages> found_fts_messages_; // random_id -> FoundMessages
std::unordered_map<int64, FoundMessages> found_message_public_forwards_; // random_id -> FoundMessages
struct PublicMessageLinks {
std::unordered_map<MessageId, std::pair<string, string>, MessageIdHash> links_;
struct MessageEmbeddingCodes {
std::unordered_map<MessageId, string, MessageIdHash> embedding_codes_;
};
std::unordered_map<DialogId, PublicMessageLinks, DialogIdHash> public_message_links_[2];
std::unordered_map<DialogId, MessageEmbeddingCodes, DialogIdHash> message_embedding_codes_[2];
std::unordered_map<int64, tl_object_ptr<td_api::chatEvents>> chat_events_; // random_id -> chat events
@ -3223,6 +3246,10 @@ class MessagesManager final : public Actor {
std::unordered_map<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_;
// full_message_id -> replies with media timestamps
std::unordered_map<FullMessageId, std::unordered_set<MessageId, MessageIdHash>, FullMessageIdHash>
replied_by_media_timestamp_messages_;
struct ActiveDialogAction {
MessageId top_thread_message_id;
UserId user_id;

View File

@ -109,9 +109,7 @@ void parse(PhotoSizeSource::FullLegacy &source, ParserT &parser) {
parse(source.volume_id, parser);
parse(source.secret, parser);
parse(source.local_id, parser);
if (source.local_id < 0) {
parser.set_error("Wrong local_id");
}
// source.local_id can be negative in secret chat thumbnails
}
template <class StorerT>

View File

@ -575,7 +575,7 @@ td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id, co
auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id;
poll_type = td_api::make_object<td_api::pollTypeQuiz>(
correct_option_id,
get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true));
get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true, -1));
} else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers);
}
@ -1581,12 +1581,12 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
auto entities =
get_message_entities(td_->contacts_manager_.get(), std::move(poll_results->solution_entities_), "on_get_poll");
auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, false);
auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, true, false);
if (status.is_error()) {
if (!clean_input_string(poll_results->solution_)) {
poll_results->solution_.clear();
}
entities = find_entities(poll_results->solution_, true);
entities = find_entities(poll_results->solution_, true, true);
}
FormattedText explanation{std::move(poll_results->solution_), std::move(entities)};

View File

@ -30,7 +30,6 @@
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/overloaded.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/SliceBuilder.h"

View File

@ -441,8 +441,8 @@ void SetSecureValue::start_up() {
for (auto it = secure_value_.files.begin(); it != secure_value_.files.end();) {
auto file_id = file_manager->get_file_view(it->file_id).file_id();
bool is_duplicate = false;
for (auto pit = secure_value_.files.begin(); pit != it; pit++) {
if (file_id == file_manager->get_file_view(pit->file_id).file_id()) {
for (auto other_it = secure_value_.files.begin(); other_it != it; ++other_it) {
if (file_id == file_manager->get_file_view(other_it->file_id).file_id()) {
is_duplicate = true;
break;
}
@ -458,8 +458,8 @@ void SetSecureValue::start_up() {
for (auto it = secure_value_.translations.begin(); it != secure_value_.translations.end();) {
auto file_id = file_manager->get_file_view(it->file_id).file_id();
bool is_duplicate = file_id == front_side_file_id || file_id == reverse_side_file_id || file_id == selfie_file_id;
for (auto pit = secure_value_.translations.begin(); pit != it; pit++) {
if (file_id == file_manager->get_file_view(pit->file_id).file_id()) {
for (auto other_it = secure_value_.translations.begin(); other_it != it; ++other_it) {
if (file_id == file_manager->get_file_view(other_it->file_id).file_id()) {
is_duplicate = true;
break;
}

View File

@ -182,10 +182,6 @@ struct EncryptedValue {
BufferSlice data;
ValueHash hash;
};
struct EncryptedFile {
std::string path;
ValueHash hash;
};
Result<EncryptedValue> encrypt_value(const Secret &secret, Slice data);
Result<ValueHash> encrypt_file(const Secret &secret, std::string src, std::string dest);

View File

@ -136,8 +136,8 @@ void update_suggested_actions(vector<SuggestedAction> &suggested_actions,
} else if (old_it == suggested_actions.end() || *new_it < *old_it) {
added_actions.push_back(*new_it++);
} else {
old_it++;
new_it++;
++old_it;
++new_it;
}
}
CHECK(!added_actions.empty() || !removed_actions.empty());

View File

@ -59,6 +59,7 @@
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/MessageThreadInfo.h"
#include "td/telegram/misc.h"
#include "td/telegram/net/ConnectionCreator.h"
#include "td/telegram/net/DcId.h"
@ -440,67 +441,6 @@ class GetInviteTextQuery final : public Td::ResultHandler {
}
};
class GetDeepLinkInfoQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
public:
explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
: promise_(std::move(promise)) {
}
void send(Slice link) {
Slice link_scheme("tg:");
if (begins_with(link, link_scheme)) {
link.remove_prefix(link_scheme.size());
if (begins_with(link, "//")) {
link.remove_prefix(2);
}
}
size_t pos = 0;
while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
pos++;
}
link.truncate(pos);
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
}
void on_result(uint64 id, BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
switch (result->get_id()) {
case telegram_api::help_deepLinkInfoEmpty::ID:
return promise_.set_value(nullptr);
case telegram_api::help_deepLinkInfo::ID: {
auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
bool need_update = (info->flags_ & telegram_api::help_deepLinkInfo::UPDATE_APP_MASK) != 0;
auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
auto status = fix_formatted_text(info->message_, entities, true, true, true, true);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
if (!clean_input_string(info->message_)) {
info->message_.clear();
}
entities = find_entities(info->message_, true);
}
FormattedText text{std::move(info->message_), std::move(entities)};
return promise_.set_value(
td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true), need_update));
}
default:
UNREACHABLE();
}
}
void on_error(uint64 id, Status status) final {
promise_.set_error(std::move(status));
}
};
class SaveAppLogQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
@ -1067,13 +1007,13 @@ class GetRepliedMessageRequest final : public RequestOnceActor {
}
};
class GetMessageThreadRequest final : public RequestActor<MessagesManager::MessageThreadInfo> {
class GetMessageThreadRequest final : public RequestActor<MessageThreadInfo> {
DialogId dialog_id_;
MessageId message_id_;
MessagesManager::MessageThreadInfo message_thread_info_;
MessageThreadInfo message_thread_info_;
void do_run(Promise<MessagesManager::MessageThreadInfo> &&promise) final {
void do_run(Promise<MessageThreadInfo> &&promise) final {
if (get_tries() < 2) {
promise.set_value(std::move(message_thread_info_));
return;
@ -1081,7 +1021,7 @@ class GetMessageThreadRequest final : public RequestActor<MessagesManager::Messa
td->messages_manager_->get_message_thread(dialog_id_, message_id_, std::move(promise));
}
void do_set_result(MessagesManager::MessageThreadInfo &&result) final {
void do_set_result(MessageThreadInfo &&result) final {
message_thread_info_ = std::move(result);
}
@ -4042,6 +3982,7 @@ void Td::close_impl(bool destroy_flag) {
close_flag_ = 1;
G()->set_close_flag();
send_closure(auth_manager_actor_, &AuthManager::on_closing, destroy_flag);
updates_manager_->timeout_expired();
// wait till all request_actors will stop.
request_actors_.clear();
@ -8347,7 +8288,7 @@ void Td::on_request(uint64 id, const td_api::getApplicationDownloadLink &request
void Td::on_request(uint64 id, td_api::getDeepLinkInfo &request) {
CLEAN_INPUT_STRING(request.link_);
CREATE_REQUEST_PROMISE();
create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(request.link_);
link_manager_->get_deep_link_info(request.link_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getApplicationConfig &request) {
@ -8510,8 +8451,9 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getTextEn
if (!check_utf8(request.text_)) {
return make_error(400, "Text must be encoded in UTF-8");
}
auto text_entities = find_entities(request.text_, false);
return make_tl_object<td_api::textEntities>(get_text_entities_object(text_entities, false));
auto text_entities = find_entities(request.text_, false, false);
return make_tl_object<td_api::textEntities>(
get_text_entities_object(text_entities, false, std::numeric_limits<int32>::max()));
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntities &request) {
@ -8546,7 +8488,7 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntiti
}
return make_tl_object<td_api::formattedText>(std::move(request.text_),
get_text_entities_object(r_entities.ok(), false));
get_text_entities_object(r_entities.ok(), false, -1));
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &request) {
@ -8559,14 +8501,14 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &
return make_error(400, r_entities.error().message());
}
auto entities = r_entities.move_as_ok();
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true);
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
if (status.is_error()) {
return make_error(400, status.error().message());
}
auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)});
fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).ensure();
return get_formatted_text_object(parsed_text, true);
fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).ensure();
return get_formatted_text_object(parsed_text, false, std::numeric_limits<int32>::max());
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText &request) {
@ -8579,12 +8521,13 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText
return make_error(400, r_entities.error().message());
}
auto entities = r_entities.move_as_ok();
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true);
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
if (status.is_error()) {
return make_error(400, status.error().message());
}
return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), true);
return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), false,
std::numeric_limits<int32>::max());
}
td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getFileMimeType &request) {

View File

@ -95,12 +95,12 @@ TermsOfService::TermsOfService(telegram_api::object_ptr<telegram_api::help_terms
id_ = std::move(terms->id_->data_);
auto entities = get_message_entities(nullptr, std::move(terms->entities_), "TermsOfService");
auto status = fix_formatted_text(terms->text_, entities, true, true, true, false);
auto status = fix_formatted_text(terms->text_, entities, true, true, true, true, false);
if (status.is_error()) {
if (!clean_input_string(terms->text_)) {
terms->text_.clear();
}
entities = find_entities(terms->text_, true);
entities = find_entities(terms->text_, true, true);
}
if (terms->text_.empty()) {
id_.clear();

View File

@ -42,7 +42,7 @@ class TermsOfService {
return nullptr;
}
return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true), min_user_age_,
return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true, -1), min_user_age_,
show_popup_);
}

File diff suppressed because it is too large Load Diff

View File

@ -95,7 +95,7 @@ class UpdatesManager final : public Actor {
void on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr, Promise<Unit> &&promise);
void add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
Promise<Unit> &&promise, const char *source);
double receive_time, Promise<Unit> &&promise, const char *source);
static std::unordered_set<int64> get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr);
@ -122,9 +122,13 @@ class UpdatesManager final : public Actor {
return running_get_difference_;
}
void timeout_expired() final;
private:
static constexpr int32 FORCED_GET_DIFFERENCE_PTS_DIFF = 100000;
static constexpr int32 GAP_TIMEOUT_UPDATE_COUNT = 20;
static const double MAX_UNFILLED_GAP_TIME;
static const double MAX_PTS_SAVE_DELAY;
static constexpr bool DROP_PTS_UPDATES = false;
friend class OnUpdate;
@ -134,10 +138,16 @@ class UpdatesManager final : public Actor {
tl_object_ptr<telegram_api::Update> update;
int32 pts;
int32 pts_count;
double receive_time;
Promise<Unit> promise;
PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, Promise<Unit> &&promise)
: update(std::move(update)), pts(pts), pts_count(pts_count), promise(std::move(promise)) {
PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, double receive_time,
Promise<Unit> &&promise)
: update(std::move(update))
, pts(pts)
, pts_count(pts_count)
, receive_time(receive_time)
, promise(std::move(promise)) {
}
};
@ -146,17 +156,24 @@ class UpdatesManager final : public Actor {
int32 seq_begin;
int32 seq_end;
int32 date;
double receive_time;
vector<tl_object_ptr<telegram_api::Update>> updates;
Promise<Unit> promise;
PendingSeqUpdates(int32 seq_begin, int32 seq_end, int32 date, vector<tl_object_ptr<telegram_api::Update>> &&updates,
Promise<Unit> &&promise)
: seq_begin(seq_begin), seq_end(seq_end), date(date), updates(std::move(updates)), promise(std::move(promise)) {
PendingSeqUpdates(int32 seq_begin, int32 seq_end, int32 date, double receive_time,
vector<tl_object_ptr<telegram_api::Update>> &&updates, Promise<Unit> &&promise)
: seq_begin(seq_begin)
, seq_end(seq_end)
, date(date)
, receive_time(receive_time)
, updates(std::move(updates))
, promise(std::move(promise)) {
}
};
class PendingQtsUpdate {
public:
double receive_time;
tl_object_ptr<telegram_api::Update> update;
vector<Promise<Unit>> promises;
};
@ -170,6 +187,11 @@ class UpdatesManager final : public Actor {
int32 seq_ = 0;
string date_source_ = "nowhere";
double last_pts_save_time_ = 0;
double last_qts_save_time_ = 0;
int32 pending_pts_ = 0;
int32 pending_qts_ = 0;
int32 short_update_date_ = 0;
int32 accumulated_pts_count_ = 0;
@ -243,13 +265,13 @@ class UpdatesManager final : public Actor {
void add_pending_qts_update(tl_object_ptr<telegram_api::Update> &&update, int32 qts, Promise<Unit> &&promise);
void on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin, int32 seq_end,
int32 date, Promise<Unit> &&promise, const char *source);
int32 date, double receive_time, Promise<Unit> &&promise, const char *source);
void process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply,
Promise<Unit> &&promise);
void postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count,
Promise<Unit> &&promise);
double receive_time, Promise<Unit> &&promise);
void process_pts_update(tl_object_ptr<telegram_api::Update> &&update);
@ -258,14 +280,18 @@ class UpdatesManager final : public Actor {
void process_qts_update(tl_object_ptr<telegram_api::Update> &&update_ptr, int32 qts, Promise<Unit> &&promise);
void process_all_pending_pts_updates();
void drop_all_pending_pts_updates();
void process_postponed_pts_updates();
void process_pending_pts_updates();
void process_pending_seq_updates();
void process_pending_qts_updates();
void drop_pending_pts_updates();
static void fill_pts_gap(void *td);
static void fill_seq_gap(void *td);
@ -292,8 +318,6 @@ class UpdatesManager final : public Actor {
void after_get_difference();
int32 get_min_pending_pts() const;
static bool have_update_pts_changed(const vector<tl_object_ptr<telegram_api::Update>> &updates);
static bool check_pts_update_dialog_id(DialogId dialog_id);

View File

@ -825,7 +825,7 @@ int64 WebPagesManager::get_web_page_preview(td_api::object_ptr<td_api::formatted
}
auto entities = r_entities.move_as_ok();
auto result = fix_formatted_text(text->text_, entities, true, false, true, false);
auto result = fix_formatted_text(text->text_, entities, true, false, true, true, false);
if (result.is_error() || text->text_.empty()) {
promise.set_value(Unit());
return 0;
@ -1225,7 +1225,7 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
FormattedText description;
description.text = web_page->description;
description.entities = find_entities(web_page->description, true);
description.entities = find_entities(web_page->description, true, false);
auto r_url = parse_url(web_page->display_url);
if (r_url.is_ok()) {
@ -1294,11 +1294,12 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
}
}
auto duration = get_web_page_media_duration(web_page);
return make_tl_object<td_api::webPage>(
web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title,
get_formatted_text_object(description, true), get_photo_object(td_->file_manager_.get(), web_page->photo),
web_page->embed_url, web_page->embed_type, web_page->embed_dimensions.width, web_page->embed_dimensions.height,
web_page->duration, web_page->author,
get_formatted_text_object(description, true, duration == 0 ? std::numeric_limits<int32>::max() : duration),
get_photo_object(td_->file_manager_.get(), web_page->photo), web_page->embed_url, web_page->embed_type,
web_page->embed_dimensions.width, web_page->embed_dimensions.height, web_page->duration, web_page->author,
web_page->document.type == Document::Type::Animation
? td_->animations_manager_->get_animation_object(web_page->document.file_id, "get_web_page_object")
: nullptr,
@ -1325,11 +1326,11 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_view_object(
WebPageId web_page_id) const {
return get_web_page_instant_view_object(get_web_page_instant_view(web_page_id));
return get_web_page_instant_view_object(web_page_id, get_web_page_instant_view(web_page_id));
}
tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_view_object(
const WebPageInstantView *web_page_instant_view) const {
WebPageId web_page_id, const WebPageInstantView *web_page_instant_view) const {
if (web_page_instant_view == nullptr) {
return nullptr;
}
@ -1337,10 +1338,12 @@ tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_
LOG(ERROR) << "Trying to get not loaded web page instant view";
return nullptr;
}
auto feedback_link =
td_api::make_object<td_api::internalLinkTypeBotStart>("previews", PSTRING() << "webpage" << web_page_id.get());
return td_api::make_object<td_api::webPageInstantView>(
get_page_block_objects(web_page_instant_view->page_blocks, td_, web_page_instant_view->url),
web_page_instant_view->view_count, web_page_instant_view->is_v2 ? 2 : 1, web_page_instant_view->is_rtl,
web_page_instant_view->is_full);
web_page_instant_view->is_full, std::move(feedback_link));
}
void WebPagesManager::on_web_page_changed(WebPageId web_page_id, bool have_web_page) {
@ -1527,7 +1530,7 @@ void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_
web_page->instant_view.is_loaded = true;
LOG(DEBUG) << "Receive web page instant view: "
<< to_string(get_web_page_instant_view_object(&web_page->instant_view));
<< to_string(get_web_page_instant_view_object(WebPageId(), &web_page->instant_view));
}
class WebPagesManager::WebPageLogEvent {
@ -1751,14 +1754,24 @@ string WebPagesManager::get_web_page_search_text(WebPageId web_page_id) const {
return PSTRING() << web_page->title + " " + web_page->description;
}
int32 WebPagesManager::get_web_page_duration(WebPageId web_page_id) const {
int32 WebPagesManager::get_web_page_media_duration(WebPageId web_page_id) const {
const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
return 0;
return -1;
}
return get_web_page_media_duration(web_page);
}
int32 WebPagesManager::get_web_page_media_duration(const WebPage *web_page) {
if (web_page->document.type == Document::Type::Audio || web_page->document.type == Document::Type::Video ||
web_page->document.type == Document::Type::VideoNote || web_page->document.type == Document::Type::VoiceNote ||
web_page->embed_type == "iframe") {
return web_page->duration;
}
return -1;
}
vector<FileId> WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const {
if (web_page == nullptr) {
return vector<FileId>();

View File

@ -91,7 +91,7 @@ class WebPagesManager final : public Actor {
string get_web_page_search_text(WebPageId web_page_id) const;
int32 get_web_page_duration(WebPageId web_page_id) const;
int32 get_web_page_media_duration(WebPageId web_page_id) const;
private:
static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1 << 0;
@ -130,7 +130,7 @@ class WebPagesManager final : public Actor {
WebPageId get_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<Unit> &&promise);
tl_object_ptr<td_api::webPageInstantView> get_web_page_instant_view_object(
const WebPageInstantView *web_page_instant_view) const;
WebPageId web_page_id, const WebPageInstantView *web_page_instant_view) const;
static void on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id);
void on_pending_web_page_timeout(WebPageId web_page_id);
@ -174,6 +174,8 @@ class WebPagesManager final : public Actor {
void tear_down() final;
static int32 get_web_page_media_duration(const WebPage *web_page);
FileSourceId get_web_page_file_source_id(WebPage *web_page);
vector<FileId> get_web_page_file_ids(const WebPage *web_page) const;

View File

@ -19,6 +19,7 @@
#include "td/utils/port/Clocks.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Random.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"

View File

@ -35,6 +35,9 @@ class Timeout final : public Actor {
bool has_timeout() const {
return Actor::has_timeout();
}
double get_timeout() const {
return Actor::get_timeout();
}
void set_timeout_in(double timeout) {
Actor::set_timeout_in(timeout);
}

View File

@ -67,6 +67,7 @@ class Actor : public ObserverBase {
void stop();
void do_stop();
bool has_timeout() const;
double get_timeout() const;
void set_timeout_in(double timeout_in);
void set_timeout_at(double timeout_at);
void cancel_timeout();

View File

@ -54,6 +54,9 @@ inline void Actor::do_stop() {
inline bool Actor::has_timeout() const {
return Scheduler::instance()->has_actor_timeout(this);
}
inline double Actor::get_timeout() const {
return Scheduler::instance()->get_actor_timeout(this);
}
inline void Actor::set_timeout_in(double timeout_in) {
Scheduler::instance()->set_actor_timeout_in(this, timeout_in);
}

View File

@ -126,6 +126,7 @@ class Scheduler {
void finish_migrate_actor(Actor *actor);
bool has_actor_timeout(const Actor *actor) const;
double get_actor_timeout(const Actor *actor) const;
void set_actor_timeout_in(Actor *actor, double timeout);
void set_actor_timeout_at(Actor *actor, double timeout_at);
void cancel_actor_timeout(Actor *actor);
@ -176,6 +177,7 @@ class Scheduler {
void start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
bool has_actor_timeout(const ActorInfo *actor_info) const;
double get_actor_timeout(const ActorInfo *actor_info) const;
void set_actor_timeout_in(ActorInfo *actor_info, double timeout);
void set_actor_timeout_at(ActorInfo *actor_info, double timeout_at);
void cancel_actor_timeout(ActorInfo *actor_info);

View File

@ -392,6 +392,7 @@ void Scheduler::do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
void Scheduler::start_migrate_actor(Actor *actor, int32 dest_sched_id) {
start_migrate_actor(actor->get_info(), dest_sched_id);
}
void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
VLOG(actor) << "Start migrate actor: " << tag("name", actor_info) << tag("ptr", actor_info)
<< tag("actor_count", actor_count_);
@ -406,6 +407,11 @@ void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id)
cancel_actor_timeout(actor_info);
}
double Scheduler::get_actor_timeout(const ActorInfo *actor_info) const {
const HeapNode *heap_node = actor_info->get_heap_node();
return heap_node->in_heap() ? timeout_queue_.get_key(heap_node) - Time::now() : 0.0;
}
void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) {
if (timeout > 1e10) {
timeout = 1e10;

View File

@ -302,6 +302,9 @@ inline void Scheduler::finish_migrate_actor(Actor *actor) {
inline bool Scheduler::has_actor_timeout(const Actor *actor) const {
return has_actor_timeout(actor->get_info());
}
inline double Scheduler::get_actor_timeout(const Actor *actor) const {
return get_actor_timeout(actor->get_info());
}
inline void Scheduler::set_actor_timeout_in(Actor *actor, double timeout) {
set_actor_timeout_in(actor->get_info(), timeout);
}

View File

@ -20,7 +20,7 @@ struct HeapNode {
void remove() {
pos_ = -1;
}
int pos_ = -1;
int32 pos_ = -1;
};
template <class KeyT, int K = 4>
@ -37,6 +37,12 @@ class KHeap {
return array_[0].key_;
}
KeyT get_key(const HeapNode *node) const {
size_t pos = static_cast<size_t>(node->pos_);
CHECK(pos < array_.size());
return array_[pos].key_;
}
const HeapNode *top() const {
return array_[0].node_;
}
@ -45,19 +51,19 @@ class KHeap {
CHECK(!empty());
HeapNode *result = array_[0].node_;
result->remove();
erase(0);
erase(static_cast<size_t>(0));
return result;
}
void insert(KeyT key, HeapNode *node) {
CHECK(!node->in_heap());
array_.push_back({key, node});
fix_up(static_cast<int>(array_.size()) - 1);
fix_up(array_.size() - 1);
}
void fix(KeyT key, HeapNode *node) {
CHECK(node->in_heap());
int pos = node->pos_;
size_t pos = static_cast<size_t>(node->pos_);
CHECK(pos < array_.size());
KeyT old_key = array_[pos].key_;
array_[pos].key_ = key;
if (key < old_key) {
@ -68,9 +74,9 @@ class KHeap {
}
void erase(HeapNode *node) {
CHECK(node->in_heap());
int pos = node->pos_;
size_t pos = static_cast<size_t>(node->pos_);
node->remove();
CHECK(pos < array_.size());
erase(pos);
}
@ -103,34 +109,34 @@ class KHeap {
};
vector<Item> array_;
void fix_up(int pos) {
void fix_up(size_t pos) {
auto item = array_[pos];
while (pos) {
int parent_pos = (pos - 1) / K;
auto parent_pos = (pos - 1) / K;
auto parent_item = array_[parent_pos];
if (parent_item.key_ < item.key_) {
break;
}
parent_item.node_->pos_ = pos;
parent_item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = parent_item;
pos = parent_pos;
}
item.node_->pos_ = pos;
item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item;
}
void fix_down(int pos) {
void fix_down(size_t pos) {
auto item = array_[pos];
while (true) {
int left_pos = pos * K + 1;
int right_pos = min(left_pos + K, static_cast<int>(array_.size()));
int next_pos = pos;
auto left_pos = pos * K + 1;
auto right_pos = min(left_pos + K, array_.size());
auto next_pos = pos;
KeyT next_key = item.key_;
for (int i = left_pos; i < right_pos; i++) {
for (auto i = left_pos; i < right_pos; i++) {
KeyT i_key = array_[i].key_;
if (i_key < next_key) {
next_key = i_key;
@ -141,21 +147,24 @@ class KHeap {
break;
}
array_[pos] = array_[next_pos];
array_[pos].node_->pos_ = pos;
array_[pos].node_->pos_ = static_cast<int32>(pos);
pos = next_pos;
}
item.node_->pos_ = pos;
item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item;
}
void erase(int pos) {
void erase(size_t pos) {
array_[pos] = array_.back();
array_.pop_back();
if (pos < static_cast<int>(array_.size())) {
if (pos < array_.size()) {
fix_down(pos);
fix_up(pos);
}
if (array_.capacity() > 50 && array_.size() < array_.capacity() / 4) {
array_.shrink_to_fit();
}
}
};

View File

@ -53,6 +53,10 @@ class ThreadSafeCounter {
return counter_.sum(0);
}
void clear() {
counter_.clear();
}
private:
ThreadSafeMultiCounter<1> counter_;
};

View File

@ -113,8 +113,8 @@ TEST(Link, parse_internal_link) {
auto change_phone_number = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeChangePhoneNumber>();
};
auto chat_invite = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeChatInvite>();
auto chat_invite = [](td::string hash) {
return td::td_api::make_object<td::td_api::internalLinkTypeChatInvite>("tg:join?invite=" + hash);
};
auto filter_settings = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeFilterSettings>();
@ -125,8 +125,8 @@ TEST(Link, parse_internal_link) {
auto language_pack = [](td::string language_pack_name) {
return td::td_api::make_object<td::td_api::internalLinkTypeLanguagePack>(language_pack_name);
};
auto message = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeMessage>();
auto message = [](td::string url) {
return td::td_api::make_object<td::td_api::internalLinkTypeMessage>(url);
};
auto message_draft = [](td::string text, bool contains_url) {
auto formatted_text = td::td_api::make_object<td::td_api::formattedText>();
@ -167,66 +167,80 @@ TEST(Link, parse_internal_link) {
auto theme_settings = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeThemeSettings>();
};
auto unknown_deep_link = [] {
return td::td_api::make_object<td::td_api::internalLinkTypeUnknownDeepLink>();
auto unknown_deep_link = [](td::string link) {
return td::td_api::make_object<td::td_api::internalLinkTypeUnknownDeepLink>(link);
};
auto voice_chat = [](td::string chat_username, td::string invite_hash) {
return td::td_api::make_object<td::td_api::internalLinkTypeVoiceChat>(chat_username, invite_hash);
};
parse_internal_link("t.me/levlam/1", message());
parse_internal_link("telegram.me/levlam/1", message());
parse_internal_link("telegram.dog/levlam/1", message());
parse_internal_link("www.t.me/levlam/1", message());
parse_internal_link("www%2etelegram.me/levlam/1", message());
parse_internal_link("www%2Etelegram.dog/levlam/1", message());
parse_internal_link("t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("telegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("telegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("www.t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("www%2etelegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("www%2Etelegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("www%252Etelegram.dog/levlam/1", nullptr);
parse_internal_link("www.t.me/s/s/s/s/s/joinchat/1", chat_invite());
parse_internal_link("www.t.me/s/%73/%73/s/%73/joinchat/1", chat_invite());
parse_internal_link("http://t.me/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/joinchat/1", chat_invite());
parse_internal_link("http://t.me/levlam/1", message());
parse_internal_link("https://t.me/levlam/1", message());
parse_internal_link("hTtp://www.t.me:443/levlam/1", message());
parse_internal_link("httPs://t.me:80/levlam/1", message());
parse_internal_link("www.t.me/s/s/s/s/s/joinchat/1", chat_invite("1"));
parse_internal_link("www.t.me/s/%73/%73/s/%73/joinchat/1", chat_invite("1"));
parse_internal_link("http://t.me/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/joinchat/1", chat_invite("1"));
parse_internal_link("http://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("https://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("hTtp://www.t.me:443/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("httPs://t.me:80/levlam/1", message("tg:resolve?domain=levlam&post=1"));
parse_internal_link("https://t.me:200/levlam/1", nullptr);
parse_internal_link("http:t.me/levlam/1", nullptr);
parse_internal_link("t.dog/levlam/1", nullptr);
parse_internal_link("t.m/levlam/1", nullptr);
parse_internal_link("t.men/levlam/1", nullptr);
parse_internal_link("tg:resolve?domain=username&post=12345&single", message());
parse_internal_link("TG://resolve?domain=username&post=12345&single&voicechat=aasd", message());
parse_internal_link("tg:resolve?domain=username&post=12345&single",
message("tg:resolve?domain=username&post=12345&single"));
parse_internal_link("tg:resolve?domain=user%31name&post=%312345&single&comment=456&t=789&single&thread=123%20%31",
message("tg:resolve?domain=user1name&post=12345&single&thread=123%201&comment=456&t=789"));
parse_internal_link("TG://resolve?domain=username&post=12345&single&voicechat=aasd",
message("tg:resolve?domain=username&post=12345&single"));
parse_internal_link("TG://test@resolve?domain=username&post=12345&single", nullptr);
parse_internal_link("tg:resolve:80?domain=username&post=12345&single", nullptr);
parse_internal_link("tg:http://resolve?domain=username&post=12345&single", nullptr);
parse_internal_link("tg:https://resolve?domain=username&post=12345&single", nullptr);
parse_internal_link("tg:resolve?domain=&post=12345&single", unknown_deep_link());
parse_internal_link("tg:resolve?domain=&post=12345&single",
unknown_deep_link("tg://resolve?domain=&post=12345&single"));
parse_internal_link("tg:resolve?domain=telegram&post=&single", public_chat("telegram"));
parse_internal_link("t.me/username/12345?single", message());
parse_internal_link("t.me/username/12345?asdasd", message());
parse_internal_link("t.me/username/12345", message());
parse_internal_link("t.me/username/12345/", message());
parse_internal_link("t.me/username/12345#asdasd", message());
parse_internal_link("t.me/username/12345//?voicechat=&single", message());
parse_internal_link("t.me/username/12345/asdasd//asd/asd/asd/?single", message());
parse_internal_link("t.me/username/1asdasdas/asdasd//asd/asd/asd/?single", message());
parse_internal_link("t.me/username/12345?single", message("tg:resolve?domain=username&post=12345&single"));
parse_internal_link("t.me/username/12345?asdasd", message("tg:resolve?domain=username&post=12345"));
parse_internal_link("t.me/username/12345", message("tg:resolve?domain=username&post=12345"));
parse_internal_link("t.me/username/12345/", message("tg:resolve?domain=username&post=12345"));
parse_internal_link("t.me/username/12345#asdasd", message("tg:resolve?domain=username&post=12345"));
parse_internal_link("t.me/username/12345//?voicechat=&single",
message("tg:resolve?domain=username&post=12345&single"));
parse_internal_link("t.me/username/12345/asdasd//asd/asd/asd/?single",
message("tg:resolve?domain=username&post=12345&single"));
parse_internal_link("t.me/username/1asdasdas/asdasd//asd/asd/asd/?single",
message("tg:resolve?domain=username&post=1&single"));
parse_internal_link("t.me/username/asd", public_chat("username"));
parse_internal_link("t.me/username/0", public_chat("username"));
parse_internal_link("t.me/username/-12345", public_chat("username"));
parse_internal_link("t.me//12345?single", nullptr);
parse_internal_link("https://telegram.dog/telegram/?single", public_chat("telegram"));
parse_internal_link("tg:privatepost?domain=username/12345&single", unknown_deep_link());
parse_internal_link("tg:privatepost?channel=username/12345&single", unknown_deep_link());
parse_internal_link("tg:privatepost?channel=username&msg_id=12345", message());
parse_internal_link("tg:privatepost?domain=username/12345&single",
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("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?single", message());
parse_internal_link("t.me/c/12345/123/asd/asd////?single", message());
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/%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"));
parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
parse_internal_link("tg:bg?color=11111%31", background("111111"));
@ -244,7 +258,8 @@ TEST(Link, parse_internal_link) {
background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
background("test?mode=12&intensity=2&bg_color=3"));
parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3", unknown_deep_link());
parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
parse_internal_link("tg:bg?color=11111%31", background("111111"));
@ -262,7 +277,8 @@ TEST(Link, parse_internal_link) {
background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
background("test?mode=12&intensity=2&bg_color=3"));
parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3", unknown_deep_link());
parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
parse_internal_link("%54.me/bg/111111#asdasd", background("111111"));
parse_internal_link("t.me/bg/11111%31", background("111111"));
@ -318,8 +334,8 @@ TEST(Link, parse_internal_link) {
parse_internal_link("https://t.me/msg?url=@&text=@", message_draft(" @\n@", true));
parse_internal_link("https://t.me/msg?url=%FF&text=1", nullptr);
parse_internal_link("tg:login?codec=12345", unknown_deep_link());
parse_internal_link("tg:login", unknown_deep_link());
parse_internal_link("tg:login?codec=12345", unknown_deep_link("tg://login?codec=12345"));
parse_internal_link("tg:login", unknown_deep_link("tg://login"));
parse_internal_link("tg:login?code=abacaba", authentication_code("abacaba"));
parse_internal_link("tg:login?code=123456", authentication_code("123456"));
@ -338,7 +354,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("t.me/login/123456/123123/12/31/a/s//21w/?asdas#test", authentication_code("123456"));
parse_internal_link("tg:login?token=abacaba", qr_code_authentication());
parse_internal_link("tg:login?token=", unknown_deep_link());
parse_internal_link("tg:login?token=", unknown_deep_link("tg://login?token="));
parse_internal_link("t.me/joinchat?invite=abcdef", nullptr);
parse_internal_link("t.me/joinchat", nullptr);
@ -347,30 +363,34 @@ TEST(Link, parse_internal_link) {
parse_internal_link("t.me/joinchat?/abcdef", nullptr);
parse_internal_link("t.me/joinchat/?abcdef", nullptr);
parse_internal_link("t.me/joinchat/#abcdef", nullptr);
parse_internal_link("t.me/joinchat/abacaba", chat_invite());
parse_internal_link("t.me/joinchat/aba%20aba", chat_invite());
parse_internal_link("t.me/joinchat/123456a", chat_invite());
parse_internal_link("t.me/joinchat/12345678901", chat_invite());
parse_internal_link("t.me/joinchat/123456", chat_invite());
parse_internal_link("t.me/joinchat/123456/123123/12/31/a/s//21w/?asdas#test", chat_invite());
parse_internal_link("t.me/joinchat/abacaba", chat_invite("abacaba"));
parse_internal_link("t.me/joinchat/aba%20aba", chat_invite("aba%20aba"));
parse_internal_link("t.me/joinchat/aba%30aba", chat_invite("aba0aba"));
parse_internal_link("t.me/joinchat/123456a", chat_invite("123456a"));
parse_internal_link("t.me/joinchat/12345678901", chat_invite("12345678901"));
parse_internal_link("t.me/joinchat/123456", chat_invite("123456"));
parse_internal_link("t.me/joinchat/123456/123123/12/31/a/s//21w/?asdas#test", chat_invite("123456"));
parse_internal_link("t.me/+?invite=abcdef", nullptr);
parse_internal_link("t.me/+a", chat_invite());
parse_internal_link("t.me/+a", chat_invite("a"));
parse_internal_link("t.me/+", nullptr);
parse_internal_link("t.me/+/abcdef", nullptr);
parse_internal_link("t.me/ ?/abcdef", nullptr);
parse_internal_link("t.me/+?abcdef", nullptr);
parse_internal_link("t.me/+#abcdef", nullptr);
parse_internal_link("t.me/ abacaba", chat_invite());
parse_internal_link("t.me/+aba%20aba", chat_invite());
parse_internal_link("t.me/+123456a", chat_invite());
parse_internal_link("t.me/%2012345678901", chat_invite());
parse_internal_link("t.me/+123456", chat_invite());
parse_internal_link("t.me/ 123456/123123/12/31/a/s//21w/?asdas#test", chat_invite());
parse_internal_link("t.me/ abacaba", chat_invite("abacaba"));
parse_internal_link("t.me/+aba%20aba", chat_invite("aba%20aba"));
parse_internal_link("t.me/+aba%30aba", chat_invite("aba0aba"));
parse_internal_link("t.me/+123456a", chat_invite("123456a"));
parse_internal_link("t.me/%2012345678901", chat_invite("12345678901"));
parse_internal_link("t.me/+123456", chat_invite("123456"));
parse_internal_link("t.me/ 123456/123123/12/31/a/s//21w/?asdas#test", chat_invite("123456"));
parse_internal_link("t.me/ /123456/123123/12/31/a/s//21w/?asdas#test", nullptr);
parse_internal_link("tg:join?invite=abcdef", chat_invite());
parse_internal_link("tg:join?invite=", unknown_deep_link());
parse_internal_link("tg:join?invite=abcdef", chat_invite("abcdef"));
parse_internal_link("tg:join?invite=abc%20def", chat_invite("abc%20def"));
parse_internal_link("tg://join?invite=abc%30def", chat_invite("abc0def"));
parse_internal_link("tg:join?invite=", unknown_deep_link("tg://join?invite="));
parse_internal_link("t.me/addstickers?set=abcdef", nullptr);
parse_internal_link("t.me/addstickers", nullptr);
@ -388,7 +408,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:addstickers?set=abcdef", sticker_set("abcdef"));
parse_internal_link("tg:addstickers?set=abc%30ef", sticker_set("abc0ef"));
parse_internal_link("tg://addstickers?set=", unknown_deep_link());
parse_internal_link("tg://addstickers?set=", unknown_deep_link("tg://addstickers?set="));
parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=", nullptr);
parse_internal_link("t.me/confirmphone/123456/123123/12/31/a/s//21w/?hash=abc%30ef&phone=123456789",
@ -396,13 +416,16 @@ TEST(Link, parse_internal_link) {
parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=123456789",
phone_number_confirmation("abc0ef", "123456789"));
parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=", unknown_deep_link());
parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=",
unknown_deep_link("tg://confirmphone?hash=abc%30ef&phone="));
parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=123456789",
phone_number_confirmation("abc0ef", "123456789"));
parse_internal_link("tg://confirmphone?hash=123&phone=123456789123456789",
phone_number_confirmation("123", "123456789123456789"));
parse_internal_link("tg://confirmphone?hash=&phone=123456789123456789", unknown_deep_link());
parse_internal_link("tg://confirmphone?hash=123456789123456789&phone=", unknown_deep_link());
parse_internal_link("tg://confirmphone?hash=&phone=123456789123456789",
unknown_deep_link("tg://confirmphone?hash=&phone=123456789123456789"));
parse_internal_link("tg://confirmphone?hash=123456789123456789&phone=",
unknown_deep_link("tg://confirmphone?hash=123456789123456789&phone="));
parse_internal_link("t.me/setlanguage?lang=abcdef", nullptr);
parse_internal_link("t.me/setlanguage", nullptr);
@ -420,7 +443,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:setlanguage?lang=abcdef", language_pack("abcdef"));
parse_internal_link("tg:setlanguage?lang=abc%30ef", language_pack("abc0ef"));
parse_internal_link("tg://setlanguage?lang=", unknown_deep_link());
parse_internal_link("tg://setlanguage?lang=", unknown_deep_link("tg://setlanguage?lang="));
parse_internal_link("t.me/addtheme?slug=abcdef", nullptr);
parse_internal_link("t.me/addtheme", nullptr);
@ -438,7 +461,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:addtheme?slug=abcdef", theme("abcdef"));
parse_internal_link("tg:addtheme?slug=abc%30ef", theme("abc0ef"));
parse_internal_link("tg://addtheme?slug=", unknown_deep_link());
parse_internal_link("tg://addtheme?slug=", unknown_deep_link("tg://addtheme?slug="));
parse_internal_link("t.me/proxy?server=1.2.3.4&port=80&secret=1234567890abcdef1234567890ABCDEF",
proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
@ -467,16 +490,19 @@ TEST(Link, parse_internal_link) {
proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
parse_internal_link("tg:proxy?server=1.2.3.4&port=80adasdas&secret=1234567890abcdef1234567890ABCDEF",
proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
parse_internal_link("tg:proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
unknown_deep_link());
parse_internal_link("tg:proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
unknown_deep_link());
parse_internal_link(
"tg:proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
unknown_deep_link("tg://proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF"));
parse_internal_link(
"tg:proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
unknown_deep_link("tg://proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF"));
parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=1234567890abcdef1234567890ABCDEF",
proxy_mtproto("google.com", 80, "1234567890abcdef1234567890ABCDEF"));
parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=dd1234567890abcdef1234567890ABCDEF",
proxy_mtproto("google.com", 80, "dd1234567890abcdef1234567890ABCDEF"));
parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
unknown_deep_link());
parse_internal_link(
"tg:proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
unknown_deep_link("tg://proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF"));
parse_internal_link("t.me/socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
parse_internal_link("t.me/socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
@ -489,8 +515,9 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
parse_internal_link("tg:socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
parse_internal_link("tg:socks?server=1.2.3.4&port=adasdas", unknown_deep_link());
parse_internal_link("tg:socks?server=1.2.3.4&port=65536", unknown_deep_link());
parse_internal_link("tg:socks?server=1.2.3.4&port=adasdas",
unknown_deep_link("tg://socks?server=1.2.3.4&port=adasdas"));
parse_internal_link("tg:socks?server=1.2.3.4&port=65536", unknown_deep_link("tg://socks?server=1.2.3.4&port=65536"));
parse_internal_link("tg:socks?server=google.com&port=8%30", proxy_socks("google.com", 80, "", ""));
parse_internal_link("tg:socks?server=google.com&port=8%30&user=1&pass=", proxy_socks("google.com", 80, "1", ""));
parse_internal_link("tg:socks?server=google.com&port=8%30&user=&pass=2", proxy_socks("google.com", 80, "", "2"));
@ -502,7 +529,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:resolve:80?domain=username&voicechat=", nullptr);
parse_internal_link("tg:http://resolve?domain=username&voicechat=", nullptr);
parse_internal_link("tg:https://resolve?domain=username&voicechat=", nullptr);
parse_internal_link("tg:resolve?domain=&voicechat=", unknown_deep_link());
parse_internal_link("tg:resolve?domain=&voicechat=", unknown_deep_link("tg://resolve?domain=&voicechat="));
parse_internal_link("tg:resolve?domain=telegram&&&&&&&voicechat=%30", voice_chat("telegram", "0"));
parse_internal_link("t.me/username/0/a//s/as?voicechat=", voice_chat("username", ""));
@ -520,7 +547,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:resolve:80?domain=username&start=", nullptr);
parse_internal_link("tg:http://resolve?domain=username&start=", nullptr);
parse_internal_link("tg:https://resolve?domain=username&start=", nullptr);
parse_internal_link("tg:resolve?domain=&start=", unknown_deep_link());
parse_internal_link("tg:resolve?domain=&start=", unknown_deep_link("tg://resolve?domain=&start="));
parse_internal_link("tg:resolve?domain=telegram&&&&&&&start=%30", bot_start("telegram", "0"));
parse_internal_link("t.me/username/0/a//s/as?start=", bot_start("username", ""));
@ -538,7 +565,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:resolve:80?domain=username&startgroup=", nullptr);
parse_internal_link("tg:http://resolve?domain=username&startgroup=", nullptr);
parse_internal_link("tg:https://resolve?domain=username&startgroup=", nullptr);
parse_internal_link("tg:resolve?domain=&startgroup=", unknown_deep_link());
parse_internal_link("tg:resolve?domain=&startgroup=", unknown_deep_link("tg://resolve?domain=&startgroup="));
parse_internal_link("tg:resolve?domain=telegram&&&&&&&startgroup=%30", bot_start_in_group("telegram", "0"));
parse_internal_link("t.me/username/0/a//s/as?startgroup=", bot_start_in_group("username", ""));
@ -556,7 +583,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:resolve:80?domain=username&game=asd", nullptr);
parse_internal_link("tg:http://resolve?domain=username&game=asd", nullptr);
parse_internal_link("tg:https://resolve?domain=username&game=asd", nullptr);
parse_internal_link("tg:resolve?domain=&game=asd", unknown_deep_link());
parse_internal_link("tg:resolve?domain=&game=asd", unknown_deep_link("tg://resolve?domain=&game=asd"));
parse_internal_link("tg:resolve?domain=telegram&&&&&&&game=%30", game("telegram", "0"));
parse_internal_link("t.me/username/0/a//s/as?game=asd", game("username", "asd"));
@ -574,7 +601,7 @@ TEST(Link, parse_internal_link) {
parse_internal_link("tg:resolve:80?domain=username", nullptr);
parse_internal_link("tg:http://resolve?domain=username", nullptr);
parse_internal_link("tg:https://resolve?domain=username", nullptr);
parse_internal_link("tg:resolve?domain=", unknown_deep_link());
parse_internal_link("tg:resolve?domain=", unknown_deep_link("tg://resolve?domain="));
parse_internal_link("tg:resolve?&&&&&&&domain=telegram", public_chat("telegram"));
parse_internal_link("t.me/a", public_chat("a"));
@ -606,16 +633,21 @@ TEST(Link, parse_internal_link) {
passport_data_request(12345, "asd", "key", "nonce", ""));
parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
passport_data_request(12345, "asd", "key", "nonce", ""));
parse_internal_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce", unknown_deep_link());
parse_internal_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce", unknown_deep_link());
parse_internal_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce", unknown_deep_link());
parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce", unknown_deep_link());
parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=", unknown_deep_link());
parse_internal_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce",
unknown_deep_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce"));
parse_internal_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce",
unknown_deep_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce"));
parse_internal_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce",
unknown_deep_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce"));
parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce",
unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce"));
parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=",
unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload="));
parse_internal_link("t.me/telegrampassport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
public_chat("telegrampassport"));
parse_internal_link("tg://settings", settings());
parse_internal_link("tg://setting", unknown_deep_link());
parse_internal_link("tg://setting", unknown_deep_link("tg://setting"));
parse_internal_link("tg://settings?asdsa?D?SADasD?asD", settings());
parse_internal_link("tg://settings#test", settings());
parse_internal_link("tg://settings/#test", settings());

View File

@ -6,6 +6,7 @@
//
#include "td/telegram/MessageEntity.h"
#include "td/utils/algorithm.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
@ -17,6 +18,7 @@
#include "td/utils/utf8.h"
#include <algorithm>
#include <utility>
static void check_mention(const td::string &str, const td::vector<td::string> &expected) {
auto result_slice = td::find_mentions(str);
@ -172,6 +174,48 @@ TEST(MessageEntities, cashtag) {
check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"});
}
static void check_media_timestamp(const td::string &str, const td::vector<std::pair<td::string, td::int32>> &expected) {
auto result = td::transform(td::find_media_timestamps(str),
[](auto &&entity) { return std::make_pair(entity.first.str(), entity.second); });
if (result != expected) {
LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
<< td::tag("expected", td::format::as_array(expected));
}
}
TEST(MessageEntities, media_timestamp) {
check_media_timestamp("", {});
check_media_timestamp(":", {});
check_media_timestamp(":1", {});
check_media_timestamp("a:1", {});
check_media_timestamp("01", {});
check_media_timestamp("01:", {});
check_media_timestamp("01::", {});
check_media_timestamp("01::", {});
check_media_timestamp("a1:1a", {});
check_media_timestamp("a1::01a", {});
check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
check_media_timestamp("0:00", {{"0:00", 0}});
check_media_timestamp("+0:00", {{"0:00", 0}});
check_media_timestamp("0:00+", {{"0:00", 0}});
check_media_timestamp("a0:00", {});
check_media_timestamp("0:00a", {});
check_media_timestamp("б0:00", {});
check_media_timestamp("0:00б", {});
check_media_timestamp("_0:00", {});
check_media_timestamp("0:00_", {});
check_media_timestamp("00:00:00:00", {});
check_media_timestamp("1:1:01 1:1:1", {{"1:1:01", 3661}});
check_media_timestamp("0:0:00 00:00 000:00 0000:00 00000:00 00:00:00 000:00:00 00:000:00 00:00:000",
{{"0:0:00", 0}, {"00:00", 0}, {"000:00", 0}, {"0000:00", 0}, {"00:00:00", 0}});
check_media_timestamp("00:0:00 0:00:00 00::00 :00:00 00:00: 00:00:0 00:00:", {{"00:0:00", 0}, {"0:00:00", 0}});
check_media_timestamp("1:1:59 1:1:-1 1:1:60", {{"1:1:59", 3719}});
check_media_timestamp("1:59:00 1:-1:00 1:60:00", {{"1:59:00", 7140}, {"1:00", 60}});
check_media_timestamp("59:59 60:00", {{"59:59", 3599}, {"60:00", 3600}});
check_media_timestamp("9999:59 99:59:59 99:60:59", {{"9999:59", 599999}, {"99:59:59", 360000 - 1}});
check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
}
static void check_bank_card_number(const td::string &str, const td::vector<td::string> &expected) {
auto result_slice = td::find_bank_card_numbers(str);
td::vector<td::string> result;
@ -655,16 +699,16 @@ static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntit
const td::vector<td::MessageEntity> &expected_entities, bool allow_empty = true,
bool skip_new_entities = false, bool skip_bot_commands = false,
bool for_draft = true) {
ASSERT_TRUE(
td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, for_draft).is_ok());
ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
.is_ok());
ASSERT_STREQ(expected_str, str);
ASSERT_EQ(expected_entities, entities);
}
static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities, bool allow_empty,
bool skip_new_entities, bool skip_bot_commands, bool for_draft) {
ASSERT_TRUE(
fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, for_draft).is_error());
ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
.is_error());
}
TEST(MessageEntities, fix_formatted_text) {
@ -689,6 +733,12 @@ TEST(MessageEntities, fix_formatted_text) {
check_fix_formatted_text(str, {}, false, false, false, false);
check_fix_formatted_text(str, {}, false, false, false, true);
check_fix_formatted_text(" aba\n ", {}, " aba\n ", {}, true, true, true, true);
check_fix_formatted_text(" aba\n ", {}, "aba", {}, true, true, true, false);
check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, true);
check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, false);
check_fix_formatted_text(" \n ", {}, false, true, true, false);
str += "a \r\n ";
fixed_str += "a \n ";
@ -1064,7 +1114,7 @@ TEST(MessageEntities, fix_formatted_text) {
return result;
};
auto old_type_mask = get_type_mask(str.size(), entities);
ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, false).is_ok());
ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, true, false).is_ok());
auto new_type_mask = get_type_mask(str.size(), entities);
auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15);
auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9);
@ -1384,7 +1434,7 @@ static void check_parse_markdown_v3(td::string text, td::vector<td::MessageEntit
bool fix = false) {
auto parsed_text = td::parse_markdown_v3({std::move(text), std::move(entities)});
if (fix) {
ASSERT_TRUE(fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).is_ok());
ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
}
ASSERT_STREQ(result_text, parsed_text.text);
ASSERT_EQ(result_entities, parsed_text.entities);
@ -1676,9 +1726,9 @@ TEST(MessageEntities, parse_markdown_v3) {
td::FormattedText text{std::move(str), std::move(entities)};
while (true) {
ASSERT_TRUE(fix_formatted_text(text.text, text.entities, true, true, true, true).is_ok());
ASSERT_TRUE(td::fix_formatted_text(text.text, text.entities, true, true, true, true, true).is_ok());
auto parsed_text = td::parse_markdown_v3(text);
ASSERT_TRUE(fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).is_ok());
ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
if (parsed_text == text) {
break;
}