mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2025-12-06 06:52:01 +01:00
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:
parent
210a3365e5
commit
abeed476e7
8
FAQ.md
8
FAQ.md
|
|
@ -349,13 +349,11 @@ 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.
|
||||||
The `manager.Users` and `manager.Chats` dictionaries will collect the users/chats data from 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)`
|
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.
|
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:
|
Notes:
|
||||||
|
|
|
||||||
|
|
@ -164,14 +164,14 @@ See [FAQ #4](https://wiz0u.github.io/WTelegramClient/FAQ#access-hash) to learn m
|
||||||
|
|
||||||
# Other things to know
|
# 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.
|
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.
|
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)
|
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.
|
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**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,44 @@ namespace TL.Generator;
|
||||||
[Generator]
|
[Generator]
|
||||||
public class MTProtoGenerator : IIncrementalGenerator
|
public class MTProtoGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName("TL.TLDefAttribute",
|
var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName("TL.TLDefAttribute",
|
||||||
(_, _) => true, (context, _) => (ClassDeclarationSyntax)context.TargetNode);
|
(_, _) => true, (context, _) => (ClassDeclarationSyntax)context.TargetNode);
|
||||||
var source = context.CompilationProvider.Combine(classDeclarations.Collect());
|
var source = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||||
context.RegisterSourceOutput(source, Execute);
|
context.RegisterSourceOutput(source, Execute);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Execute(SourceProductionContext context, (Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes) unit)
|
static void Execute(SourceProductionContext context, (Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes) unit)
|
||||||
{
|
{
|
||||||
var object_ = unit.compilation.GetSpecialType(SpecialType.System_Object);
|
var object_ = unit.compilation.GetSpecialType(SpecialType.System_Object);
|
||||||
if (unit.compilation.GetTypeByMetadataName("TL.TLDefAttribute") is not { } tlDefAttribute) return;
|
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.IfFlagAttribute") is not { } ifFlagAttribute) return;
|
||||||
if (unit.compilation.GetTypeByMetadataName("TL.Layer") is not { } layer) return;
|
if (unit.compilation.GetTypeByMetadataName("TL.Layer") is not { } layer) return;
|
||||||
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();
|
||||||
.AppendLine("\t\tpublic static IObject ReadTL(this BinaryReader reader, uint ctorId = 0) => (ctorId != 0 ? ctorId : reader.ReadUInt32()) switch")
|
source
|
||||||
.AppendLine("\t\t{");
|
.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)
|
foreach (var classDecl in unit.classes)
|
||||||
{
|
{
|
||||||
var semanticModel = unit.compilation.GetSemanticModel(classDecl.SyntaxTree);
|
var semanticModel = unit.compilation.GetSemanticModel(classDecl.SyntaxTree);
|
||||||
if (semanticModel.GetDeclaredSymbol(classDecl) is not { } symbol) continue;
|
if (semanticModel.GetDeclaredSymbol(classDecl) is not { } symbol) continue;
|
||||||
var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
|
var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
|
||||||
if (tldef == null) continue;
|
if (tldef == null) continue;
|
||||||
var id = (uint)tldef.ConstructorArguments[0].Value;
|
var id = (uint)tldef.ConstructorArguments[0].Value;
|
||||||
var inheritBefore = (bool?)tldef.NamedArguments.FirstOrDefault(k => k.Key == "inheritBefore").Value.Value ?? false;
|
var inheritBefore = (bool?)tldef.NamedArguments.FirstOrDefault(k => k.Key == "inheritBefore").Value.Value ?? false;
|
||||||
StringBuilder writeTl = new(), ctorTL = new();
|
StringBuilder writeTl = new(), ctorTL = new();
|
||||||
|
|
@ -55,7 +65,10 @@ public class MTProtoGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
if (parentMethods == null)
|
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();
|
parentClasses[name] = writeTl.ToString();
|
||||||
writeTl.Clear();
|
writeTl.Clear();
|
||||||
}
|
}
|
||||||
|
|
@ -72,40 +85,43 @@ 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\t{");
|
.AppendLine($"\t\tpublic new {name} ReadTL(BinaryReader reader)")
|
||||||
|
.AppendLine("\t\t{");
|
||||||
writeTl
|
writeTl
|
||||||
.AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)")
|
.AppendLine("\t\t[EditorBrowsable(EditorBrowsableState.Never)]")
|
||||||
.AppendLine("\t\t{")
|
.AppendLine($"\t\tpublic {override_}void WriteTL(BinaryWriter writer)")
|
||||||
.AppendLine($"\t\t\twriter.Write(0x{id:X8});");
|
.AppendLine("\t\t{")
|
||||||
var members = symbol.GetMembers().ToList();
|
.AppendLine($"\t\t\twriter.Write(0x{id:X8});");
|
||||||
for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType)
|
var members = symbol.GetMembers().ToList();
|
||||||
if (inheritBefore) members.InsertRange(0, parent.GetMembers());
|
for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType)
|
||||||
else members.AddRange(parent.GetMembers());
|
if (inheritBefore) members.InsertRange(0, parent.GetMembers());
|
||||||
|
else members.AddRange(parent.GetMembers());
|
||||||
foreach (var member in members.OfType<IFieldSymbol>())
|
foreach (var member in members.OfType<IFieldSymbol>())
|
||||||
{
|
{
|
||||||
if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue;
|
if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue;
|
||||||
ctorTL.Append("\t\t\t");
|
ctorTL.Append("\t\t\t");
|
||||||
writeTl.Append("\t\t\t");
|
writeTl.Append("\t\t\t");
|
||||||
var ifFlag = (int?)member.GetAttributes().FirstOrDefault(a => a.AttributeClass == ifFlagAttribute)?.ConstructorArguments[0].Value;
|
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) "
|
var condition = ifFlag < 32 ? $"if (((uint)flags & 0x{1 << ifFlag:X}) != 0) "
|
||||||
: $"if (((uint)flags2 & 0x{1 << (ifFlag - 32):X}) != 0) ";
|
: $"if (((uint)flags2 & 0x{1 << (ifFlag - 32):X}) != 0) ";
|
||||||
ctorTL.Append(condition);
|
ctorTL.Append(condition);
|
||||||
writeTl.Append(condition);
|
writeTl.Append(condition);
|
||||||
}
|
}
|
||||||
string memberType = member.Type.ToString();
|
string memberType = member.Type.ToString();
|
||||||
switch (memberType)
|
switch (memberType)
|
||||||
{
|
{
|
||||||
case "int":
|
case "int":
|
||||||
ctorTL.AppendLine($"{member.Name} = reader.ReadInt32();");
|
ctorTL.AppendLine($"{member.Name} = reader.ReadInt32();");
|
||||||
writeTl.AppendLine($"writer.Write({member.Name});");
|
writeTl.AppendLine($"writer.Write({member.Name});");
|
||||||
|
|
@ -127,34 +143,34 @@ public class MTProtoGenerator : IIncrementalGenerator
|
||||||
writeTl.AppendLine($"writer.WriteTLStamp({member.Name});");
|
writeTl.AppendLine($"writer.WriteTLStamp({member.Name});");
|
||||||
break;
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
ctorTL.AppendLine($"{member.Name} = reader.ReadTLString();");
|
ctorTL.AppendLine($"{member.Name} = reader.ReadTLString();");
|
||||||
writeTl.AppendLine($"writer.WriteTLString({member.Name});");
|
writeTl.AppendLine($"writer.WriteTLString({member.Name});");
|
||||||
|
break;
|
||||||
|
case "byte[]":
|
||||||
|
ctorTL.AppendLine($"{member.Name} = reader.ReadTLBytes();");
|
||||||
|
writeTl.AppendLine($"writer.WriteTLBytes({member.Name});");
|
||||||
break;
|
break;
|
||||||
case "byte[]":
|
|
||||||
ctorTL.AppendLine($"{member.Name} = reader.ReadTLBytes();");
|
|
||||||
writeTl.AppendLine($"writer.WriteTLBytes({member.Name});");
|
|
||||||
break;
|
|
||||||
case "TL.Int128":
|
case "TL.Int128":
|
||||||
ctorTL.AppendLine($"{member.Name} = new Int128(reader);");
|
ctorTL.AppendLine($"{member.Name} = new Int128(reader);");
|
||||||
writeTl.AppendLine($"writer.Write({member.Name});");
|
writeTl.AppendLine($"writer.Write({member.Name});");
|
||||||
break;
|
break;
|
||||||
case "TL.Int256":
|
case "TL.Int256":
|
||||||
ctorTL.AppendLine($"{member.Name} = new Int256(reader);");
|
ctorTL.AppendLine($"{member.Name} = new Int256(reader);");
|
||||||
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>":
|
||||||
ctorTL.AppendLine($"{member.Name} = {(memberType == "TL.IObject" ? "" : $"({memberType})")}reader.ReadTL();");
|
ctorTL.AppendLine($"{member.Name} = {(memberType == "TL.IObject" ? "" : $"({memberType})")}reader.ReadTL();");
|
||||||
writeTl.AppendLine($"{member.Name}.WriteTL(writer);");
|
writeTl.AppendLine($"{member.Name}.WriteTL(writer);");
|
||||||
break;
|
break;
|
||||||
case "System.Collections.Generic.Dictionary<long, TL.User>":
|
case "System.Collections.Generic.Dictionary<long, TL.User>":
|
||||||
ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary<User>();");
|
ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary<User>();");
|
||||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
||||||
break;
|
break;
|
||||||
case "System.Collections.Generic.Dictionary<long, TL.ChatBase>":
|
case "System.Collections.Generic.Dictionary<long, TL.ChatBase>":
|
||||||
ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary<ChatBase>();");
|
ctorTL.AppendLine($"{member.Name} = reader.ReadTLDictionary<ChatBase>();");
|
||||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
||||||
break;
|
break;
|
||||||
|
|
@ -180,56 +196,49 @@ public class MTProtoGenerator : IIncrementalGenerator
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
writeTl.AppendLine($"Cannot serialize {memberType}");
|
writeTl.AppendLine($"Cannot serialize {memberType}");
|
||||||
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('{');
|
||||||
foreach (var method in namesp.Value)
|
foreach (var method in namesp.Value)
|
||||||
source.AppendLine().Append("\tpartial class ").AppendLine(method.Key).AppendLine("\t{").Append(method.Value).AppendLine("\t}");
|
source.AppendLine().Append("\tpartial class ").AppendLine(method.Key).AppendLine("\t{").Append(method.Value).AppendLine("\t}");
|
||||||
source.AppendLine("}").AppendLine();
|
source.AppendLine("}").AppendLine();
|
||||||
}
|
}
|
||||||
string text = source.ToString();
|
string text = source.ToString();
|
||||||
Debug.Write(text);
|
Debug.Write(text);
|
||||||
context.AddSource("TL.Generated.cs", text);
|
context.AddSource("TL.Generated.cs", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, uint> LoadNullables(INamedTypeSymbol layer)
|
private static Dictionary<string, uint> LoadNullables(INamedTypeSymbol layer)
|
||||||
{
|
{
|
||||||
var nullables = layer.GetMembers("Nullables").Single() as IFieldSymbol;
|
var nullables = layer.GetMembers("Nullables").Single() as IFieldSymbol;
|
||||||
var initializer = nullables.DeclaringSyntaxReferences[0].GetSyntax().ToString();
|
var initializer = nullables.DeclaringSyntaxReferences[0].GetSyntax().ToString();
|
||||||
var table = new Dictionary<string, uint>();
|
var table = new Dictionary<string, uint>();
|
||||||
foreach (var line in initializer.Split('\n'))
|
foreach (var line in initializer.Split('\n'))
|
||||||
{
|
{
|
||||||
int index = line.IndexOf("[typeof(");
|
int index = line.IndexOf("[typeof(");
|
||||||
if (index == -1) continue;
|
if (index == -1) continue;
|
||||||
int index2 = line.IndexOf(')', index += 8);
|
int index2 = line.IndexOf(')', index += 8);
|
||||||
string className = "TL." + line.Substring(index, index2 - index);
|
string className = "TL." + line.Substring(index, index2 - index);
|
||||||
index = line.IndexOf("= 0x", index2);
|
index = line.IndexOf("= 0x", index2);
|
||||||
if (index == -1) continue;
|
if (index == -1) continue;
|
||||||
index2 = line.IndexOf(',', index += 4);
|
index2 = line.IndexOf(',', index += 4);
|
||||||
table[className] = uint.Parse(line.Substring(index, index2 - index), System.Globalization.NumberStyles.HexNumber);
|
table[className] = uint.Parse(line.Substring(index, index2 - index), System.Globalization.NumberStyles.HexNumber);
|
||||||
}
|
}
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,98 +5,104 @@ 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);
|
||||||
lock (_users)
|
if (chats != null) Collect(chats.Values);
|
||||||
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;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
#if MTPG
|
|
||||||
public override void WriteTL(System.IO.BinaryWriter writer) => throw new NotImplementedException();
|
public void Collect(IEnumerable<TL.User> users)
|
||||||
#endif
|
{
|
||||||
|
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<ChatBase> 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); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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)
|
||||||
prevEntity.length = offset - prevEntity.offset;
|
if (offset == prevEntity.offset)
|
||||||
|
entities.Remove(prevEntity);
|
||||||
|
else
|
||||||
|
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 })
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
10
src/TL.cs
10
src/TL.cs
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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'">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue