Custom emoji Markdown/Html syntax updated for tdlib compatibility

- Markdown: ![x](tg://emoji?id=...)
- Html: <tg-emoji emoji-id="...">
Previous syntaxes are still accepted
Also, changed Github examples links with ts=4
This commit is contained in:
Wizou 2022-12-05 20:32:32 +01:00
parent 42be950896
commit 231bf632e2
6 changed files with 32 additions and 29 deletions

View file

@ -95,7 +95,7 @@ foreach (Dialog dialog in dialogs.dialogs)
Notes: Notes:
- The lists returned by Messages_GetAllDialogs contains the `access_hash` for those chats and users. - The lists returned by Messages_GetAllDialogs contains the `access_hash` for those chats and users.
- See also the `Main` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs#L20). - See also the `Main` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L20).
- To retrieve the dialog information about a specific [peer](README.md#terminology), use `client.Messages_GetPeerDialogs(inputPeer)` - To retrieve the dialog information about a specific [peer](README.md#terminology), use `client.Messages_GetPeerDialogs(inputPeer)`
<a name="list-chats"></a> <a name="list-chats"></a>
@ -114,7 +114,7 @@ Notes:
- The list returned by Messages_GetAllChats contains the `access_hash` for those chats. Read [FAQ #4](FAQ.md#access-hash) about this. - The list returned by Messages_GetAllChats contains the `access_hash` for those chats. Read [FAQ #4](FAQ.md#access-hash) about this.
- If a basic chat group has been migrated to a supergroup, you may find both the old `Chat` and a `Channel` with different IDs in the `chats.chats` result, - If a basic chat group has been migrated to a supergroup, you may find both the old `Chat` and a `Channel` with different IDs in the `chats.chats` result,
but the old `Chat` will be marked with flag [deactivated] and should not be used anymore. See [Terminology in ReadMe](README.md#terminology). but the old `Chat` will be marked with flag [deactivated] and should not be used anymore. See [Terminology in ReadMe](README.md#terminology).
- You can find a longer version of this method call in [Examples/Program_GetAllChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_GetAllChats.cs#L32) - You can find a longer version of this method call in [Examples/Program_GetAllChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_GetAllChats.cs?ts=4#L32)
<a name="list-members"></a> <a name="list-members"></a>
## List the members from a chat ## List the members from a chat
@ -189,14 +189,14 @@ Notes:
This is done through the `client.OnUpdate` callback event. This is done through the `client.OnUpdate` callback event.
Your event handler implementation can either return `Task.CompletedTask` or be an `async Task` method. Your event handler implementation can either return `Task.CompletedTask` or be an `async Task` method.
See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs#L23). See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L23).
<a name="monitor-msg"></a> <a name="monitor-msg"></a>
## Monitor new messages being posted in chats in real-time ## Monitor new messages being posted in chats in real-time
You have to handle `client.OnUpdate` events containing an `UpdateNewMessage`. You have to handle `client.OnUpdate` events containing an `UpdateNewMessage`.
See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs#L23). See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L23).
You can filter specific chats the message are posted in, by looking at the `Message.peer_id` field. You can filter specific chats the message are posted in, by looking at the `Message.peer_id` field.
@ -206,7 +206,7 @@ You can filter specific chats the message are posted in, by looking at the `Mess
This is done using the helper method `client.DownloadFileAsync(file, outputStream)` This is done using the helper method `client.DownloadFileAsync(file, outputStream)`
that simplifies the download of a photo/document/file once you get a reference to its location *(through updates or API calls)*. that simplifies the download of a photo/document/file once you get a reference to its location *(through updates or API calls)*.
See [Examples/Program_DownloadSavedMedia.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_DownloadSavedMedia.cs#L31) that download all media files you forward to yourself (Saved Messages) See [Examples/Program_DownloadSavedMedia.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_DownloadSavedMedia.cs?ts=4#L31) that download all media files you forward to yourself (Saved Messages)
<a name="upload"></a> <a name="upload"></a>
## Upload a media file and post it with caption to a chat ## Upload a media file and post it with caption to a chat
@ -323,10 +323,10 @@ await Task.Delay(5000);
## Fun with custom emojies and reactions on pinned messages ## Fun with custom emojies and reactions on pinned messages
```csharp ```csharp
// • Sending a message with custom emojies in Markdown to ourself: // • Sending a message with custom emojies in Markdown to ourself:
var text = "Vicksy says Hi! [👋](emoji?id=5190875290439525089)"; var text = "Vicksy says Hi! ![👋](tg://emoji?id=5190875290439525089)";
var entities = client.MarkdownToEntities(ref text, premium: true); var entities = client.MarkdownToEntities(ref text, premium: true);
await client.SendMessageAsync(InputPeer.Self, text, entities: entities); await client.SendMessageAsync(InputPeer.Self, text, entities: entities);
// also available in HTML: <tg-emoji id="5190875290439525089">👋</tg-emoji> // also available in HTML: <tg-emoji emoji-id="5190875290439525089">👋</tg-emoji>
// • Fetch all available standard emoji reactions // • Fetch all available standard emoji reactions
var all_emoji = await client.Messages_GetAvailableReactions(); var all_emoji = await client.Messages_GetAvailableReactions();
@ -448,7 +448,7 @@ You can automate the collection of `access_hash` for the various resources obtai
so that you don't have to remember them by yourself or ask the API about them each time. so that you don't have to remember them by yourself or ask the API about them each time.
This is done by activating the experimental `client.CollectAccessHash` system. This is done by activating the experimental `client.CollectAccessHash` system.
See [Examples/Program_CollectAccessHash.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_CollectAccessHash.cs#L22) for how to enable it, and save/restore them for later use. See [Examples/Program_CollectAccessHash.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_CollectAccessHash.cs?ts=4#L22) for how to enable it, and save/restore them for later use.
<a name="proxy"></a> <a name="proxy"></a>
## Use a proxy or MTProxy to connect to Telegram ## Use a proxy or MTProxy to connect to Telegram
@ -509,14 +509,14 @@ you can choose to store the session data somewhere else, like in a database.
The WTelegram.Client constructor takes an optional `sessionStore` parameter to allow storing sessions in an alternate manner. The WTelegram.Client constructor takes an optional `sessionStore` parameter to allow storing sessions in an alternate manner.
Use it to pass a custom Stream-derived class that will **read** (first initial call to Length & Read) and **store** (subsequent Writes) session data to database. Use it to pass a custom Stream-derived class that will **read** (first initial call to Length & Read) and **store** (subsequent Writes) session data to database.
You can find an example for such custom session store in [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs#L61) You can find an example for such custom session store in [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs?ts=4#L61)
<a name="e2e"></a><a name="secrets"></a> <a name="e2e"></a><a name="secrets"></a>
## Send/receive end-to-end encrypted messages & files in Secret Chats ## Send/receive end-to-end encrypted messages & files in Secret Chats
This can be done easily using the helper class `WTelegram.SecretChats` offering methods to manage/encrypt/decrypt secret chats & encrypted messages/files. This can be done easily using the helper class `WTelegram.SecretChats` offering methods to manage/encrypt/decrypt secret chats & encrypted messages/files.
You can view a full working example at [Examples/Program_SecretChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_SecretChats.cs#L11). You can view a full working example at [Examples/Program_SecretChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_SecretChats.cs?ts=4#L11).
Secret Chats have been tested successfully with Telegram Android & iOS official clients. Secret Chats have been tested successfully with Telegram Android & iOS official clients.
You can also check our [FAQ for more implementation details](FAQ.md#14-secret-chats-implementation-details). You can also check our [FAQ for more implementation details](FAQ.md#14-secret-chats-implementation-details).

2
FAQ.md
View file

@ -243,7 +243,7 @@ and hosted online on any [VPS Hosting](https://www.google.com/search?q=vps+hosti
Pure WebApp hosts might not be adequate as they will recycle (stop) your app if there is no incoming HTTP requests. Pure WebApp hosts might not be adequate as they will recycle (stop) your app if there is no incoming HTTP requests.
There are many cheap VPS Hosting offers available, for example Heroku: There are many cheap VPS Hosting offers available, for example Heroku:
See [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs#L9) for such an implementation and the steps to host/deploy it. See [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs?ts=4#L9) for such an implementation and the steps to host/deploy it.
<a name="secrets"></a> <a name="secrets"></a>
## 14. Secret Chats implementation details ## 14. Secret Chats implementation details

View file

@ -156,23 +156,23 @@ and in the [Examples subdirectory](https://github.com/wiz0u/WTelegramClient/tree
# Terminology in Telegram Client API # Terminology in Telegram Client API
In the API, Telegram uses some terms/classnames that can be confusing as they differ from the terms shown to end-users: In the API, Telegram uses some terms/classnames that can be confusing as they differ from the terms shown to end-users:
- `Channel` : A (large or public) chat group *(sometimes called [supergroup](https://corefork.telegram.org/api/channel#supergroups))* - `Channel`: A (large or public) chat group *(sometimes called [supergroup](https://corefork.telegram.org/api/channel#supergroups))*,
or a [broadcast channel](https://corefork.telegram.org/api/channel#channels) (the `broadcast` flag differentiate those) or a [broadcast channel](https://corefork.telegram.org/api/channel#channels) (the `broadcast` flag differentiate those)
- `Chat` : A private [basic chat group](https://corefork.telegram.org/api/channel#basic-groups) with less than 200 members - `Chat`: A private [basic chat group](https://corefork.telegram.org/api/channel#basic-groups) with less than 200 members
(it may be migrated to a supergroup `Channel` with a new ID when it gets bigger or public, in which case the old `Chat` will still exist but will be `deactivated`) (it may be migrated to a supergroup `Channel` with a new ID when it gets bigger or public, in which case the old `Chat` will still exist but will be `deactivated`)
**⚠️ Most chat groups you see are really of type `Channel`, not `Chat`!** **⚠️ Most chat groups you see are really of type `Channel`, not `Chat`!**
- chats : In plural or general meaning, it means either `Chat` or `Channel` *(therefore, no private user discussions)* - **chats**: In plural or general meaning, it means either `Chat` or `Channel` *(therefore, no private user discussions)*
- `Peer` : Either a `Chat`, a `Channel` or a `User` - `Peer`: Either a `Chat`, a `Channel` or a `User`
- Dialog : Status of chat with a `Peer` *(draft, last message, unread count, pinned...)*. It represents each line from your Telegram chat list. - **Dialog**: Status of chat with a `Peer` *(draft, last message, unread count, pinned...)*. It represents each line from your Telegram chat list.
- Access Hash : Telegram requires you to provide a specific `access_hash` for users, channels, and other resources before interacting with them. - **Access Hash**: Telegram requires you to provide a specific `access_hash` for users, channels, and other resources before interacting with them.
See [FAQ #4](https://wiz0u.github.io/WTelegramClient/FAQ#access-hash) to learn more about it. See [FAQ #4](https://wiz0u.github.io/WTelegramClient/FAQ#access-hash) to learn more about it.
- DC (DataCenter) : There are a few datacenters depending on where in the world the user (or an uploaded media file) is from. - **DC** (DataCenter): There are a few datacenters depending on where in the world the user (or an uploaded media file) is from.
- Session or Authorization : Pairing between a device and a phone number. You can have several active sessions for the same phone number. - **Session** or **Authorization**: Pairing between a device and a phone number. You can have several active sessions for the same phone number.
# Other things to know # Other things to know
The Client class also offers an `OnUpdate` event that is triggered when Telegram servers sends Updates (like new messages or status), independently of your API requests. The Client class also offers an `OnUpdate` event that is triggered when Telegram servers sends Updates (like new messages or status), independently of your API requests.
See [Examples/Program_ListenUpdates.cs](https://wiz0u.github.io/WTelegramClient/Examples/Program_ListenUpdates.cs) See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L23)
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. 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.

View file

@ -25,7 +25,7 @@ namespace WTelegram
/// <summary>Retrieve the access_hash associated with this id (for a TL class) if it was collected</summary> /// <summary>Retrieve the access_hash associated with this id (for a TL class) if it was collected</summary>
/// <remarks>This requires <see cref="CollectAccessHash"/> to be set to <see langword="true"/> first. /// <remarks>This requires <see cref="CollectAccessHash"/> to be set to <see langword="true"/> first.
/// <para>See <see href="https://github.com/wiz0u/WTelegramClient/tree/master/Examples/Program_CollectAccessHash.cs">Examples/Program_CollectAccessHash.cs</see> for how to use this</para></remarks> /// <para>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_CollectAccessHash.cs?ts=4#L22">Examples/Program_CollectAccessHash.cs</see> for how to use this</para></remarks>
/// <typeparam name="T">a TL object class. For example User, Channel or Photo</typeparam> /// <typeparam name="T">a TL object class. For example User, Channel or Photo</typeparam>
public long GetAccessHashFor<T>(long id) where T : IObject public long GetAccessHashFor<T>(long id) where T : IObject
{ {

View file

@ -24,7 +24,7 @@ namespace WTelegram
public partial class Client : IDisposable public partial class Client : IDisposable
{ {
/// <summary>This event will be called when unsollicited updates/messages are sent by Telegram servers</summary> /// <summary>This event will be called when unsollicited updates/messages are sent by Telegram servers</summary>
/// <remarks>Make your handler <see langword="async"/>, or return <see cref="Task.CompletedTask"/> or <see langword="null"/><br/>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs">Examples/Program_ListenUpdate.cs</see> for how to use this</remarks> /// <remarks>Make your handler <see langword="async"/>, or return <see cref="Task.CompletedTask"/> or <see langword="null"/><br/>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L23">Examples/Program_ListenUpdate.cs</see> for how to use this</remarks>
public event Func<IObject, Task> OnUpdate; public event Func<IObject, Task> OnUpdate;
/// <summary>Used to create a TcpClient connected to the given address/port, or throw an exception on failure</summary> /// <summary>Used to create a TcpClient connected to the given address/port, or throw an exception on failure</summary>
public TcpFactory TcpHandler { get; set; } = DefaultTcpHandler; public TcpFactory TcpHandler { get; set; } = DefaultTcpHandler;

View file

@ -91,6 +91,9 @@ namespace TL
else else
ProcessEntity<MessageEntityCode>(); ProcessEntity<MessageEntityCode>();
break; break;
case '!' when offset + 1 < sb.Length && sb[offset + 1] == '[':
sb.Remove(offset, 1);
goto case '[';
case '[': case '[':
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 }); entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 });
sb.Remove(offset, 1); sb.Remove(offset, 1);
@ -112,7 +115,7 @@ namespace TL
textUrl.url = sb.ToString(offset + 2, offset2 - offset - 3); textUrl.url = sb.ToString(offset + 2, offset2 - offset - 3);
if (textUrl.url.StartsWith("tg://user?id=") && long.TryParse(textUrl.url[13..], out var id) && client.GetAccessHashFor<User>(id) is long hash) if (textUrl.url.StartsWith("tg://user?id=") && long.TryParse(textUrl.url[13..], out var id) && client.GetAccessHashFor<User>(id) is long hash)
entities[lastIndex] = new InputMessageEntityMentionName { offset = textUrl.offset, length = textUrl.length, user_id = new InputUser(id, hash) }; entities[lastIndex] = new InputMessageEntityMentionName { offset = textUrl.offset, length = textUrl.length, user_id = new InputUser(id, hash) };
else if (textUrl.url.StartsWith("emoji?id=") && long.TryParse(textUrl.url[9..], out id)) else if ((textUrl.url.StartsWith("tg://emoji?id=") || textUrl.url.StartsWith("emoji?id=")) && long.TryParse(textUrl.url[(textUrl.url.IndexOf('=') + 1)..], out id))
if (premium) entities[lastIndex] = new MessageEntityCustomEmoji { offset = textUrl.offset, length = textUrl.length, document_id = id }; if (premium) entities[lastIndex] = new MessageEntityCustomEmoji { offset = textUrl.offset, length = textUrl.length, document_id = id };
else entities.RemoveAt(lastIndex); else entities.RemoveAt(lastIndex);
sb.Remove(offset, offset2 - offset); sb.Remove(offset, offset2 - offset);
@ -165,7 +168,7 @@ namespace TL
if (entityToMD.TryGetValue(nextEntity.GetType(), out var md)) if (entityToMD.TryGetValue(nextEntity.GetType(), out var md))
{ {
var closing = (nextEntity.offset + nextEntity.length, md); var closing = (nextEntity.offset + nextEntity.length, md);
if (md[0] == '[') if (md[0] is '[' or '!')
{ {
if (nextEntity is MessageEntityTextUrl metu) if (nextEntity is MessageEntityTextUrl metu)
closing.md = $"]({metu.url.Replace("\\", "\\\\").Replace(")", "\\)").Replace(">", "%3E")})"; closing.md = $"]({metu.url.Replace("\\", "\\\\").Replace(")", "\\)").Replace(">", "%3E")})";
@ -174,7 +177,7 @@ namespace TL
else if (nextEntity is InputMessageEntityMentionName imemn) else if (nextEntity is InputMessageEntityMentionName imemn)
closing.md = $"](tg://user?id={imemn.user_id.UserId ?? client.UserId})"; closing.md = $"](tg://user?id={imemn.user_id.UserId ?? client.UserId})";
else if (nextEntity is MessageEntityCustomEmoji mecu) else if (nextEntity is MessageEntityCustomEmoji mecu)
if (premium) closing.md = $"](emoji?id={mecu.document_id})"; if (premium) closing.md = $"](tg://emoji?id={mecu.document_id})";
else continue; else continue;
} }
else if (nextEntity is MessageEntityPre mep) else if (nextEntity is MessageEntityPre mep)
@ -208,7 +211,7 @@ namespace TL
[typeof(MessageEntityUnderline)] = "__", [typeof(MessageEntityUnderline)] = "__",
[typeof(MessageEntityStrike)] = "~", [typeof(MessageEntityStrike)] = "~",
[typeof(MessageEntitySpoiler)] = "||", [typeof(MessageEntitySpoiler)] = "||",
[typeof(MessageEntityCustomEmoji)] = "[", [typeof(MessageEntityCustomEmoji)] = "![",
}; };
/// <summary>Insert backslashes in front of Markdown reserved characters</summary> /// <summary>Insert backslashes in front of Markdown reserved characters</summary>
@ -304,8 +307,8 @@ namespace TL
if (entities.LastOrDefault(e => e.length == -1) is MessageEntityPre prevEntity) if (entities.LastOrDefault(e => e.length == -1) is MessageEntityPre prevEntity)
prevEntity.language = tag[21..^1]; prevEntity.language = tag[21..^1];
} }
else if (premium && tag.StartsWith("tg-emoji id=\"")) else if (premium && (tag.StartsWith("tg-emoji emoji-id=\"") || tag.StartsWith("tg-emoji id=\"")))
entities.Add(new MessageEntityCustomEmoji { offset = offset, length = -1, document_id = long.Parse(tag[13..^1]) }); entities.Add(new MessageEntityCustomEmoji { offset = offset, length = -1, document_id = long.Parse(tag[(tag.IndexOf('=') + 2)..^1]) });
break; break;
} }
@ -361,7 +364,7 @@ namespace TL
tag = $"<a href=\"tg://user?id={imemn.user_id.UserId ?? client.UserId}\">"; tag = $"<a href=\"tg://user?id={imemn.user_id.UserId ?? client.UserId}\">";
} }
else if (nextEntity is MessageEntityCustomEmoji mecu) else if (nextEntity is MessageEntityCustomEmoji mecu)
if (premium) tag = $"<tg-emoji id=\"{mecu.document_id}\">"; if (premium) tag = $"<tg-emoji emoji-id=\"{mecu.document_id}\">";
else continue; else continue;
else if (nextEntity is MessageEntityPre mep && !string.IsNullOrEmpty(mep.language)) else if (nextEntity is MessageEntityPre mep && !string.IsNullOrEmpty(mep.language))
{ {