several enhancements:

- fixed UpdateAffectedMessages on wrong mbox
- MarkdownToEntities allow reserved characters in code block
- collector system more flexible & open
- improved UpdateManager resilience
- some MTPG improvements
This commit is contained in:
Wizou 2024-04-03 21:05:07 +02:00
parent 210a3365e5
commit abeed476e7
11 changed files with 290 additions and 244 deletions

2
FAQ.md
View file

@ -349,8 +349,6 @@ var manager = client.WithUpdateManager(OnUpdate);
var manager = client.WithUpdateManager(OnUpdate, "Updates.state"); var manager = client.WithUpdateManager(OnUpdate, "Updates.state");
// to save the state later, preferably after disposing the client: // to save the state later, preferably after disposing the client:
manager.SaveState("Updates.state") manager.SaveState("Updates.state")
// (WithUpdateManager has other parameters for advanced use)
``` ```
Your `OnUpdate` method will directly take a single `Update` as parameter, instead of a container of updates. Your `OnUpdate` method will directly take a single `Update` as parameter, instead of a container of updates.

View file

@ -171,7 +171,7 @@ See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient
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.
To [prevent getting banned](https://wiz0u.github.io/WTelegramClient/FAQ#prevent-ban) during dev, you can connect to [test servers](https://docs.pyrogram.org/topics/test-servers), by adding this line in your Config callback: To [prevent getting banned](https://wiz0u.github.io/WTelegramClient/FAQ#prevent-ban) during dev, you can connect to [test servers](https://docs.pyrogram.org/topics/test-servers), by adding this line in your Config callback:
`case "server_address": return "149.154.167.40:443"; // test DC` `case "server_address": return "2>149.154.167.40:443"; // test DC`
The other configuration items that you can provide include: **session_pathname, email, email_verification_code, session_key, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, firebase, user_id, bot_token** The other configuration items that you can provide include: **session_pathname, email, email_verification_code, session_key, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, firebase, user_id, bot_token**

View file

@ -31,8 +31,18 @@ public class MTProtoGenerator : IIncrementalGenerator
if (unit.compilation.GetTypeByMetadataName("TL.IObject") is not { } iobject) return; if (unit.compilation.GetTypeByMetadataName("TL.IObject") is not { } iobject) return;
var nullables = LoadNullables(layer); var nullables = LoadNullables(layer);
var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods
var readTL = new StringBuilder(); var makeTL = new StringBuilder();
readTL var source = new StringBuilder();
source
.AppendLine("using System;")
.AppendLine("using System.ComponentModel;")
.AppendLine("using System.IO;")
.AppendLine("using System.Linq;")
.AppendLine("using TL;")
.AppendLine()
.AppendLine("#pragma warning disable CS0109")
.AppendLine();
makeTL
.AppendLine("\t\tpublic static IObject ReadTL(this BinaryReader reader, uint ctorId = 0) => (ctorId != 0 ? ctorId : reader.ReadUInt32()) switch") .AppendLine("\t\tpublic static IObject ReadTL(this BinaryReader reader, uint ctorId = 0) => (ctorId != 0 ? ctorId : reader.ReadUInt32()) switch")
.AppendLine("\t\t{"); .AppendLine("\t\t{");
@ -55,6 +65,9 @@ public class MTProtoGenerator : IIncrementalGenerator
{ {
if (parentMethods == null) if (parentMethods == null)
{ {
if (name is "Peer")
writeTl.AppendLine("\t\tpublic virtual void WriteTL(BinaryWriter writer) => throw new NotSupportedException();");
else
writeTl.AppendLine("\t\tpublic abstract void WriteTL(BinaryWriter writer);"); writeTl.AppendLine("\t\tpublic abstract void WriteTL(BinaryWriter writer);");
parentClasses[name] = writeTl.ToString(); parentClasses[name] = writeTl.ToString();
writeTl.Clear(); writeTl.Clear();
@ -72,17 +85,20 @@ public class MTProtoGenerator : IIncrementalGenerator
continue; continue;
} }
if (id == 0x3072CFA1) // GzipPacked if (id == 0x3072CFA1) // GzipPacked
readTL.AppendLine($"\t\t\t0x{id:X8} => reader.ReadTLGzipped(),"); makeTL.AppendLine($"\t\t\t0x{id:X8} => reader.ReadTLGzipped(),");
else if (name != "Null" && (ns != "TL.Methods" || name == "Ping")) else if (name != "Null" && (ns != "TL.Methods" || name == "Ping"))
readTL.AppendLine($"\t\t\t0x{id:X8} => new {(ns == "TL" ? "" : ns + '.')}{name}(reader),"); makeTL.AppendLine($"\t\t\t0x{id:X8} => new {(ns == "TL" ? "" : ns + '.')}{name}().ReadTL(reader),");
var override_ = symbol.BaseType == object_ ? "" : "override "; var override_ = symbol.BaseType == object_ ? "" : "override ";
if (name == "Messages_AffectedMessages") override_ = "virtual "; if (name == "Messages_AffectedMessages") override_ = "virtual ";
if (symbol.Constructors[0].IsImplicitlyDeclared) //if (symbol.Constructors[0].IsImplicitlyDeclared)
ctorTL.AppendLine($"\t\tpublic {name}() {{ }}"); // ctorTL.AppendLine($"\t\tpublic {name}() {{ }}");
if (symbol.IsGenericType) name += "<X>";
ctorTL ctorTL
.AppendLine($"\t\tpublic {name}(BinaryReader reader)") .AppendLine("\t\t[EditorBrowsable(EditorBrowsableState.Never)]")
.AppendLine($"\t\tpublic new {name} ReadTL(BinaryReader reader)")
.AppendLine("\t\t{"); .AppendLine("\t\t{");
writeTl writeTl
.AppendLine("\t\t[EditorBrowsable(EditorBrowsableState.Never)]")
.AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)") .AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)")
.AppendLine("\t\t{") .AppendLine("\t\t{")
.AppendLine($"\t\t\twriter.Write(0x{id:X8});"); .AppendLine($"\t\t\twriter.Write(0x{id:X8});");
@ -143,7 +159,7 @@ public class MTProtoGenerator : IIncrementalGenerator
writeTl.AppendLine($"writer.Write({member.Name});"); writeTl.AppendLine($"writer.Write({member.Name});");
break; break;
case "TL._Message[]": case "TL._Message[]":
ctorTL.AppendLine($"throw new NotSupportedException();"); ctorTL.AppendLine($"{member.Name} = reader.ReadTLRawVector<_Message>(0x5BB8E511);");
writeTl.AppendLine($"writer.WriteTLMessages({member.Name});"); writeTl.AppendLine($"writer.WriteTLMessages({member.Name});");
break; break;
case "TL.IObject": case "TL.IMethod<X>": case "TL.IObject": case "TL.IMethod<X>":
@ -183,25 +199,18 @@ public class MTProtoGenerator : IIncrementalGenerator
break; break;
} }
} }
ctorTL.AppendLine("\t\t\treturn this;");
ctorTL.AppendLine("\t\t}"); ctorTL.AppendLine("\t\t}");
writeTl.AppendLine("\t\t}"); writeTl.AppendLine("\t\t}");
ctorTL.Append(writeTl.ToString()); ctorTL.Append(writeTl.ToString());
if (symbol.IsGenericType) name += "<X>";
classes[name] = ctorTL.ToString(); classes[name] = ctorTL.ToString();
} }
var source = new StringBuilder();
source
.AppendLine("using System;")
.AppendLine("using System.IO;")
.AppendLine("using System.Linq;")
.AppendLine("using TL;")
.AppendLine();
foreach (var nullable in nullables) foreach (var nullable in nullables)
readTL.AppendLine($"\t\t\t0x{nullable.Value:X8} => null,"); makeTL.AppendLine($"\t\t\t0x{nullable.Value:X8} => null,");
readTL.AppendLine("\t\t\tvar ctorNb => throw new Exception($\"Cannot find type for ctor #{ctorNb:x}\")"); makeTL.AppendLine("\t\t\tvar ctorNb => throw new Exception($\"Cannot find type for ctor #{ctorNb:x}\")");
readTL.AppendLine("\t\t};"); makeTL.AppendLine("\t\t};");
namespaces["TL"]["Layer"] = readTL.ToString(); namespaces["TL"]["Layer"] = makeTL.ToString();
foreach (var namesp in namespaces) foreach (var namesp in namespaces)
{ {
source.Append("namespace ").AppendLine(namesp.Key).Append('{'); source.Append("namespace ").AppendLine(namesp.Key).Append('{');

View file

@ -586,8 +586,6 @@ namespace WTelegram
if (OnOwnUpdates != null) if (OnOwnUpdates != null)
if (result is UpdatesBase updates) if (result is UpdatesBase updates)
RaiseOwnUpdates(updates); RaiseOwnUpdates(updates);
else if (result is Messages_AffectedMessages affected)
RaiseOwnUpdates(new UpdateShort { update = new UpdateAffectedMessages { affected = affected }, date = MsgIdToStamp(_lastRecvMsgId) });
} }
rpc.tcs.SetResult(result); rpc.tcs.SetResult(result);
@ -1454,5 +1452,16 @@ namespace WTelegram
throw new WTException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}"); throw new WTException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}");
} }
} }
public async Task<T> InvokeAffected<T>(IMethod<T> query, long peerId) where T : Messages_AffectedMessages
{
var result = await Invoke(query);
RaiseOwnUpdates(new UpdateShort
{
update = new UpdateAffectedMessages { mbox_id = peerId, pts = result.pts, pts_count = result.pts_count},
date = MsgIdToStamp(_lastRecvMsgId)
});
return result;
}
} }
} }

