Renamed Update event to OnUpdate, returning Task

(to gracefully handle async exceptions)
This commit is contained in:
Wizou 2022-07-29 15:24:18 +02:00
parent 6977641b2d
commit 668b19e3e8
8 changed files with 32 additions and 29 deletions

View file

@ -312,14 +312,15 @@ finally
<a name="updates"></a> <a name="updates"></a>
### Monitor all Telegram events happening for the user ### Monitor all Telegram events happening for the user
This is done through the `client.Update` 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.
See [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs). See [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
<a name="monitor-msg"></a> <a name="monitor-msg"></a>
### Monitor new messages being posted in chats ### Monitor new messages being posted in chats
You have to handle `client.Update` events containing an `UpdateNewMessage`. You have to handle `client.OnUpdate` events containing an `UpdateNewMessage`.
See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs). See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
@ -336,7 +337,7 @@ See [Examples/Program_DownloadSavedMedia.cs](Examples/Program_DownloadSavedMedia
<a name="collect-access-hash"></a> <a name="collect-access-hash"></a>
### Collect Access Hash and save them for later use ### Collect Access Hash and save them for later use
You can automate the collection of `access_hash` for the various resources obtained in response to API calls or Update events, 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. 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.

View file

@ -15,10 +15,10 @@ namespace WTelegramClientTest
Console.WriteLine("The program will download photos/medias from messages you send/forward to yourself (Saved Messages)"); Console.WriteLine("The program will download photos/medias from messages you send/forward to yourself (Saved Messages)");
using var client = new WTelegram.Client(Environment.GetEnvironmentVariable); using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
var user = await client.LoginUserIfNeeded(); var user = await client.LoginUserIfNeeded();
client.Update += Client_Update; client.OnUpdate += Client_OnUpdate;
Console.ReadKey(); Console.ReadKey();
async void Client_Update(IObject arg) async Task Client_OnUpdate(IObject arg)
{ {
if (arg is not Updates { updates: var updates } upd) return; if (arg is not Updates { updates: var updates } upd) return;
foreach (var update in updates) foreach (var update in updates)

View file

@ -30,7 +30,7 @@ namespace WTelegramClientTest
Client = new WTelegram.Client(store.Length == 0 ? null : Environment.GetEnvironmentVariable, store); Client = new WTelegram.Client(store.Length == 0 ? null : Environment.GetEnvironmentVariable, store);
using (Client) using (Client)
{ {
Client.Update += Client_Update; Client.OnUpdate += Client_OnUpdate;
My = await Client.LoginUserIfNeeded(); My = await Client.LoginUserIfNeeded();
Console.WriteLine($"We are logged-in as {My.username ?? My.first_name + " " + My.last_name} (id {My.id})"); Console.WriteLine($"We are logged-in as {My.username ?? My.first_name + " " + My.last_name} (id {My.id})");
var dialogs = await Client.Messages_GetAllDialogs(); var dialogs = await Client.Messages_GetAllDialogs();
@ -39,7 +39,7 @@ namespace WTelegramClientTest
} }
} }
private static async void Client_Update(IObject arg) private static async Task Client_OnUpdate(IObject arg)
{ {
if (arg is not UpdatesBase updates) return; if (arg is not UpdatesBase updates) return;
updates.CollectUsersChats(Users, Chats); updates.CollectUsersChats(Users, Chats);

View file

@ -20,7 +20,7 @@ namespace WTelegramClientTest
Client = new WTelegram.Client(Environment.GetEnvironmentVariable); Client = new WTelegram.Client(Environment.GetEnvironmentVariable);
using (Client) using (Client)
{ {
Client.Update += Client_Update; Client.OnUpdate += Client_OnUpdate;
My = await Client.LoginUserIfNeeded(); My = await Client.LoginUserIfNeeded();
Users[My.id] = My; Users[My.id] = My;
// Note: on login, Telegram may sends a bunch of updates/messages that happened in the past and were not acknowledged // Note: on login, Telegram may sends a bunch of updates/messages that happened in the past and were not acknowledged
@ -32,9 +32,10 @@ namespace WTelegramClientTest
} }
} }
private static void Client_Update(IObject arg) // in this example, we're not using async/await, so we just return Task.CompletedTask
private static Task Client_OnUpdate(IObject arg)
{ {
if (arg is not UpdatesBase updates) return; if (arg is not UpdatesBase updates) return Task.CompletedTask;
updates.CollectUsersChats(Users, Chats); updates.CollectUsersChats(Users, Chats);
foreach (var update in updates.UpdateList) foreach (var update in updates.UpdateList)
switch (update) switch (update)
@ -52,6 +53,7 @@ namespace WTelegramClientTest
case UpdateUserPhoto uup: Console.WriteLine($"{User(uup.user_id)} has changed profile photo"); break; case UpdateUserPhoto uup: Console.WriteLine($"{User(uup.user_id)} has changed profile photo"); break;
default: Console.WriteLine(update.GetType().Name); break; // there are much more update types than the above cases default: Console.WriteLine(update.GetType().Name); break; // there are much more update types than the above cases
} }
return Task.CompletedTask;
} }
private static void DisplayMessage(MessageBase messageBase, bool edit = false) private static void DisplayMessage(MessageBase messageBase, bool edit = false)

