diff --git a/EXAMPLES.md b/EXAMPLES.md
index de51ccf..933e51e 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -76,7 +76,7 @@ var sent2 = await client.SendMessageAsync(InputPeer.Self, text2, entities: entit
text2 = client.EntitiesToMarkdown(sent2.message, sent2.entities);
```
See [HTML formatting style](https://core.telegram.org/bots/api/#html-style) and [MarkdownV2 formatting style](https://core.telegram.org/bots/api/#markdownv2-style) for details.
-*Note: For the `tg://user?id=` notation to work, that user's access hash must have been collected first ([see below](#collect-access-hash))*
+*Note: For the `tg://user?id=` notation to work, you need to pass the _users dictionary in arguments ([see below](#collect-users-chats))*
## List all dialogs (chats/groups/channels/user chat) we are currently in
@@ -442,13 +442,39 @@ finally
```
-## Collect Access Hash and save them for later use
+
+
+## Collect Users/Chats description structures and access hash
-You can automate the collection of `access_hash` for the various resources obtained in response to API calls or Updates,
-so that you don't have to remember them by yourself or ask the API about them each time.
+Many API calls return a structure with a `users` and a `chats` field at the root of the structure.
+This is also the case for updates passed to `client.OnUpdate`.
-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?ts=4#L22) for how to enable it, and save/restore them for later use.
+These two dictionaries give details about the various users/chats that will be typically referenced in subobjects deeper in the structure,
+typically in the form of a `Peer` object or a `user_id` field.
+
+In such case, the root structure inherits the `IPeerResolver` interface, and you can use the `UserOrChat(peer)` method to resolve a `Peer`
+into either a `User` or `ChatBase` (`Chat`,`Channel`...) description structure *(depending what kind of peer it was describing)*
+
+You can also use the `CollectUsersChats` helper method to collect these 2 fields into 2 aggregate dictionaries to remember details
+*(including access hashes)* about all the users/chats you've encountered so far.
+
+Example of usage for `CollectUsersChats`:
+```csharp
+static Dictionary _users = new();
+static Dictionary _chats = new();
+...
+var dialogs = await client.Messages_GetAllDialogs();
+dialogs.CollectUsersChats(_users, _chats);
+...
+private static async Task OnUpdate(IObject arg)
+{
+ if (arg is not UpdatesBase updates) return;
+ updates.CollectUsersChats(_users, _chats);
+ ...
+}
+```
+
+*Note: If you need to save/restore those dictionaries between runs of your program, it's up to you to serialize their content to disk*
## Use a proxy or MTProxy to connect to Telegram
diff --git a/Examples/Program_CollectAccessHash.cs b/Examples/Program_CollectAccessHash.cs
deleted file mode 100644
index 9537987..0000000
--- a/Examples/Program_CollectAccessHash.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.Json;
-using System.Threading.Tasks;
-using TL;
-
-namespace WTelegramClientTest
-{
- static class Program_CollectAccessHash
- {
- private const string StateFilename = "SavedState.json";
- private const long DurovID = 1006503122; // known ID for Durov's Channel
- private static SavedState savedState = new();
-
- // go to Project Properties > Debug > Environment variables and add at least these: api_id, api_hash, phone_number
- static async Task Main(string[] _)
- {
- Console.WriteLine("The program demonstrate how to load/save/use collected access hash.");
- WTelegram.Helpers.Log = (l, s) => System.Diagnostics.Debug.WriteLine(s);
- using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
- client.CollectAccessHash = true;
-
- if (File.Exists(StateFilename))
- {
- Console.WriteLine("Loading previously saved access hashes from disk...");
- using (var stateStream = File.OpenRead(StateFilename))
- savedState = await JsonSerializer.DeserializeAsync(stateStream);
- foreach (var id_hash in savedState.Channels) client.SetAccessHashFor(id_hash.Key, id_hash.Value);
- foreach (var id_hash in savedState.Users) client.SetAccessHashFor(id_hash.Key, id_hash.Value);
- }
-
- Console.WriteLine("Connecting to Telegram...");
- await client.LoginUserIfNeeded();
-
- var durovAccessHash = client.GetAccessHashFor(DurovID);
- if (durovAccessHash != 0)
- {
- // we already know the access hash for Durov's Channel, so we can directly use it
- Console.WriteLine($"Channel @durov has ID {DurovID} and access hash was already collected: {durovAccessHash:X}");
- }
- else
- {
- // Zero means the access hash for Durov's Channel was not collected yet.
- // So we need to obtain it through Client API calls whose results contains the access_hash field, such as:
- // - Messages_GetAllChats (see Program_GetAllChats.cs for an example on how to use it)
- // - Messages_GetAllDialogs (see Program_ListenUpdates.cs for an example on how to use it)
- // - Contacts_ResolveUsername (see below for an example on how to use it)
- // and many more API methods...
- // The access_hash fields can be found inside instance of User, Channel, Photo, Document, etc..
- // usually listed through their base class UserBase, ChatBase, PhotoBase, DocumentBase, etc...
- Console.WriteLine("Resolving channel @durov to get its ID, access hash and other infos...");
- var durovResolved = await client.Contacts_ResolveUsername("durov"); // @durov = Durov's Channel
- if (durovResolved.peer.ID != DurovID)
- throw new Exception("@durov has changed channel ID ?!");
- durovAccessHash = client.GetAccessHashFor(DurovID); // should have been collected from the previous API result
- if (durovAccessHash == 0)
- throw new Exception("No access hash was automatically collected !? (shouldn't happen)");
- Console.WriteLine($"Channel @durov has ID {DurovID} and access hash was automatically collected: {durovAccessHash:X}");
- }
-
- Console.WriteLine("With the access hash, we can now join the channel for example.");
- await client.Channels_JoinChannel(new InputChannel(DurovID, durovAccessHash));
-
- Console.WriteLine("Channel joined. Press any key to save and exit");
- Console.ReadKey(true);
-
- Console.WriteLine("Saving all collected access hashes to disk for next run...");
- savedState.Channels = client.AllAccessHashesFor().ToList();
- savedState.Users = client.AllAccessHashesFor().ToList();
- using (var stateStream = File.Create(StateFilename))
- await JsonSerializer.SerializeAsync(stateStream, savedState);
- }
-
- class SavedState
- {
- public List> Channels { get; set; } = new();
- public List> Users { get; set; } = new();
- }
- }
-}
diff --git a/src/Client.Helpers.cs b/src/Client.Helpers.cs
index 115f05a..ce1580d 100644
--- a/src/Client.Helpers.cs
+++ b/src/Client.Helpers.cs
@@ -16,7 +16,9 @@ namespace WTelegram
partial class Client
{
#region Collect Access Hash system
- /// Enable the collection of id/access_hash pairs (experimental)
See
+ #pragma warning disable CS0618 // Type or member is obsolete
+ /// Enable the collection of id/access_hash pairs (deprecated)
+ [Obsolete("This system will be removed in a future version. You should use CollectUsersChats helper on API results or updates instead. See https://wiz0u.github.io/WTelegramClient/EXAMPLES#collect-users-chats")]
public bool CollectAccessHash { get; set; }
public IEnumerable> AllAccessHashesFor() where T : IObject => _accessHashes.GetValueOrDefault(typeof(T));
private readonly Dictionary> _accessHashes = new();
@@ -53,6 +55,7 @@ namespace WTelegram
lock (_accessHashes)
_accessHashes.GetOrCreate(type)[id] = accessHash;
}
+ #pragma warning restore CS0618 // Type or member is obsolete
#endregion
#region Client TL Helpers
diff --git a/src/TL.Extensions.cs b/src/TL.Extensions.cs
index 01fe82d..9139778 100644
--- a/src/TL.Extensions.cs
+++ b/src/TL.Extensions.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
+using WTelegram; // for GetValueOrDefault
namespace TL
{
@@ -48,8 +49,9 @@ namespace TL
/// Client, used for getting access_hash for tg://user?id= URLs
/// [in] The Markdown text
[out] The same (plain) text, stripped of all Markdown notation
/// Generate premium entities if any
+ /// Dictionary used for tg://user?id= notation
/// The array of formatting entities that you can pass (along with the plain text) to SendMessageAsync or SendMediaAsync
- public static MessageEntity[] MarkdownToEntities(this WTelegram.Client client, ref string text, bool premium = false)
+ public static MessageEntity[] MarkdownToEntities(this WTelegram.Client client, ref string text, bool premium = false, Dictionary users = null)
{
var entities = new List();
var sb = new StringBuilder(text);
@@ -119,7 +121,7 @@ namespace TL
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 id) && client.GetAccessHashFor(id) is long hash)
+ if (textUrl.url.StartsWith("tg://user?id=") && long.TryParse(textUrl.url[13..], out var id) && (users?.GetValueOrDefault(id)?.access_hash ?? client.GetAccessHashFor(id)) is long hash)
entities[lastIndex] = new InputMessageEntityMentionName { offset = textUrl.offset, length = textUrl.length, user_id = new InputUser(id, hash) };
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 };
@@ -247,8 +249,9 @@ namespace TL
/// Client, used for getting access_hash for tg://user?id= URLs
/// [in] The HTML-formatted text
[out] The same (plain) text, stripped of all HTML tags
/// Generate premium entities if any
+ /// Dictionary used for tg://user?id= notation
/// The array of formatting entities that you can pass (along with the plain text) to SendMessageAsync or SendMediaAsync
- public static MessageEntity[] HtmlToEntities(this WTelegram.Client client, ref string text, bool premium = false)
+ public static MessageEntity[] HtmlToEntities(this WTelegram.Client client, ref string text, bool premium = false, Dictionary users = null)
{
var entities = new List();
var sb = new StringBuilder(text);
@@ -303,7 +306,7 @@ namespace TL
else if (tag.StartsWith("a href=\"") && tag.EndsWith("\""))
{
tag = tag[8..^1];
- if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && client.GetAccessHashFor(user_id) is long hash)
+ if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && (users?.GetValueOrDefault(user_id)?.access_hash ?? client.GetAccessHashFor(user_id)) is long hash)
entities.Add(new InputMessageEntityMentionName { offset = offset, length = -1, user_id = new InputUser(user_id, hash) });
else
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag });
diff --git a/src/TL.cs b/src/TL.cs
index c7b1767..b5544b4 100644
--- a/src/TL.cs
+++ b/src/TL.cs
@@ -95,7 +95,9 @@ namespace TL
if (field.FieldType.IsEnum)
if (field.Name == "flags") flags = (uint)value;
else if (field.Name == "flags2") flags |= (ulong)(uint)value << 32;
+#pragma warning disable CS0618 // Type or member is obsolete
if (reader.Client?.CollectAccessHash == true) reader.Client.CollectField(field, obj, value);
+#pragma warning restore CS0618 // Type or member is obsolete
}
return (IObject)obj;
}