View file

@ -267,7 +267,9 @@ namespace WTelegram
public class WTException : ApplicationException public class WTException : ApplicationException
{ {
public readonly int ErrorCode;
public WTException(string message) : base(message) { } public WTException(string message) : base(message) { }
public WTException(string message, int code) : base(message) => ErrorCode = code;
public WTException(string message, Exception innerException) : base(message, innerException) { } public WTException(string message, Exception innerException) : base(message, innerException) { }
} }
} }

View file

@ -5,22 +5,26 @@ 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 using WTelegram;
namespace TL namespace TL
{ {
public static class Extensions public static class Extensions
{ {
internal sealed partial class CollectorPeer : Peer public sealed partial class CollectorPeer(IDictionary<long, User> _users, IDictionary<long, ChatBase> _chats) : Peer, IPeerCollector
{ {
public override long ID => 0; public override long ID => 0;
internal IDictionary<long, User> _users;
internal IDictionary<long, ChatBase> _chats;
protected internal override IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats) protected internal override IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats)
{ {
if (_users != null) if (users != null) Collect(users.Values);
if (chats != null) Collect(chats.Values);
return null;
}
public void Collect(IEnumerable<TL.User> users)
{
lock (_users) lock (_users)
foreach (var user in users.Values) foreach (var user in users)
if (user != null) if (user != null)
if (!user.flags.HasFlag(User.Flags.min) || !_users.TryGetValue(user.id, out var prevUser) || prevUser.flags.HasFlag(User.Flags.min)) if (!user.flags.HasFlag(User.Flags.min) || !_users.TryGetValue(user.id, out var prevUser) || prevUser.flags.HasFlag(User.Flags.min))
_users[user.id] = user; _users[user.id] = user;
@ -56,13 +60,16 @@ namespace TL
prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet
_users[user.id] = prevUser; _users[user.id] = prevUser;
} }
if (_chats != null) }
public void Collect(IEnumerable<ChatBase> chats)
{
lock (_chats) lock (_chats)
foreach (var kvp in chats) foreach (var chat in chats)
if (kvp.Value is not Channel channel) if (chat is not Channel channel)
_chats[kvp.Key] = kvp.Value; _chats[chat.ID] = chat;
else if (!channel.flags.HasFlag(Channel.Flags.min) || !_chats.TryGetValue(kvp.Key, out var prevChat) || prevChat is not Channel prevChannel || prevChannel.flags.HasFlag(Channel.Flags.min)) else if (!channel.flags.HasFlag(Channel.Flags.min) || !_chats.TryGetValue(channel.id, out var prevChat) || prevChat is not Channel prevChannel || prevChannel.flags.HasFlag(Channel.Flags.min))
_chats[kvp.Key] = channel; _chats[channel.id] = channel;
else else
{ // update previously full channel from min channel: { // update previously full channel from min channel:
const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0; const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0;
@ -84,19 +91,18 @@ namespace TL
prevChannel.profile_color = channel.profile_color; // tdlib/tdesktop: ignored prevChannel.profile_color = channel.profile_color; // tdlib/tdesktop: ignored
prevChannel.emoji_status = channel.emoji_status; // tdlib: not updated ; tdesktop: updated prevChannel.emoji_status = channel.emoji_status; // tdlib: not updated ; tdesktop: updated
prevChannel.level = channel.level; // tdlib: not updated ; tdesktop: updated prevChannel.level = channel.level; // tdlib: not updated ; tdesktop: updated
_chats[kvp.Key] = prevChannel; _chats[channel.id] = prevChannel;
} }
return null;
} }
#if MTPG
public override void WriteTL(System.IO.BinaryWriter writer) => throw new NotImplementedException(); public bool HasUser(long id) { lock (_users) return _users.ContainsKey(id); }
#endif public bool HasChat(long id) { lock (_chats) return _chats.ContainsKey(id); }
} }
/// <summary>Accumulate users/chats found in this structure in your dictionaries, ignoring <see href="https://core.telegram.org/api/min">Min constructors</see> when the full object is already stored</summary> /// <summary>Accumulate users/chats found in this structure in your dictionaries, ignoring <see href="https://core.telegram.org/api/min">Min constructors</see> when the full object is already stored</summary>
/// <param name="structure">The structure having a <c>users</c></param> /// <param name="structure">The structure having a <c>users</c></param>
public static void CollectUsersChats(this IPeerResolver structure, IDictionary<long, User> users, IDictionary<long, ChatBase> chats) public static void CollectUsersChats(this IPeerResolver structure, IDictionary<long, User> users, IDictionary<long, ChatBase> chats)
=> structure.UserOrChat(new CollectorPeer { _users = users, _chats = chats }); => structure.UserOrChat(new CollectorPeer(users, chats));
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public static Task<Messages_Chats> Messages_GetChats(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats"); public static Task<Messages_Chats> Messages_GetChats(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats");
@ -120,6 +126,7 @@ namespace TL
{ {
var entities = new List<MessageEntity>(); var entities = new List<MessageEntity>();
MessageEntityBlockquote lastBlockQuote = null; MessageEntityBlockquote lastBlockQuote = null;
int inCode = 0;
var sb = new StringBuilder(text); var sb = new StringBuilder(text);
for (int offset = 0; offset < sb.Length;) for (int offset = 0; offset < sb.Length;)
{ {
@ -127,9 +134,9 @@ namespace TL
{ {
case '\r': sb.Remove(offset, 1); break; case '\r': sb.Remove(offset, 1); break;
case '\\': sb.Remove(offset++, 1); break; case '\\': sb.Remove(offset++, 1); break;
case '*': ProcessEntity<MessageEntityBold>(); break; case '*' when inCode == 0: ProcessEntity<MessageEntityBold>(); break;
case '~': ProcessEntity<MessageEntityStrike>(); break; case '~' when inCode == 0: ProcessEntity<MessageEntityStrike>(); break;
case '_': case '_' when inCode == 0:
if (offset + 1 < sb.Length && sb[offset + 1] == '_') if (offset + 1 < sb.Length && sb[offset + 1] == '_')
{ {
sb.Remove(offset, 1); sb.Remove(offset, 1);
@ -139,7 +146,7 @@ namespace TL
ProcessEntity<MessageEntityItalic>(); ProcessEntity<MessageEntityItalic>();
break; break;
case '|': case '|':
if (offset + 1 < sb.Length && sb[offset + 1] == '|') if (inCode == 0 && offset + 1 < sb.Length && sb[offset + 1] == '|')
{ {
sb.Remove(offset, 1); sb.Remove(offset, 1);
ProcessEntity<MessageEntitySpoiler>(); ProcessEntity<MessageEntitySpoiler>();
@ -148,6 +155,7 @@ namespace TL
offset++; offset++;
break; break;
case '`': case '`':
int count = entities.Count;
if (offset + 2 < sb.Length && sb[offset + 1] == '`' && sb[offset + 2] == '`') if (offset + 2 < sb.Length && sb[offset + 1] == '`' && sb[offset + 2] == '`')
{ {
int len = 3; int len = 3;
@ -164,8 +172,9 @@ namespace TL
} }
else else
ProcessEntity<MessageEntityCode>(); ProcessEntity<MessageEntityCode>();
if (entities.Count > count) inCode++; else inCode--;
break; break;
case '>' when offset == 0 || sb[offset - 1] == '\n': case '>' when inCode == 0 && offset == 0 || sb[offset - 1] == '\n':
sb.Remove(offset, 1); sb.Remove(offset, 1);
if (lastBlockQuote is null || lastBlockQuote.length < offset - lastBlockQuote.offset) if (lastBlockQuote is null || lastBlockQuote.length < offset - lastBlockQuote.offset)
entities.Add(lastBlockQuote = new MessageEntityBlockquote { offset = offset, length = -1 }); entities.Add(lastBlockQuote = new MessageEntityBlockquote { offset = offset, length = -1 });
@ -175,15 +184,15 @@ namespace TL
case '\n' when lastBlockQuote is { length: -1 }: case '\n' when lastBlockQuote is { length: -1 }:
lastBlockQuote.length = ++offset - lastBlockQuote.offset; lastBlockQuote.length = ++offset - lastBlockQuote.offset;
break; break;
case '!' when offset + 1 < sb.Length && sb[offset + 1] == '[': case '!' when inCode == 0 && offset + 1 < sb.Length && sb[offset + 1] == '[':
sb.Remove(offset, 1); sb.Remove(offset, 1);
goto case '['; break;
case '[': case '[' when inCode == 0:
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 }); entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 });
sb.Remove(offset, 1); sb.Remove(offset, 1);
break; break;
case ']': case ']':
if (offset + 2 < sb.Length && sb[offset + 1] == '(') if (inCode == 0 && offset + 2 < sb.Length && sb[offset + 1] == '(')
{ {
var lastIndex = entities.FindLastIndex(e => e.length == -1); var lastIndex = entities.FindLastIndex(e => e.length == -1);
if (lastIndex >= 0 && entities[lastIndex] is MessageEntityTextUrl textUrl) if (lastIndex >= 0 && entities[lastIndex] is MessageEntityTextUrl textUrl)
@ -213,11 +222,14 @@ namespace TL
void ProcessEntity<T>() where T : MessageEntity, new() void ProcessEntity<T>() where T : MessageEntity, new()
{ {
sb.Remove(offset, 1);
if (entities.LastOrDefault(e => e.length == -1) is T prevEntity) if (entities.LastOrDefault(e => e.length == -1) is T prevEntity)
if (offset == prevEntity.offset)
entities.Remove(prevEntity);
else
prevEntity.length = offset - prevEntity.offset; prevEntity.length = offset - prevEntity.offset;
else else
entities.Add(new T { offset = offset, length = -1 }); entities.Add(new T { offset = offset, length = -1 });
sb.Remove(offset, 1);
} }
} }
if (lastBlockQuote is { length: -1 }) if (lastBlockQuote is { length: -1 })

View file

@ -105,7 +105,7 @@ namespace TL
{ {
file = inputFile; file = inputFile;
mime_type = mimeType; mime_type = mimeType;
if (inputFile.Name is string filename) attributes = new[] { new DocumentAttributeFilename { file_name = filename } }; if (inputFile.Name is string filename) attributes = [new DocumentAttributeFilename { file_name = filename }];
} }
public InputMediaUploadedDocument(InputFileBase inputFile, string mimeType, params DocumentAttribute[] attribs) public InputMediaUploadedDocument(InputFileBase inputFile, string mimeType, params DocumentAttribute[] attribs)
{ {

View file

@ -1604,11 +1604,11 @@ namespace TL
/// <param name="peer">Target user or group</param> /// <param name="peer">Target user or group</param>
/// <param name="max_id">If a positive value is passed, only messages with identifiers less or equal than the given one will be read</param> /// <param name="max_id">If a positive value is passed, only messages with identifiers less or equal than the given one will be read</param>
public static Task<Messages_AffectedMessages> Messages_ReadHistory(this Client client, InputPeer peer, int max_id = default) public static Task<Messages_AffectedMessages> Messages_ReadHistory(this Client client, InputPeer peer, int max_id = default)
=> client.Invoke(new Messages_ReadHistory => client.InvokeAffected(new Messages_ReadHistory
{ {
peer = peer, peer = peer,
max_id = max_id, max_id = max_id,
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary>Deletes communication history. <para>See <a href="https://corefork.telegram.org/method/messages.deleteHistory"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.deleteHistory#possible-errors">details</a>)</para></summary> /// <summary>Deletes communication history. <para>See <a href="https://corefork.telegram.org/method/messages.deleteHistory"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.deleteHistory#possible-errors">details</a>)</para></summary>
/// <param name="just_clear">Just clear history for the current user, without actually removing messages for every chat user</param> /// <param name="just_clear">Just clear history for the current user, without actually removing messages for every chat user</param>
@ -1618,24 +1618,24 @@ namespace TL
/// <param name="min_date">Delete all messages newer than this UNIX timestamp</param> /// <param name="min_date">Delete all messages newer than this UNIX timestamp</param>
/// <param name="max_date">Delete all messages older than this UNIX timestamp</param> /// <param name="max_date">Delete all messages older than this UNIX timestamp</param>
public static Task<Messages_AffectedHistory> Messages_DeleteHistory(this Client client, InputPeer peer, int max_id = default, DateTime? min_date = null, DateTime? max_date = null, bool just_clear = false, bool revoke = false) public static Task<Messages_AffectedHistory> Messages_DeleteHistory(this Client client, InputPeer peer, int max_id = default, DateTime? min_date = null, DateTime? max_date = null, bool just_clear = false, bool revoke = false)
=> client.Invoke(new Messages_DeleteHistory => client.InvokeAffected(new Messages_DeleteHistory
{ {
flags = (Messages_DeleteHistory.Flags)((min_date != null ? 0x4 : 0) | (max_date != null ? 0x8 : 0) | (just_clear ? 0x1 : 0) | (revoke ? 0x2 : 0)), flags = (Messages_DeleteHistory.Flags)((min_date != null ? 0x4 : 0) | (max_date != null ? 0x8 : 0) | (just_clear ? 0x1 : 0) | (revoke ? 0x2 : 0)),
peer = peer, peer = peer,
max_id = max_id, max_id = max_id,
min_date = min_date.GetValueOrDefault(), min_date = min_date.GetValueOrDefault(),
max_date = max_date.GetValueOrDefault(), max_date = max_date.GetValueOrDefault(),
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Deletes messages by their identifiers. <para>See <a href="https://corefork.telegram.org/method/messages.deleteMessages"/> [bots: ✓]</para> <para>Possible <see cref="RpcException"/> codes: 400,403 (<a href="https://corefork.telegram.org/method/messages.deleteMessages#possible-errors">details</a>)</para></summary> /// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Deletes messages by their identifiers. <para>See <a href="https://corefork.telegram.org/method/messages.deleteMessages"/> [bots: ✓]</para> <para>Possible <see cref="RpcException"/> codes: 400,403 (<a href="https://corefork.telegram.org/method/messages.deleteMessages#possible-errors">details</a>)</para></summary>
/// <param name="revoke">Whether to delete messages for all participants of the chat</param> /// <param name="revoke">Whether to delete messages for all participants of the chat</param>
/// <param name="id">Message ID list</param> /// <param name="id">Message ID list</param>
public static Task<Messages_AffectedMessages> Messages_DeleteMessages(this Client client, int[] id, bool revoke = false) public static Task<Messages_AffectedMessages> Messages_DeleteMessages(this Client client, int[] id, bool revoke = false)
=> client.Invoke(new Messages_DeleteMessages => client.InvokeAffected(new Messages_DeleteMessages
{ {
flags = (Messages_DeleteMessages.Flags)(revoke ? 0x1 : 0), flags = (Messages_DeleteMessages.Flags)(revoke ? 0x1 : 0),
id = id, id = id,
}); }, 0);
/// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Confirms receipt of messages by a client, cancels PUSH-notification sending. <para>See <a href="https://corefork.telegram.org/method/messages.receivedMessages"/></para></summary> /// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Confirms receipt of messages by a client, cancels PUSH-notification sending. <para>See <a href="https://corefork.telegram.org/method/messages.receivedMessages"/></para></summary>
/// <param name="max_id">Maximum message ID available in a client.</param> /// <param name="max_id">Maximum message ID available in a client.</param>
@ -1977,10 +1977,10 @@ namespace TL
/// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Notifies the sender about the recipient having listened a voice message or watched a video. <para>See <a href="https://corefork.telegram.org/method/messages.readMessageContents"/></para></summary> /// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Notifies the sender about the recipient having listened a voice message or watched a video. <para>See <a href="https://corefork.telegram.org/method/messages.readMessageContents"/></para></summary>
/// <param name="id">Message ID list</param> /// <param name="id">Message ID list</param>
public static Task<Messages_AffectedMessages> Messages_ReadMessageContents(this Client client, params int[] id) public static Task<Messages_AffectedMessages> Messages_ReadMessageContents(this Client client, params int[] id)
=> client.Invoke(new Messages_ReadMessageContents => client.InvokeAffected(new Messages_ReadMessageContents
{ {
id = id, id = id,
}); }, 0);
/// <summary>Get stickers by emoji <para>See <a href="https://corefork.telegram.org/method/messages.getStickers"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.getStickers#possible-errors">details</a>)</para></summary> /// <summary>Get stickers by emoji <para>See <a href="https://corefork.telegram.org/method/messages.getStickers"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.getStickers#possible-errors">details</a>)</para></summary>
/// <param name="emoticon">The emoji</param> /// <param name="emoticon">The emoji</param>
@ -2646,12 +2646,12 @@ namespace TL
/// <param name="peer">Dialog</param> /// <param name="peer">Dialog</param>
/// <param name="top_msg_id">Mark as read only mentions within the specified <a href="https://corefork.telegram.org/api/forum#forum-topics">forum topic</a></param> /// <param name="top_msg_id">Mark as read only mentions within the specified <a href="https://corefork.telegram.org/api/forum#forum-topics">forum topic</a></param>
public static Task<Messages_AffectedHistory> Messages_ReadMentions(this Client client, InputPeer peer, int? top_msg_id = null) public static Task<Messages_AffectedHistory> Messages_ReadMentions(this Client client, InputPeer peer, int? top_msg_id = null)
=> client.Invoke(new Messages_ReadMentions => client.InvokeAffected(new Messages_ReadMentions
{ {
flags = (Messages_ReadMentions.Flags)(top_msg_id != null ? 0x1 : 0), flags = (Messages_ReadMentions.Flags)(top_msg_id != null ? 0x1 : 0),
peer = peer, peer = peer,
top_msg_id = top_msg_id.GetValueOrDefault(), top_msg_id = top_msg_id.GetValueOrDefault(),
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary>Get live location history of a certain user <para>See <a href="https://corefork.telegram.org/method/messages.getRecentLocations"/></para></summary> /// <summary>Get live location history of a certain user <para>See <a href="https://corefork.telegram.org/method/messages.getRecentLocations"/></para></summary>
/// <param name="peer">User</param> /// <param name="peer">User</param>
@ -3057,12 +3057,12 @@ namespace TL
/// <param name="peer">Chat where to unpin</param> /// <param name="peer">Chat where to unpin</param>
/// <param name="top_msg_id"><a href="https://corefork.telegram.org/api/forum#forum-topics">Forum topic</a> where to unpin</param> /// <param name="top_msg_id"><a href="https://corefork.telegram.org/api/forum#forum-topics">Forum topic</a> where to unpin</param>
public static Task<Messages_AffectedHistory> Messages_UnpinAllMessages(this Client client, InputPeer peer, int? top_msg_id = null) public static Task<Messages_AffectedHistory> Messages_UnpinAllMessages(this Client client, InputPeer peer, int? top_msg_id = null)
=> client.Invoke(new Messages_UnpinAllMessages => client.InvokeAffected(new Messages_UnpinAllMessages
{ {
flags = (Messages_UnpinAllMessages.Flags)(top_msg_id != null ? 0x1 : 0), flags = (Messages_UnpinAllMessages.Flags)(top_msg_id != null ? 0x1 : 0),
peer = peer, peer = peer,
top_msg_id = top_msg_id.GetValueOrDefault(), top_msg_id = top_msg_id.GetValueOrDefault(),
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Delete a <a href="https://corefork.telegram.org/api/channel">chat</a> <para>See <a href="https://corefork.telegram.org/method/messages.deleteChat"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.deleteChat#possible-errors">details</a>)</para></summary> /// <summary><para>⚠ <b>This method is only for basic Chat</b>. See <see href="https://wiz0u.github.io/WTelegramClient/#terminology">Terminology</see> in the README to understand what this means<br/>Search for a similar method name starting with <c>Channels_</c> if you're dealing with a <see cref="Channel"/></para> Delete a <a href="https://corefork.telegram.org/api/channel">chat</a> <para>See <a href="https://corefork.telegram.org/method/messages.deleteChat"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.deleteChat#possible-errors">details</a>)</para></summary>
/// <param name="chat_id">Chat ID</param> /// <param name="chat_id">Chat ID</param>
@ -3075,10 +3075,10 @@ namespace TL
/// <summary>Delete the entire phone call history. <para>See <a href="https://corefork.telegram.org/method/messages.deletePhoneCallHistory"/></para></summary> /// <summary>Delete the entire phone call history. <para>See <a href="https://corefork.telegram.org/method/messages.deletePhoneCallHistory"/></para></summary>
/// <param name="revoke">Whether to remove phone call history for participants as well</param> /// <param name="revoke">Whether to remove phone call history for participants as well</param>
public static Task<Messages_AffectedFoundMessages> Messages_DeletePhoneCallHistory(this Client client, bool revoke = false) public static Task<Messages_AffectedFoundMessages> Messages_DeletePhoneCallHistory(this Client client, bool revoke = false)
=> client.Invoke(new Messages_DeletePhoneCallHistory => client.InvokeAffected(new Messages_DeletePhoneCallHistory
{ {
flags = (Messages_DeletePhoneCallHistory.Flags)(revoke ? 0x1 : 0), flags = (Messages_DeletePhoneCallHistory.Flags)(revoke ? 0x1 : 0),
}); }, 0);
/// <summary>Obtains information about a chat export file, generated by a foreign chat app, <a href="https://corefork.telegram.org/api/import">click here for more info about imported chats »</a>. <para>See <a href="https://corefork.telegram.org/method/messages.checkHistoryImport"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.checkHistoryImport#possible-errors">details</a>)</para></summary> /// <summary>Obtains information about a chat export file, generated by a foreign chat app, <a href="https://corefork.telegram.org/api/import">click here for more info about imported chats »</a>. <para>See <a href="https://corefork.telegram.org/method/messages.checkHistoryImport"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.checkHistoryImport#possible-errors">details</a>)</para></summary>
/// <param name="import_head">Beginning of the message file; up to 100 lines.</param> /// <param name="import_head">Beginning of the message file; up to 100 lines.</param>
@ -3446,12 +3446,12 @@ namespace TL
/// <param name="peer">Peer</param> /// <param name="peer">Peer</param>
/// <param name="top_msg_id">Mark as read only reactions to messages within the specified <a href="https://corefork.telegram.org/api/forum#forum-topics">forum topic</a></param> /// <param name="top_msg_id">Mark as read only reactions to messages within the specified <a href="https://corefork.telegram.org/api/forum#forum-topics">forum topic</a></param>
public static Task<Messages_AffectedHistory> Messages_ReadReactions(this Client client, InputPeer peer, int? top_msg_id = null) public static Task<Messages_AffectedHistory> Messages_ReadReactions(this Client client, InputPeer peer, int? top_msg_id = null)
=> client.Invoke(new Messages_ReadReactions => client.InvokeAffected(new Messages_ReadReactions
{ {
flags = (Messages_ReadReactions.Flags)(top_msg_id != null ? 0x1 : 0), flags = (Messages_ReadReactions.Flags)(top_msg_id != null ? 0x1 : 0),
peer = peer, peer = peer,
top_msg_id = top_msg_id.GetValueOrDefault(), top_msg_id = top_msg_id.GetValueOrDefault(),
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary>View and search recently sent media.<br/>This method does not support pagination. <para>See <a href="https://corefork.telegram.org/method/messages.searchSentMedia"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.searchSentMedia#possible-errors">details</a>)</para></summary> /// <summary>View and search recently sent media.<br/>This method does not support pagination. <para>See <a href="https://corefork.telegram.org/method/messages.searchSentMedia"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.searchSentMedia#possible-errors">details</a>)</para></summary>
/// <param name="q">Optional search query</param> /// <param name="q">Optional search query</param>
@ -3859,14 +3859,14 @@ namespace TL
/// <param name="min_date">Delete all messages newer than this UNIX timestamp</param> /// <param name="min_date">Delete all messages newer than this UNIX timestamp</param>
/// <param name="max_date">Delete all messages older than this UNIX timestamp</param> /// <param name="max_date">Delete all messages older than this UNIX timestamp</param>
public static Task<Messages_AffectedHistory> Messages_DeleteSavedHistory(this Client client, InputPeer peer, int max_id = default, DateTime? min_date = null, DateTime? max_date = null) public static Task<Messages_AffectedHistory> Messages_DeleteSavedHistory(this Client client, InputPeer peer, int max_id = default, DateTime? min_date = null, DateTime? max_date = null)
=> client.Invoke(new Messages_DeleteSavedHistory => client.InvokeAffected(new Messages_DeleteSavedHistory
{ {
flags = (Messages_DeleteSavedHistory.Flags)((min_date != null ? 0x4 : 0) | (max_date != null ? 0x8 : 0)), flags = (Messages_DeleteSavedHistory.Flags)((min_date != null ? 0x4 : 0) | (max_date != null ? 0x8 : 0)),
peer = peer, peer = peer,
max_id = max_id, max_id = max_id,
min_date = min_date.GetValueOrDefault(), min_date = min_date.GetValueOrDefault(),
max_date = max_date.GetValueOrDefault(), max_date = max_date.GetValueOrDefault(),
}); }, peer is InputPeerChannel ipc ? ipc.channel_id : 0);
/// <summary>Get pinned <a href="https://corefork.telegram.org/api/saved-messages">saved dialogs, see here »</a> for more info. <para>See <a href="https://corefork.telegram.org/method/messages.getPinnedSavedDialogs"/></para></summary> /// <summary>Get pinned <a href="https://corefork.telegram.org/api/saved-messages">saved dialogs, see here »</a> for more info. <para>See <a href="https://corefork.telegram.org/method/messages.getPinnedSavedDialogs"/></para></summary>
public static Task<Messages_SavedDialogsBase> Messages_GetPinnedSavedDialogs(this Client client) public static Task<Messages_SavedDialogsBase> Messages_GetPinnedSavedDialogs(this Client client)
@ -4421,11 +4421,11 @@ namespace TL
/// <param name="channel"><a href="https://corefork.telegram.org/api/channel">Channel/supergroup</a></param> /// <param name="channel"><a href="https://corefork.telegram.org/api/channel">Channel/supergroup</a></param>
/// <param name="id">IDs of messages to delete</param> /// <param name="id">IDs of messages to delete</param>
public static Task<Messages_AffectedMessages> Channels_DeleteMessages(this Client client, InputChannelBase channel, params int[] id) public static Task<Messages_AffectedMessages> Channels_DeleteMessages(this Client client, InputChannelBase channel, params int[] id)
=> client.Invoke(new Channels_DeleteMessages => client.InvokeAffected(new Channels_DeleteMessages
{ {
channel = channel, channel = channel,
id = id, id = id,
}); }, channel.ChannelId);
/// <summary>Reports some messages from a user in a supergroup as spam; requires administrator rights in the supergroup <para>See <a href="https://corefork.telegram.org/method/channels.reportSpam"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.reportSpam#possible-errors">details</a>)</para></summary> /// <summary>Reports some messages from a user in a supergroup as spam; requires administrator rights in the supergroup <para>See <a href="https://corefork.telegram.org/method/channels.reportSpam"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.reportSpam#possible-errors">details</a>)</para></summary>
/// <param name="channel">Supergroup</param> /// <param name="channel">Supergroup</param>
@ -4811,11 +4811,11 @@ namespace TL
/// <param name="channel">Supergroup</param> /// <param name="channel">Supergroup</param>
/// <param name="participant">The participant whose messages should be deleted</param> /// <param name="participant">The participant whose messages should be deleted</param>
public static Task<Messages_AffectedHistory> Channels_DeleteParticipantHistory(this Client client, InputChannelBase channel, InputPeer participant) public static Task<Messages_AffectedHistory> Channels_DeleteParticipantHistory(this Client client, InputChannelBase channel, InputPeer participant)
=> client.Invoke(new Channels_DeleteParticipantHistory => client.InvokeAffected(new Channels_DeleteParticipantHistory
{ {
channel = channel, channel = channel,
participant = participant, participant = participant,
}); }, channel.ChannelId);
/// <summary>Set whether all users <a href="https://corefork.telegram.org/api/discussion#requiring-users-to-join-the-group">should join a discussion group in order to comment on a post »</a> <para>See <a href="https://corefork.telegram.org/method/channels.toggleJoinToSend"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.toggleJoinToSend#possible-errors">details</a>)</para></summary> /// <summary>Set whether all users <a href="https://corefork.telegram.org/api/discussion#requiring-users-to-join-the-group">should join a discussion group in order to comment on a post »</a> <para>See <a href="https://corefork.telegram.org/method/channels.toggleJoinToSend"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.toggleJoinToSend#possible-errors">details</a>)</para></summary>
/// <param name="channel">Discussion group</param> /// <param name="channel">Discussion group</param>
@ -4960,11 +4960,11 @@ namespace TL
/// <param name="channel">Forum</param> /// <param name="channel">Forum</param>
/// <param name="top_msg_id">Topic ID</param> /// <param name="top_msg_id">Topic ID</param>
public static Task<Messages_AffectedHistory> Channels_DeleteTopicHistory(this Client client, InputChannelBase channel, int top_msg_id) public static Task<Messages_AffectedHistory> Channels_DeleteTopicHistory(this Client client, InputChannelBase channel, int top_msg_id)
=> client.Invoke(new Channels_DeleteTopicHistory => client.InvokeAffected(new Channels_DeleteTopicHistory
{ {
channel = channel, channel = channel,
top_msg_id = top_msg_id, top_msg_id = top_msg_id,
}); }, channel.ChannelId);
/// <summary>Reorder pinned forum topics <para>See <a href="https://corefork.telegram.org/method/channels.reorderPinnedForumTopics"/> [bots: ✓]</para></summary> /// <summary>Reorder pinned forum topics <para>See <a href="https://corefork.telegram.org/method/channels.reorderPinnedForumTopics"/> [bots: ✓]</para></summary>
/// <param name="force">If not set, the order of only the topics present both server-side and in <c>order</c> will be changed (i.e. mentioning topics not pinned server-side in <c>order</c> will not pin them, and not mentioning topics pinned server-side will not unpin them). <br/>If set, the entire server-side pinned topic list will be replaced with <c>order</c> (i.e. mentioning topics not pinned server-side in <c>order</c> will pin them, and not mentioning topics pinned server-side will unpin them)</param> /// <param name="force">If not set, the order of only the topics present both server-side and in <c>order</c> will be changed (i.e. mentioning topics not pinned server-side in <c>order</c> will not pin them, and not mentioning topics pinned server-side will not unpin them). <br/>If set, the entire server-side pinned topic list will be replaced with <c>order</c> (i.e. mentioning topics not pinned server-side in <c>order</c> will pin them, and not mentioning topics pinned server-side will unpin them)</param>

View file

@ -32,9 +32,9 @@ namespace TL
public readonly int Bit = bit; public readonly int Bit = bit;
} }
public sealed class RpcException(int code, string message, int x = -1) : WTelegram.WTException(message) public sealed class RpcException(int code, string message, int x = -1) : WTelegram.WTException(message, code)
{ {
public readonly int Code = code; public int Code => ErrorCode;
/// <summary>The value of X in the message, -1 if no variable X was found</summary> /// <summary>The value of X in the message, -1 if no variable X was found</summary>
public readonly int X = x; public readonly int X = x;
public override string ToString() { var str = base.ToString(); return str.Insert(str.IndexOf(':') + 1, " " + Code); } public override string ToString() { var str = base.ToString(); return str.Insert(str.IndexOf(':') + 1, " " + Code); }
@ -398,8 +398,10 @@ namespace TL
public sealed partial class UpdateAffectedMessages : Update // auto-generated for OnOwnUpdates in case of such API call result public sealed partial class UpdateAffectedMessages : Update // auto-generated for OnOwnUpdates in case of such API call result
{ {
public Messages_AffectedMessages affected; public long mbox_id;
public override (long, int, int) GetMBox() => (0, affected.pts, affected.pts_count); public int pts;
public int pts_count;
public override (long, int, int) GetMBox() => (mbox_id, pts, pts_count);
#if MTPG #if MTPG
public override void WriteTL(BinaryWriter writer) => throw new NotSupportedException(); public override void WriteTL(BinaryWriter writer) => throw new NotSupportedException();
#endif #endif

View file

@ -9,8 +9,6 @@ using TL;
namespace WTelegram namespace WTelegram
{ {
public delegate IPeerInfo UserChatCollector(Dictionary<long, User> users, Dictionary<long, ChatBase> chats);
public class UpdateManager : IPeerResolver public class UpdateManager : IPeerResolver
{ {
/// <summary>Collected info about Users <i>(only if using the default collector)</i></summary> /// <summary>Collected info about Users <i>(only if using the default collector)</i></summary>
@ -37,10 +35,9 @@ namespace WTelegram
private readonly Client _client; private readonly Client _client;
private readonly Func<Update, Task> _onUpdate; private readonly Func<Update, Task> _onUpdate;
private readonly UserChatCollector _onCollect; private readonly IPeerCollector _collector;
private readonly bool _reentrant; private readonly bool _reentrant;
private readonly SemaphoreSlim _sem = new(1); private readonly SemaphoreSlim _sem = new(1);
private readonly Extensions.CollectorPeer _collector;
private readonly List<(Update update, UpdatesBase updates, bool own, DateTime stamp)> _pending = []; private readonly List<(Update update, UpdatesBase updates, bool own, DateTime stamp)> _pending = [];
private readonly Dictionary<long, MBoxState> _local; // -2 for seq/date, -1 for qts, 0 for common pts, >0 for channel pts private readonly Dictionary<long, MBoxState> _local; // -2 for seq/date, -1 for qts, 0 for common pts, >0 for channel pts
private const int L_SEQ = -2, L_QTS = -1, L_PTS = 0; private const int L_SEQ = -2, L_QTS = -1, L_PTS = 0;
@ -55,17 +52,11 @@ namespace WTelegram
/// <param name="state">(optional) Resume session by recovering all updates that occured since this state</param> /// <param name="state">(optional) Resume session by recovering all updates that occured since this state</param>
/// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param> /// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param>
/// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param> /// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param>
public UpdateManager(Client client, Func<Update, Task> onUpdate, IDictionary<long, MBoxState> state = null, UserChatCollector collector = null, bool reentrant = false) public UpdateManager(Client client, Func<Update, Task> onUpdate, IDictionary<long, MBoxState> state = null, IPeerCollector collector = null, bool reentrant = false)
{ {
_client = client; _client = client;
_onUpdate = onUpdate; _onUpdate = onUpdate;
if (collector != null) _collector = collector ?? new Extensions.CollectorPeer(Users = [], Chats = []);
_onCollect = collector;
else
{
_collector = new() { _users = Users = [], _chats = Chats = [] };
_onCollect = _collector.UserOrChat;
}
if (state == null) if (state == null)
_local = new() { [L_SEQ] = new() { access_hash = UndefinedSeqDate }, [L_QTS] = new(), [L_PTS] = new() }; _local = new() { [L_SEQ] = new() { access_hash = UndefinedSeqDate }, [L_QTS] = new(), [L_PTS] = new() };
@ -85,7 +76,7 @@ namespace WTelegram
if (_local[L_PTS].pts != 0) await ResyncState(); if (_local[L_PTS].pts != 0) await ResyncState();
break; break;
case User user when user.flags.HasFlag(User.Flags.self): case User user when user.flags.HasFlag(User.Flags.self):
if (Users != null) Users[user.id] = user; _collector.Collect([user]);
goto newSession; goto newSession;
case NewSessionCreated when _client.User != null: case NewSessionCreated when _client.User != null:
newSession: newSession:
@ -130,15 +121,14 @@ namespace WTelegram
var now = _lastUpdateStamp = DateTime.UtcNow; var now = _lastUpdateStamp = DateTime.UtcNow;
var updateList = updates.UpdateList; var updateList = updates.UpdateList;
if (updates is UpdateShortSentMessage sent) if (updates is UpdateShortSentMessage sent)
updateList = new[] { new UpdateNewMessage { pts = sent.pts, pts_count = sent.pts_count, message = new Message { updateList = [new UpdateNewMessage { pts = sent.pts, pts_count = sent.pts_count, message = new Message {
flags = (Message.Flags)sent.flags, flags = (Message.Flags)sent.flags,
id = sent.id, date = sent.date, entities = sent.entities, media = sent.media, ttl_period = sent.ttl_period, id = sent.id, date = sent.date, entities = sent.entities, media = sent.media, ttl_period = sent.ttl_period,
} } }; } }];
else if (Users != null) else if (updates is UpdateShortMessage usm && !_collector.HasUser(usm.user_id))
if (updates is UpdateShortMessage usm && !Users.ContainsKey(usm.user_id)) RaiseCollect(await _client.Updates_GetDifference(usm.pts - usm.pts_count, usm.date, 0));
(await _client.Updates_GetDifference(usm.pts - usm.pts_count, usm.date, 0)).UserOrChat(_collector); else if (updates is UpdateShortChatMessage uscm && (!_collector.HasUser(uscm.from_id) || !_collector.HasChat(uscm.chat_id)))
else if (updates is UpdateShortChatMessage uscm && (!Users.ContainsKey(uscm.from_id) || !Chats.ContainsKey(uscm.chat_id))) RaiseCollect(await _client.Updates_GetDifference(uscm.pts - uscm.pts_count, uscm.date, 0));
(await _client.Updates_GetDifference(uscm.pts - uscm.pts_count, uscm.date, 0)).UserOrChat(_collector);
bool ptsChanged = false, gotUPts = false; bool ptsChanged = false, gotUPts = false;
int seq = 0; int seq = 0;
@ -261,10 +251,12 @@ namespace WTelegram
Log?.Invoke(2, $"({mbox_id,10}, new +{pts_count}->{pts,-6}) {update,-30} First appearance of MBox {ExtendedLog(update)}"); Log?.Invoke(2, $"({mbox_id,10}, new +{pts_count}->{pts,-6}) {update,-30} First appearance of MBox {ExtendedLog(update)}");
else if (local.access_hash == -1) // no valid access_hash for this channel, so just raise this update else if (local.access_hash == -1) // no valid access_hash for this channel, so just raise this update
Log?.Invoke(3, $"({mbox_id,10}, {local.pts,6}+{pts_count}->{pts,-6}) {update,-30} No access_hash to recover {ExtendedLog(update)}"); Log?.Invoke(3, $"({mbox_id,10}, {local.pts,6}+{pts_count}->{pts,-6}) {update,-30} No access_hash to recover {ExtendedLog(update)}");
else if (local.pts + pts_count - pts >= 0)
getDiffSuccess = true;
else else
{ {
Log?.Invoke(1, $"({mbox_id,10}, {local.pts,6}+{pts_count}->{pts,-6}) {update,-30} Calling GetDifference {ExtendedLog(update)}"); Log?.Invoke(1, $"({mbox_id,10}, {local.pts,6}+{pts_count}->{pts,-6}) {update,-30} Calling GetDifference {ExtendedLog(update)}");
getDiffSuccess = await GetDifference(mbox_id, pts - pts_count, local); getDiffSuccess = await GetDifference(mbox_id, pts, local);
} }
if (!getDiffSuccess) // no getDiff => just raise received pending updates in order if (!getDiffSuccess) // no getDiff => just raise received pending updates in order
{ {
@ -382,6 +374,11 @@ namespace WTelegram
catch (Exception ex) catch (Exception ex)
{ {
Log?.Invoke(4, $"GetDifference({mbox_id}, {local.pts}->{expected_pts}) raised {ex}"); Log?.Invoke(4, $"GetDifference({mbox_id}, {local.pts}->{expected_pts}) raised {ex}");
if (ex.Message == "PERSISTENT_TIMESTAMP_INVALID") // oh boy, we're lost!
if (mbox_id <= 0)
await HandleDifference(null, null, await _client.Updates_GetState(), null);
else if ((await _client.Channels_GetFullChannel(await GetInputChannel(mbox_id, local))).full_chat is ChannelFull full)
local.pts = full.pts;
} }
finally finally
{ {
@ -441,6 +438,14 @@ namespace WTelegram
} }
} }
private void RaiseCollect(Updates_DifferenceBase diff)
{
if (diff is Updates_DifferenceSlice uds)
RaiseCollect(uds.users, uds.chats);
else if (diff is Updates_Difference ud)
RaiseCollect(ud.users, ud.chats);
}
private void RaiseCollect(Dictionary<long, User> users, Dictionary<long, ChatBase> chats) private void RaiseCollect(Dictionary<long, User> users, Dictionary<long, ChatBase> chats)
{ {
try try
@ -449,11 +454,12 @@ namespace WTelegram
if (chat is Channel channel && !channel.flags.HasFlag(Channel.Flags.min)) if (chat is Channel channel && !channel.flags.HasFlag(Channel.Flags.min))
if (_local.TryGetValue(channel.id, out var local)) if (_local.TryGetValue(channel.id, out var local))
local.access_hash = channel.access_hash; local.access_hash = channel.access_hash;
_onCollect(users, chats); _collector.Collect(users.Values);
_collector.Collect(chats.Values);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log?.Invoke(4, $"onCollect({users?.Count},{chats?.Count}) raised {ex}"); Log?.Invoke(4, $"Collect({users?.Count},{chats?.Count}) raised {ex}");
} }
} }
@ -516,6 +522,14 @@ namespace WTelegram
/// <summary>returns a <see cref="User"/> or <see cref="ChatBase"/> for the given Peer</summary> /// <summary>returns a <see cref="User"/> or <see cref="ChatBase"/> for the given Peer</summary>
public IPeerInfo UserOrChat(Peer peer) => peer?.UserOrChat(Users, Chats); public IPeerInfo UserOrChat(Peer peer) => peer?.UserOrChat(Users, Chats);
} }
public interface IPeerCollector
{
void Collect(IEnumerable<User> users);
void Collect(IEnumerable<ChatBase> chats);
bool HasUser(long id);
bool HasChat(long id);
}
} }
namespace TL namespace TL
@ -530,7 +544,7 @@ namespace TL
/// <param name="statePath">Resume session by recovering all updates that occured since the state saved in this file</param> /// <param name="statePath">Resume session by recovering all updates that occured since the state saved in this file</param>
/// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param> /// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param>
/// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param> /// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param>
public static UpdateManager WithUpdateManager(this Client client, Func<TL.Update, Task> onUpdate, string statePath, UserChatCollector collector = null, bool reentrant = false) public static UpdateManager WithUpdateManager(this Client client, Func<TL.Update, Task> onUpdate, string statePath, IPeerCollector collector = null, bool reentrant = false)
=> new(client, onUpdate, UpdateManager.LoadState(statePath), collector, reentrant); => new(client, onUpdate, UpdateManager.LoadState(statePath), collector, reentrant);
/// <summary>Manager ensuring that you receive Telegram updates in correct order, without missing any</summary> /// <summary>Manager ensuring that you receive Telegram updates in correct order, without missing any</summary>
@ -538,7 +552,7 @@ namespace TL
/// <param name="state">(optional) Resume session by recovering all updates that occured since this state</param> /// <param name="state">(optional) Resume session by recovering all updates that occured since this state</param>
/// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param> /// <param name="collector">Custom users/chats collector. By default, those are collected in properties Users/Chats</param>
/// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param> /// <param name="reentrant"><see langword="true"/> if your <paramref name="onUpdate"/> method can be called again even when last async call didn't return yet</param>
public static UpdateManager WithUpdateManager(this Client client, Func<TL.Update, Task> onUpdate, IDictionary<long, UpdateManager.MBoxState> state = null, UserChatCollector collector = null, bool reentrant = false) public static UpdateManager WithUpdateManager(this Client client, Func<TL.Update, Task> onUpdate, IDictionary<long, UpdateManager.MBoxState> state = null, IPeerCollector collector = null, bool reentrant = false)
=> new(client, onUpdate, state, collector, reentrant); => new(client, onUpdate, state, collector, reentrant);
} }
} }

View file

@ -44,7 +44,7 @@
</ItemGroup>--> </ItemGroup>-->
<ItemGroup Condition="$(DefineConstants.Contains('MTPG'))" > <ItemGroup Condition="$(DefineConstants.Contains('MTPG'))" >
<ProjectReference Include="..\generator\MTProtoGenerator.csproj" OutputItemType="Analyzer" /> <ProjectReference Include="..\generator\MTProtoGenerator.csproj" OutputItemType="Analyzer" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">