4
FAQ.md
View file

@ -134,7 +134,7 @@ Here are some advices from [another similar library](https://github.com/gotd/td/
Some additional advices from me: Some additional advices from me:
5. Avoid repetitive polling or repetitive sequence of actions/requests: Save the initial results of your queries, and update those results when you're informed of a change through `Update` events. 5. Avoid repetitive polling or repetitive sequence of actions/requests: Save the initial results of your queries, and update those results when you're informed of a change through `OnUpdate` events.
6. If a phone number is brand new, it will be closely monitored by Telegram for abuse, and it can even already be considered a bad user due to bad behavior from the previous owner of that phone number (which may happens often with VoIP or other easy-to-buy-online numbers, so expect fast ban) 6. If a phone number is brand new, it will be closely monitored by Telegram for abuse, and it can even already be considered a bad user due to bad behavior from the previous owner of that phone number (which may happens often with VoIP or other easy-to-buy-online numbers, so expect fast ban)
7. You may want to use your new phone number account with an official Telegram client and act like a normal user for some time (some weeks/months), before using it for automation with WTelegramClient. 7. You may want to use your new phone number account with an official Telegram client and act like a normal user for some time (some weeks/months), before using it for automation with WTelegramClient.
8. When creating a new API ID/Hash, I recommend you use your own phone number with long history of normal Telegram usage, rather than a brand new phone number with short history. 8. When creating a new API ID/Hash, I recommend you use your own phone number with long history of normal Telegram usage, rather than a brand new phone number with short history.
@ -191,7 +191,7 @@ If Telegram servers decide to shutdown this secondary connection, it's not an is
This should be transparent and pending API calls should automatically be resent upon reconnection. This should be transparent and pending API calls should automatically be resent upon reconnection.
You can choose to increase `MaxAutoReconnects` if it happens too often because your Internet connection is unstable. You can choose to increase `MaxAutoReconnects` if it happens too often because your Internet connection is unstable.
3) If you reach `MaxAutoReconnects` disconnections, then the **Update** event handler will receive a `ReactorError` object to notify you of the problem, 3) If you reach `MaxAutoReconnects` disconnections, then the **OnUpdate** event handler will receive a `ReactorError` object to notify you of the problem.
and pending API calls throw the network IOException. and pending API calls throw the network IOException.
In this case, the recommended action would be to dispose the client and recreate one In this case, the recommended action would be to dispose the client and recreate one

View file

@ -130,7 +130,7 @@ In the API, Telegram uses some terms/classnames that can be confusing as they di
# Other things to know # Other things to know
The Client class also offers an `Update` 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://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs) 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://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs)
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

