Add textEntityTypeSpoiler.

This commit is contained in:
levlam 2021-12-28 20:41:37 +03:00
parent ed766a4d2f
commit 68539fe846
4 changed files with 173 additions and 57 deletions

View File

@ -73,7 +73,7 @@ textEntity offset:int32 length:int32 type:TextEntityType = TextEntity;
textEntities entities:vector<textEntity> = TextEntities;
//@description A text with some entities @text The text @entities Entities contained in the text. Entities can be nested, but must not mutually intersect with each other.
//-Pre, Code and PreCode entities can't contain other entities. Bold, Italic, Underline and Strikethrough entities can contain and to be contained in all other entities. All other entities can't contain each other
//-Pre, Code and PreCode entities can't contain other entities. Bold, Italic, Underline, Strikethrough, and Spoiler entities can contain and to be contained in all other entities. All other entities can't contain each other
formattedText text:string entities:vector<textEntity> = FormattedText;
@ -1913,6 +1913,9 @@ textEntityTypeUnderline = TextEntityType;
//@description A strikethrough text
textEntityTypeStrikethrough = TextEntityType;
//@description A spoiler text. Not supported in secret chats
textEntityTypeSpoiler = TextEntityType;
//@description Text that must be formatted as if inside a code HTML tag
textEntityTypeCode = TextEntityType;
@ -1964,7 +1967,7 @@ messageCopyOptions send_copy:Bool replace_caption:Bool new_caption:formattedText
//@class InputMessageContent @description The content of a message to send
//@description A text message @text Formatted text to be sent; 1-GetOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually
//@description A text message @text Formatted text to be sent; 1-GetOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually
//@disable_web_page_preview True, if rich web page previews for URLs in the message text must be disabled @clear_draft True, if a chat message draft must be deleted
inputMessageText text:formattedText disable_web_page_preview:Bool clear_draft:Bool = InputMessageContent;
@ -4539,11 +4542,11 @@ editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:Messa
//@description Returns all entities (mentions, hashtags, cashtags, bot commands, bank card numbers, URLs, and email addresses) contained in the text. Can be called synchronously @text The text in which to look for entites
getTextEntities text:string = TextEntities;
//@description Parses Bold, Italic, Underline, Strikethrough, Code, Pre, PreCode, TextUrl and MentionName entities contained in the text. Can be called synchronously @text The text to parse @parse_mode Text parse mode
//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, Code, Pre, PreCode, TextUrl and MentionName entities contained in the text. Can be called synchronously @text The text to parse @parse_mode Text parse mode
parseTextEntities text:string parse_mode:TextParseMode = FormattedText;
//@description Parses Markdown entities in a human-friendly format, ignoring markup errors. Can be called synchronously
//@text The text to parse. For example, "__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold italic__bold**"
//@text The text to parse. For example, "__italic__ ~~strikethrough~~ ||spoiler|| **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold italic__bold**"
parseMarkdown text:formattedText = FormattedText;
//@description Replaces text entities with Markdown formatting in a human-friendly format. Entities that can't be represented in Markdown unambiguously are kept as is. Can be called synchronously @text The text

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, 50};
static const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50, 50, 94};
static_assert(sizeof(types) / sizeof(types[0]) == static_cast<size_t>(MessageEntity::Type::Size), "");
return types[static_cast<int32>(type)];
}
@ -74,6 +74,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty
return string_builder << "BankCardNumber";
case MessageEntity::Type::MediaTimestamp:
return string_builder << "MediaTimestamp";
case MessageEntity::Type::Spoiler:
return string_builder << "Spoiler";
default:
UNREACHABLE();
return string_builder << "Impossible";
@ -137,6 +139,8 @@ tl_object_ptr<td_api::TextEntityType> MessageEntity::get_text_entity_type_object
return make_tl_object<td_api::textEntityTypeBankCardNumber>();
case MessageEntity::Type::MediaTimestamp:
return make_tl_object<td_api::textEntityTypeMediaTimestamp>(media_timestamp);
case MessageEntity::Type::Spoiler:
return make_tl_object<td_api::textEntityTypeSpoiler>();
default:
UNREACHABLE();
return nullptr;
@ -1395,7 +1399,7 @@ static constexpr int32 get_entity_type_mask(MessageEntity::Type type) {
static constexpr int32 get_splittable_entities_mask() {
return get_entity_type_mask(MessageEntity::Type::Bold) | get_entity_type_mask(MessageEntity::Type::Italic) |
get_entity_type_mask(MessageEntity::Type::Underline) |
get_entity_type_mask(MessageEntity::Type::Strikethrough);
get_entity_type_mask(MessageEntity::Type::Strikethrough) | get_entity_type_mask(MessageEntity::Type::Spoiler);
}
static constexpr int32 get_blockquote_entities_mask() {
@ -1449,15 +1453,18 @@ static int32 is_hidden_data_entity(MessageEntity::Type type) {
get_pre_entities_mask())) != 0;
}
static constexpr size_t SPLITTABLE_ENTITY_TYPE_COUNT = 4;
static constexpr size_t SPLITTABLE_ENTITY_TYPE_COUNT = 5;
static size_t get_splittable_entity_type_index(MessageEntity::Type type) {
if (static_cast<int32>(type) <= static_cast<int32>(MessageEntity::Type::Bold) + 1) {
// Bold or Italic
return static_cast<int32>(type) - static_cast<int32>(MessageEntity::Type::Bold);
} else {
} else if (static_cast<int32>(type) <= static_cast<int32>(MessageEntity::Type::Underline) + 1) {
// Underline or Strikethrough
return static_cast<int32>(type) - static_cast<int32>(MessageEntity::Type::Underline) + 2;
} else {
CHECK(type == MessageEntity::Type::Spoiler);
return 4;
}
}
@ -1765,6 +1772,8 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break;
case MessageEntity::Type::MediaTimestamp:
break;
case MessageEntity::Type::Spoiler:
break;
default:
UNREACHABLE();
}
@ -1963,6 +1972,8 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
return c == '_' && text[i + 1] == '_';
case MessageEntity::Type::Strikethrough:
return c == '~';
case MessageEntity::Type::Spoiler:
return c == '|' && text[i + 1] == '|';
default:
UNREACHABLE();
return false;
@ -1990,6 +2001,15 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
case '~':
type = MessageEntity::Type::Strikethrough;
break;
case '|':
if (text[i + 1] == '|') {
i++;
type = MessageEntity::Type::Spoiler;
} else {
return Status::Error(400, PSLICE() << "Character '" << text[i]
<< "' is reserved and must be escaped with the preceding '\\'");
}
break;
case '[':
type = MessageEntity::Type::TextUrl;
break;
@ -2038,6 +2058,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
case MessageEntity::Type::Strikethrough:
break;
case MessageEntity::Type::Underline:
case MessageEntity::Type::Spoiler:
i++;
break;
case MessageEntity::Type::Pre:
@ -2364,14 +2385,28 @@ static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vecto
if (is_utf8_character_first_code_unit(c)) {
utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
if ((c == '_' || c == '*' || c == '~') && text[i] == text[i + 1] && unallowed_boundaries.count(utf16_offset) == 0) {
if ((c == '_' || c == '*' || c == '~' || c == '|') && text[i] == text[i + 1] &&
unallowed_boundaries.count(utf16_offset) == 0) {
auto j = i + 2;
while (j != text.size() && text[j] == text[i] && unallowed_boundaries.count(utf16_offset + j - i - 1) == 0) {
j++;
}
if (j == i + 2) {
auto type = c == '_' ? MessageEntity::Type::Italic
: (c == '*' ? MessageEntity::Type::Bold : MessageEntity::Type::Strikethrough);
auto type = [c] {
switch (c) {
case '_':
return MessageEntity::Type::Italic;
case '*':
return MessageEntity::Type::Bold;
case '~':
return MessageEntity::Type::Strikethrough;
case '|':
return MessageEntity::Type::Spoiler;
default:
UNREACHABLE();
return MessageEntity::Type::Size;
}
}();
auto index = get_splittable_entity_type_index(type);
if (splittable_entity_offset[index] != 0) {
auto length = utf16_offset - splittable_entity_offset[index] - 1;
@ -2391,7 +2426,7 @@ static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vecto
}
// entities must be valid and can contain only splittable and continuous entities
// __italic__ ~~strikethrough~~ **bold** and [text_url](telegram.org) entities are left to be parsed
// __italic__ ~~strikethrough~~ **bold** ||spoiler|| and [text_url](telegram.org) entities are left to be parsed
static FormattedText parse_markdown_v3_without_pre(Slice text, vector<MessageEntity> entities) {
check_is_sorted(entities);
@ -2405,7 +2440,7 @@ static FormattedText parse_markdown_v3_without_pre(Slice text, vector<MessageEnt
bool have_splittable_entities = false;
for (size_t i = 0; i + 1 < text.size(); i++) {
if ((text[i] == '_' || text[i] == '*' || text[i] == '~') && text[i] == text[i + 1]) {
if ((text[i] == '_' || text[i] == '*' || text[i] == '~' || text[i] == '|') && text[i] == text[i + 1]) {
have_splittable_entities = true;
break;
}
@ -2719,6 +2754,10 @@ FormattedText get_markdown_v3(FormattedText text) {
result.text += "~~";
utf16_added += 2;
break;
case MessageEntity::Type::Spoiler:
result.text += "||";
utf16_added += 2;
break;
case MessageEntity::Type::TextUrl:
result.text += "](";
result.text += entity->argument;
@ -2757,6 +2796,10 @@ FormattedText get_markdown_v3(FormattedText text) {
result.text += "~~";
utf16_added += 2;
break;
case MessageEntity::Type::Spoiler:
result.text += "||";
utf16_added += 2;
break;
case MessageEntity::Type::TextUrl:
result.text += '[';
utf16_added++;
@ -2895,7 +2938,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
string tag_name = to_lower(text.substr(begin_pos + 1, i - begin_pos - 1));
if (tag_name != "a" && tag_name != "b" && tag_name != "strong" && tag_name != "i" && tag_name != "em" &&
tag_name != "s" && tag_name != "strike" && tag_name != "del" && tag_name != "u" && tag_name != "ins" &&
tag_name != "pre" && tag_name != "code") {
tag_name != "span" && tag_name != "pre" && tag_name != "code") {
return Status::Error(400, PSLICE()
<< "Unsupported start tag \"" << tag_name << "\" at byte offset " << begin_pos);
}
@ -2971,9 +3014,16 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
} else if (tag_name == "code" && attribute_name == Slice("class") &&
begins_with(attribute_value, "language-")) {
argument = attribute_value.substr(9);
} else if (tag_name == "span" && attribute_name == Slice("class") && begins_with(attribute_value, "tg-")) {
argument = attribute_value.substr(3);
}
}
if (tag_name == "span" && argument != "spoiler") {
return Status::Error(400, PSLICE()
<< "Tag \"span\" must have class \"tg-spoiler\" at byte offset " << begin_pos);
}
nested_entities.emplace_back(std::move(tag_name), std::move(argument), utf16_offset, result.size());
} else {
// end of an entity
@ -3009,6 +3059,9 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
entities.emplace_back(MessageEntity::Type::Strikethrough, entity_offset, entity_length);
} else if (tag_name == "u" || tag_name == "ins") {
entities.emplace_back(MessageEntity::Type::Underline, entity_offset, entity_length);
} else if (tag_name == "span") {
CHECK(nested_entities.back().argument == "spoiler");
entities.emplace_back(MessageEntity::Type::Spoiler, entity_offset, entity_length);
} else if (tag_name == "a") {
auto url = std::move(nested_entities.back().argument);
if (url.empty()) {
@ -3139,6 +3192,8 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
break;
case MessageEntity::Type::MediaTimestamp:
break;
case MessageEntity::Type::Spoiler:
break;
default:
UNREACHABLE();
}
@ -3239,6 +3294,9 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity->media_timestamp_);
break;
}
case td_api::textEntityTypeSpoiler::ID:
entities.emplace_back(MessageEntity::Type::Spoiler, offset, length);
break;
default:
UNREACHABLE();
}
@ -3319,8 +3377,11 @@ vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manag
entities.emplace_back(MessageEntity::Type::Strikethrough, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntitySpoiler::ID:
case telegram_api::messageEntitySpoiler::ID: {
auto entity = static_cast<const telegram_api::messageEntitySpoiler *>(server_entity.get());
entities.emplace_back(MessageEntity::Type::Spoiler, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityBlockquote::ID: {
auto entity = static_cast<const telegram_api::messageEntityBlockquote *>(server_entity.get());
entities.emplace_back(MessageEntity::Type::BlockQuote, entity->offset_, entity->length_);
@ -3736,7 +3797,7 @@ static void split_entities(vector<MessageEntity> &entities, const vector<Message
auto add_entities = [&](int32 end_offset) {
auto flush_entities = [&](int32 offset) {
for (auto type : {MessageEntity::Type::Bold, MessageEntity::Type::Italic, MessageEntity::Type::Underline,
MessageEntity::Type::Strikethrough}) {
MessageEntity::Type::Strikethrough, MessageEntity::Type::Spoiler}) {
auto index = get_splittable_entity_type_index(type);
if (end_pos[index] != 0 && begin_pos[index] < offset) {
if (end_pos[index] <= offset) {
@ -4194,6 +4255,9 @@ vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(co
case MessageEntity::Type::Strikethrough:
result.push_back(make_tl_object<telegram_api::messageEntityStrike>(entity.offset, entity.length));
break;
case MessageEntity::Type::Spoiler:
result.push_back(make_tl_object<telegram_api::messageEntitySpoiler>(entity.offset, entity.length));
break;
default:
UNREACHABLE();
}

View File

@ -48,6 +48,7 @@ class MessageEntity {
BlockQuote,
BankCardNumber,
MediaTimestamp,
Spoiler,
Size
};
Type type = Type::Size;

View File

@ -979,7 +979,7 @@ TEST(MessageEntities, fix_formatted_text) {
{td::MessageEntity::Type::Mention, 7, 6},
{td::MessageEntity::Type::Italic, 7, 6}});
// _a*b*_
// __a~b~__
check_fix_formatted_text(
"ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 1, 1}}, "ab",
{{td::MessageEntity::Type::Underline, 0, 1},
@ -1007,41 +1007,41 @@ TEST(MessageEntities, fix_formatted_text) {
{td::MessageEntity::Type::Underline, 1, 1},
{td::MessageEntity::Type::Strikethrough, 1, 1}});
// _*a*b_
check_fix_formatted_text(
"ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 1}}, "ab",
{{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 1}});
check_fix_formatted_text(
"ab",
{{td::MessageEntity::Type::Underline, 0, 1},
{td::MessageEntity::Type::Underline, 1, 1},
{td::MessageEntity::Type::Strikethrough, 0, 1}},
"ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 1}});
// __||a||b__
check_fix_formatted_text("ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}},
"ab",
{{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
check_fix_formatted_text("ab",
{{td::MessageEntity::Type::Underline, 0, 1},
{td::MessageEntity::Type::Underline, 1, 1},
{td::MessageEntity::Type::Spoiler, 0, 1}},
"ab",
{{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
// _*a*_\r_*b*_
check_fix_formatted_text("a\rb",
{{td::MessageEntity::Type::Bold, 0, 1},
{td::MessageEntity::Type::Strikethrough, 0, 1},
{td::MessageEntity::Type::Italic, 0, 1},
{td::MessageEntity::Type::Bold, 2, 1},
{td::MessageEntity::Type::Strikethrough, 2, 1}},
"ab",
{{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 2}});
{td::MessageEntity::Type::Italic, 2, 1}},
"ab", {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Italic, 0, 2}});
check_fix_formatted_text("a\nb",
{{td::MessageEntity::Type::Bold, 0, 1},
{td::MessageEntity::Type::Strikethrough, 0, 1},
{td::MessageEntity::Type::Italic, 0, 1},
{td::MessageEntity::Type::Bold, 2, 1},
{td::MessageEntity::Type::Strikethrough, 2, 1}},
{td::MessageEntity::Type::Italic, 2, 1}},
"a\nb",
{{td::MessageEntity::Type::Bold, 0, 1},
{td::MessageEntity::Type::Strikethrough, 0, 1},
{td::MessageEntity::Type::Italic, 0, 1},
{td::MessageEntity::Type::Bold, 2, 1},
{td::MessageEntity::Type::Strikethrough, 2, 1}});
{td::MessageEntity::Type::Italic, 2, 1}});
// ||`a`||
check_fix_formatted_text("a", {{td::MessageEntity::Type::Pre, 0, 1}, {td::MessageEntity::Type::Spoiler, 0, 1}}, "a",
{{td::MessageEntity::Type::Pre, 0, 1}});
check_fix_formatted_text("a", {{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Pre, 0, 1}}, "a",
{{td::MessageEntity::Type::Pre, 0, 1}});
// _`a`_
check_fix_formatted_text("a", {{td::MessageEntity::Type::Pre, 0, 1}, {td::MessageEntity::Type::Strikethrough, 0, 1}},
"a", {{td::MessageEntity::Type::Pre, 0, 1}});
check_fix_formatted_text("a", {{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Pre, 0, 1}},
"a", {{td::MessageEntity::Type::Pre, 0, 1}});
check_fix_formatted_text("abc",
{{td::MessageEntity::Type::Pre, 0, 3}, {td::MessageEntity::Type::Strikethrough, 1, 1}},
"abc", {{td::MessageEntity::Type::Pre, 0, 3}});
@ -1259,6 +1259,14 @@ TEST(MessageEntities, parse_html) {
check_parse_html("<i>\t</i>", "\t", {{td::MessageEntity::Type::Italic, 0, 1}});
check_parse_html("<i>\r</i>", "\r", {{td::MessageEntity::Type::Italic, 0, 1}});
check_parse_html("<i>\n</i>", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
check_parse_html("➡️ ➡️<span class = \"tg-spoiler\">➡️ ➡️</span><b>➡️ ➡️</b>",
"➡️ ➡️➡️ ➡️➡️ ➡️",
{{td::MessageEntity::Type::Spoiler, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &lt🏟</span>", "🏟 🏟🏟 <🏟",
{{td::MessageEntity::Type::Spoiler, 5, 6}});
check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &gt;<b aba = caba>&lt🏟</b></span>",
"🏟 🏟🏟 ><🏟",
{{td::MessageEntity::Type::Spoiler, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
check_parse_html("<a href=telegram.org>\t</a>", "\t",
{{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
check_parse_html("<a href=telegram.org>\r</a>", "\r",
@ -1360,6 +1368,7 @@ TEST(MessageEntities, parse_markdown) {
check_parse_markdown("[telegram\\.org](asd", "Can't find end of a URL at byte offset 16");
check_parse_markdown("🏟 🏟__🏟 _🏟___", "Can't find end of Italic entity at byte offset 23");
check_parse_markdown("🏟 🏟__", "Can't find end of Underline entity at byte offset 9");
check_parse_markdown("🏟 🏟||test\\|", "Can't find end of Spoiler entity at byte offset 9");
check_parse_markdown("", "", {});
check_parse_markdown("\\\\", "\\", {});
@ -1401,6 +1410,7 @@ TEST(MessageEntities, parse_markdown) {
check_parse_markdown("🏟 🏟```🏟 \\\\\\`🏟```", "🏟 🏟 \\`🏟",
{{td::MessageEntity::Type::PreCode, 5, 5, "🏟"}});
check_parse_markdown("🏟 🏟**", "🏟 🏟", {});
check_parse_markdown("||test||", "test", {{td::MessageEntity::Type::Spoiler, 0, 4}});
check_parse_markdown("🏟 🏟``", "🏟 🏟", {});
check_parse_markdown("🏟 🏟``````", "🏟 🏟", {});
check_parse_markdown("🏟 🏟____", "🏟 🏟", {});
@ -1569,6 +1579,9 @@ TEST(MessageEntities, parse_markdown_v3) {
"[text](example.com)",
{{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Strikethrough, 5, 14}}, "text",
{{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
check_parse_markdown_v3("[text](example.com)",
{{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Spoiler, 5, 14}}, "text",
{{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
check_parse_markdown_v3("🏟[🏟](t.me) `🏟` [🏟](t.me) `a`", "🏟🏟 🏟 🏟 a",
{{td::MessageEntity::Type::TextUrl, 2, 2, "http://t.me/"},
@ -1580,14 +1593,16 @@ TEST(MessageEntities, parse_markdown_v3) {
check_parse_markdown_v3("__\n__", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
check_parse_markdown_v3("__ __a", " a", {}, true);
check_parse_markdown_v3("__\n__a", "\na", {}, true);
check_parse_markdown_v3("**** __a__ **b** ~~c~~", "**** a b c",
check_parse_markdown_v3("**** __a__ **b** ~~c~~ ||d||", "**** a b c d",
{{td::MessageEntity::Type::Italic, 5, 1},
{td::MessageEntity::Type::Bold, 7, 1},
{td::MessageEntity::Type::Strikethrough, 9, 1}});
check_parse_markdown_v3("тест __аааа__ **бббб** ~~вввв~~", "тест аааа бббб вввв",
{td::MessageEntity::Type::Strikethrough, 9, 1},
{td::MessageEntity::Type::Spoiler, 11, 1}});
check_parse_markdown_v3("тест __аааа__ **бббб** ~~вввв~~ ||гггг||", "тест аааа бббб вввв гггг",
{{td::MessageEntity::Type::Italic, 5, 4},
{td::MessageEntity::Type::Bold, 10, 4},
{td::MessageEntity::Type::Strikethrough, 15, 4}});
{td::MessageEntity::Type::Strikethrough, 15, 4},
{td::MessageEntity::Type::Spoiler, 20, 4}});
check_parse_markdown_v3("___a___ ***b** ~c~~", "___a___ ***b** ~c~~", {});
check_parse_markdown_v3(
"__asd[ab__cd](t.me)", "asdabcd",
@ -1614,6 +1629,18 @@ TEST(MessageEntities, parse_markdown_v3) {
check_parse_markdown_v3("__#test__test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
{{td::MessageEntity::Type::Italic, 0, 5}});
check_parse_markdown_v3(
"~~**~~||**a||", {{td::MessageEntity::Type::Strikethrough, 2, 1}, {td::MessageEntity::Type::Bold, 6, 1}},
"**||**a||", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}}, true);
check_parse_markdown_v3("**||**a||",
{{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
"||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, true);
check_parse_markdown_v3("||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, "a",
{{td::MessageEntity::Type::Spoiler, 0, 1}}, true);
check_parse_markdown_v3("~~||~~#test||test", "#testtest", {{td::MessageEntity::Type::Spoiler, 0, 5}});
check_parse_markdown_v3("||#test||test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
{{td::MessageEntity::Type::Spoiler, 0, 5}});
check_parse_markdown_v3("__[ab_](t.me)_", "__ab__", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
check_parse_markdown_v3(
"__[ab__](t.me)_", "ab_",
@ -1628,6 +1655,20 @@ TEST(MessageEntities, parse_markdown_v3) {
check_parse_markdown_v3("`a` __ab__", {{td::MessageEntity::Type::Underline, 5, 1}}, "a __ab__",
{{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
check_parse_markdown_v3("||[ab|](t.me)|", "||ab||", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
check_parse_markdown_v3(
"||[ab||](t.me)|", "ab|",
{{td::MessageEntity::Type::TextUrl, 0, 2, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 0, 2}});
check_parse_markdown_v3("||[||ab||](t.me)||", "||||ab||||",
{{td::MessageEntity::Type::TextUrl, 2, 6, "http://t.me/"}});
check_parse_markdown_v3(
"||[||ab||](t.me)a||", "||||aba",
{{td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 6, 1}});
check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Bold, 6, 3}}, "a ||ab||",
{{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Bold, 4, 3}});
check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Underline, 5, 1}}, "a ||ab||",
{{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
check_parse_markdown_v3("`a` @test__test__test", "a @test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
check_parse_markdown_v3("`a` #test__test__test", "a #test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
check_parse_markdown_v3("`a` __@test_test_test__", "a @test_test_test",
@ -1652,13 +1693,17 @@ TEST(MessageEntities, parse_markdown_v3) {
{{td::MessageEntity::Type::Italic, 0, 6},
{td::MessageEntity::Type::Bold, 2, 6},
{td::MessageEntity::Type::Strikethrough, 4, 6}});
check_parse_markdown_v3("__ab**cd~~ef__gh**ij~~", "abcdefghij",
check_parse_markdown_v3("__ab**cd~~ef||gh__ij**kl~~mn||", "abcdefghijklmn",
{{td::MessageEntity::Type::Italic, 0, 2},
{td::MessageEntity::Type::Bold, 2, 2},
{td::MessageEntity::Type::Italic, 2, 2},
{td::MessageEntity::Type::Strikethrough, 4, 6},
{td::MessageEntity::Type::Bold, 4, 4},
{td::MessageEntity::Type::Italic, 4, 2}},
{td::MessageEntity::Type::Bold, 4, 2},
{td::MessageEntity::Type::Italic, 4, 2},
{td::MessageEntity::Type::Strikethrough, 4, 2},
{td::MessageEntity::Type::Spoiler, 6, 8},
{td::MessageEntity::Type::Strikethrough, 6, 6},
{td::MessageEntity::Type::Bold, 6, 4},
{td::MessageEntity::Type::Italic, 6, 2}},
true);
check_parse_markdown_v3("__ab**[cd~~ef__](t.me)gh**ij~~", "abcdefghij",
{{td::MessageEntity::Type::Italic, 0, 6},
@ -1682,9 +1727,9 @@ TEST(MessageEntities, parse_markdown_v3) {
check_parse_markdown_v3(
"__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold "
"italic__bold**__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) "
"__italic**bold italic__bold**",
"__italic**bold italic__bold** ||spoiler||",
"italic strikethrough bold code pre italic text_url italicbold italicbolditalic strikethrough bold code pre "
"italic text_url italicbold italicbold",
"italic text_url italicbold italicbold spoiler",
{{td::MessageEntity::Type::Italic, 0, 6},
{td::MessageEntity::Type::Strikethrough, 7, 13},
{td::MessageEntity::Type::Bold, 21, 4},
@ -1702,14 +1747,15 @@ TEST(MessageEntities, parse_markdown_v3) {
{td::MessageEntity::Type::TextUrl, 107, 15, "http://telegram.org/"},
{td::MessageEntity::Type::Italic, 107, 6},
{td::MessageEntity::Type::Italic, 123, 17},
{td::MessageEntity::Type::Bold, 129, 15}});
{td::MessageEntity::Type::Bold, 129, 15},
{td::MessageEntity::Type::Spoiler, 145, 7}});
td::vector<td::string> parts{"a", " #test__a", "__", "**", "~~", "[", "](t.me)", "`"};
td::vector<td::string> parts{"a", " #test__a", "__", "**", "~~", "||", "[", "](t.me)", "`"};
td::vector<td::MessageEntity::Type> types{
td::MessageEntity::Type::Bold, td::MessageEntity::Type::Italic, td::MessageEntity::Type::Underline,
td::MessageEntity::Type::Strikethrough, td::MessageEntity::Type::Code, td::MessageEntity::Type::Pre,
td::MessageEntity::Type::PreCode, td::MessageEntity::Type::TextUrl, td::MessageEntity::Type::MentionName,
td::MessageEntity::Type::Cashtag};
td::MessageEntity::Type::Strikethrough, td::MessageEntity::Type::Spoiler, td::MessageEntity::Type::Code,
td::MessageEntity::Type::Pre, td::MessageEntity::Type::PreCode, td::MessageEntity::Type::TextUrl,
td::MessageEntity::Type::MentionName, td::MessageEntity::Type::Cashtag};
for (size_t test_n = 0; test_n < 1000; test_n++) {
td::string str;
int part_n = td::Random::fast(1, 200);
@ -1767,11 +1813,13 @@ TEST(MessageEntities, get_markdown_v3) {
check_get_markdown_v3("__ __", {}, " ", {{td::MessageEntity::Type::Italic, 0, 1}});
check_get_markdown_v3("** **", {}, " ", {{td::MessageEntity::Type::Bold, 0, 1}});
check_get_markdown_v3("~~ ~~", {}, " ", {{td::MessageEntity::Type::Strikethrough, 0, 1}});
check_get_markdown_v3("__a__ **b** ~~c~~ d", {{td::MessageEntity::Type::PreCode, 18, 1, "C++"}}, "a b c d",
check_get_markdown_v3("|| ||", {}, " ", {{td::MessageEntity::Type::Spoiler, 0, 1}});
check_get_markdown_v3("__a__ **b** ~~c~~ ||d|| e", {{td::MessageEntity::Type::PreCode, 24, 1, "C++"}}, "a b c d e",
{{td::MessageEntity::Type::Italic, 0, 1},
{td::MessageEntity::Type::Bold, 2, 1},
{td::MessageEntity::Type::Strikethrough, 4, 1},
{td::MessageEntity::Type::PreCode, 6, 1, "C++"}});
{td::MessageEntity::Type::Spoiler, 6, 1},
{td::MessageEntity::Type::PreCode, 8, 1, "C++"}});
check_get_markdown_v3("`ab` ```cd``` ef", {{td::MessageEntity::Type::PreCode, 14, 2, "C++"}}, "ab cd ef",
{{td::MessageEntity::Type::Code, 0, 2},
{td::MessageEntity::Type::Pre, 3, 2},