deprecate the experimental CollectAccessHash system

This commit is contained in:
Wizou 2023-01-12 01:37:12 +01:00
parent 8f10df8849
commit 66d8b75463
5 changed files with 45 additions and 93 deletions

View file

@ -76,7 +76,7 @@ var sent2 = await client.SendMessageAsync(InputPeer.Self, text2, entities: entit
text2 = client.EntitiesToMarkdown(sent2.message, sent2.entities); 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. 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))*
<a name="list-dialogs"></a> <a name="list-dialogs"></a>
## List all dialogs (chats/groups/channels/user chat) we are currently in ## List all dialogs (chats/groups/channels/user chat) we are currently in
@ -442,13 +442,39 @@ finally
``` ```
<a name="collect-access-hash"></a> <a name="collect-access-hash"></a>
## Collect Access Hash and save them for later use <a name="collect-users-chats"></a>
<a name="user-or-chat"></a>
## 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, Many API calls return a structure with a `users` and a `chats` field at the root of the structure.
so that you don't have to remember them by yourself or ask the API about them each time. This is also the case for updates passed to `client.OnUpdate`.
This is done by activating the experimental `client.CollectAccessHash` system. These two dictionaries give details about the various users/chats that will be typically referenced in subobjects deeper in the structure,
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. 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<long, User> _users = new();
static Dictionary<long, ChatBase> _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*
<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

View file

@ -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<SavedState>(stateStream);
foreach (var id_hash in savedState.Channels) client.SetAccessHashFor<Channel>(id_hash.Key, id_hash.Value);
foreach (var id_hash in savedState.Users) client.SetAccessHashFor<User>(id_hash.Key, id_hash.Value);
}
Console.WriteLine("Connecting to Telegram...");
await client.LoginUserIfNeeded();
var durovAccessHash = client.GetAccessHashFor<Channel>(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<Channel>(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<Channel>().ToList();
savedState.Users = client.AllAccessHashesFor<User>().ToList();
using (var stateStream = File.Create(StateFilename))
await JsonSerializer.SerializeAsync(stateStream, savedState);
}
class SavedState
{
public List<KeyValuePair<long, long>> Channels { get; set; } = new();
public List<KeyValuePair<long, long>> Users { get; set; } = new();
}
}
}

View file