@ -189,7 +189,7 @@ namespace WTelegram
else else
updates = await this.Messages_SendMedia(peer, media, text, random_id, entities: entities, updates = await this.Messages_SendMedia(peer, media, text, random_id, entities: entities,
reply_to_msg_id: reply_to_msg_id == 0 ? null : reply_to_msg_id, schedule_date: schedule_date == default ? null : schedule_date); reply_to_msg_id: reply_to_msg_id == 0 ? null : reply_to_msg_id, schedule_date: schedule_date == default ? null : schedule_date);
OnUpdate(updates); RaiseUpdate(updates);
int msgId = -1; int msgId = -1;
foreach (var update in updates.UpdateList) foreach (var update in updates.UpdateList)
{ {
@ -282,7 +282,7 @@ namespace WTelegram
if (entities != null) lastMedia.flags = InputSingleMedia.Flags.has_entities; if (entities != null) lastMedia.flags = InputSingleMedia.Flags.has_entities;
var updates = await this.Messages_SendMultiMedia(peer, multiMedia, reply_to_msg_id: reply_to_msg_id, schedule_date: schedule_date); var updates = await this.Messages_SendMultiMedia(peer, multiMedia, reply_to_msg_id: reply_to_msg_id, schedule_date: schedule_date);
OnUpdate(updates); RaiseUpdate(updates);
var msgIds = new int[medias.Length]; var msgIds = new int[medias.Length];
var result = new Message[medias.Length]; var result = new Message[medias.Length];
foreach (var update in updates.UpdateList) foreach (var update in updates.UpdateList)

View file

@ -25,8 +25,8 @@ 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>See <see href="https://github.com/wiz0u/WTelegramClient/tree/master/Examples/Program_ListenUpdate.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"></see><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>
public event Action<IObject> Update; 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;
public delegate Task<TcpClient> TcpFactory(string host, int port); public delegate Task<TcpClient> TcpFactory(string host, int port);
@ -150,7 +150,7 @@ namespace WTelegram
public static void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem); public static void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem);
/// <summary>Builds a structure that is used to validate a 2FA password</summary> /// <summary>Builds a structure that is used to validate a 2FA password</summary>
/// <param name="accountPassword">Password validation configuration. You can obtain this though an Update event as part of the login process</param> /// <param name="accountPassword">Password validation configuration. You can obtain this via <c>Account_GetPassword</c> or through OnUpdate as part of the login process</param>
/// <param name="password">The password to validate</param> /// <param name="password">The password to validate</param>
public static Task<InputCheckPasswordSRP> InputCheckPassword(Account_Password accountPassword, string password) public static Task<InputCheckPasswordSRP> InputCheckPassword(Account_Password accountPassword, string password)
=> Check2FA(accountPassword, () => Task.FromResult(password)); => Check2FA(accountPassword, () => Task.FromResult(password));
@ -321,7 +321,7 @@ namespace WTelegram
if (IsMainDC) if (IsMainDC)
{ {
var updatesState = await this.Updates_GetState(); // this call reenables incoming Updates var updatesState = await this.Updates_GetState(); // this call reenables incoming Updates
OnUpdate(updatesState); RaiseUpdate(updatesState);
} }
} }
else else
@ -329,7 +329,7 @@ namespace WTelegram
} }
catch catch
{ {
OnUpdate(reactorError); RaiseUpdate(reactorError);
lock (_pendingRpcs) // abort all pending requests lock (_pendingRpcs) // abort all pending requests
{ {
foreach (var rpc in _pendingRpcs.Values) foreach (var rpc in _pendingRpcs.Values)
@ -633,7 +633,7 @@ namespace WTelegram
rpc.tcs.SetException(new ApplicationException($"BadMsgNotification {badMsgNotification.error_code}")); rpc.tcs.SetException(new ApplicationException($"BadMsgNotification {badMsgNotification.error_code}"));
} }
else else
OnUpdate(obj); RaiseUpdate(obj);
break; break;
default: default:
if (_bareRpc != null) if (_bareRpc != null)
@ -648,7 +648,7 @@ namespace WTelegram
else else
Helpers.Log(4, $"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); Helpers.Log(4, $"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}");
} }
OnUpdate(obj); RaiseUpdate(obj);
break; break;
} }
@ -658,19 +658,19 @@ namespace WTelegram
if (rpc != null) if (rpc != null)
rpc.tcs.SetResult(result); rpc.tcs.SetResult(result);
else else
OnUpdate(obj); RaiseUpdate(obj);
} }
} }
private void OnUpdate(IObject obj) private async void RaiseUpdate(IObject obj)
{ {
try try
{ {
Update?.Invoke(obj); await OnUpdate?.Invoke(obj);
} }
catch (Exception ex) catch (Exception ex)
{ {
Helpers.Log(4, $"{nameof(Update)} callback on {obj.GetType().Name} raised {ex}"); Helpers.Log(4, $"{nameof(OnUpdate)}({obj?.GetType().Name}) raised {ex}");
} }
} }
@ -943,7 +943,7 @@ namespace WTelegram
{ {
resent: resent:
var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(sentCode.timeout); var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(sentCode.timeout);
OnUpdate(sentCode); RaiseUpdate(sentCode);
Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}"); Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}");
for (int retry = 1; authorization == null; retry++) for (int retry = 1; authorization == null; retry++)
try try
@ -973,7 +973,7 @@ namespace WTelegram
try try
{ {
var accountPassword = await this.Account_GetPassword(); var accountPassword = await this.Account_GetPassword();
OnUpdate(accountPassword); RaiseUpdate(accountPassword);
var checkPasswordSRP = await Check2FA(accountPassword, () => ConfigAsync("password")); var checkPasswordSRP = await Check2FA(accountPassword, () => ConfigAsync("password"));
authorization = await this.Auth_CheckPassword(checkPasswordSRP); authorization = await this.Auth_CheckPassword(checkPasswordSRP);
} }
@ -992,7 +992,7 @@ namespace WTelegram
if (authorization is Auth_AuthorizationSignUpRequired signUpRequired) if (authorization is Auth_AuthorizationSignUpRequired signUpRequired)
{ {
var waitUntil = DateTime.UtcNow.AddSeconds(3); var waitUntil = DateTime.UtcNow.AddSeconds(3);
OnUpdate(signUpRequired); // give caller the possibility to read and accept TOS RaiseUpdate(signUpRequired); // give caller the possibility to read and accept TOS
var first_name = Config("first_name"); var first_name = Config("first_name");
var last_name = Config("last_name"); var last_name = Config("last_name");
var wait = waitUntil - DateTime.UtcNow; var wait = waitUntil - DateTime.UtcNow;