From 6b44dbae8a4caa7103fc3c3ee8ee963cd99c431d Mon Sep 17 00:00:00 2001 From: Wizou <11647984+wiz0u@users.noreply.github.com> Date: Fri, 17 Nov 2023 18:36:49 +0100 Subject: [PATCH] Support blockquotes in HTML/Markdown --- README.md | 5 ++++- src/TL.Extensions.cs | 30 ++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 63e9b2e..e73b22e 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,10 @@ See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient An invalid API request can result in a `RpcException` being raised, reflecting the [error code and status text](https://revgram.github.io/errors.html) of the problem. -The other configuration items that you can provide include: **session_pathname, email, email_verification_code, session_key, server_address, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, firebase, user_id, bot_token** +To [prevent getting banned](https://wiz0u.github.io/WTelegramClient/FAQ#prevent-ban) during dev, you can connect to [test servers](https://docs.pyrogram.org/topics/test-servers), by adding this line in your Config callback: +`case "server_address": return "149.154.167.40:443"; // test DC` + +The other configuration items that you can provide include: **session_pathname, email, email_verification_code, session_key, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, firebase, user_id, bot_token** Optional API parameters have a default value of `null` when unset. Passing `null` for a required string/array is the same as *empty* (0-length). Required API parameters/fields can sometimes be set to 0 or `null` when unused (check API documentation or experiment). diff --git a/src/TL.Extensions.cs b/src/TL.Extensions.cs index dc20256..aef2c56 100644 --- a/src/TL.Extensions.cs +++ b/src/TL.Extensions.cs @@ -56,6 +56,7 @@ namespace TL public static MessageEntity[] MarkdownToEntities(this Client _, ref string text, bool premium = false, IReadOnlyDictionary users = null) { var entities = new List(); + MessageEntityBlockquote lastBlockQuote = null; var sb = new StringBuilder(text); for (int offset = 0; offset < sb.Length;) { @@ -101,6 +102,16 @@ namespace TL else ProcessEntity(); break; + case '>' when offset == 0 || sb[offset - 1] == '\n': + sb.Remove(offset, 1); + if (lastBlockQuote is null || lastBlockQuote.length < offset - lastBlockQuote.offset) + entities.Add(lastBlockQuote = new MessageEntityBlockquote { offset = offset, length = -1 }); + else + lastBlockQuote.length = -1; + break; + case '\n' when lastBlockQuote is { length: -1 }: + lastBlockQuote.length = ++offset - lastBlockQuote.offset; + break; case '!' when offset + 1 < sb.Length && sb[offset + 1] == '[': sb.Remove(offset, 1); goto case '['; @@ -146,6 +157,8 @@ namespace TL sb.Remove(offset, 1); } } + if (lastBlockQuote is { length: -1 }) + lastBlockQuote.length = sb.Length - lastBlockQuote.offset; text = sb.ToString(); return entities.Count == 0 ? null : entities.ToArray(); } @@ -163,16 +176,20 @@ namespace TL var sb = new StringBuilder(message); int entityIndex = 0; var nextEntity = entities[entityIndex]; + bool inBlockQuote = false; + char lastCh = '\0'; for (int offset = 0, i = 0; ; offset++, i++) { while (closings.Count != 0 && offset == closings[0].offset) { var md = closings[0].md; - if (i > 0 && md[0] == '_' && sb[i - 1] == '_') md = '\r' + md; - sb.Insert(i, md); i += md.Length; closings.RemoveAt(0); + if (i > 0 && md[0] == '_' && sb[i - 1] == '_') md = '\r' + md; + if (md[0] == '>') { inBlockQuote = false; if (lastCh != '\n' && i < sb.Length && sb[i] != '\n') md = "\n"; else continue; } + sb.Insert(i, md); i += md.Length; } if (i == sb.Length) break; + if (lastCh == '\n' && inBlockQuote) sb.Insert(i++, '>'); for (; offset == nextEntity?.offset; nextEntity = ++entityIndex < entities.Length ? entities[entityIndex] : null) { if (EntityToMD.TryGetValue(nextEntity.GetType(), out var md)) @@ -190,6 +207,8 @@ namespace TL if (premium) closing.md = $"](tg://emoji?id={mecu.document_id})"; else continue; } + else if (md[0] == '>') + { inBlockQuote = true; if (lastCh is not '\n' and not '\0') md = "\n>"; } else if (nextEntity is MessageEntityPre mep) md = $"```{mep.language}\n"; int index = ~closings.BinarySearch(closing, Comparer<(int, string)>.Create((x, y) => x.Item1.CompareTo(y.Item1) | 1)); @@ -198,11 +217,11 @@ namespace TL sb.Insert(i, md); i += md.Length; } } - switch (sb[i]) + switch (lastCh = sb[i]) { case '_': case '*': case '~': case '`': case '#': case '+': case '-': case '=': case '.': case '!': case '[': case ']': case '(': case ')': case '{': case '}': case '>': case '|': case '\\': - sb.Insert(i, '\\'); i++; + sb.Insert(i++, '\\'); break; } } @@ -222,6 +241,7 @@ namespace TL [typeof(MessageEntityStrike)] = "~", [typeof(MessageEntitySpoiler)] = "||", [typeof(MessageEntityCustomEmoji)] = "![", + [typeof(MessageEntityBlockquote)] = ">", }; /// Insert backslashes in front of Markdown reserved characters @@ -295,6 +315,7 @@ namespace TL case "code": ProcessEntity(); break; case "pre": ProcessEntity(); break; case "tg-emoji" when closing: ProcessEntity(); break; + case "blockquote": ProcessEntity(); break; default: if (closing) { @@ -412,6 +433,7 @@ namespace TL [typeof(MessageEntityStrike)] = "s", [typeof(MessageEntitySpoiler)] = "tg-spoiler", [typeof(MessageEntityCustomEmoji)] = "tg-emoji", + [typeof(MessageEntityBlockquote)] = "blockquote", }; /// Replace special HTML characters with their &xx; equivalent