diff --git a/EXAMPLES.md b/EXAMPLES.md index b6f7c94..63397cb 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -34,13 +34,13 @@ if (contacts.imported.Length > 0) ### Send a Markdown message to ourself (Saved Messages) ```csharp using var client = new WTelegram.Client(Environment.GetEnvironmentVariable); -await client.LoginUserIfNeeded(); -var text = "Hello __dear *friend*__!\nEnjoy this `userbot` written with [WTelegramClient](https://github.com/wiz0u/WTelegramClient)"; +var user = await client.LoginUserIfNeeded(); +var text = $"Hello __dear *{Markdown.Escape(user.first_name)}*__\nEnjoy this `userbot` written with [WTelegramClient](https://github.com/wiz0u/WTelegramClient)"; var entities = client.MarkdownToEntities(ref text); await client.SendMessageAsync(InputPeer.Self, text, entities: entities); ``` See [MarkdownV2 formatting style](https://core.telegram.org/bots/api/#markdownv2-style) for details. -
For the `tg://user?id=` notation to work, that user's access hash must have been collected first ([see below](#Collect-Access-Hash-and-save-them-for-later-use)) +
*Note: For the `tg://user?id=` notation to work, that user's access hash must have been collected first ([see below](#Collect-Access-Hash-and-save-them-for-later-use))* ### List all chats (groups/channels) the user is in and send a message to one ```csharp diff --git a/src/Helpers.TL.cs b/src/Helpers.TL.cs index 8164e79..cf1c50a 100644 --- a/src/Helpers.TL.cs +++ b/src/Helpers.TL.cs @@ -354,7 +354,7 @@ namespace TL } } - public static class Helpers + public static class Markdown { public static MessageEntity[] MarkdownToEntities(this WTelegram.Client client, ref string text) { @@ -365,9 +365,8 @@ namespace TL switch (sb[offset]) { case '\\': sb.Remove(offset++, 1); break; - case '*': - ProcessEntity(); - break; + case '*': ProcessEntity(); break; + case '~': ProcessEntity(); break; case '_': if (offset + 1 < sb.Length && sb[offset + 1] == '_') { @@ -377,69 +376,82 @@ namespace TL else ProcessEntity(); break; - case '~': - ProcessEntity(); - break; case '`': if (offset + 2 < sb.Length && sb[offset + 1] == '`' && sb[offset + 2] == '`') { - int header = 3; - if (entities.FindLast(e => e.length == 0) is MessageEntityPre pre) + int len = 3; + if (entities.FindLast(e => e.length == -1) is MessageEntityPre pre) pre.length = offset - pre.offset; else { - while (offset + header < sb.Length && !char.IsWhiteSpace(sb[offset + header])) - header++; - entities.Add(new MessageEntityPre { offset = offset, language = sb.ToString(offset + 3, header - 3) }); + while (offset + len < sb.Length && !char.IsWhiteSpace(sb[offset + len])) + len++; + entities.Add(new MessageEntityPre { offset = offset, length = -1, language = sb.ToString(offset + 3, len - 3) }); } - sb.Remove(offset, header); + sb.Remove(offset, len); } else ProcessEntity(); break; case '[': - entities.Add(new MessageEntityTextUrl { offset = offset }); + entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 }); sb.Remove(offset, 1); break; case ']': - if (offset + 1 < sb.Length && sb[offset + 1] == '(' && entities.FindLast(e => e.length == 0) is MessageEntityTextUrl textUrl) + if (offset + 2 < sb.Length && sb[offset + 1] == '(') { - textUrl.length = offset - textUrl.offset; - sb.Remove(offset, 2); - } - else - offset++; - break; - case ')': - var lastIndex = entities.FindLastIndex(e => e.length == 0 || e is MessageEntityTextUrl { url: null }); - MessageEntity entity; - if (lastIndex >= 0 && (entity = entities[lastIndex]).length != 0) - { - var urlStart = entity.offset + entity.length; - var urlLength = offset - urlStart; - var url = sb.ToString(urlStart, urlLength); - sb.Remove(urlStart, urlLength + 1); - offset = urlStart; - if (url.StartsWith("tg://user?id=") && long.TryParse(url[13..], out var user_id) && client.GetAccessHashFor(user_id) is long hash) - entities[lastIndex] = new InputMessageEntityMentionName { offset = entity.offset, length = entity.length, user_id = new InputUser { user_id = user_id, access_hash = hash } }; - else - ((MessageEntityTextUrl)entity).url = url; + var lastIndex = entities.FindLastIndex(e => e.length == -1); + if (lastIndex >= 0 && entities[lastIndex] is MessageEntityTextUrl textUrl) + { + textUrl.length = offset - textUrl.offset; + int offset2 = offset + 2; + while (offset2 < sb.Length) + { + char c = sb[offset2++]; + if (c == '\\') sb.Remove(offset2 - 1, 1); + else if (c == ')') break; + } + textUrl.url = sb.ToString(offset + 2, offset2 - offset - 3); + if (textUrl.url.StartsWith("tg://user?id=") && long.TryParse(textUrl.url[13..], out var user_id) && client.GetAccessHashFor(user_id) is long hash) + entities[lastIndex] = new InputMessageEntityMentionName { offset = textUrl.offset, length = textUrl.length, user_id = new InputUser { user_id = user_id, access_hash = hash } }; + sb.Remove(offset, offset2 - offset); + break; + } } + offset++; break; default: offset++; break; } void ProcessEntity() where T : MessageEntity, new() { - if (entities.LastOrDefault(e => e.length == 0) is T prevEntity) + if (entities.LastOrDefault(e => e.length == -1) is T prevEntity) prevEntity.length = offset - prevEntity.offset; else - entities.Add(new T { offset = offset }); + entities.Add(new T { offset = offset, length = -1 }); sb.Remove(offset, 1); } } text = sb.ToString(); return entities.Count == 0 ? null : entities.ToArray(); } + + + public static string Escape(string text) + { + StringBuilder sb = null; + for (int index = 0, added = 0; index < text.Length; index++) + { + switch (text[index]) + { + case '_': case '*': case '~': case '`': case '#': case '+': case '-': case '=': case '.': case '!': + case '[': case ']': case '(': case ')': case '{': case '}': case '>': case '|': case '\\': + sb ??= new StringBuilder(text, text.Length + 32); + sb.Insert(index + added++, '\\'); + break; + } + } + return sb?.ToString() ?? text; + } } }