diff --git a/FAQ.md b/FAQ.md index e24b20c..6e58d2f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -349,13 +349,11 @@ var manager = client.WithUpdateManager(OnUpdate); var manager = client.WithUpdateManager(OnUpdate, "Updates.state"); // to save the state later, preferably after disposing the client: 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. -The `manager.Users` and `manager.Chats` dictionaries will collect the users/chats data from updates. -You can also feed them manually from result of your API calls by calling `result.CollectUsersChats(manager.Users, manager.Chats);` and resolve Peer fields via `manager.UserOrChat(peer)` +Your `OnUpdate` method will directly take a single `Update` as parameter, instead of a container of updates. +The `manager.Users` and `manager.Chats` dictionaries will collect the users/chats data from updates. +You can also feed them manually from result of your API calls by calling `result.CollectUsersChats(manager.Users, manager.Chats);` and resolve Peer fields via `manager.UserOrChat(peer)` See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21) for an example of implementation. Notes: diff --git a/README.md b/README.md index 15877e3..eab56a5 100644 --- a/README.md +++ b/README.md @@ -164,14 +164,14 @@ See [FAQ #4](https://wiz0u.github.io/WTelegramClient/FAQ#access-hash) to learn m # Other things to know -The Client class offers `OnUpdates` and `OnOther` events that are triggered when Telegram servers sends Updates (like new messages or status) or other notifications, independently of your API requests. -You can also use the [UpdateManager class](https://wiz0u.github.io/WTelegramClient/FAQ#manager) to simplify the handling of such updates. +The Client class offers `OnUpdates` and `OnOther` events that are triggered when Telegram servers sends Updates (like new messages or status) or other notifications, independently of your API requests. +You can also use the [UpdateManager class](https://wiz0u.github.io/WTelegramClient/FAQ#manager) to simplify the handling of such updates. See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21) and [Examples/Program_ReactorError.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ReactorError.cs?ts=4#L30) 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: -`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** diff --git a/generator/MTProtoGenerator.cs b/generator/MTProtoGenerator.cs index 4901bd8..5bd831a 100644 --- a/generator/MTProtoGenerator.cs +++ b/generator/MTProtoGenerator.cs @@ -14,34 +14,44 @@ namespace TL.Generator; [Generator] public class MTProtoGenerator : IIncrementalGenerator { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName("TL.TLDefAttribute", - (_, _) => true, (context, _) => (ClassDeclarationSyntax)context.TargetNode); + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName("TL.TLDefAttribute", + (_, _) => true, (context, _) => (ClassDeclarationSyntax)context.TargetNode); var source = context.CompilationProvider.Combine(classDeclarations.Collect()); context.RegisterSourceOutput(source, Execute); } static void Execute(SourceProductionContext context, (Compilation compilation, ImmutableArray classes) unit) - { - var object_ = unit.compilation.GetSpecialType(SpecialType.System_Object); - if (unit.compilation.GetTypeByMetadataName("TL.TLDefAttribute") is not { } tlDefAttribute) return; - if (unit.compilation.GetTypeByMetadataName("TL.IfFlagAttribute") is not { } ifFlagAttribute) return; + { + var object_ = unit.compilation.GetSpecialType(SpecialType.System_Object); + if (unit.compilation.GetTypeByMetadataName("TL.TLDefAttribute") is not { } tlDefAttribute) return; + if (unit.compilation.GetTypeByMetadataName("TL.IfFlagAttribute") is not { } ifFlagAttribute) return; if (unit.compilation.GetTypeByMetadataName("TL.Layer") is not { } layer) return; if (unit.compilation.GetTypeByMetadataName("TL.IObject") is not { } iobject) return; - var nullables = LoadNullables(layer); - var namespaces = new Dictionary>(); // namespace,class,methods - var readTL = new StringBuilder(); - readTL - .AppendLine("\t\tpublic static IObject ReadTL(this BinaryReader reader, uint ctorId = 0) => (ctorId != 0 ? ctorId : reader.ReadUInt32()) switch") - .AppendLine("\t\t{"); + var nullables = LoadNullables(layer); + var namespaces = new Dictionary>(); // namespace,class,methods + var makeTL = new StringBuilder(); + 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\t{"); foreach (var classDecl in unit.classes) - { - var semanticModel = unit.compilation.GetSemanticModel(classDecl.SyntaxTree); - if (semanticModel.GetDeclaredSymbol(classDecl) is not { } symbol) continue; - var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute); - if (tldef == null) continue; + { + var semanticModel = unit.compilation.GetSemanticModel(classDecl.SyntaxTree); + if (semanticModel.GetDeclaredSymbol(classDecl) is not { } symbol) continue; + var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute); + if (tldef == null) continue; var id = (uint)tldef.ConstructorArguments[0].Value; var inheritBefore = (bool?)tldef.NamedArguments.FirstOrDefault(k => k.Key == "inheritBefore").Value.Value ?? false; StringBuilder writeTl = new(), ctorTL = new(); @@ -55,7 +65,10 @@ public class MTProtoGenerator : IIncrementalGenerator { if (parentMethods == null) { - writeTl.AppendLine("\t\tpublic abstract void WriteTL(BinaryWriter writer);"); + 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);"); parentClasses[name] = writeTl.ToString(); writeTl.Clear(); } @@ -72,40 +85,43 @@ public class MTProtoGenerator : IIncrementalGenerator continue; } 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")) - readTL.AppendLine($"\t\t\t0x{id:X8} => new {(ns == "TL" ? "" : ns + '.')}{name}(reader),"); - var override_ = symbol.BaseType == object_ ? "" : "override "; + makeTL.AppendLine($"\t\t\t0x{id:X8} => new {(ns == "TL" ? "" : ns + '.')}{name}().ReadTL(reader),"); + var override_ = symbol.BaseType == object_ ? "" : "override "; if (name == "Messages_AffectedMessages") override_ = "virtual "; - if (symbol.Constructors[0].IsImplicitlyDeclared) - ctorTL.AppendLine($"\t\tpublic {name}() {{ }}"); + //if (symbol.Constructors[0].IsImplicitlyDeclared) + // ctorTL.AppendLine($"\t\tpublic {name}() {{ }}"); + if (symbol.IsGenericType) name += ""; ctorTL - .AppendLine($"\t\tpublic {name}(BinaryReader reader)") - .AppendLine("\t\t{"); + .AppendLine("\t\t[EditorBrowsable(EditorBrowsableState.Never)]") + .AppendLine($"\t\tpublic new {name} ReadTL(BinaryReader reader)") + .AppendLine("\t\t{"); writeTl - .AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)") - .AppendLine("\t\t{") - .AppendLine($"\t\t\twriter.Write(0x{id:X8});"); - var members = symbol.GetMembers().ToList(); - for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType) - if (inheritBefore) members.InsertRange(0, parent.GetMembers()); - else members.AddRange(parent.GetMembers()); + .AppendLine("\t\t[EditorBrowsable(EditorBrowsableState.Never)]") + .AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)") + .AppendLine("\t\t{") + .AppendLine($"\t\t\twriter.Write(0x{id:X8});"); + var members = symbol.GetMembers().ToList(); + for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType) + if (inheritBefore) members.InsertRange(0, parent.GetMembers()); + else members.AddRange(parent.GetMembers()); foreach (var member in members.OfType()) - { - if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue; - ctorTL.Append("\t\t\t"); + { + if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue; + ctorTL.Append("\t\t\t"); writeTl.Append("\t\t\t"); var ifFlag = (int?)member.GetAttributes().FirstOrDefault(a => a.AttributeClass == ifFlagAttribute)?.ConstructorArguments[0].Value; - if (ifFlag != null) + if (ifFlag != null) { - var condition = ifFlag < 32 ? $"if (((uint)flags & 0x{1 << ifFlag:X}) != 0) " - : $"if (((uint)flags2 & 0x{1 << (ifFlag - 32):X}) != 0) "; - ctorTL.Append(condition); - writeTl.Append(condition); + var condition = ifFlag < 32 ? $"if (((uint)flags & 0x{1 << ifFlag:X}) != 0) " + : $"if (((uint)flags2 & 0x{1 << (ifFlag - 32):X}) != 0) "; + ctorTL.Append(condition); + writeTl.Append(condition); } string memberType = member.Type.ToString(); switch (memberType) - { + { case "int": ctorTL.AppendLine($"{member.Name} = reader.ReadInt32();"); writeTl.AppendLine($"writer.Write({member.Name});"); @@ -127,34 +143,34 @@ public class MTProtoGenerator : IIncrementalGenerator writeTl.AppendLine($"writer.WriteTLStamp({member.Name});"); break; case "string": - ctorTL.AppendLine($"{member.Name} = reader.ReadTLString();"); - writeTl.AppendLine($"writer.WriteTLString({member.Name});"); + ctorTL.AppendLine($"{member.Name} = reader.ReadTLString();"); + writeTl.AppendLine($"writer.WriteTLString({member.Name});"); + break; + case "byte[]": + ctorTL.AppendLine($"{member.Name} = reader.ReadTLBytes();"); + writeTl.AppendLine($"writer.WriteTLBytes({member.Name});"); break; - case "byte[]": - ctorTL.AppendLine($"{member.Name} = reader.ReadTLBytes();"); - writeTl.AppendLine($"writer.WriteTLBytes({member.Name});"); - break; case "TL.Int128": ctorTL.AppendLine($"{member.Name} = new Int128(reader);"); writeTl.AppendLine($"writer.Write({member.Name});"); break; case "TL.Int256": ctorTL.AppendLine($"{member.Name} = new Int256(reader);"); - writeTl.AppendLine($"writer.Write({member.Name});"); + writeTl.AppendLine($"writer.Write({member.Name});"); break; - case "TL._Message[]": - ctorTL.AppendLine($"throw new NotSupportedException();"); - writeTl.AppendLine($"writer.WriteTLMessages({member.Name});"); - break; - case "TL.IObject": case "TL.IMethod": + case "TL._Message[]": + ctorTL.AppendLine($"{member.Name} = reader.ReadTLRawVector<_Message>(0x5BB8E511);"); + writeTl.AppendLine($"writer.WriteTLMessages({member.Name});"); + break; + case "TL.IObject": case "TL.IMethod": ctorTL.AppendLine($"{member.Name} = {(memberType == "TL.IObject" ? "" : $"({memberType})")}reader.ReadTL();"); writeTl.AppendLine($"{member.Name}.WriteTL(writer);"); - break; - case "System.Collections.Generic.Dictionary": + break; + case "System.Collections.Generic.Dictionary": ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary();"); writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());"); break; - case "System.Collections.Generic.Dictionary": + case "System.Collections.Generic.Dictionary": ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary();"); writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());"); break; @@ -180,56 +196,49 @@ public class MTProtoGenerator : IIncrementalGenerator } else writeTl.AppendLine($"Cannot serialize {memberType}"); - break; + break; } } + ctorTL.AppendLine("\t\t\treturn this;"); ctorTL.AppendLine("\t\t}"); writeTl.AppendLine("\t\t}"); ctorTL.Append(writeTl.ToString()); - if (symbol.IsGenericType) name += ""; 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) - readTL.AppendLine($"\t\t\t0x{nullable.Value:X8} => null,"); - readTL.AppendLine("\t\t\tvar ctorNb => throw new Exception($\"Cannot find type for ctor #{ctorNb:x}\")"); - readTL.AppendLine("\t\t};"); - namespaces["TL"]["Layer"] = readTL.ToString(); - foreach (var namesp in namespaces) - { - source.Append("namespace ").AppendLine(namesp.Key).Append('{'); - foreach (var method in namesp.Value) - source.AppendLine().Append("\tpartial class ").AppendLine(method.Key).AppendLine("\t{").Append(method.Value).AppendLine("\t}"); + makeTL.AppendLine($"\t\t\t0x{nullable.Value:X8} => null,"); + makeTL.AppendLine("\t\t\tvar ctorNb => throw new Exception($\"Cannot find type for ctor #{ctorNb:x}\")"); + makeTL.AppendLine("\t\t};"); + namespaces["TL"]["Layer"] = makeTL.ToString(); + foreach (var namesp in namespaces) + { + source.Append("namespace ").AppendLine(namesp.Key).Append('{'); + foreach (var method in namesp.Value) + source.AppendLine().Append("\tpartial class ").AppendLine(method.Key).AppendLine("\t{").Append(method.Value).AppendLine("\t}"); source.AppendLine("}").AppendLine(); } string text = source.ToString(); - Debug.Write(text); + Debug.Write(text); context.AddSource("TL.Generated.cs", text); } private static Dictionary LoadNullables(INamedTypeSymbol layer) { - var nullables = layer.GetMembers("Nullables").Single() as IFieldSymbol; - var initializer = nullables.DeclaringSyntaxReferences[0].GetSyntax().ToString(); + var nullables = layer.GetMembers("Nullables").Single() as IFieldSymbol; + var initializer = nullables.DeclaringSyntaxReferences[0].GetSyntax().ToString(); var table = new Dictionary(); foreach (var line in initializer.Split('\n')) - { - int index = line.IndexOf("[typeof("); - if (index == -1) continue; - int index2 = line.IndexOf(')', index += 8); - string className = "TL." + line.Substring(index, index2 - index); - index = line.IndexOf("= 0x", index2); - if (index == -1) continue; - index2 = line.IndexOf(',', index += 4); - table[className] = uint.Parse(line.Substring(index, index2 - index), System.Globalization.NumberStyles.HexNumber); - } - return table; + { + int index = line.IndexOf("[typeof("); + if (index == -1) continue; + int index2 = line.IndexOf(')', index += 8); + string className = "TL." + line.Substring(index, index2 - index); + index = line.IndexOf("= 0x", index2); + if (index == -1) continue; + index2 = line.IndexOf(',', index += 4); + table[className] = uint.Parse(line.Substring(index, index2 - index), System.Globalization.NumberStyles.HexNumber); + } + return table; } } diff --git a/src/Client.cs b/src/Client.cs index 4e34177..8cf1339 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -586,8 +586,6 @@ namespace WTelegram if (OnOwnUpdates != null) if (result is UpdatesBase 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); @@ -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}"); } } + + public async Task InvokeAffected(IMethod 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; + } } } diff --git a/src/Helpers.cs b/src/Helpers.cs index d483cb6..5562e9f 100644 --- a/src/Helpers.cs +++ b/src/Helpers.cs @@ -267,7 +267,9 @@ namespace WTelegram public class WTException : ApplicationException { + public readonly int ErrorCode; 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) { } } } diff --git a/src/TL.Extensions.cs b/src/TL.Extensions.cs index 8aa400a..7a51640 100644 --- a/src/TL.Extensions.cs +++ b/src/TL.Extensions.cs @@ -5,98 +5,104 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; -using WTelegram; // for GetValueOrDefault +using WTelegram; namespace TL { public static class Extensions { - internal sealed partial class CollectorPeer : Peer + public sealed partial class CollectorPeer(IDictionary _users, IDictionary _chats) : Peer, IPeerCollector { public override long ID => 0; - internal IDictionary _users; - internal IDictionary _chats; protected internal override IPeerInfo UserOrChat(Dictionary users, Dictionary chats) { - if (_users != null) - lock (_users) - foreach (var user in users.Values) - if (user != null) - if (!user.flags.HasFlag(User.Flags.min) || !_users.TryGetValue(user.id, out var prevUser) || prevUser.flags.HasFlag(User.Flags.min)) - _users[user.id] = user; - else - { // update previously full user from min user: - const User.Flags updated_flags = (User.Flags)0x5DAFE000; - const User.Flags2 updated_flags2 = (User.Flags2)0x711; - // tdlib updated flags: deleted | bot | bot_chat_history | bot_nochats | verified | bot_inline_geo - // | support | scam | fake | bot_attach_menu | premium - // tdesktop non-updated flags: bot | bot_chat_history | bot_nochats | bot_attach_menu - // updated flags2: stories_unavailable (tdlib) | contact_require_premium (tdesktop) - prevUser.flags = (prevUser.flags & ~updated_flags) | (user.flags & updated_flags); - prevUser.flags2 = (prevUser.flags2 & ~updated_flags2) | (user.flags2 & updated_flags2); - prevUser.first_name ??= user.first_name; // tdlib: not updated ; tdesktop: updated only if unknown - prevUser.last_name ??= user.last_name; // tdlib: not updated ; tdesktop: updated only if unknown - //prevUser.username ??= user.username; // tdlib/tdesktop: not updated - prevUser.phone ??= user.phone; // tdlib: updated only if unknown ; tdesktop: not updated - if (prevUser.flags.HasFlag(User.Flags.apply_min_photo) && user.photo != null) - { - prevUser.photo = user.photo; // tdlib/tdesktop: updated on apply_min_photo - prevUser.flags |= User.Flags.has_photo; - } - prevUser.bot_info_version = user.bot_info_version; // tdlib: updated ; tdesktop: not updated - prevUser.restriction_reason = user.restriction_reason; // tdlib: updated ; tdesktop: not updated - prevUser.bot_inline_placeholder = user.bot_inline_placeholder;// tdlib: updated ; tdesktop: ignored - if (user.lang_code != null) - prevUser.lang_code = user.lang_code; // tdlib: updated if present ; tdesktop: ignored - prevUser.emoji_status = user.emoji_status; // tdlib/tdesktop: updated - prevUser.usernames = user.usernames; // tdlib: not updated ; tdesktop: updated - if (user.stories_max_id > 0) - prevUser.stories_max_id = user.stories_max_id; // tdlib: updated if > 0 ; tdesktop: not updated - prevUser.color = user.color; // tdlib/tdesktop: updated - prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet - _users[user.id] = prevUser; - } - if (_chats != null) - lock (_chats) - foreach (var kvp in chats) - if (kvp.Value is not Channel channel) - _chats[kvp.Key] = kvp.Value; - 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)) - _chats[kvp.Key] = channel; - else - { // update previously full channel from min channel: - const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0; - const Channel.Flags2 updated_flags2 = (Channel.Flags2)0x781; - // tdesktop updated flags: broadcast | verified | megagroup | signatures | scam | has_link | slowmode_enabled - // | call_active | call_not_empty | fake | gigagroup | noforwards | join_to_send | join_request | forum - // tdlib nonupdated flags: broadcast | signatures | call_active | call_not_empty | noforwards - prevChannel.flags = (prevChannel.flags & ~updated_flags) | (channel.flags & updated_flags); - prevChannel.flags2 = (prevChannel.flags2 & ~updated_flags2) | (channel.flags2 & updated_flags2); - prevChannel.title = channel.title; // tdlib/tdesktop: updated - prevChannel.username = channel.username; // tdlib/tdesktop: updated - prevChannel.photo = channel.photo; // tdlib: updated if not banned ; tdesktop: updated - prevChannel.restriction_reason = channel.restriction_reason; // tdlib: updated ; tdesktop: not updated - prevChannel.default_banned_rights = channel.default_banned_rights; // tdlib/tdesktop: updated - if (channel.participants_count > 0) - prevChannel.participants_count = channel.participants_count; // tdlib/tdesktop: updated if present - prevChannel.usernames = channel.usernames; // tdlib/tdesktop: updated - prevChannel.color = channel.color; // tdlib: not updated ; tdesktop: updated - prevChannel.profile_color = channel.profile_color; // tdlib/tdesktop: ignored - prevChannel.emoji_status = channel.emoji_status; // tdlib: not updated ; tdesktop: updated - prevChannel.level = channel.level; // tdlib: not updated ; tdesktop: updated - _chats[kvp.Key] = prevChannel; - } + if (users != null) Collect(users.Values); + if (chats != null) Collect(chats.Values); return null; } -#if MTPG - public override void WriteTL(System.IO.BinaryWriter writer) => throw new NotImplementedException(); -#endif + + public void Collect(IEnumerable users) + { + lock (_users) + foreach (var user in users) + if (user != null) + if (!user.flags.HasFlag(User.Flags.min) || !_users.TryGetValue(user.id, out var prevUser) || prevUser.flags.HasFlag(User.Flags.min)) + _users[user.id] = user; + else + { // update previously full user from min user: + const User.Flags updated_flags = (User.Flags)0x5DAFE000; + const User.Flags2 updated_flags2 = (User.Flags2)0x711; + // tdlib updated flags: deleted | bot | bot_chat_history | bot_nochats | verified | bot_inline_geo + // | support | scam | fake | bot_attach_menu | premium + // tdesktop non-updated flags: bot | bot_chat_history | bot_nochats | bot_attach_menu + // updated flags2: stories_unavailable (tdlib) | contact_require_premium (tdesktop) + prevUser.flags = (prevUser.flags & ~updated_flags) | (user.flags & updated_flags); + prevUser.flags2 = (prevUser.flags2 & ~updated_flags2) | (user.flags2 & updated_flags2); + prevUser.first_name ??= user.first_name; // tdlib: not updated ; tdesktop: updated only if unknown + prevUser.last_name ??= user.last_name; // tdlib: not updated ; tdesktop: updated only if unknown + //prevUser.username ??= user.username; // tdlib/tdesktop: not updated + prevUser.phone ??= user.phone; // tdlib: updated only if unknown ; tdesktop: not updated + if (prevUser.flags.HasFlag(User.Flags.apply_min_photo) && user.photo != null) + { + prevUser.photo = user.photo; // tdlib/tdesktop: updated on apply_min_photo + prevUser.flags |= User.Flags.has_photo; + } + prevUser.bot_info_version = user.bot_info_version; // tdlib: updated ; tdesktop: not updated + prevUser.restriction_reason = user.restriction_reason; // tdlib: updated ; tdesktop: not updated + prevUser.bot_inline_placeholder = user.bot_inline_placeholder;// tdlib: updated ; tdesktop: ignored + if (user.lang_code != null) + prevUser.lang_code = user.lang_code; // tdlib: updated if present ; tdesktop: ignored + prevUser.emoji_status = user.emoji_status; // tdlib/tdesktop: updated + prevUser.usernames = user.usernames; // tdlib: not updated ; tdesktop: updated + if (user.stories_max_id > 0) + prevUser.stories_max_id = user.stories_max_id; // tdlib: updated if > 0 ; tdesktop: not updated + prevUser.color = user.color; // tdlib/tdesktop: updated + prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet + _users[user.id] = prevUser; + } + } + + public void Collect(IEnumerable chats) + { + lock (_chats) + foreach (var chat in chats) + if (chat is not Channel channel) + _chats[chat.ID] = chat; + 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[channel.id] = channel; + else + { // update previously full channel from min channel: + const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0; + const Channel.Flags2 updated_flags2 = (Channel.Flags2)0x781; + // tdesktop updated flags: broadcast | verified | megagroup | signatures | scam | has_link | slowmode_enabled + // | call_active | call_not_empty | fake | gigagroup | noforwards | join_to_send | join_request | forum + // tdlib nonupdated flags: broadcast | signatures | call_active | call_not_empty | noforwards + prevChannel.flags = (prevChannel.flags & ~updated_flags) | (channel.flags & updated_flags); + prevChannel.flags2 = (prevChannel.flags2 & ~updated_flags2) | (channel.flags2 & updated_flags2); + prevChannel.title = channel.title; // tdlib/tdesktop: updated + prevChannel.username = channel.username; // tdlib/tdesktop: updated + prevChannel.photo = channel.photo; // tdlib: updated if not banned ; tdesktop: updated + prevChannel.restriction_reason = channel.restriction_reason; // tdlib: updated ; tdesktop: not updated + prevChannel.default_banned_rights = channel.default_banned_rights; // tdlib/tdesktop: updated + if (channel.participants_count > 0) + prevChannel.participants_count = channel.participants_count; // tdlib/tdesktop: updated if present + prevChannel.usernames = channel.usernames; // tdlib/tdesktop: updated + prevChannel.color = channel.color; // tdlib: not updated ; tdesktop: updated + prevChannel.profile_color = channel.profile_color; // tdlib/tdesktop: ignored + prevChannel.emoji_status = channel.emoji_status; // tdlib: not updated ; tdesktop: updated + prevChannel.level = channel.level; // tdlib: not updated ; tdesktop: updated + _chats[channel.id] = prevChannel; + } + } + + public bool HasUser(long id) { lock (_users) return _users.ContainsKey(id); } + public bool HasChat(long id) { lock (_chats) return _chats.ContainsKey(id); } } /// Accumulate users/chats found in this structure in your dictionaries, ignoring Min constructors when the full object is already stored /// The structure having a users public static void CollectUsersChats(this IPeerResolver structure, IDictionary users, IDictionary chats) - => structure.UserOrChat(new CollectorPeer { _users = users, _chats = chats }); + => structure.UserOrChat(new CollectorPeer(users, chats)); [EditorBrowsable(EditorBrowsableState.Never)] public static Task 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(); MessageEntityBlockquote lastBlockQuote = null; + int inCode = 0; var sb = new StringBuilder(text); for (int offset = 0; offset < sb.Length;) { @@ -127,9 +134,9 @@ namespace TL { case '\r': sb.Remove(offset, 1); break; case '\\': sb.Remove(offset++, 1); break; - case '*': ProcessEntity(); break; - case '~': ProcessEntity(); break; - case '_': + case '*' when inCode == 0: ProcessEntity(); break; + case '~' when inCode == 0: ProcessEntity(); break; + case '_' when inCode == 0: if (offset + 1 < sb.Length && sb[offset + 1] == '_') { sb.Remove(offset, 1); @@ -139,7 +146,7 @@ namespace TL ProcessEntity(); break; case '|': - if (offset + 1 < sb.Length && sb[offset + 1] == '|') + if (inCode == 0 && offset + 1 < sb.Length && sb[offset + 1] == '|') { sb.Remove(offset, 1); ProcessEntity(); @@ -148,6 +155,7 @@ namespace TL offset++; break; case '`': + int count = entities.Count; if (offset + 2 < sb.Length && sb[offset + 1] == '`' && sb[offset + 2] == '`') { int len = 3; @@ -164,8 +172,9 @@ namespace TL } else ProcessEntity(); + if (entities.Count > count) inCode++; else inCode--; break; - case '>' when offset == 0 || sb[offset - 1] == '\n': + case '>' when inCode == 0 && offset == 0 || sb[offset - 1] == '\n': sb.Remove(offset, 1); if (lastBlockQuote is null || lastBlockQuote.length < offset - lastBlockQuote.offset) entities.Add(lastBlockQuote = new MessageEntityBlockquote { offset = offset, length = -1 }); @@ -175,15 +184,15 @@ namespace TL case '\n' when lastBlockQuote is { length: -1 }: lastBlockQuote.length = ++offset - lastBlockQuote.offset; 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); - goto case '['; - case '[': + break; + case '[' when inCode == 0: entities.Add(new MessageEntityTextUrl { offset = offset, length = -1 }); sb.Remove(offset, 1); break; 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); if (lastIndex >= 0 && entities[lastIndex] is MessageEntityTextUrl textUrl) @@ -213,11 +222,14 @@ namespace TL void ProcessEntity() where T : MessageEntity, new() { + sb.Remove(offset, 1); if (entities.LastOrDefault(e => e.length == -1) is T prevEntity) - prevEntity.length = offset - prevEntity.offset; + if (offset == prevEntity.offset) + entities.Remove(prevEntity); + else + prevEntity.length = offset - prevEntity.offset; else entities.Add(new T { offset = offset, length = -1 }); - sb.Remove(offset, 1); } } if (lastBlockQuote is { length: -1 }) diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs index 4755b21..6726515 100644 --- a/src/TL.Helpers.cs +++ b/src/TL.Helpers.cs @@ -105,7 +105,7 @@ namespace TL { file = inputFile; 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) { diff --git a/src/TL.SchemaFuncs.cs b/src/TL.SchemaFuncs.cs index 8240551..e67c12b 100644 --- a/src/TL.SchemaFuncs.cs +++ b/src/TL.SchemaFuncs.cs @@ -1604,11 +1604,11 @@ namespace TL /// Target user or group /// If a positive value is passed, only messages with identifiers less or equal than the given one will be read public static Task Messages_ReadHistory(this Client client, InputPeer peer, int max_id = default) - => client.Invoke(new Messages_ReadHistory + => client.InvokeAffected(new Messages_ReadHistory { peer = peer, max_id = max_id, - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// Deletes communication history. See Possible codes: 400 (details) /// Just clear history for the current user, without actually removing messages for every chat user @@ -1618,24 +1618,24 @@ namespace TL /// Delete all messages newer than this UNIX timestamp /// Delete all messages older than this UNIX timestamp public static Task 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)), peer = peer, max_id = max_id, min_date = min_date.GetValueOrDefault(), max_date = max_date.GetValueOrDefault(), - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// This method is only for basic Chat. See Terminology in the README to understand what this means
Search for a similar method name starting with Channels_ if you're dealing with a
Deletes messages by their identifiers. See [bots: ✓] Possible codes: 400,403 (details)
/// Whether to delete messages for all participants of the chat /// Message ID list public static Task 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), id = id, - }); + }, 0); /// This method is only for basic Chat. See Terminology in the README to understand what this means
Search for a similar method name starting with Channels_ if you're dealing with a
Confirms receipt of messages by a client, cancels PUSH-notification sending. See
/// Maximum message ID available in a client. @@ -1977,10 +1977,10 @@ namespace TL /// This method is only for basic Chat. See Terminology in the README to understand what this means
Search for a similar method name starting with Channels_ if you're dealing with a
Notifies the sender about the recipient having listened a voice message or watched a video. See
/// Message ID list public static Task Messages_ReadMessageContents(this Client client, params int[] id) - => client.Invoke(new Messages_ReadMessageContents + => client.InvokeAffected(new Messages_ReadMessageContents { id = id, - }); + }, 0); /// Get stickers by emoji See Possible codes: 400 (details) /// The emoji @@ -2646,12 +2646,12 @@ namespace TL /// Dialog /// Mark as read only mentions within the specified forum topic public static Task 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), peer = peer, top_msg_id = top_msg_id.GetValueOrDefault(), - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// Get live location history of a certain user See /// User @@ -3057,12 +3057,12 @@ namespace TL /// Chat where to unpin /// Forum topic where to unpin public static Task 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), peer = peer, top_msg_id = top_msg_id.GetValueOrDefault(), - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// This method is only for basic Chat. See Terminology in the README to understand what this means
Search for a similar method name starting with Channels_ if you're dealing with a
Delete a chat See Possible codes: 400 (details)
/// Chat ID @@ -3075,10 +3075,10 @@ namespace TL /// Delete the entire phone call history. See /// Whether to remove phone call history for participants as well public static Task 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), - }); + }, 0); /// Obtains information about a chat export file, generated by a foreign chat app, click here for more info about imported chats ». See Possible codes: 400 (details) /// Beginning of the message file; up to 100 lines. @@ -3446,12 +3446,12 @@ namespace TL /// Peer /// Mark as read only reactions to messages within the specified forum topic public static Task 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), peer = peer, top_msg_id = top_msg_id.GetValueOrDefault(), - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// View and search recently sent media.
This method does not support pagination. See Possible codes: 400 (details)
/// Optional search query @@ -3859,14 +3859,14 @@ namespace TL /// Delete all messages newer than this UNIX timestamp /// Delete all messages older than this UNIX timestamp public static Task 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)), peer = peer, max_id = max_id, min_date = min_date.GetValueOrDefault(), max_date = max_date.GetValueOrDefault(), - }); + }, peer is InputPeerChannel ipc ? ipc.channel_id : 0); /// Get pinned saved dialogs, see here » for more info. See public static Task Messages_GetPinnedSavedDialogs(this Client client) @@ -4421,11 +4421,11 @@ namespace TL /// Channel/supergroup /// IDs of messages to delete public static Task Channels_DeleteMessages(this Client client, InputChannelBase channel, params int[] id) - => client.Invoke(new Channels_DeleteMessages + => client.InvokeAffected(new Channels_DeleteMessages { channel = channel, id = id, - }); + }, channel.ChannelId); /// Reports some messages from a user in a supergroup as spam; requires administrator rights in the supergroup See Possible codes: 400 (details) /// Supergroup @@ -4811,11 +4811,11 @@ namespace TL /// Supergroup /// The participant whose messages should be deleted public static Task Channels_DeleteParticipantHistory(this Client client, InputChannelBase channel, InputPeer participant) - => client.Invoke(new Channels_DeleteParticipantHistory + => client.InvokeAffected(new Channels_DeleteParticipantHistory { channel = channel, participant = participant, - }); + }, channel.ChannelId); /// Set whether all users should join a discussion group in order to comment on a post » See Possible codes: 400 (details) /// Discussion group @@ -4960,11 +4960,11 @@ namespace TL /// Forum /// Topic ID public static Task Channels_DeleteTopicHistory(this Client client, InputChannelBase channel, int top_msg_id) - => client.Invoke(new Channels_DeleteTopicHistory + => client.InvokeAffected(new Channels_DeleteTopicHistory { channel = channel, top_msg_id = top_msg_id, - }); + }, channel.ChannelId); /// Reorder pinned forum topics See [bots: ✓] /// If not set, the order of only the topics present both server-side and in order will be changed (i.e. mentioning topics not pinned server-side in order will not pin them, and not mentioning topics pinned server-side will not unpin them).
If set, the entire server-side pinned topic list will be replaced with order (i.e. mentioning topics not pinned server-side in order will pin them, and not mentioning topics pinned server-side will unpin them) diff --git a/src/TL.cs b/src/TL.cs index e526e40..ff07a6b 100644 --- a/src/TL.cs +++ b/src/TL.cs @@ -32,9 +32,9 @@ namespace TL 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; /// The value of X in the message, -1 if no variable X was found public readonly int X = x; 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 Messages_AffectedMessages affected; - public override (long, int, int) GetMBox() => (0, affected.pts, affected.pts_count); + public long mbox_id; + public int pts; + public int pts_count; + public override (long, int, int) GetMBox() => (mbox_id, pts, pts_count); #if MTPG public override void WriteTL(BinaryWriter writer) => throw new NotSupportedException(); #endif diff --git a/src/UpdateManager.cs b/src/UpdateManager.cs index b26fa70..615f8ae 100644 --- a/src/UpdateManager.cs +++ b/src/UpdateManager.cs @@ -9,8 +9,6 @@ using TL; namespace WTelegram { - public delegate IPeerInfo UserChatCollector(Dictionary users, Dictionary chats); - public class UpdateManager : IPeerResolver { /// Collected info about Users (only if using the default collector) @@ -37,10 +35,9 @@ namespace WTelegram private readonly Client _client; private readonly Func _onUpdate; - private readonly UserChatCollector _onCollect; + private readonly IPeerCollector _collector; private readonly bool _reentrant; 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 Dictionary _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; @@ -55,17 +52,11 @@ namespace WTelegram /// (optional) Resume session by recovering all updates that occured since this state /// Custom users/chats collector. By default, those are collected in properties Users/Chats /// if your method can be called again even when last async call didn't return yet - public UpdateManager(Client client, Func onUpdate, IDictionary state = null, UserChatCollector collector = null, bool reentrant = false) + public UpdateManager(Client client, Func onUpdate, IDictionary state = null, IPeerCollector collector = null, bool reentrant = false) { _client = client; _onUpdate = onUpdate; - if (collector != null) - _onCollect = collector; - else - { - _collector = new() { _users = Users = [], _chats = Chats = [] }; - _onCollect = _collector.UserOrChat; - } + _collector = collector ?? new Extensions.CollectorPeer(Users = [], Chats = []); if (state == null) _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(); break; case User user when user.flags.HasFlag(User.Flags.self): - if (Users != null) Users[user.id] = user; + _collector.Collect([user]); goto newSession; case NewSessionCreated when _client.User != null: newSession: @@ -130,15 +121,14 @@ namespace WTelegram var now = _lastUpdateStamp = DateTime.UtcNow; var updateList = updates.UpdateList; if (updates is UpdateShortSentMessage sent) - updateList = new[] { new UpdateNewMessage { pts = sent.pts, pts_count = sent.pts_count, message = new Message { - flags = (Message.Flags)sent.flags, - id = sent.id, date = sent.date, entities = sent.entities, media = sent.media, ttl_period = sent.ttl_period, - } } }; - else if (Users != null) - if (updates is UpdateShortMessage usm && !Users.ContainsKey(usm.user_id)) - (await _client.Updates_GetDifference(usm.pts - usm.pts_count, usm.date, 0)).UserOrChat(_collector); - else if (updates is UpdateShortChatMessage uscm && (!Users.ContainsKey(uscm.from_id) || !Chats.ContainsKey(uscm.chat_id))) - (await _client.Updates_GetDifference(uscm.pts - uscm.pts_count, uscm.date, 0)).UserOrChat(_collector); + updateList = [new UpdateNewMessage { pts = sent.pts, pts_count = sent.pts_count, message = new Message { + flags = (Message.Flags)sent.flags, + id = sent.id, date = sent.date, entities = sent.entities, media = sent.media, ttl_period = sent.ttl_period, + } }]; + else if (updates is UpdateShortMessage usm && !_collector.HasUser(usm.user_id)) + RaiseCollect(await _client.Updates_GetDifference(usm.pts - usm.pts_count, usm.date, 0)); + else if (updates is UpdateShortChatMessage uscm && (!_collector.HasUser(uscm.from_id) || !_collector.HasChat(uscm.chat_id))) + RaiseCollect(await _client.Updates_GetDifference(uscm.pts - uscm.pts_count, uscm.date, 0)); bool ptsChanged = false, gotUPts = false; 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)}"); 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)}"); + else if (local.pts + pts_count - pts >= 0) + getDiffSuccess = true; else { 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 { @@ -382,6 +374,11 @@ namespace WTelegram catch (Exception 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 { @@ -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 users, Dictionary chats) { try @@ -449,11 +454,12 @@ namespace WTelegram if (chat is Channel channel && !channel.flags.HasFlag(Channel.Flags.min)) if (_local.TryGetValue(channel.id, out var local)) local.access_hash = channel.access_hash; - _onCollect(users, chats); + _collector.Collect(users.Values); + _collector.Collect(chats.Values); } 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 /// returns a or for the given Peer public IPeerInfo UserOrChat(Peer peer) => peer?.UserOrChat(Users, Chats); } + + public interface IPeerCollector + { + void Collect(IEnumerable users); + void Collect(IEnumerable chats); + bool HasUser(long id); + bool HasChat(long id); + } } namespace TL @@ -530,7 +544,7 @@ namespace TL /// Resume session by recovering all updates that occured since the state saved in this file /// Custom users/chats collector. By default, those are collected in properties Users/Chats /// if your method can be called again even when last async call didn't return yet - public static UpdateManager WithUpdateManager(this Client client, Func onUpdate, string statePath, UserChatCollector collector = null, bool reentrant = false) + public static UpdateManager WithUpdateManager(this Client client, Func onUpdate, string statePath, IPeerCollector collector = null, bool reentrant = false) => new(client, onUpdate, UpdateManager.LoadState(statePath), collector, reentrant); /// Manager ensuring that you receive Telegram updates in correct order, without missing any @@ -538,7 +552,7 @@ namespace TL /// (optional) Resume session by recovering all updates that occured since this state /// Custom users/chats collector. By default, those are collected in properties Users/Chats /// if your method can be called again even when last async call didn't return yet - public static UpdateManager WithUpdateManager(this Client client, Func onUpdate, IDictionary state = null, UserChatCollector collector = null, bool reentrant = false) + public static UpdateManager WithUpdateManager(this Client client, Func onUpdate, IDictionary state = null, IPeerCollector collector = null, bool reentrant = false) => new(client, onUpdate, state, collector, reentrant); } } \ No newline at end of file diff --git a/src/WTelegramClient.csproj b/src/WTelegramClient.csproj index bf00d11..694692f 100644 --- a/src/WTelegramClient.csproj +++ b/src/WTelegramClient.csproj @@ -44,7 +44,7 @@ --> - +