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; }