@ -16,7 +16,9 @@ namespace WTelegram
partial class Client partial class Client
{ {
#region Collect Access Hash system #region Collect Access Hash system
/// <summary>Enable the collection of id/access_hash pairs (experimental)<br/>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/FAQ.md#access-hash"/></summary> #pragma warning disable CS0618 // Type or member is obsolete
/// <summary>Enable the collection of id/access_hash pairs (deprecated)</summary>
[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 bool CollectAccessHash { get; set; }
public IEnumerable<KeyValuePair<long, long>> AllAccessHashesFor<T>() where T : IObject => _accessHashes.GetValueOrDefault(typeof(T)); public IEnumerable<KeyValuePair<long, long>> AllAccessHashesFor<T>() where T : IObject => _accessHashes.GetValueOrDefault(typeof(T));
private readonly Dictionary<Type, Dictionary<long, long>> _accessHashes = new(); private readonly Dictionary<Type, Dictionary<long, long>> _accessHashes = new();
@ -53,6 +55,7 @@ namespace WTelegram
lock (_accessHashes) lock (_accessHashes)
_accessHashes.GetOrCreate(type)[id] = accessHash; _accessHashes.GetOrCreate(type)[id] = accessHash;
} }
#pragma warning restore CS0618 // Type or member is obsolete
#endregion #endregion
#region Client TL Helpers #region Client TL Helpers

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using WTelegram; // for GetValueOrDefault
namespace TL namespace TL
{ {
@ -48,8 +49,9 @@ namespace TL
/// <param name="client">Client, used for getting access_hash for <c>tg://user?id=</c> URLs</param> /// <param name="client">Client, used for getting access_hash for <c>tg://user?id=</c> URLs</param>
/// <param name="text">[in] The Markdown text<br/>[out] The same (plain) text, stripped of all Markdown notation</param> /// <param name="text">[in] The Markdown text<br/>[out] The same (plain) text, stripped of all Markdown notation</param>
/// <param name="premium">Generate premium entities if any</param> /// <param name="premium">Generate premium entities if any</param>
/// <param name="users">Dictionary used for <c>tg://user?id=</c> notation</param>
/// <returns>The array of formatting entities that you can pass (along with the plain text) to <see cref="WTelegram.Client.SendMessageAsync">SendMessageAsync</see> or <see cref="WTelegram.Client.SendMediaAsync">SendMediaAsync</see></returns> /// <returns>The array of formatting entities that you can pass (along with the plain text) to <see cref="WTelegram.Client.SendMessageAsync">SendMessageAsync</see> or <see cref="WTelegram.Client.SendMediaAsync">SendMediaAsync</see></returns>
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<long, User> users = null)
{ {
var entities = new List<MessageEntity>(); var entities = new List<MessageEntity>();
var sb = new StringBuilder(text); var sb = new StringBuilder(text);
@ -119,7 +121,7 @@ namespace TL
else if (c == ')') break; else if (c == ')') break;
} }
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) && (users?.GetValueOrDefault(id)?.access_hash ?? 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("tg://emoji?id=") || textUrl.url.StartsWith("emoji?id=")) && long.TryParse(textUrl.url[(textUrl.url.IndexOf('=') + 1)..], 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 };
@ -247,8 +249,9 @@ namespace TL
/// <param name="client">Client, used for getting access_hash for <c>tg://user?id=</c> URLs</param> /// <param name="client">Client, used for getting access_hash for <c>tg://user?id=</c> URLs</param>
/// <param name="text">[in] The HTML-formatted text<br/>[out] The same (plain) text, stripped of all HTML tags</param> /// <param name="text">[in] The HTML-formatted text<br/>[out] The same (plain) text, stripped of all HTML tags</param>
/// <param name="premium">Generate premium entities if any</param> /// <param name="premium">Generate premium entities if any</param>
/// <param name="users">Dictionary used for <c>tg://user?id=</c> notation</param>
/// <returns>The array of formatting entities that you can pass (along with the plain text) to <see cref="WTelegram.Client.SendMessageAsync">SendMessageAsync</see> or <see cref="WTelegram.Client.SendMediaAsync">SendMediaAsync</see></returns> /// <returns>The array of formatting entities that you can pass (along with the plain text) to <see cref="WTelegram.Client.SendMessageAsync">SendMessageAsync</see> or <see cref="WTelegram.Client.SendMediaAsync">SendMediaAsync</see></returns>
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<long, User> users = null)
{ {
var entities = new List<MessageEntity>(); var entities = new List<MessageEntity>();
var sb = new StringBuilder(text); var sb = new StringBuilder(text);
@ -303,7 +306,7 @@ namespace TL
else if (tag.StartsWith("a href=\"") && tag.EndsWith("\"")) else if (tag.StartsWith("a href=\"") && tag.EndsWith("\""))
{ {
tag = tag[8..^1]; tag = tag[8..^1];
if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && client.GetAccessHashFor<User>(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>(user_id)) is long hash)
entities.Add(new InputMessageEntityMentionName { offset = offset, length = -1, user_id = new InputUser(user_id, hash) }); entities.Add(new InputMessageEntityMentionName { offset = offset, length = -1, user_id = new InputUser(user_id, hash) });
else else
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag }); entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag });

View file

@ -95,7 +95,9 @@ namespace TL
if (field.FieldType.IsEnum) if (field.FieldType.IsEnum)
if (field.Name == "flags") flags = (uint)value; if (field.Name == "flags") flags = (uint)value;
else if (field.Name == "flags2") flags |= (ulong)(uint)value << 32; 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); if (reader.Client?.CollectAccessHash == true) reader.Client.CollectField(field, obj, value);
#pragma warning restore CS0618 // Type or member is obsolete
} }
return (IObject)obj; return (IObject)obj;
} }