mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-28 19:24:19 +01:00
Compare commits
46 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f531f4966 | ||
|
|
1912632722 | ||
|
|
9c839128bb | ||
|
|
d3ad4789a1 | ||
|
|
208ab626c1 | ||
|
|
bfc8e0e1b5 | ||
|
|
e923d65d53 | ||
|
|
4ad2f0a212 | ||
|
|
30bc536ebc | ||
|
|
d6fdcab440 | ||
|
|
9ec2f31f72 | ||
|
|
4ccfddd22e | ||
|
|
9693037ef2 | ||
|
|
40bcf69bfb | ||
|
|
4875f75774 | ||
|
|
2e95576be5 | ||
|
|
48d005b605 | ||
|
|
a5323eaa86 | ||
|
|
610d059b4c | ||
|
|
3f1036a559 | ||
|
|
4578dea3a3 | ||
|
|
253249e06a | ||
|
|
eb52dccfa7 | ||
|
|
a9bbdb9fc4 | ||
|
|
5f411d45f9 | ||
|
|
e16e39bfba | ||
|
|
7faa3873f8 | ||
|
|
d9e4b7cc0f | ||
|
|
30a982b0ac | ||
|
|
e9543a690b | ||
|
|
a8fa32dfd5 | ||
|
|
56ba15bc13 | ||
|
|
a3f41330b5 | ||
|
|
52d948af2a | ||
|
|
bdcf389ed2 | ||
|
|
4f7954db61 | ||
|
|
fa90e236e7 | ||
|
|
25990a8477 | ||
|
|
3ff1200068 | ||
|
|
04e043222e | ||
|
|
d49d620edd | ||
|
|
5358471574 | ||
|
|
8836f8372b | ||
|
|
6fb59286bd | ||
|
|
eaea2d051a | ||
|
|
6d238dc528 |
33
.github/workflows/dev.yml
vendored
33
.github/workflows/dev.yml
vendored
|
|
@ -12,14 +12,16 @@ env:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
id-token: write # enable GitHub OIDC token issuance for this job
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 100
|
||||
fetch-depth: 30
|
||||
- name: Determine version
|
||||
run: |
|
||||
git fetch --depth=100 --tags
|
||||
git fetch --depth=30 --tags
|
||||
DESCR_TAG=$(git describe --tags)
|
||||
COMMITS=${DESCR_TAG#*-}
|
||||
COMMITS=${COMMITS%-*}
|
||||
|
|
@ -29,24 +31,37 @@ jobs:
|
|||
if [[ "$RELEASE_VERSION" > "$NEXT_VERSION" ]] then VERSION=$RELEASE_VERSION; else VERSION=$NEXT_VERSION; fi
|
||||
echo Last tag: $LAST_TAG · Next version: $NEXT_VERSION · Release version: $RELEASE_VERSION · Build version: $VERSION
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
# - name: Setup .NET
|
||||
# uses: actions/setup-dotnet@v4
|
||||
# with:
|
||||
# dotnet-version: 8.0.x
|
||||
- name: Pack
|
||||
run: dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION "-p:ReleaseNotes=\"$RELEASE_NOTES\"" --output packages
|
||||
run: |
|
||||
RELEASE_NOTES=${RELEASE_NOTES//$'\n'/%0A}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//\"/%22}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//,/%2C}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//;/%3B}
|
||||
dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION -p:ReleaseNotes="$RELEASE_NOTES" --output packages
|
||||
# - name: Upload artifact
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: packages
|
||||
# path: packages/*.nupkg
|
||||
|
||||
- name: NuGet login (OIDC → temp API key)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ${{ secrets.NUGET_USER }}
|
||||
- name: Nuget push
|
||||
run: dotnet nuget push packages/*.nupkg --api-key ${{secrets.NUGETAPIKEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
|
||||
run: dotnet nuget push packages/*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
|
||||
|
||||
- name: Deployment Notification
|
||||
env:
|
||||
JSON: |
|
||||
{
|
||||
"status": "success", "complete": true, "commitMessage": ${{ toJSON(github.event.head_commit.message) }},
|
||||
"status": "success", "complete": true, "commitMessage": ${{ toJSON(env.RELEASE_NOTES) }},
|
||||
"message": "{ \"commitId\": \"${{ github.sha }}\", \"buildNumber\": \"${{ env.VERSION }}\", \"repoName\": \"${{ github.repository }}\"}"
|
||||
}
|
||||
run: |
|
||||
|
|
|
|||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
|
|
@ -21,7 +21,8 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # For git tag
|
||||
contents: write # For git tag
|
||||
id-token: write # enable GitHub OIDC token issuance for this job
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -37,20 +38,45 @@ jobs:
|
|||
if [[ "$RELEASE_VERSION" > "$NEXT_VERSION" ]] then VERSION=$RELEASE_VERSION; else VERSION=$NEXT_VERSION; fi
|
||||
echo Last tag: $LAST_TAG · Next version: $NEXT_VERSION · Release version: $RELEASE_VERSION · Build version: $VERSION
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Pack
|
||||
run: dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION "-p:ReleaseNotes=\"$RELEASE_NOTES\"" --output packages
|
||||
run: |
|
||||
RELEASE_NOTES=${RELEASE_NOTES//|/%0A}
|
||||
RELEASE_NOTES=${RELEASE_NOTES// - /%0A- }
|
||||
RELEASE_NOTES=${RELEASE_NOTES// /%0A%0A}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//$'\n'/%0A}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//\"/%22}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//,/%2C}
|
||||
RELEASE_NOTES=${RELEASE_NOTES//;/%3B}
|
||||
dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION -p:ReleaseNotes="$RELEASE_NOTES" --output packages
|
||||
# - name: Upload artifact
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: packages
|
||||
# path: packages/*.nupkg
|
||||
|
||||
- name: NuGet login (OIDC → temp API key)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ${{ secrets.NUGET_USER }}
|
||||
- name: Nuget push
|
||||
run: dotnet nuget push packages/*.nupkg --api-key ${{secrets.NUGETAPIKEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
|
||||
run: dotnet nuget push packages/*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
|
||||
|
||||
- name: Git tag
|
||||
run: |
|
||||
git tag $VERSION
|
||||
git push --tags
|
||||
- name: Deployment Notification
|
||||
env:
|
||||
JSON: |
|
||||
{
|
||||
"status": "success", "complete": true, "commitMessage": ${{ toJSON(env.RELEASE_NOTES) }},
|
||||
"message": "{ \"commitId\": \"${{ github.sha }}\", \"buildNumber\": \"${{ env.VERSION }}\", \"repoName\": \"${{ github.repository }}\"}"
|
||||
}
|
||||
run: |
|
||||
curl -X POST -H "Content-Type: application/json" -d "$JSON" ${{ secrets.DEPLOYED_WEBHOOK }}
|
||||
|
|
|
|||
3
.github/workflows/telegram-api.yml
vendored
3
.github/workflows/telegram-api.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
if: contains(github.event.issue.labels.*.name, 'telegram api')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v3.0.0
|
||||
- uses: dessant/support-requests@v4
|
||||
with:
|
||||
support-label: 'telegram api'
|
||||
issue-comment: >
|
||||
|
|
@ -26,3 +26,4 @@ jobs:
|
|||
|
||||
If the above links didn't answer your problem, [click here to ask your question on **StackOverflow**](https://stackoverflow.com/questions/ask?tags=c%23+wtelegramclient+telegram-api) so the whole community can help and benefit.
|
||||
close-issue: true
|
||||
issue-close-reason: 'not planned'
|
||||
|
|
|
|||
37
EXAMPLES.md
37
EXAMPLES.md
|
|
@ -210,7 +210,7 @@ that simplifies the download of a photo/document/file once you get a reference t
|
|||
|
||||
See [Examples/Program_DownloadSavedMedia.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_DownloadSavedMedia.cs?ts=4#L28) that download all media files you forward to yourself (Saved Messages)
|
||||
|
||||
_Note: To abort an ongoing download, you can throw an exception via the `progress` callback argument._
|
||||
_Note: To abort an ongoing download, you can throw an exception via the `progress` callback argument. Example: `(t,s) => ct.ThrowIfCancellationRequested()`_
|
||||
|
||||
<a name="upload"></a>
|
||||
## Upload a media file and post it with caption to a chat
|
||||
|
|
@ -224,6 +224,41 @@ var inputFile = await client.UploadFileAsync(Filepath);
|
|||
await client.SendMediaAsync(peer, "Here is the photo", inputFile);
|
||||
```
|
||||
|
||||
<a name="upload-video"></a>
|
||||
## Upload a streamable video with optional custom thumbnail
|
||||
```csharp
|
||||
var chats = await client.Messages_GetAllChats();
|
||||
InputPeer peer = chats.chats[1234567890]; // the chat we want
|
||||
const string videoPath = @"C:\...\video.mp4";
|
||||
const string thumbnailPath = @"C:\...\thumbnail.jpg";
|
||||
|
||||
// Extract video information using FFMpegCore or similar library
|
||||
var mediaInfo = await FFmpeg.GetMediaInfo(videoPath);
|
||||
var videoStream = mediaInfo.VideoStreams.FirstOrDefault();
|
||||
int width = videoStream?.Width ?? 0;
|
||||
int height = videoStream?.Height ?? 0;
|
||||
int duration = (int)mediaInfo.Duration.TotalSeconds;
|
||||
|
||||
// Upload video file
|
||||
var inputFile = await Client.UploadFileAsync(videoPath);
|
||||
|
||||
// Prepare InputMedia structure with video attributes
|
||||
var media = new InputMediaUploadedDocument(inputFile, "video/mp4",
|
||||
new DocumentAttributeVideo { w = width, h = height, duration = duration,
|
||||
flags = DocumentAttributeVideo.Flags.supports_streaming });
|
||||
if (thumbnailPath != null)
|
||||
{
|
||||
// upload custom thumbnail and complete InputMedia structure
|
||||
var inputThumb = await client.UploadFileAsync(thumbnailPath);
|
||||
media.thumb = inputThumb;
|
||||
media.flags |= InputMediaUploadedDocument.Flags.has_thumb;
|
||||
}
|
||||
|
||||
// Send the media message
|
||||
await client.SendMessageAsync(peer, "caption", media);
|
||||
```
|
||||
*Note: This example requires FFMpegCore NuGet package for video metadata extraction. You can also manually set width, height, and duration if you know the video properties.*
|
||||
|
||||
<a name="album"></a>
|
||||
## Send a grouped media album using photos from various sources
|
||||
```csharp
|
||||
|
|
|
|||
|
|
@ -62,10 +62,8 @@ namespace WTelegramClientTest
|
|||
{
|
||||
private readonly NpgsqlConnection _sql;
|
||||
private readonly string _sessionName;
|
||||
private byte[] _data;
|
||||
private int _dataLen;
|
||||
private DateTime _lastWrite;
|
||||
private Task _delayedWrite;
|
||||
private readonly byte[] _data;
|
||||
private readonly int _dataLen;
|
||||
|
||||
/// <param name="databaseUrl">Heroku DB URL of the form "postgres://user:password@host:port/database"</param>
|
||||
/// <param name="sessionName">Entry name for the session data in the WTelegram_sessions table (default: "Heroku")</param>
|
||||
|
|
@ -85,7 +83,6 @@ namespace WTelegramClientTest
|
|||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_delayedWrite?.Wait();
|
||||
_sql.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -97,18 +94,9 @@ namespace WTelegramClientTest
|
|||
|
||||
public override void Write(byte[] buffer, int offset, int count) // Write call and buffer modifications are done within a lock()
|
||||
{
|
||||
_data = buffer; _dataLen = count;
|
||||
if (_delayedWrite != null) return;
|
||||
var left = 1000 - (int)(DateTime.UtcNow - _lastWrite).TotalMilliseconds;
|
||||
if (left < 0)
|
||||
{
|
||||
using var cmd = new NpgsqlCommand($"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data", _sql);
|
||||
cmd.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
|
||||
cmd.ExecuteNonQuery();
|
||||
_lastWrite = DateTime.UtcNow;
|
||||
}
|
||||
else // delay writings for a full second
|
||||
_delayedWrite = Task.Delay(left).ContinueWith(t => { lock (this) { _delayedWrite = null; Write(_data, 0, _dataLen); } });
|
||||
using var cmd = new NpgsqlCommand($"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data", _sql);
|
||||
cmd.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public override long Length => _dataLen;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[](https://corefork.telegram.org/methods)
|
||||
[](https://corefork.telegram.org/methods)
|
||||
[](https://www.nuget.org/packages/WTelegramClient/)
|
||||
[](https://www.nuget.org/packages/WTelegramClient/absoluteLatest)
|
||||
[](https://buymeacoffee.com/wizou)
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
This library allows you to connect to Telegram and control a user programmatically (or a bot, but [WTelegramBot](https://www.nuget.org/packages/WTelegramBot) is much easier for that).
|
||||
All the Telegram Client APIs (MTProto) are supported so you can do everything the user could do with a full Telegram GUI client.
|
||||
|
||||
Library was developed solely by one unemployed guy. [Donations are welcome](https://buymeacoffee.com/wizou).
|
||||
Library was developed solely by one unemployed guy. [Donations](https://buymeacoffee.com/wizou) or [Patreon memberships are welcome](https://patreon.com/wizou).
|
||||
|
||||
This ReadMe is a **quick but important tutorial** to learn the fundamentals about this library. Please read it all.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
var nullables = LoadNullables(layer);
|
||||
var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods
|
||||
var tableTL = new StringBuilder();
|
||||
var methodsTL = new StringBuilder();
|
||||
var source = new StringBuilder();
|
||||
source
|
||||
.AppendLine("using System;")
|
||||
|
|
@ -46,6 +47,9 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
tableTL
|
||||
.AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Table = new()")
|
||||
.AppendLine("\t\t{");
|
||||
methodsTL
|
||||
.AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Methods = new()")
|
||||
.AppendLine("\t\t{");
|
||||
|
||||
foreach (var classDecl in unit.classes)
|
||||
{
|
||||
|
|
@ -54,7 +58,6 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
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(), readTL = new();
|
||||
var ns = symbol.BaseType.ContainingNamespace.ToString();
|
||||
var name = symbol.BaseType.Name;
|
||||
|
|
@ -80,15 +83,20 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
ns = symbol.ContainingNamespace.ToString();
|
||||
name = symbol.Name;
|
||||
if (!namespaces.TryGetValue(ns, out var classes)) namespaces[ns] = classes = [];
|
||||
if (name is "_Message" or "RpcResult" or "MsgCopy")
|
||||
if (name is "_Message" or "MsgCopy")
|
||||
{
|
||||
classes[name] = "\t\tpublic void WriteTL(BinaryWriter writer) => throw new NotSupportedException();";
|
||||
continue;
|
||||
}
|
||||
if (id == 0x3072CFA1) // GzipPacked
|
||||
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = reader => (IObject)reader.ReadTLGzipped(typeof(IObject)),");
|
||||
else if (name != "Null" && (ns != "TL.Methods" || name == "Ping"))
|
||||
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}.ReadTL,");
|
||||
else if (name != "Null")
|
||||
{
|
||||
if (ns == "TL.Methods")
|
||||
methodsTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}{(symbol.IsGenericType ? "<object>" : "")}.ReadTL,");
|
||||
if (ns != "TL.Methods" || name == "Ping")
|
||||
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}.ReadTL,");
|
||||
}
|
||||
var override_ = symbol.BaseType == object_ ? "" : "override ";
|
||||
if (name == "Messages_AffectedMessages") override_ = "virtual ";
|
||||
//if (symbol.Constructors[0].IsImplicitlyDeclared)
|
||||
|
|
@ -105,8 +113,12 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
.AppendLine($"\t\t\twriter.Write(0x{id:X8});");
|
||||
var members = symbol.GetMembers().ToList();
|
||||
for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType)
|
||||
{
|
||||
var inheritBefore = (bool?)tldef.NamedArguments.FirstOrDefault(k => k.Key == "inheritBefore").Value.Value ?? false;
|
||||
if (inheritBefore) members.InsertRange(0, parent.GetMembers());
|
||||
else members.AddRange(parent.GetMembers());
|
||||
tldef = parent.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
|
||||
}
|
||||
foreach (var member in members.OfType<IFieldSymbol>())
|
||||
{
|
||||
if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue;
|
||||
|
|
@ -164,25 +176,34 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
writeTl.AppendLine($"writer.WriteTLMessages({member.Name});");
|
||||
break;
|
||||
case "TL.IObject": case "TL.IMethod<X>":
|
||||
readTL.AppendLine($"r.{member.Name} = {(memberType == "TL.IObject" ? "" : $"({memberType})")}reader.ReadTLObject();");
|
||||
readTL.AppendLine($"r.{member.Name} = {(memberType == "TL.IObject" ? "reader.ReadTLObject()" : "reader.ReadTLMethod<X>()")};");
|
||||
writeTl.AppendLine($"{member.Name}.WriteTL(writer);");
|
||||
break;
|
||||
case "System.Collections.Generic.Dictionary<long, TL.User>":
|
||||
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<User>();");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}?.Values.ToArray());");
|
||||
break;
|
||||
case "System.Collections.Generic.Dictionary<long, TL.ChatBase>":
|
||||
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<ChatBase>();");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name}?.Values.ToArray());");
|
||||
break;
|
||||
case "object":
|
||||
readTL.AppendLine($"r.{member.Name} = reader.ReadTLObject();");
|
||||
writeTl.AppendLine($"writer.WriteTLValue({member.Name}, {member.Name}.GetType());");
|
||||
break;
|
||||
default:
|
||||
if (member.Type is IArrayTypeSymbol arrayType)
|
||||
{
|
||||
if (name is "FutureSalts")
|
||||
{
|
||||
readTL.AppendLine($"r.{member.Name} = reader.ReadTLRawVector<{memberType.Substring(0, memberType.Length - 2)}>(0x0949D9DC).ToArray();");
|
||||
writeTl.AppendLine($"writer.WriteTLRawVector({member.Name}, 16);");
|
||||
}
|
||||
else
|
||||
{
|
||||
readTL.AppendLine($"r.{member.Name} = reader.ReadTLVector<{memberType.Substring(0, memberType.Length - 2)}>();");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name});");
|
||||
writeTl.AppendLine($"writer.WriteTLVector({member.Name});");
|
||||
}
|
||||
}
|
||||
else if (member.Type.BaseType.SpecialType == SpecialType.System_Enum)
|
||||
{
|
||||
|
|
@ -210,7 +231,8 @@ public class MTProtoGenerator : IIncrementalGenerator
|
|||
foreach (var nullable in nullables)
|
||||
tableTL.AppendLine($"\t\t\t[0x{nullable.Value:X8}] = null,");
|
||||
tableTL.AppendLine("\t\t};");
|
||||
namespaces["TL"]["Layer"] = tableTL.ToString();
|
||||
methodsTL.AppendLine("\t\t};");
|
||||
namespaces["TL"]["Layer"] = tableTL.ToString() + methodsTL.ToString();
|
||||
foreach (var namesp in namespaces)
|
||||
{
|
||||
source.Append("namespace ").AppendLine(namesp.Key).Append('{');
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ namespace WTelegram
|
|||
{
|
||||
partial class Client
|
||||
{
|
||||
#region Client TL Helpers
|
||||
/// <summary>Used to indicate progression of file download/upload</summary>
|
||||
/// <param name="transmitted">transmitted bytes</param>
|
||||
/// <param name="totalSize">total size of file in bytes, or 0 if unknown</param>
|
||||
|
|
@ -36,9 +35,10 @@ namespace WTelegram
|
|||
var client = await GetClientForDC(-_dcSession.DcID, true);
|
||||
using (stream)
|
||||
{
|
||||
const long SMALL_FILE_MAX_SIZE = 10 << 20;
|
||||
bool hasLength = stream.CanSeek;
|
||||
long transmitted = 0, length = hasLength ? stream.Length : -1;
|
||||
bool isBig = !hasLength || length >= 10 * 1024 * 1024;
|
||||
bool isBig = !hasLength || length > SMALL_FILE_MAX_SIZE;
|
||||
int file_total_parts = hasLength ? (int)((length - 1) / FilePartSize) + 1 : -1;
|
||||
long file_id = Helpers.RandomLong();
|
||||
int file_part = 0, read;
|
||||
|
|
@ -94,19 +94,19 @@ namespace WTelegram
|
|||
/// <summary>Search messages in chat with <see href="https://corefork.telegram.org/type/MessagesFilter">filter</see> and text <para>See <a href="https://corefork.telegram.org/method/messages.search"/></para></summary>
|
||||
/// <typeparam name="T">See <see cref="MessagesFilter"/> for a list of possible filter types</typeparam>
|
||||
/// <param name="peer">User or chat, histories with which are searched, or <see langword="null"/> constructor for global search</param>
|
||||
/// <param name="text">Text search request</param>
|
||||
/// <param name="q">Text search request</param>
|
||||
/// <param name="offset_id">Only return messages starting from the specified message ID</param>
|
||||
/// <param name="limit"><a href="https://corefork.telegram.org/api/offsets">Number of results to return</a></param>
|
||||
public Task<Messages_MessagesBase> Messages_Search<T>(InputPeer peer, string text = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
|
||||
=> this.Messages_Search(peer, text, new T(), offset_id: offset_id, limit: limit);
|
||||
public Task<Messages_MessagesBase> Messages_Search<T>(InputPeer peer, string q = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
|
||||
=> this.Messages_Search(peer, q, new T(), offset_id: offset_id, limit: limit);
|
||||
|
||||
/// <summary>Search messages globally with <see href="https://corefork.telegram.org/type/MessagesFilter">filter</see> and text <para>See <a href="https://corefork.telegram.org/method/messages.searchGlobal"/></para></summary>
|
||||
/// <typeparam name="T">See <see cref="MessagesFilter"/> for a list of possible filter types</typeparam>
|
||||
/// <param name="text">Text search request</param>
|
||||
/// <param name="q">Query</param>
|
||||
/// <param name="offset_id">Only return messages starting from the specified message ID</param>
|
||||
/// <param name="limit"><a href="https://corefork.telegram.org/api/offsets">Number of results to return</a></param>
|
||||
public Task<Messages_MessagesBase> Messages_SearchGlobal<T>(string text = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
|
||||
=> this.Messages_SearchGlobal(text, new T(), offset_id: offset_id, limit: limit);
|
||||
public Task<Messages_MessagesBase> Messages_SearchGlobal<T>(string q = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
|
||||
=> this.Messages_SearchGlobal(q, new T(), offset_id: offset_id, limit: limit);
|
||||
|
||||
/// <summary>Helper method to send a media message more easily</summary>
|
||||
/// <param name="peer">Destination of message (chat group, channel, user chat, etc..) </param>
|
||||
|
|
@ -334,6 +334,18 @@ namespace WTelegram
|
|||
return await DownloadFileAsync(fileLocation, outputStream, photo.dc_id, photoSize.FileSize, progress);
|
||||
}
|
||||
|
||||
/// <summary>Download an animated photo from Telegram into the outputStream</summary>
|
||||
/// <param name="photo">The photo to download</param>
|
||||
/// <param name="outputStream">Stream to write the file content to. This method does not close/dispose the stream</param>
|
||||
/// <param name="videoSize">A specific size/version of the animated photo. Use <c>photo.LargestVideoSize</c> to download the largest version of the animated photo</param>
|
||||
/// <param name="progress">(optional) Callback for tracking the progression of the transfer</param>
|
||||
/// <returns>The file type of the photo</returns>
|
||||
public async Task<Storage_FileType> DownloadFileAsync(Photo photo, Stream outputStream, VideoSize videoSize, ProgressCallback progress = null)
|
||||
{
|
||||
var fileLocation = photo.ToFileLocation(videoSize);
|
||||
return await DownloadFileAsync(fileLocation, outputStream, photo.dc_id, videoSize.size, progress);
|
||||
}
|
||||
|
||||
/// <summary>Download a document from Telegram into the outputStream</summary>
|
||||
/// <param name="document">The document to download</param>
|
||||
/// <param name="outputStream">Stream to write the file content to. This method does not close/dispose the stream</param>
|
||||
|
|
@ -349,6 +361,18 @@ namespace WTelegram
|
|||
return thumbSize == null ? document.mime_type : "image/" + fileType;
|
||||
}
|
||||
|
||||
/// <summary>Download a document from Telegram into the outputStream</summary>
|
||||
/// <param name="document">The document to download</param>
|
||||
/// <param name="outputStream">Stream to write the file content to. This method does not close/dispose the stream</param>
|
||||
/// <param name="videoSize">A specific size/version of the animated photo. Use <c>photo.LargestVideoSize</c> to download the largest version of the animated photo</param>
|
||||
/// <param name="progress">(optional) Callback for tracking the progression of the transfer</param>
|
||||
/// <returns>MIME type of the document/thumbnail</returns>
|
||||
public async Task<Storage_FileType> DownloadFileAsync(Document document, Stream outputStream, VideoSize videoSize, ProgressCallback progress = null)
|
||||
{
|
||||
var fileLocation = document.ToFileLocation(videoSize);
|
||||
return await DownloadFileAsync(fileLocation, outputStream, document.dc_id, videoSize.size, progress);
|
||||
}
|
||||
|
||||
/// <summary>Download a file from Telegram into the outputStream</summary>
|
||||
/// <param name="fileLocation">Telegram file identifier, typically obtained with a .ToFileLocation() call</param>
|
||||
/// <param name="outputStream">Stream to write file content to. This method does not close/dispose the stream</param>
|
||||
|
|
@ -523,18 +547,20 @@ namespace WTelegram
|
|||
case Messages_DialogsSlice mds:
|
||||
var dialogList = new List<DialogBase>();
|
||||
var messageList = new List<MessageBase>();
|
||||
while (dialogs.Dialogs.Length != 0)
|
||||
int skip = 0;
|
||||
while (dialogs.Dialogs.Length > skip)
|
||||
{
|
||||
dialogList.AddRange(dialogs.Dialogs);
|
||||
dialogList.AddRange(skip == 0 ? dialogs.Dialogs : dialogs.Dialogs[skip..]);
|
||||
messageList.AddRange(dialogs.Messages);
|
||||
skip = 0;
|
||||
int last = dialogs.Dialogs.Length - 1;
|
||||
var lastDialog = dialogs.Dialogs[last];
|
||||
retryDate:
|
||||
var lastPeer = dialogs.UserOrChat(lastDialog).ToInputPeer();
|
||||
var lastMsgId = lastDialog.TopMessage;
|
||||
retryDate:
|
||||
var lastDate = dialogs.Messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage)?.Date ?? default;
|
||||
if (lastDate == default)
|
||||
if (--last < 0) break; else { lastDialog = dialogs.Dialogs[last]; goto retryDate; }
|
||||
if (--last < 0) break; else { ++skip; lastDialog = dialogs.Dialogs[last]; goto retryDate; }
|
||||
dialogs = await this.Messages_GetDialogs(lastDate, lastMsgId, lastPeer, folder_id: folder_id);
|
||||
if (dialogs is not Messages_Dialogs md) break;
|
||||
foreach (var (key, value) in md.chats) mds.chats[key] = value;
|
||||
|
|
@ -626,18 +652,18 @@ namespace WTelegram
|
|||
}
|
||||
|
||||
/// <summary>Helper simplified method: Get all <a href="https://corefork.telegram.org/api/forum">topics of a forum</a> <para>See <a href="https://corefork.telegram.org/method/channels.getForumTopics"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.getForumTopics#possible-errors">details</a>)</para></summary>
|
||||
/// <param name="channel">Supergroup</param>
|
||||
/// <param name="peer">Supergroup or Bot peer</param>
|
||||
/// <param name="q">Search query</param>
|
||||
public async Task<Messages_ForumTopics> Channels_GetAllForumTopics(InputChannelBase channel, string q = null)
|
||||
public async Task<Messages_ForumTopics> Channels_GetAllForumTopics(InputPeer peer, string q = null)
|
||||
{
|
||||
var result = await this.Channels_GetForumTopics(channel, offset_date: DateTime.MaxValue, q: q);
|
||||
var result = await this.Messages_GetForumTopics(peer, offset_date: DateTime.MaxValue, q: q);
|
||||
if (result.topics.Length < result.count)
|
||||
{
|
||||
var topics = result.topics.ToList();
|
||||
var messages = result.messages.ToList();
|
||||
while (true)
|
||||
{
|
||||
var more_topics = await this.Channels_GetForumTopics(channel, messages[^1].Date, messages[^1].ID, topics[^1].ID);
|
||||
var more_topics = await this.Messages_GetForumTopics(peer, messages[^1].Date, messages[^1].ID, topics[^1].ID);
|
||||
if (more_topics.topics.Length == 0) break;
|
||||
topics.AddRange(more_topics.topics);
|
||||
messages.AddRange(more_topics.messages);
|
||||
|
|
@ -897,6 +923,28 @@ namespace WTelegram
|
|||
}
|
||||
return chat;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>Receive updates for a given group/channel until cancellation is requested.</summary>
|
||||
/// <param name="channel">Group/channel to monitor without joining</param>
|
||||
/// <param name="ct">Cancel token to stop the monitoring</param>
|
||||
/// <remarks>After cancelling, you may still receive updates for a few more seconds</remarks>
|
||||
public async void OpenChat(InputChannel channel, CancellationToken ct)
|
||||
{
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, ct);
|
||||
try
|
||||
{
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
var diff = await this.Updates_GetChannelDifference(channel, null, 1, 1, true);
|
||||
var timeout = diff.Timeout * 1000;
|
||||
await Task.Delay(timeout != 0 ? timeout : 30000, cts.Token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!cts.IsCancellationRequested)
|
||||
Console.WriteLine($"An exception occured for OpenChat {channel.channel_id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
200
src/Client.cs
200
src/Client.cs
|
|
@ -59,6 +59,18 @@ namespace WTelegram
|
|||
public long UserId => _session.UserId;
|
||||
/// <summary>Info about the current logged-in user. This is only filled after a successful (re)login, not updated later</summary>
|
||||
public User User { get; private set; }
|
||||
/// <summary>Number of parallel transfers operations (uploads/downloads) allowed at the same time.</summary>
|
||||
/// <remarks>Don't use this property while transfers are ongoing!</remarks>
|
||||
public int ParallelTransfers
|
||||
{
|
||||
get => _parallelTransfers.CurrentCount;
|
||||
set
|
||||
{
|
||||
int delta = value - _parallelTransfers.CurrentCount;
|
||||
for (; delta < 0; delta++) _parallelTransfers.Wait();
|
||||
if (delta > 0) _parallelTransfers.Release(delta);
|
||||
}
|
||||
}
|
||||
|
||||
private Func<string, string> _config;
|
||||
private readonly Session _session;
|
||||
|
|
@ -83,7 +95,7 @@ namespace WTelegram
|
|||
private int _reactorReconnects = 0;
|
||||
private const string ConnectionShutDown = "Could not read payload length : Connection shut down";
|
||||
private const long Ticks5Secs = 5 * TimeSpan.TicksPerSecond;
|
||||
private readonly SemaphoreSlim _parallelTransfers = new(10); // max parallel part uploads/downloads
|
||||
private readonly SemaphoreSlim _parallelTransfers = new(2); // max parallel part uploads/downloads
|
||||
private readonly SHA256 _sha256 = SHA256.Create();
|
||||
private readonly SHA256 _sha256Recv = SHA256.Create();
|
||||
#if OBFUSCATION
|
||||
|
|
@ -115,7 +127,7 @@ namespace WTelegram
|
|||
_session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key));
|
||||
if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id"));
|
||||
if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession);
|
||||
_dcSession ??= new() { Id = Helpers.RandomLong() };
|
||||
_dcSession ??= new();
|
||||
_dcSession.Client = this;
|
||||
var version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
Helpers.Log(1, $"WTelegramClient {version} running under {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
|
||||
|
|
@ -128,6 +140,7 @@ namespace WTelegram
|
|||
TcpHandler = cloneOf.TcpHandler;
|
||||
MTProxyUrl = cloneOf.MTProxyUrl;
|
||||
PingInterval = cloneOf.PingInterval;
|
||||
MaxAutoReconnects = cloneOf.MaxAutoReconnects;
|
||||
TLConfig = cloneOf.TLConfig;
|
||||
_dcSession = dcSession;
|
||||
}
|
||||
|
|
@ -172,10 +185,6 @@ namespace WTelegram
|
|||
return Console.ReadLine();
|
||||
}
|
||||
|
||||
/// <summary>Load a specific Telegram server public key</summary>
|
||||
/// <param name="pem">A string starting with <c>-----BEGIN RSA PUBLIC KEY-----</c></param>
|
||||
public static void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem);
|
||||
|
||||
/// <summary>Builds a structure that is used to validate a 2FA password</summary>
|
||||
/// <param name="accountPassword">Password validation configuration. You can obtain this via <c>Account_GetPassword</c> or through OnOther as part of the login process</param>
|
||||
/// <param name="password">The password to validate</param>
|
||||
|
|
@ -209,7 +218,7 @@ namespace WTelegram
|
|||
if (_tcpClient != null) throw new InvalidOperationException("Cannot switch to HTTP after TCP connection");
|
||||
_httpClient = httpClient ?? new();
|
||||
_httpWait = defaultHttpWait;
|
||||
while (_parallelTransfers.CurrentCount > 1) _parallelTransfers.Wait();
|
||||
ParallelTransfers = 1;
|
||||
}
|
||||
|
||||
/// <summary>Disconnect from Telegram <i>(shouldn't be needed in normal usage)</i></summary>
|
||||
|
|
@ -264,16 +273,15 @@ namespace WTelegram
|
|||
|
||||
private Session.DCSession GetOrCreateDCSession(int dcId, DcOption.Flags flags)
|
||||
{
|
||||
if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.AuthKey != null)
|
||||
if (dcSession.Client != null || dcSession.DataCenter.flags == flags)
|
||||
return dcSession; // if we have already a session with this DC and we are connected or it is a perfect match, use it
|
||||
if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.Client != null)
|
||||
return dcSession; // we have already a connected session with this DC, use it
|
||||
if (dcSession == null && _session.DCSessions.TryGetValue(-dcId, out dcSession) && dcSession.AuthKey != null)
|
||||
{
|
||||
// we have already negociated an AuthKey with this DC
|
||||
if (dcSession.DataCenter.flags == flags && _session.DCSessions.Remove(-dcId))
|
||||
return _session.DCSessions[dcId] = dcSession; // we found a misclassed DC, change its sign
|
||||
dcSession = new Session.DCSession { Id = Helpers.RandomLong(), // clone AuthKey for a session on the matching media_only DC
|
||||
AuthKeyID = dcSession.AuthKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId };
|
||||
dcSession = new Session.DCSession { // clone AuthKey for a session on the matching media_only DC
|
||||
authKeyID = dcSession.authKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId };
|
||||
}
|
||||
// try to find the most appropriate DcOption for this DC
|
||||
if (dcSession?.AuthKey == null) // we'll need to negociate an AuthKey => can't use media_only DC
|
||||
|
|
@ -282,9 +290,10 @@ namespace WTelegram
|
|||
dcId = Math.Abs(dcId);
|
||||
}
|
||||
var dcOptions = GetDcOptions(Math.Abs(dcId), flags);
|
||||
var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}");
|
||||
dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing
|
||||
dcSession.DataCenter = dcOption;
|
||||
var dcOption = dcOptions.FirstOrDefault();
|
||||
dcSession ??= new(); // create new session only if not already existing
|
||||
if (dcOption != null) dcSession.DataCenter = dcOption;
|
||||
else if (dcSession.DataCenter == null) throw new WTException($"Could not find adequate dc_option for DC {dcId}");
|
||||
return _session.DCSessions[dcId] = dcSession;
|
||||
}
|
||||
|
||||
|
|
@ -302,6 +311,7 @@ namespace WTelegram
|
|||
var flags = _dcSession.DataCenter.flags;
|
||||
if (dcId < 0) flags = (flags & DcOption.Flags.ipv6) | DcOption.Flags.media_only;
|
||||
altSession = GetOrCreateDCSession(dcId, flags);
|
||||
_session.Save();
|
||||
if (altSession.Client?.Disconnected ?? false) { altSession.Client.Dispose(); altSession.Client = null; }
|
||||
altSession.Client ??= new Client(this, altSession);
|
||||
}
|
||||
|
|
@ -312,7 +322,7 @@ namespace WTelegram
|
|||
try
|
||||
{
|
||||
Auth_ExportedAuthorization exported = null;
|
||||
if (_session.UserId != 0 && IsMainDC && altSession.UserId != _session.UserId && altSession.DcID != _dcSession.DcID)
|
||||
if (_session.UserId != 0 && IsMainDC && altSession.UserId != _session.UserId && Math.Abs(altSession.DcID) != Math.Abs(_dcSession.DcID))
|
||||
exported = await this.Auth_ExportAuthorization(Math.Abs(dcId));
|
||||
await altSession.Client.ConnectAsync();
|
||||
if (exported != null)
|
||||
|
|
@ -321,6 +331,7 @@ namespace WTelegram
|
|||
if (authorization is not Auth_Authorization { user: User user })
|
||||
throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
|
||||
altSession.UserId = user.id;
|
||||
lock (_session) _session.Save();
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -331,19 +342,19 @@ namespace WTelegram
|
|||
return altSession.Client;
|
||||
}
|
||||
|
||||
private async Task Reactor(Stream stream, CancellationTokenSource cts)
|
||||
private async Task Reactor(Stream stream, CancellationToken ct)
|
||||
{
|
||||
const int MinBufferSize = 1024;
|
||||
var data = new byte[MinBufferSize];
|
||||
while (!cts.IsCancellationRequested)
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
IObject obj = null;
|
||||
try
|
||||
{
|
||||
if (await stream.FullReadAsync(data, 4, cts.Token) != 4)
|
||||
if (await stream.FullReadAsync(data, 4, ct) != 4)
|
||||
throw new WTException(ConnectionShutDown);
|
||||
#if OBFUSCATION
|
||||
_recvCtr.EncryptDecrypt(data, 4);
|
||||
_recvCtr.EncryptDecrypt(data.AsSpan(0, 4));
|
||||
#endif
|
||||
int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data);
|
||||
if (payloadLen <= 0)
|
||||
|
|
@ -352,23 +363,23 @@ namespace WTelegram
|
|||
data = new byte[payloadLen];
|
||||
else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4)
|
||||
data = new byte[Math.Max(payloadLen, MinBufferSize)];
|
||||
if (await stream.FullReadAsync(data, payloadLen, cts.Token) != payloadLen)
|
||||
if (await stream.FullReadAsync(data, payloadLen, ct) != payloadLen)
|
||||
throw new WTException("Could not read frame data : Connection shut down");
|
||||
#if OBFUSCATION
|
||||
_recvCtr.EncryptDecrypt(data, payloadLen);
|
||||
_recvCtr.EncryptDecrypt(data.AsSpan(0, payloadLen));
|
||||
#endif
|
||||
obj = ReadFrame(data, payloadLen);
|
||||
}
|
||||
catch (Exception ex) // an exception in RecvAsync is always fatal
|
||||
{
|
||||
if (cts.IsCancellationRequested) return;
|
||||
if (ct.IsCancellationRequested) return;
|
||||
bool disconnectedAltDC = !IsMainDC && ex is WTException { Message: ConnectionShutDown } or IOException { InnerException: SocketException };
|
||||
if (disconnectedAltDC)
|
||||
Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}");
|
||||
else
|
||||
Helpers.Log(5, $"{_dcSession.DcID}>An exception occured in the reactor: {ex}");
|
||||
var oldSemaphore = _sendSemaphore;
|
||||
await oldSemaphore.WaitAsync(cts.Token); // prevent any sending while we reconnect
|
||||
await oldSemaphore.WaitAsync(ct); // prevent any sending while we reconnect
|
||||
var reactorError = new ReactorError { Exception = ex };
|
||||
try
|
||||
{
|
||||
|
|
@ -419,7 +430,7 @@ namespace WTelegram
|
|||
}
|
||||
|
||||
internal DateTime MsgIdToStamp(long serverMsgId)
|
||||
=> new((serverMsgId >> 32) * 10000000 - _dcSession.ServerTicksOffset + 621355968000000000L, DateTimeKind.Utc);
|
||||
=> new((serverMsgId >> 32) * 10000000 - _dcSession.serverTicksOffset + 621355968000000000L, DateTimeKind.Utc);
|
||||
|
||||
internal IObject ReadFrame(byte[] data, int dataLen)
|
||||
{
|
||||
|
|
@ -432,7 +443,7 @@ namespace WTelegram
|
|||
throw new WTException($"Packet payload too small: {dataLen}");
|
||||
|
||||
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
|
||||
if (authKeyId != _dcSession.AuthKeyID)
|
||||
if (authKeyId != _dcSession.authKeyID)
|
||||
throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}");
|
||||
if (authKeyId == 0) // Unencrypted message
|
||||
{
|
||||
|
|
@ -468,7 +479,7 @@ namespace WTelegram
|
|||
|
||||
if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}");
|
||||
if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new WTException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}");
|
||||
if (sessionId != _dcSession.Id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.Id}");
|
||||
if (sessionId != _dcSession.id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.id}");
|
||||
if ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}");
|
||||
if (!_dcSession.CheckNewMsgId(msgId))
|
||||
{
|
||||
|
|
@ -477,19 +488,20 @@ namespace WTelegram
|
|||
}
|
||||
var utcNow = DateTime.UtcNow;
|
||||
if (_lastRecvMsgId == 0) // resync ServerTicksOffset on first message
|
||||
_dcSession.ServerTicksOffset = (msgId >> 32) * 10000000 - utcNow.Ticks + 621355968000000000L;
|
||||
_dcSession.serverTicksOffset = (msgId >> 32) * 10000000 - utcNow.Ticks + 621355968000000000L;
|
||||
var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId);
|
||||
long deltaTicks = (msgStamp - utcNow).Ticks;
|
||||
if (deltaTicks is > 0)
|
||||
if (deltaTicks < Ticks5Secs) // resync if next message is less than 5 seconds in the future
|
||||
_dcSession.ServerTicksOffset += deltaTicks;
|
||||
else if (_dcSession.ServerTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.ServerTicksOffset < 0)
|
||||
_dcSession.ServerTicksOffset += deltaTicks;
|
||||
_dcSession.serverTicksOffset += deltaTicks;
|
||||
else if (_dcSession.serverTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.serverTicksOffset < 0)
|
||||
_dcSession.serverTicksOffset += deltaTicks;
|
||||
if (serverSalt != _dcSession.Salt && serverSalt != _dcSession.OldSalt && serverSalt != _dcSession.Salts?.Values.ElementAtOrDefault(1))
|
||||
{
|
||||
Helpers.Log(3, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}");
|
||||
_dcSession.OldSalt = _dcSession.Salt;
|
||||
_dcSession.Salt = serverSalt;
|
||||
lock (_session) _session.Save();
|
||||
if (++_saltChangeCounter >= 10)
|
||||
throw new WTException("Server salt changed too often! Security issue?");
|
||||
CheckSalt();
|
||||
|
|
@ -499,7 +511,7 @@ namespace WTelegram
|
|||
var ctorNb = reader.ReadUInt32();
|
||||
if (ctorNb != Layer.BadMsgCtor && deltaTicks / TimeSpan.TicksPerSecond is > 30 or < -300)
|
||||
{ // msg_id values that belong over 30 seconds in the future or over 300 seconds in the past are to be ignored.
|
||||
Helpers.Log(1, $"{_dcSession.DcID}>Ignoring 0x{ctorNb:X8} because of wrong timestamp {msgStamp:u} - {utcNow:u} Δ={new TimeSpan(_dcSession.ServerTicksOffset):c}");
|
||||
Helpers.Log(1, $"{_dcSession.DcID}>Ignoring 0x{ctorNb:X8} because of wrong timestamp {msgStamp:u} - {utcNow:u} Δ={new TimeSpan(_dcSession.serverTicksOffset):c}");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
|
|
@ -546,9 +558,11 @@ namespace WTelegram
|
|||
{
|
||||
var keys = _dcSession.Salts.Keys;
|
||||
if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing
|
||||
var now = DateTime.UtcNow.AddTicks(_dcSession.ServerTicksOffset);
|
||||
for (; keys.Count > 1 && keys[1] < now; _dcSession.OldSalt = _dcSession.Salt, _dcSession.Salt = _dcSession.Salts.Values[0])
|
||||
var now = DateTime.UtcNow.AddTicks(_dcSession.serverTicksOffset - TimeSpan.TicksPerMinute);
|
||||
bool removed = false;
|
||||
for (; keys.Count > 1 && keys[1] < now; _dcSession.OldSalt = _dcSession.Salt, _dcSession.Salt = _dcSession.Salts.Values[0], removed = true)
|
||||
_dcSession.Salts.RemoveAt(0);
|
||||
if (removed) _session.Save();
|
||||
if (_dcSession.Salts.Count > 48) return;
|
||||
}
|
||||
_dcSession.Salts[DateTime.MaxValue] = 0;
|
||||
|
|
@ -575,20 +589,20 @@ namespace WTelegram
|
|||
{
|
||||
var msg = new _Message(reader.ReadInt64(), reader.ReadInt32(), null) { bytes = reader.ReadInt32() };
|
||||
messages.Add(msg);
|
||||
if ((msg.seq_no & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
|
||||
if ((msg.seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
|
||||
var pos = reader.BaseStream.Position;
|
||||
try
|
||||
{
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (ctorNb == Layer.RpcResultCtor)
|
||||
{
|
||||
Helpers.Log(1, $" → {"RpcResult",-38} {MsgIdToStamp(msg.msg_id):u}");
|
||||
Helpers.Log(1, $" → {"RpcResult",-38} {MsgIdToStamp(msg.msg_id):u}");
|
||||
msg.body = ReadRpcResult(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
var obj = msg.body = reader.ReadTLObject(ctorNb);
|
||||
Helpers.Log(1, $" → {obj.GetType().Name,-38} {MsgIdToStamp(msg.msg_id):u} {((msg.seq_no & 1) != 0 ? "" : "(svc)")} {((msg.msg_id & 2) == 0 ? "" : "NAR")}");
|
||||
Helpers.Log(1, $" → {obj.GetType().Name,-38} {MsgIdToStamp(msg.msg_id):u} {((msg.seqno & 1) != 0 ? "" : "(svc)")} {((msg.msg_id & 2) == 0 ? "" : "NAR")}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -659,9 +673,9 @@ namespace WTelegram
|
|||
|
||||
var typeName = result?.GetType().Name;
|
||||
if (MsgIdToStamp(msgId) >= _session.SessionStart)
|
||||
Helpers.Log(4, $" → {typeName,-37} for unknown msgId #{(short)msgId.GetHashCode():X4}");
|
||||
Helpers.Log(4, $" → {typeName,-37} for unknown msgId #{(short)msgId.GetHashCode():X4}");
|
||||
else
|
||||
Helpers.Log(1, $" → {typeName,-37} for past msgId #{(short)msgId.GetHashCode():X4}");
|
||||
Helpers.Log(1, $" → {typeName,-37} for past msgId #{(short)msgId.GetHashCode():X4}");
|
||||
}
|
||||
return new RpcResult { req_msg_id = msgId, result = result };
|
||||
}
|
||||
|
|
@ -694,7 +708,7 @@ namespace WTelegram
|
|||
rpc.tcs.SetResult(obj);
|
||||
return;
|
||||
}
|
||||
else if (_dcSession.AuthKeyID == 0)
|
||||
else if (_dcSession.authKeyID == 0)
|
||||
throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}");
|
||||
lock (_pendingRpcs)
|
||||
_pendingRpcs[_bareRpc.msgId] = _bareRpc;
|
||||
|
|
@ -725,25 +739,22 @@ namespace WTelegram
|
|||
case MsgsAck msgsAck:
|
||||
break; // we don't do anything with these, for now
|
||||
case BadMsgNotification badMsgNotification:
|
||||
await _sendSemaphore.WaitAsync();
|
||||
bool retryLast = badMsgNotification.bad_msg_id == _dcSession.LastSentMsgId;
|
||||
var lastSentMsg = _lastSentMsg;
|
||||
_sendSemaphore.Release();
|
||||
bool retryRpcs = true;
|
||||
var logLevel = badMsgNotification.error_code == 48 ? 2 : 4;
|
||||
Helpers.Log(logLevel, $"BadMsgNotification {badMsgNotification.error_code} for msg #{(short)badMsgNotification.bad_msg_id.GetHashCode():X4}");
|
||||
switch (badMsgNotification.error_code)
|
||||
{
|
||||
case 16: // msg_id too low (most likely, client time is wrong; synchronize it using msg_id notifications and re-send the original message)
|
||||
case 17: // msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)
|
||||
_dcSession.LastSentMsgId = 0;
|
||||
_dcSession.lastSentMsgId = 0;
|
||||
var localTime = DateTime.UtcNow;
|
||||
_dcSession.ServerTicksOffset = (_lastRecvMsgId >> 32) * 10000000 - localTime.Ticks + 621355968000000000L;
|
||||
Helpers.Log(1, $"Time offset: {_dcSession.ServerTicksOffset} | Server: {MsgIdToStamp(_lastRecvMsgId).AddTicks(_dcSession.ServerTicksOffset).TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
|
||||
_dcSession.serverTicksOffset = (_lastRecvMsgId >> 32) * 10000000 - localTime.Ticks + 621355968000000000L;
|
||||
Helpers.Log(1, $"Time offset: {_dcSession.serverTicksOffset} | Server: {MsgIdToStamp(_lastRecvMsgId).AddTicks(_dcSession.serverTicksOffset).TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
|
||||
break;
|
||||
case 32: // msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)
|
||||
case 33: // msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)
|
||||
if (_dcSession.Seqno <= 1)
|
||||
retryLast = false;
|
||||
if (_dcSession.seqno <= 1)
|
||||
retryRpcs = false;
|
||||
else
|
||||
{
|
||||
await ResetAsync(false, false);
|
||||
|
|
@ -754,28 +765,23 @@ namespace WTelegram
|
|||
case 48: // incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)
|
||||
_dcSession.OldSalt = _dcSession.Salt;
|
||||
_dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt;
|
||||
lock (_session) _session.Save();
|
||||
CheckSalt();
|
||||
break;
|
||||
default:
|
||||
retryLast = false;
|
||||
retryRpcs = false;
|
||||
break;
|
||||
}
|
||||
if (retryLast)
|
||||
if (retryRpcs)
|
||||
{
|
||||
Rpc prevRequest;
|
||||
lock (_pendingRpcs)
|
||||
_pendingRpcs.TryGetValue(badMsgNotification.bad_msg_id, out prevRequest);
|
||||
await SendAsync(lastSentMsg, lastSentMsg is not MsgContainer, prevRequest);
|
||||
lock (_pendingRpcs)
|
||||
_pendingRpcs.Remove(badMsgNotification.bad_msg_id);
|
||||
}
|
||||
else if (PullPendingRequest(badMsgNotification.bad_msg_id) is Rpc rpc)
|
||||
{
|
||||
if (_bareRpc?.msgId == badMsgNotification.bad_msg_id) _bareRpc = null;
|
||||
rpc.tcs.SetException(new WTException($"BadMsgNotification {badMsgNotification.error_code}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var rpc in _pendingRpcs.Values)
|
||||
rpc.tcs.TrySetResult(new RpcError { error_code = -503, error_message = $"BadMsgNotification {badMsgNotification.error_code}" });
|
||||
_pendingRpcs.Clear();
|
||||
}
|
||||
RaiseUpdates(badMsgNotification);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RaiseUpdates(obj);
|
||||
|
|
@ -865,14 +871,14 @@ namespace WTelegram
|
|||
{
|
||||
_cts = new();
|
||||
IPEndPoint endpoint = null;
|
||||
bool needMigrate = false;
|
||||
byte[] preamble, secret = null;
|
||||
int dcId = _dcSession?.DcID ?? 0;
|
||||
if (dcId == 0) dcId = 2;
|
||||
if (MTProxyUrl != null)
|
||||
{
|
||||
#if OBFUSCATION
|
||||
if (TLConfig?.test_mode == true) dcId += 10000;
|
||||
if (_dcSession.DataCenter?.flags.HasFlag(DcOption.Flags.media_only) == true) dcId = -dcId;
|
||||
if (TLConfig?.test_mode == true) dcId += dcId < 0 ? -10000 : 10000;
|
||||
var parms = HttpUtility.ParseQueryString(MTProxyUrl[MTProxyUrl.IndexOf('?')..]);
|
||||
var server = parms["server"];
|
||||
int port = int.Parse(parms["port"]);
|
||||
|
|
@ -938,11 +944,12 @@ namespace WTelegram
|
|||
{
|
||||
endpoint = GetDefaultEndpoint(out defaultDc); // re-ask callback for an address
|
||||
if (!triedEndpoints.Add(endpoint)) throw;
|
||||
needMigrate = _dcSession.DataCenter.id == _session.MainDC && defaultDc != _session.MainDC;
|
||||
_dcSession.Client = null;
|
||||
// is it address for a known DCSession?
|
||||
_dcSession = _session.DCSessions.Values.FirstOrDefault(dcs => dcs.EndPoint.Equals(endpoint));
|
||||
if (defaultDc != 0) _dcSession ??= _session.DCSessions.GetValueOrDefault(defaultDc);
|
||||
_dcSession ??= new() { Id = Helpers.RandomLong() };
|
||||
_dcSession ??= new();
|
||||
_dcSession.Client = this;
|
||||
_dcSession.DataCenter = null;
|
||||
Helpers.Log(2, $"Connecting to {endpoint}...");
|
||||
|
|
@ -970,13 +977,13 @@ namespace WTelegram
|
|||
#endif
|
||||
await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token);
|
||||
|
||||
_reactorTask = Reactor(_networkStream, _cts);
|
||||
_reactorTask = Reactor(_networkStream, _cts.Token);
|
||||
}
|
||||
_sendSemaphore.Release();
|
||||
|
||||
try
|
||||
{
|
||||
if (_dcSession.AuthKeyID == 0)
|
||||
if (_dcSession.authKeyID == 0)
|
||||
await CreateAuthorizationKey(this, _dcSession);
|
||||
|
||||
if (_networkStream != null) _ = KeepAlive(_cts.Token);
|
||||
|
|
@ -996,6 +1003,7 @@ namespace WTelegram
|
|||
_session.DCSessions[TLConfig.this_dc] = _dcSession;
|
||||
}
|
||||
if (_session.MainDC == 0) _session.MainDC = TLConfig.this_dc;
|
||||
else if (needMigrate) await MigrateToDC(_session.MainDC);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -1120,11 +1128,12 @@ namespace WTelegram
|
|||
{
|
||||
try
|
||||
{
|
||||
var users = await this.Users_GetUsers(InputUser.Self); // this calls also reenable incoming Updates
|
||||
var users = await this.Users_GetUsers(InputUser.Self); // this call also reenable incoming Updates
|
||||
var self = users[0] as User;
|
||||
if (self.id == long.Parse(botToken.Split(':')[0]))
|
||||
{
|
||||
_session.UserId = _dcSession.UserId = self.id;
|
||||
lock (_session) _session.Save();
|
||||
RaiseUpdates(self);
|
||||
return User = self;
|
||||
}
|
||||
|
|
@ -1164,6 +1173,7 @@ namespace WTelegram
|
|||
self.phone == string.Concat((phone_number = Config("phone_number")).Where(char.IsDigit)))
|
||||
{
|
||||
_session.UserId = _dcSession.UserId = self.id;
|
||||
lock (_session) _session.Save();
|
||||
RaiseUpdates(self);
|
||||
return User = self;
|
||||
}
|
||||
|
|
@ -1227,10 +1237,13 @@ namespace WTelegram
|
|||
if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be)
|
||||
sentCodeBase = verifiedLogin.sent_code;
|
||||
}
|
||||
RaiseUpdates(sentCodeBase);
|
||||
}
|
||||
resent:
|
||||
if (sentCodeBase is Auth_SentCodeSuccess success)
|
||||
authorization = success.authorization;
|
||||
else if (sentCodeBase is Auth_SentCodePaymentRequired paymentRequired)
|
||||
throw new WTException("Auth_SentCodePaymentRequired unsupported");
|
||||
else if (sentCodeBase is Auth_SentCode sentCode)
|
||||
{
|
||||
phone_code_hash = sentCode.phone_code_hash;
|
||||
|
|
@ -1399,7 +1412,7 @@ namespace WTelegram
|
|||
public User LoginAlreadyDone(Auth_AuthorizationBase authorization)
|
||||
{
|
||||
if (authorization is not Auth_Authorization { user: User self })
|
||||
throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
|
||||
throw new WTException("Failed to get Authorization: " + authorization?.GetType().Name);
|
||||
_session.UserId = _dcSession.UserId = self.id;
|
||||
lock (_session) _session.Save();
|
||||
RaiseUpdates(self);
|
||||
|
|
@ -1420,13 +1433,12 @@ namespace WTelegram
|
|||
internal (long msgId, int seqno) NewMsgId(bool isContent)
|
||||
{
|
||||
int seqno;
|
||||
long msgId = DateTime.UtcNow.Ticks + _dcSession.ServerTicksOffset - 621355968000000000L;
|
||||
long msgId = DateTime.UtcNow.Ticks + _dcSession.serverTicksOffset - 621355968000000000L;
|
||||
msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4
|
||||
lock (_session)
|
||||
{
|
||||
if (msgId <= _dcSession.LastSentMsgId) msgId = _dcSession.LastSentMsgId += 4; else _dcSession.LastSentMsgId = msgId;
|
||||
seqno = isContent ? _dcSession.Seqno++ * 2 + 1 : _dcSession.Seqno * 2;
|
||||
_session.Save();
|
||||
if (msgId <= _dcSession.lastSentMsgId) msgId = _dcSession.lastSentMsgId += 4; else _dcSession.lastSentMsgId = msgId;
|
||||
seqno = isContent ? _dcSession.seqno++ * 2 + 1 : _dcSession.seqno * 2;
|
||||
}
|
||||
return (msgId, seqno);
|
||||
}
|
||||
|
|
@ -1434,7 +1446,7 @@ namespace WTelegram
|
|||
private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null)
|
||||
{
|
||||
if (_reactorTask == null) throw new WTException("You must connect to Telegram first");
|
||||
isContent &= _dcSession.AuthKeyID != 0;
|
||||
isContent &= _dcSession.authKeyID != 0;
|
||||
var (msgId, seqno) = NewMsgId(isContent);
|
||||
if (rpc != null)
|
||||
lock (_pendingRpcs)
|
||||
|
|
@ -1462,13 +1474,13 @@ namespace WTelegram
|
|||
using var writer = new BinaryWriter(memStream);
|
||||
writer.Write(0); // int32 payload_len (to be patched with payload length)
|
||||
|
||||
if (_dcSession.AuthKeyID == 0) // send unencrypted message
|
||||
if (_dcSession.authKeyID == 0) // send unencrypted message
|
||||
{
|
||||
if (_bareRpc == null) throw new WTException($"Shouldn't send a {msg.GetType().Name} unencrypted");
|
||||
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
|
||||
writer.Write(msgId); // int64 message_id
|
||||
writer.Write(0); // int32 message_data_length (to be patched)
|
||||
Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_')}...");
|
||||
Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_')}");
|
||||
writer.WriteTLObject(msg); // bytes message_data
|
||||
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(20), (int)memStream.Length - 24); // patch message_data_length
|
||||
}
|
||||
|
|
@ -1479,7 +1491,7 @@ namespace WTelegram
|
|||
using var clearWriter = new BinaryWriter(clearStream);
|
||||
clearWriter.Write(_dcSession.AuthKey, 88, 32);
|
||||
clearWriter.Write(_dcSession.Salt); // int64 salt
|
||||
clearWriter.Write(_dcSession.Id); // int64 session_id
|
||||
clearWriter.Write(_dcSession.id); // int64 session_id
|
||||
clearWriter.Write(msgId); // int64 message_id
|
||||
clearWriter.Write(seqno); // int32 msg_seqno
|
||||
clearWriter.Write(0); // int32 message_data_length (to be patched)
|
||||
|
|
@ -1499,13 +1511,13 @@ namespace WTelegram
|
|||
const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding)
|
||||
byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(32, clearLength + padding), true, 0, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha256);
|
||||
|
||||
writer.Write(_dcSession.AuthKeyID); // int64 auth_key_id
|
||||
writer.Write(_dcSession.authKeyID); // int64 auth_key_id
|
||||
writer.Write(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key
|
||||
writer.Write(encrypted_data); // bytes encrypted_data
|
||||
}
|
||||
if (_paddedMode) // Padded intermediate mode => append random padding
|
||||
{
|
||||
var padding = new byte[_random.Next(_dcSession.AuthKeyID == 0 ? 257 : 16)];
|
||||
var padding = new byte[_random.Next(_dcSession.authKeyID == 0 ? 257 : 16)];
|
||||
RNG.GetBytes(padding);
|
||||
writer.Write(padding);
|
||||
}
|
||||
|
|
@ -1513,7 +1525,7 @@ namespace WTelegram
|
|||
int frameLength = (int)memStream.Length;
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
|
||||
#if OBFUSCATION
|
||||
_sendCtr?.EncryptDecrypt(buffer, frameLength);
|
||||
_sendCtr?.EncryptDecrypt(buffer.AsSpan(0, frameLength));
|
||||
#endif
|
||||
if (_networkStream != null)
|
||||
await _networkStream.WriteAsync(buffer, 0, frameLength);
|
||||
|
|
@ -1572,7 +1584,7 @@ namespace WTelegram
|
|||
/// <returns>Wait for the reply and return the resulting object, or throws an RpcException if an error was replied</returns>
|
||||
public async Task<T> Invoke<T>(IMethod<T> query)
|
||||
{
|
||||
if (_dcSession.WithoutUpdates && query is not IMethod<Pong> and not IMethod<FutureSalts>)
|
||||
if (_dcSession.withoutUpdates && query is not IMethod<Pong> and not IMethod<FutureSalts>)
|
||||
query = new TL.Methods.InvokeWithoutUpdates<T> { query = query };
|
||||
bool got503 = false;
|
||||
retry:
|
||||
|
|
@ -1610,7 +1622,7 @@ namespace WTelegram
|
|||
{
|
||||
if (x <= FloodRetryThreshold)
|
||||
{
|
||||
if (x == 0) x =1;
|
||||
if (x == 0) x = 1;
|
||||
await Task.Delay(x * 1000);
|
||||
goto retry;
|
||||
}
|
||||
|
|
@ -1620,17 +1632,29 @@ namespace WTelegram
|
|||
got503 = true;
|
||||
goto retry;
|
||||
}
|
||||
else if (code == 401 && !IsMainDC && message is "SESSION_REVOKED" or "AUTH_KEY_UNREGISTERED") // need to renegociate alt-DC auth
|
||||
{
|
||||
lock (_session)
|
||||
{
|
||||
_session.DCSessions.Remove(_dcSession.DcID);
|
||||
if (_session.MainDC != -_dcSession.DcID) _session.DCSessions.Remove(-_dcSession.DcID);
|
||||
_session.Save();
|
||||
}
|
||||
await DisposeAsync();
|
||||
}
|
||||
else if (code == 400 && message == "CONNECTION_NOT_INITED")
|
||||
{
|
||||
await InitConnection();
|
||||
lock (_session) _session.Save();
|
||||
goto retry;
|
||||
}
|
||||
else if (code == 500 && message == "AUTH_RESTART")
|
||||
{
|
||||
_session.UserId = 0; // force a full login authorization flow, next time
|
||||
User = null;
|
||||
lock (_session) _session.Save();
|
||||
}
|
||||
lock (_session)
|
||||
{
|
||||
_session.UserId = 0; // force a full login authorization flow, next time
|
||||
User = null;
|
||||
_session.Save();
|
||||
}
|
||||
throw new RpcException(code, message, x);
|
||||
case ReactorError:
|
||||
goto retry;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using static WTelegram.Compat;
|
|||
|
||||
namespace WTelegram
|
||||
{
|
||||
internal static class Encryption
|
||||
public static class Encryption
|
||||
{
|
||||
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = [];
|
||||
internal static readonly RandomNumberGenerator RNG = RandomNumberGenerator.Create();
|
||||
|
|
@ -94,7 +94,7 @@ namespace WTelegram
|
|||
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok");
|
||||
if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch");
|
||||
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
|
||||
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce);
|
||||
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(sha1, resPQ.server_nonce, pqInnerData.new_nonce);
|
||||
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
|
||||
|
||||
using var answerReader = new BinaryReader(new MemoryStream(answer));
|
||||
|
|
@ -110,9 +110,9 @@ namespace WTelegram
|
|||
var g_a = BigEndianInteger(serverDHinnerData.g_a);
|
||||
var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime);
|
||||
CheckGoodPrime(dh_prime, serverDHinnerData.g);
|
||||
session.LastSentMsgId = 0;
|
||||
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
|
||||
Helpers.Log(1, $"Time offset: {session.ServerTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
|
||||
session.lastSentMsgId = 0;
|
||||
session.serverTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
|
||||
Helpers.Log(1, $"Time offset: {session.serverTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
|
||||
//6)
|
||||
var salt = new byte[256];
|
||||
RNG.GetBytes(salt);
|
||||
|
|
@ -159,30 +159,30 @@ namespace WTelegram
|
|||
if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4)))
|
||||
throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch");
|
||||
|
||||
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
|
||||
session.authKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
|
||||
session.AuthKey = authKey;
|
||||
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
|
||||
session.OldSalt = session.Salt;
|
||||
}
|
||||
|
||||
(byte[] key, byte[] iv) ConstructTmpAESKeyIV(TL.Int128 server_nonce, Int256 new_nonce)
|
||||
{
|
||||
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
|
||||
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
|
||||
sha1.TransformFinalBlock(server_nonce, 0, 16);
|
||||
sha1.Hash.CopyTo(tmp_aes_key, 0); // tmp_aes_key := SHA1(new_nonce + server_nonce)
|
||||
sha1.Initialize();
|
||||
sha1.TransformBlock(server_nonce, 0, 16, null, 0);
|
||||
sha1.TransformFinalBlock(new_nonce, 0, 32);
|
||||
Array.Copy(sha1.Hash, 0, tmp_aes_key, 20, 12); // + SHA1(server_nonce, new_nonce)[0:12]
|
||||
Array.Copy(sha1.Hash, 12, tmp_aes_iv, 0, 8); // tmp_aes_iv != SHA1(server_nonce, new_nonce)[12:8]
|
||||
sha1.Initialize();
|
||||
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
|
||||
sha1.TransformFinalBlock(new_nonce, 0, 32);
|
||||
sha1.Hash.CopyTo(tmp_aes_iv, 8); // + SHA(new_nonce + new_nonce)
|
||||
Array.Copy(new_nonce, 0, tmp_aes_iv, 28, 4); // + new_nonce[0:4]
|
||||
sha1.Initialize();
|
||||
return (tmp_aes_key, tmp_aes_iv);
|
||||
}
|
||||
public static (byte[] key, byte[] iv) ConstructTmpAESKeyIV(SHA1 sha1, TL.Int128 server_nonce, Int256 new_nonce)
|
||||
{
|
||||
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
|
||||
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
|
||||
sha1.TransformFinalBlock(server_nonce, 0, 16);
|
||||
sha1.Hash.CopyTo(tmp_aes_key, 0); // tmp_aes_key := SHA1(new_nonce + server_nonce)
|
||||
sha1.Initialize();
|
||||
sha1.TransformBlock(server_nonce, 0, 16, null, 0);
|
||||
sha1.TransformFinalBlock(new_nonce, 0, 32);
|
||||
Array.Copy(sha1.Hash, 0, tmp_aes_key, 20, 12); // + SHA1(server_nonce, new_nonce)[0:12]
|
||||
Array.Copy(sha1.Hash, 12, tmp_aes_iv, 0, 8); // tmp_aes_iv != SHA1(server_nonce, new_nonce)[12:8]
|
||||
sha1.Initialize();
|
||||
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
|
||||
sha1.TransformFinalBlock(new_nonce, 0, 32);
|
||||
sha1.Hash.CopyTo(tmp_aes_iv, 8); // + SHA(new_nonce + new_nonce)
|
||||
Array.Copy(new_nonce, 0, tmp_aes_iv, 28, 4); // + new_nonce[0:4]
|
||||
sha1.Initialize();
|
||||
return (tmp_aes_key, tmp_aes_iv);
|
||||
}
|
||||
|
||||
internal static void CheckGoodPrime(BigInteger p, int g)
|
||||
|
|
@ -237,6 +237,8 @@ namespace WTelegram
|
|||
throw new WTException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
|
||||
}
|
||||
|
||||
/// <summary>Load a specific Telegram server public key</summary>
|
||||
/// <param name="pem">A string starting with <c>-----BEGIN RSA PUBLIC KEY-----</c></param>
|
||||
public static void LoadPublicKey(string pem)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
|
|
@ -245,10 +247,7 @@ namespace WTelegram
|
|||
var rsaParam = rsa.ExportParameters(false);
|
||||
if (rsaParam.Modulus[0] == 0) rsaParam.Modulus = rsaParam.Modulus[1..];
|
||||
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
|
||||
using var memStream = new MemoryStream(280);
|
||||
using (var writer = new BinaryWriter(memStream))
|
||||
writer.WriteTLObject(publicKey);
|
||||
var bareData = memStream.ToArray();
|
||||
var bareData = publicKey.ToBytes();
|
||||
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1
|
||||
PublicKeys[fingerprint] = publicKey;
|
||||
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
|
||||
|
|
@ -276,7 +275,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
-----END RSA PUBLIC KEY-----");
|
||||
}
|
||||
|
||||
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, int x, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
|
||||
public static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, int x, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
|
||||
{
|
||||
// first, construct AES key & IV
|
||||
byte[] aes_key = new byte[32], aes_iv = new byte[32];
|
||||
|
|
@ -297,7 +296,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
|
||||
}
|
||||
|
||||
internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
|
||||
public static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
|
||||
{
|
||||
if (input.Length % 16 != 0) throw new WTException("AES_IGE input size not divisible by 16");
|
||||
|
||||
|
|
@ -305,8 +304,8 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
var output = new byte[input.Length];
|
||||
var prevBytes = (byte[])aes_iv.Clone();
|
||||
var span = MemoryMarshal.Cast<byte, long>(input);
|
||||
var sout = MemoryMarshal.Cast<byte, long>(output);
|
||||
var prev = MemoryMarshal.Cast<byte, long>(prevBytes);
|
||||
var sout = MemoryMarshal.Cast<byte, long>(output.AsSpan());
|
||||
var prev = MemoryMarshal.Cast<byte, long>(prevBytes.AsSpan());
|
||||
if (!encrypt) { (prev[2], prev[0]) = (prev[0], prev[2]); (prev[3], prev[1]) = (prev[1], prev[3]); }
|
||||
for (int i = 0, count = input.Length / 8; i < count;)
|
||||
{
|
||||
|
|
@ -319,7 +318,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
}
|
||||
|
||||
#if OBFUSCATION
|
||||
internal sealed class AesCtr(byte[] key, byte[] ivec) : IDisposable
|
||||
public sealed class AesCtr(byte[] key, byte[] ivec) : IDisposable
|
||||
{
|
||||
readonly ICryptoTransform _encryptor = AesECB.CreateEncryptor(key, null);
|
||||
readonly byte[] _ecount = new byte[16];
|
||||
|
|
@ -327,9 +326,9 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
|
||||
public void Dispose() => _encryptor.Dispose();
|
||||
|
||||
public void EncryptDecrypt(byte[] buffer, int length)
|
||||
public void EncryptDecrypt(Span<byte> buffer)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
if (_num == 0)
|
||||
{
|
||||
|
|
@ -373,7 +372,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
var sendCtr = new AesCtr(sendKey, sendIV);
|
||||
var recvCtr = new AesCtr(recvKey, recvIV);
|
||||
var encrypted = (byte[])preamble.Clone();
|
||||
sendCtr.EncryptDecrypt(encrypted, 64);
|
||||
sendCtr.EncryptDecrypt(encrypted);
|
||||
for (int i = 56; i < 64; i++)
|
||||
preamble[i] = encrypted[i];
|
||||
return (sendCtr, recvCtr, preamble);
|
||||
|
|
@ -557,7 +556,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
{
|
||||
count = count + 15 & ~15;
|
||||
var span = MemoryMarshal.Cast<byte, long>(buffer.AsSpan(offset, count));
|
||||
var prev = MemoryMarshal.Cast<byte, long>(_prevBytes);
|
||||
var prev = MemoryMarshal.Cast<byte, long>(_prevBytes.AsSpan());
|
||||
for (offset = 0, count /= 8; offset < count;)
|
||||
{
|
||||
prev[0] ^= span[offset]; prev[1] ^= span[offset + 1];
|
||||
|
|
|
|||
|
|
@ -27,8 +27,88 @@ namespace WTelegram
|
|||
public static readonly JsonSerializerOptions JsonOptions = new() { IncludeFields = true, WriteIndented = true,
|
||||
#if NET8_0_OR_GREATER
|
||||
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault ? null : WTelegramContext.Default,
|
||||
Converters = { new TLJsonConverter(), new JsonStringEnumConverter() },
|
||||
#endif
|
||||
IgnoreReadOnlyProperties = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
public sealed class TLJsonConverter : JsonConverter<object>
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
=> typeToConvert.IsAbstract || typeToConvert == typeof(Dictionary<long, TL.User>) || typeToConvert == typeof(Dictionary<long, TL.ChatBase>);
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (typeToConvert == typeof(Dictionary<long, TL.User>))
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartArray) throw new JsonException("Expected array for users dictionary");
|
||||
var users = new Dictionary<long, TL.User>();
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
|
||||
{
|
||||
var user = JsonSerializer.Deserialize<TL.User>(ref reader, options);
|
||||
if (user != null) users[user.id] = user;
|
||||
}
|
||||
return users;
|
||||
}
|
||||
else if (typeToConvert == typeof(Dictionary<long, TL.ChatBase>))
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartArray) throw new JsonException("Expected array for chats dictionary");
|
||||
var chats = new Dictionary<long, TL.ChatBase>();
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
|
||||
{
|
||||
var chat = (TL.ChatBase)Read(ref reader, typeof(TL.ChatBase), options);
|
||||
if (chat != null) chats[chat.ID] = chat;
|
||||
}
|
||||
return chats;
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.Null)
|
||||
return null;
|
||||
else if (reader.TokenType == JsonTokenType.StartObject)
|
||||
{
|
||||
var typeReader = reader;
|
||||
if (!typeReader.Read() || typeReader.TokenType != JsonTokenType.PropertyName || typeReader.GetString() != "$")
|
||||
throw new JsonException("Expected $ type property");
|
||||
if (!typeReader.Read() || typeReader.TokenType != JsonTokenType.String)
|
||||
throw new JsonException("Invalid $ type property");
|
||||
var type = typeReader.GetString();
|
||||
var actualType = typeToConvert.Assembly.GetType("TL." + type);
|
||||
if (!typeToConvert.IsAssignableFrom(actualType))
|
||||
throw new JsonException($"Incompatible $ type: {type} -> {typeToConvert}");
|
||||
return JsonSerializer.Deserialize(ref reader, actualType, options);
|
||||
}
|
||||
throw new JsonException($"Unexpected token type: {reader.TokenType}");
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is Dictionary<long, TL.User> users)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var element in users.Values)
|
||||
JsonSerializer.Serialize(writer, element, options);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
else if (value is Dictionary<long, TL.ChatBase> chats)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var element in chats.Values)
|
||||
Write(writer, element, options);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
else if (value is null)
|
||||
writer.WriteNullValue();
|
||||
else
|
||||
{
|
||||
var actualType = value.GetType();
|
||||
var jsonObject = JsonSerializer.SerializeToElement(value, actualType, options);
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("$", actualType.Name);
|
||||
foreach (var property in jsonObject.EnumerateObject())
|
||||
if (char.IsLower(property.Name[0]))
|
||||
property.WriteTo(writer);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
IgnoreReadOnlyProperties = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
|
||||
|
||||
private static readonly ConsoleColor[] LogLevelToColor = [ ConsoleColor.DarkGray, ConsoleColor.DarkCyan,
|
||||
ConsoleColor.Cyan, ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue ];
|
||||
|
|
|
|||
|
|
@ -29,13 +29,20 @@ namespace TL
|
|||
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;
|
||||
{ // update previously full user from min user:
|
||||
// see https://github.com/tdlib/td/blob/master/td/telegram/UserManager.cpp#L2689
|
||||
// and https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/data/data_session.cpp#L515
|
||||
const User.Flags updated_flags = User.Flags.deleted | User.Flags.bot | User.Flags.bot_chat_history |
|
||||
User.Flags.bot_nochats | User.Flags.verified | User.Flags.restricted | User.Flags.has_bot_inline_placeholder |
|
||||
User.Flags.bot_inline_geo | User.Flags.support | User.Flags.scam | User.Flags.fake | User.Flags.bot_attach_menu |
|
||||
User.Flags.premium | User.Flags.has_emoji_status;
|
||||
const User.Flags2 updated_flags2 = User.Flags2.has_usernames | User.Flags2.stories_unavailable |
|
||||
User.Flags2.has_color | User.Flags2.has_profile_color | User.Flags2.contact_require_premium |
|
||||
User.Flags2.bot_business | User.Flags2.bot_has_main_app | User.Flags2.bot_forum_view;
|
||||
// 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)
|
||||
// updated flags2: stories_unavailable | main_app | bot_business | bot_forum_view (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
|
||||
|
|
@ -53,8 +60,8 @@ namespace TL
|
|||
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.usernames = user.usernames; // tdlib/tdesktop: not updated
|
||||
if (user.stories_max_id != null)
|
||||
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
|
||||
|
|
@ -68,8 +75,13 @@ namespace TL
|
|||
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))
|
||||
else if (!_chats.TryGetValue(channel.id, out var prevChat) || prevChat is not Channel prevChannel)
|
||||
_chats[channel.id] = channel;
|
||||
else if (!channel.flags.HasFlag(Channel.Flags.min) || prevChannel.flags.HasFlag(Channel.Flags.min))
|
||||
{
|
||||
if (channel.participants_count == 0) channel.participants_count = prevChannel.participants_count; // non-min channel can lack this info
|
||||
_chats[channel.id] = channel;
|
||||
}
|
||||
else
|
||||
{ // update previously full channel from min channel:
|
||||
const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0;
|
||||
|
|
@ -376,15 +388,15 @@ namespace TL
|
|||
end = offset + 1;
|
||||
if (end < sb.Length && sb[end] == '#') end++;
|
||||
while (end < sb.Length && sb[end] is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9') end++;
|
||||
if (end >= sb.Length || sb[end] != ';') break;
|
||||
var html = HttpUtility.HtmlDecode(sb.ToString(offset, end - offset + 1));
|
||||
var html = HttpUtility.HtmlDecode(end >= sb.Length || sb[end] != ';'
|
||||
? sb.ToString(offset, end - offset) + ";" : sb.ToString(offset, ++end - offset));
|
||||
if (html.Length == 1)
|
||||
{
|
||||
sb[offset] = html[0];
|
||||
sb.Remove(++offset, end - offset + 1);
|
||||
sb.Remove(++offset, end - offset);
|
||||
}
|
||||
else
|
||||
offset = end + 1;
|
||||
offset = end;
|
||||
}
|
||||
else if (c == '<')
|
||||
{
|
||||
|
|
@ -401,6 +413,7 @@ namespace TL
|
|||
case "u": case "ins": ProcessEntity<MessageEntityUnderline>(); break;
|
||||
case "s": case "strike": case "del": ProcessEntity<MessageEntityStrike>(); break;
|
||||
case "span class=\"tg-spoiler\"":
|
||||
case "span class='tg-spoiler'":
|
||||
case "span" when closing:
|
||||
case "tg-spoiler": ProcessEntity<MessageEntitySpoiler>(); break;
|
||||
case "code": ProcessEntity<MessageEntityCode>(); break;
|
||||
|
|
@ -420,7 +433,8 @@ namespace TL
|
|||
prevEntity.length = offset - prevEntity.offset;
|
||||
}
|
||||
}
|
||||
else if (tag.StartsWith("a href=\"") && tag[^1] == '"')
|
||||
else if ((tag[^1] == '"' && tag.StartsWith("a href=\""))
|
||||
|| (tag[^1] == '\'' && tag.StartsWith("a href='")))
|
||||
{
|
||||
tag = HttpUtility.HtmlDecode(tag[8..^1]);
|
||||
if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && users?.GetValueOrDefault(user_id)?.access_hash is long hash)
|
||||
|
|
@ -428,12 +442,13 @@ namespace TL
|
|||
else
|
||||
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag });
|
||||
}
|
||||
else if (tag.StartsWith("code class=\"language-") && tag[^1] == '"')
|
||||
else if ((tag[^1] == '"' && tag.StartsWith("code class=\"language-"))
|
||||
|| (tag[^1] == '\'' && tag.StartsWith("code class='language-")))
|
||||
{
|
||||
if (entities.LastOrDefault(e => e.length == -1) is MessageEntityPre prevEntity)
|
||||
prevEntity.language = tag[21..^1];
|
||||
}
|
||||
else if (premium && (tag.StartsWith("tg-emoji emoji-id=\"") || tag.StartsWith("tg-emoji id=\"")))
|
||||
else if (premium && (tag.StartsWith("tg-emoji emoji-id=\"") || tag.StartsWith("tg-emoji emoji-id='")))
|
||||
entities.Add(new MessageEntityCustomEmoji { offset = offset, length = -1, document_id = long.Parse(tag[(tag.IndexOf('=') + 2)..^1]) });
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,25 +21,25 @@ namespace WTelegram
|
|||
|
||||
public sealed class DCSession
|
||||
{
|
||||
public long Id;
|
||||
public long AuthKeyID;
|
||||
public byte[] AuthKey; // 2048-bit = 256 bytes
|
||||
public long UserId;
|
||||
public long OldSalt; // still accepted for a further 1800 seconds
|
||||
public long Salt;
|
||||
public SortedList<DateTime, long> Salts;
|
||||
public int Seqno;
|
||||
public long ServerTicksOffset;
|
||||
public long LastSentMsgId;
|
||||
public TL.DcOption DataCenter;
|
||||
public bool WithoutUpdates;
|
||||
public int Layer;
|
||||
|
||||
internal long id = Helpers.RandomLong();
|
||||
internal long authKeyID;
|
||||
internal int seqno;
|
||||
internal long serverTicksOffset;
|
||||
internal long lastSentMsgId;
|
||||
internal bool withoutUpdates;
|
||||
internal Client Client;
|
||||
internal int DcID => DataCenter?.id ?? 0;
|
||||
internal int DcID => DataCenter == null ? 0 : DataCenter.flags.HasFlag(TL.DcOption.Flags.media_only) ? -DataCenter.id : DataCenter.id;
|
||||
internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port);
|
||||
internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); Id = Helpers.RandomLong(); Seqno = 0; LastSentMsgId = 0; }
|
||||
public void DisableUpdates(bool disable = true) { if (WithoutUpdates != disable) { WithoutUpdates = disable; Renew(); } }
|
||||
internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); id = Helpers.RandomLong(); seqno = 0; lastSentMsgId = 0; }
|
||||
public void DisableUpdates(bool disable = true) { if (withoutUpdates != disable) { withoutUpdates = disable; Renew(); } }
|
||||
|
||||
const int MsgIdsN = 512;
|
||||
private long[] _msgIds;
|
||||
|
|
@ -117,6 +117,9 @@ namespace WTelegram
|
|||
throw new WTException("Integrity check failed in session loading");
|
||||
session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
|
||||
Helpers.Log(2, "Loaded previous session");
|
||||
using var sha1 = SHA1.Create();
|
||||
foreach (var dcs in session.DCSessions.Values)
|
||||
dcs.authKeyID = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(dcs.AuthKey).AsSpan(12));
|
||||
}
|
||||
session ??= new Session();
|
||||
session._store = store;
|
||||
|
|
|
|||
|
|
@ -109,15 +109,13 @@ namespace TL
|
|||
public Int128 new_nonce_hash3;
|
||||
}
|
||||
|
||||
public enum DestroyAuthKeyRes : uint
|
||||
{
|
||||
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_ok"/></summary>
|
||||
Ok = 0xF660E1D4,
|
||||
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_none"/></summary>
|
||||
None = 0x0A9F2259,
|
||||
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_fail"/></summary>
|
||||
Fail = 0xEA109B13,
|
||||
}
|
||||
public abstract partial class DestroyAuthKeyRes : IObject { }
|
||||
[TLDef(0xF660E1D4)] //destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes
|
||||
public sealed partial class DestroyAuthKeyOk : DestroyAuthKeyRes { }
|
||||
[TLDef(0x0A9F2259)] //destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes
|
||||
public sealed partial class DestroyAuthKeyNone : DestroyAuthKeyRes { }
|
||||
[TLDef(0xEA109B13)] //destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes
|
||||
public sealed partial class DestroyAuthKeyFail : DestroyAuthKeyRes { }
|
||||
|
||||
[TLDef(0x62D6B459)] //msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck
|
||||
public sealed partial class MsgsAck : IObject
|
||||
|
|
@ -327,12 +325,12 @@ namespace TL
|
|||
});
|
||||
|
||||
public static Task<DestroyAuthKeyRes> DestroyAuthKey(this Client client)
|
||||
=> client.InvokeBare(new DestroyAuthKey
|
||||
=> client.Invoke(new DestroyAuthKey
|
||||
{
|
||||
});
|
||||
|
||||
public static Task<RpcDropAnswer> RpcDropAnswer(this Client client, long req_msg_id)
|
||||
=> client.InvokeBare(new Methods.RpcDropAnswer
|
||||
=> client.Invoke(new Methods.RpcDropAnswer
|
||||
{
|
||||
req_msg_id = req_msg_id,
|
||||
});
|
||||
|
|
@ -357,7 +355,7 @@ namespace TL
|
|||
});
|
||||
|
||||
public static Task<DestroySessionRes> DestroySession(this Client client, long session_id)
|
||||
=> client.InvokeBare(new DestroySession
|
||||
=> client.Invoke(new DestroySession
|
||||
{
|
||||
session_id = session_id,
|
||||
});
|
||||
|
|
|
|||
2623
src/TL.Schema.cs
2623
src/TL.Schema.cs
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@
|
|||
namespace TL
|
||||
{
|
||||
#pragma warning disable IDE1006, CS1574
|
||||
/// <summary>Object describes the contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessage"/></para></summary>
|
||||
/// <summary>Object describes the contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessage"/></para> <para>Derived classes: <see cref="DecryptedMessage"/>, <see cref="DecryptedMessageService"/></para></summary>
|
||||
public abstract partial class DecryptedMessageBase : IObject
|
||||
{
|
||||
/// <summary>Flags, see <a href="https://corefork.telegram.org/mtproto/TL-combinators#conditional-fields">TL conditional fields</a> (added in layer 45)</summary>
|
||||
|
|
@ -24,11 +24,12 @@ namespace TL
|
|||
public virtual long ReplyToRandom => default;
|
||||
/// <summary>Random group ID, assigned by the author of message.<br/>Multiple encrypted messages with a photo attached and with the same group ID indicate an <a href="https://corefork.telegram.org/api/files#albums-grouped-media">album or grouped media</a> (parameter added in layer 45)</summary>
|
||||
public virtual long Grouped => default;
|
||||
/// <summary>Random bytes, removed in layer 17.</summary>
|
||||
public virtual byte[] RandomBytes => default;
|
||||
public virtual DecryptedMessageAction Action => default;
|
||||
}
|
||||
|
||||
/// <summary>Object describes media contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageMedia"/></para></summary>
|
||||
/// <summary>Object describes media contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageMedia"/></para> <para>Derived classes: <see cref="DecryptedMessageMediaPhoto"/>, <see cref="DecryptedMessageMediaVideo"/>, <see cref="DecryptedMessageMediaGeoPoint"/>, <see cref="DecryptedMessageMediaContact"/>, <see cref="DecryptedMessageMediaDocument"/>, <see cref="DecryptedMessageMediaAudio"/>, <see cref="DecryptedMessageMediaExternalDocument"/>, <see cref="DecryptedMessageMediaVenue"/>, <see cref="DecryptedMessageMediaWebPage"/></para></summary>
|
||||
/// <remarks>a <see langword="null"/> value means <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaEmpty">decryptedMessageMediaEmpty</a></remarks>
|
||||
public abstract partial class DecryptedMessageMedia : IObject
|
||||
{
|
||||
|
|
@ -36,14 +37,17 @@ namespace TL
|
|||
internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new WTelegram.WTException("Incompatible DecryptedMessageMedia"); }
|
||||
}
|
||||
|
||||
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para></summary>
|
||||
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para> <para>Derived classes: <see cref="DecryptedMessageActionSetMessageTTL"/>, <see cref="DecryptedMessageActionReadMessages"/>, <see cref="DecryptedMessageActionDeleteMessages"/>, <see cref="DecryptedMessageActionScreenshotMessages"/>, <see cref="DecryptedMessageActionFlushHistory"/>, <see cref="DecryptedMessageActionResend"/>, <see cref="DecryptedMessageActionNotifyLayer"/>, <see cref="DecryptedMessageActionTyping"/>, <see cref="DecryptedMessageActionRequestKey"/>, <see cref="DecryptedMessageActionAcceptKey"/>, <see cref="DecryptedMessageActionAbortKey"/>, <see cref="DecryptedMessageActionCommitKey"/>, <see cref="DecryptedMessageActionNoop"/></para></summary>
|
||||
public abstract partial class DecryptedMessageAction : IObject { }
|
||||
|
||||
/// <summary>Indicates the location of a photo, will be deprecated soon <para>See <a href="https://corefork.telegram.org/type/FileLocation"/></para></summary>
|
||||
/// <summary>Indicates the location of a photo, will be deprecated soon <para>See <a href="https://corefork.telegram.org/type/FileLocation"/></para> <para>Derived classes: <see cref="FileLocationUnavailable"/>, <see cref="FileLocation"/></para></summary>
|
||||
public abstract partial class FileLocationBase : IObject
|
||||
{
|
||||
/// <summary>Volume ID</summary>
|
||||
public virtual long VolumeId => default;
|
||||
/// <summary>Local ID</summary>
|
||||
public virtual int LocalId => default;
|
||||
/// <summary>Secret</summary>
|
||||
public virtual long Secret => default;
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +59,7 @@ namespace TL
|
|||
{
|
||||
/// <summary>Random message ID, assigned by the author of message.<br/>Must be equal to the ID passed to sending method.</summary>
|
||||
public long random_id;
|
||||
/// <summary>Random bytes, removed in layer 17.</summary>
|
||||
public byte[] random_bytes;
|
||||
/// <summary>Message text</summary>
|
||||
public string message;
|
||||
|
|
@ -67,6 +72,7 @@ namespace TL
|
|||
public override string Message => message;
|
||||
/// <summary>Media content</summary>
|
||||
public override DecryptedMessageMedia Media => media;
|
||||
/// <summary>Random bytes, removed in layer 17.</summary>
|
||||
public override byte[] RandomBytes => random_bytes;
|
||||
}
|
||||
/// <summary>Contents of an encrypted service message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageService"/></para></summary>
|
||||
|
|
@ -75,12 +81,14 @@ namespace TL
|
|||
{
|
||||
/// <summary>Random message ID, assigned by the message author.<br/>Must be equal to the ID passed to the sending method.</summary>
|
||||
public long random_id;
|
||||
/// <summary>Random bytes, removed in Layer 17.</summary>
|
||||
public byte[] random_bytes;
|
||||
/// <summary>Action relevant to the service message</summary>
|
||||
public DecryptedMessageAction action;
|
||||
|
||||
/// <summary>Random message ID, assigned by the message author.<br/>Must be equal to the ID passed to the sending method.</summary>
|
||||
public override long RandomId => random_id;
|
||||
/// <summary>Random bytes, removed in Layer 17.</summary>
|
||||
public override byte[] RandomBytes => random_bytes;
|
||||
/// <summary>Action relevant to the service message</summary>
|
||||
public override DecryptedMessageAction Action => action;
|
||||
|
|
@ -167,6 +175,7 @@ namespace TL
|
|||
public int thumb_w;
|
||||
/// <summary>Thumbnail height</summary>
|
||||
public int thumb_h;
|
||||
/// <summary>File name, moved to <c>attributes</c> in Layer 45.</summary>
|
||||
public string file_name;
|
||||
/// <summary>File MIME-type</summary>
|
||||
public string mime_type;
|
||||
|
|
@ -498,25 +507,38 @@ namespace TL
|
|||
[TLDef(0x7C596B46)]
|
||||
public sealed partial class FileLocationUnavailable : FileLocationBase
|
||||
{
|
||||
/// <summary>Volume ID</summary>
|
||||
public long volume_id;
|
||||
/// <summary>Local ID</summary>
|
||||
public int local_id;
|
||||
/// <summary>Secret</summary>
|
||||
public long secret;
|
||||
|
||||
/// <summary>Volume ID</summary>
|
||||
public override long VolumeId => volume_id;
|
||||
/// <summary>Local ID</summary>
|
||||
public override int LocalId => local_id;
|
||||
/// <summary>Secret</summary>
|
||||
public override long Secret => secret;
|
||||
}
|
||||
/// <summary>File location. <para>See <a href="https://corefork.telegram.org/constructor/fileLocation"/></para></summary>
|
||||
[TLDef(0x53D69076)]
|
||||
public sealed partial class FileLocation : FileLocationBase
|
||||
{
|
||||
/// <summary>DC ID</summary>
|
||||
public int dc_id;
|
||||
/// <summary>Volume ID</summary>
|
||||
public long volume_id;
|
||||
/// <summary>Local ID</summary>
|
||||
public int local_id;
|
||||
/// <summary>Secret</summary>
|
||||
public long secret;
|
||||
|
||||
/// <summary>Volume ID</summary>
|
||||
public override long VolumeId => volume_id;
|
||||
/// <summary>Local ID</summary>
|
||||
public override int LocalId => local_id;
|
||||
/// <summary>Secret</summary>
|
||||
public override long Secret => secret;
|
||||
}
|
||||
}
|
||||
|
|
@ -775,7 +797,6 @@ namespace TL
|
|||
{
|
||||
/// <summary>Field <see cref="reply_to_random_id"/> has a value</summary>
|
||||
has_reply_to_random_id = 0x8,
|
||||
/// <summary>Whether this is a silent message (no notification triggered)</summary>
|
||||
silent = 0x20,
|
||||
/// <summary>Field <see cref="entities"/> has a value</summary>
|
||||
has_entities = 0x80,
|
||||
|
|
|
|||
231
src/TL.Table.cs
231
src/TL.Table.cs
|
|
@ -6,7 +6,7 @@ namespace TL
|
|||
{
|
||||
public static partial class Layer
|
||||
{
|
||||
public const int Version = 201; // fetched 26/03/2025 23:35:58
|
||||
public const int Version = 220; // fetched 12/06/2025 13:11:05
|
||||
internal const int SecretChats = 144;
|
||||
internal const int MTProto2 = 73;
|
||||
internal const uint VectorCtor = 0x1CB5C415;
|
||||
|
|
@ -41,7 +41,9 @@ namespace TL
|
|||
[0x3BCBF734] = typeof(DhGenOk),
|
||||
[0x46DC1FB9] = typeof(DhGenRetry),
|
||||
[0xA69DAE02] = typeof(DhGenFail),
|
||||
[0x7ABE77EC] = typeof(Methods.Ping),
|
||||
[0xF660E1D4] = typeof(DestroyAuthKeyOk),
|
||||
[0x0A9F2259] = typeof(DestroyAuthKeyNone),
|
||||
[0xEA109B13] = typeof(DestroyAuthKeyFail),
|
||||
[0x62D6B459] = typeof(MsgsAck),
|
||||
[0xA7EFF811] = typeof(BadMsgNotification),
|
||||
[0xEDAB447B] = typeof(BadServerSalt),
|
||||
|
|
@ -66,6 +68,7 @@ namespace TL
|
|||
[0x37982646] = typeof(IpPortSecret),
|
||||
[0x4679B65F] = typeof(AccessPointRule),
|
||||
[0x5A592A6C] = typeof(Help_ConfigSimple),
|
||||
[0x7ABE77EC] = typeof(Methods.Ping),
|
||||
// from TL.SchemaExtensions:
|
||||
[0x3FEDD339] = typeof(True),
|
||||
[0xC4B9F9BB] = typeof(Error),
|
||||
|
|
@ -81,7 +84,7 @@ namespace TL
|
|||
[0xF7C1B13F] = typeof(InputUserSelf),
|
||||
[0xF21158C6] = typeof(InputUser),
|
||||
[0x1DA448E2] = typeof(InputUserFromMessage),
|
||||
[0xF392B7F4] = typeof(InputPhoneContact),
|
||||
[0x6A1DC4BE] = typeof(InputPhoneContact),
|
||||
[0xF52FF27F] = typeof(InputFile),
|
||||
[0xFA4F0BB5] = typeof(InputFileBig),
|
||||
[0x62DC8B48] = typeof(InputFileStoryDocument),
|
||||
|
|
@ -103,6 +106,7 @@ namespace TL
|
|||
[0x89FDD778] = typeof(InputMediaStory),
|
||||
[0xC21B8849] = typeof(InputMediaWebPage),
|
||||
[0xC4103386] = typeof(InputMediaPaidMedia),
|
||||
[0x9FC55FDE] = typeof(InputMediaTodo),
|
||||
[0x1CA48F57] = null,//InputChatPhotoEmpty
|
||||
[0xBDCDAEC0] = typeof(InputChatUploadedPhoto),
|
||||
[0x8953AD37] = typeof(InputChatPhoto),
|
||||
|
|
@ -124,7 +128,7 @@ namespace TL
|
|||
[0x36C6019A] = typeof(PeerChat),
|
||||
[0xA2A5371E] = typeof(PeerChannel),
|
||||
[0xD3BC4B7A] = typeof(UserEmpty),
|
||||
[0x020B1422] = typeof(User),
|
||||
[0x31774388] = typeof(User),
|
||||
[0x4F11BAE1] = null,//UserProfilePhotoEmpty
|
||||
[0x82D1F706] = typeof(UserProfilePhoto),
|
||||
[0x09D05049] = null,//UserStatusEmpty
|
||||
|
|
@ -136,10 +140,10 @@ namespace TL
|
|||
[0x29562865] = typeof(ChatEmpty),
|
||||
[0x41CBF256] = typeof(Chat),
|
||||
[0x6592A1A7] = typeof(ChatForbidden),
|
||||
[0x7482147E] = typeof(Channel),
|
||||
[0x1C32B11C] = typeof(Channel),
|
||||
[0x17D493D5] = typeof(ChannelForbidden),
|
||||
[0x2633421B] = typeof(ChatFull),
|
||||
[0x52D6806B] = typeof(ChannelFull),
|
||||
[0xE4E0B29D] = typeof(ChannelFull),
|
||||
[0xC02D4007] = typeof(ChatParticipant),
|
||||
[0xE46BCEE4] = typeof(ChatParticipantCreator),
|
||||
[0xA0933F5B] = typeof(ChatParticipantAdmin),
|
||||
|
|
@ -148,8 +152,8 @@ namespace TL
|
|||
[0x37C1011C] = null,//ChatPhotoEmpty
|
||||
[0x1C6E1C11] = typeof(ChatPhoto),
|
||||
[0x90A6CA84] = typeof(MessageEmpty),
|
||||
[0xEABCDD4D] = typeof(Message),
|
||||
[0xD3D28540] = typeof(MessageService),
|
||||
[0xB92F76CF] = typeof(Message),
|
||||
[0x7A800E0A] = typeof(MessageService),
|
||||
[0x3DED6320] = null,//MessageMediaEmpty
|
||||
[0x695150D7] = typeof(MessageMediaPhoto),
|
||||
[0x56E0D474] = typeof(MessageMediaGeo),
|
||||
|
|
@ -167,6 +171,8 @@ namespace TL
|
|||
[0xAA073BEB] = typeof(MessageMediaGiveaway),
|
||||
[0xCEAA3EA1] = typeof(MessageMediaGiveawayResults),
|
||||
[0xA8852491] = typeof(MessageMediaPaidMedia),
|
||||
[0x8A53B014] = typeof(MessageMediaToDo),
|
||||
[0xCA5CAB89] = typeof(MessageMediaVideoStream),
|
||||
[0xB6AEF7B0] = null,//MessageActionEmpty
|
||||
[0xBD47CBAD] = typeof(MessageActionChatCreate),
|
||||
[0xB5A1CE5A] = typeof(MessageActionChatEditTitle),
|
||||
|
|
@ -195,17 +201,17 @@ namespace TL
|
|||
[0x502F92F7] = typeof(MessageActionInviteToGroupCall),
|
||||
[0x3C134D7B] = typeof(MessageActionSetMessagesTTL),
|
||||
[0xB3A07661] = typeof(MessageActionGroupCallScheduled),
|
||||
[0xAA786345] = typeof(MessageActionSetChatTheme),
|
||||
[0xB91BBD3A] = typeof(MessageActionSetChatTheme),
|
||||
[0xEBBCA3CB] = typeof(MessageActionChatJoinedByRequest),
|
||||
[0x47DD8079] = typeof(MessageActionWebViewDataSentMe),
|
||||
[0xB4C38CB5] = typeof(MessageActionWebViewDataSent),
|
||||
[0x6C6274FA] = typeof(MessageActionGiftPremium),
|
||||
[0x48E91302] = typeof(MessageActionGiftPremium),
|
||||
[0x0D999256] = typeof(MessageActionTopicCreate),
|
||||
[0xC0944820] = typeof(MessageActionTopicEdit),
|
||||
[0x57DE635E] = typeof(MessageActionSuggestProfilePhoto),
|
||||
[0x31518E9B] = typeof(MessageActionRequestedPeer),
|
||||
[0x5060A3F4] = typeof(MessageActionSetChatWallPaper),
|
||||
[0x56D03994] = typeof(MessageActionGiftCode),
|
||||
[0x31C48347] = typeof(MessageActionGiftCode),
|
||||
[0xA80F51E4] = typeof(MessageActionGiveawayLaunch),
|
||||
[0x87E2F155] = typeof(MessageActionGiveawayResults),
|
||||
[0xCC02AA6D] = typeof(MessageActionBoostApply),
|
||||
|
|
@ -213,10 +219,20 @@ namespace TL
|
|||
[0x41B3E202] = typeof(MessageActionPaymentRefunded),
|
||||
[0x45D5B021] = typeof(MessageActionGiftStars),
|
||||
[0xB00C47A2] = typeof(MessageActionPrizeStars),
|
||||
[0x4717E8A4] = typeof(MessageActionStarGift),
|
||||
[0xACDFCB81] = typeof(MessageActionStarGiftUnique),
|
||||
[0xEA2C31D3] = typeof(MessageActionStarGift),
|
||||
[0x95728543] = typeof(MessageActionStarGiftUnique),
|
||||
[0xAC1F1FCD] = typeof(MessageActionPaidMessagesRefunded),
|
||||
[0xBCD71419] = typeof(MessageActionPaidMessagesPrice),
|
||||
[0x84B88578] = typeof(MessageActionPaidMessagesPrice),
|
||||
[0x2FFE2F7A] = typeof(MessageActionConferenceCall),
|
||||
[0xCC7C5C89] = typeof(MessageActionTodoCompletions),
|
||||
[0xC7EDBC83] = typeof(MessageActionTodoAppendTasks),
|
||||
[0xEE7A1596] = typeof(MessageActionSuggestedPostApproval),
|
||||
[0x95DDCF69] = typeof(MessageActionSuggestedPostSuccess),
|
||||
[0x69F916F8] = typeof(MessageActionSuggestedPostRefund),
|
||||
[0xA8A3C699] = typeof(MessageActionGiftTon),
|
||||
[0x2C8F2A25] = typeof(MessageActionSuggestBirthday),
|
||||
[0x774278D4] = typeof(MessageActionStarGiftPurchaseOffer),
|
||||
[0x73ADA76B] = typeof(MessageActionStarGiftPurchaseOfferDeclined),
|
||||
[0xD58A08C6] = typeof(Dialog),
|
||||
[0x71BD134C] = typeof(DialogFolder),
|
||||
[0x2331B22D] = typeof(PhotoEmpty),
|
||||
|
|
@ -231,7 +247,7 @@ namespace TL
|
|||
[0xB2A2F663] = typeof(GeoPoint),
|
||||
[0x5E002502] = typeof(Auth_SentCode),
|
||||
[0x2390FE44] = typeof(Auth_SentCodeSuccess),
|
||||
[0xD7CEF980] = typeof(Auth_SentCodePaymentRequired),
|
||||
[0xE0955A3C] = typeof(Auth_SentCodePaymentRequired),
|
||||
[0x2EA2C0D4] = typeof(Auth_Authorization),
|
||||
[0x44747E9A] = typeof(Auth_AuthorizationSignUpRequired),
|
||||
[0xB434E2B8] = typeof(Auth_ExportedAuthorization),
|
||||
|
|
@ -245,7 +261,7 @@ namespace TL
|
|||
[0xF47741F7] = typeof(PeerSettings),
|
||||
[0xA437C3ED] = typeof(WallPaper),
|
||||
[0xE0804116] = typeof(WallPaperNoFile),
|
||||
[0x99E78045] = typeof(UserFull),
|
||||
[0xA02BC13E] = typeof(UserFull),
|
||||
[0x145ADE0B] = typeof(Contact),
|
||||
[0xC13E3C50] = typeof(ImportedContact),
|
||||
[0x16D9703B] = typeof(ContactStatus),
|
||||
|
|
@ -257,8 +273,8 @@ namespace TL
|
|||
[0x15BA6C40] = typeof(Messages_Dialogs),
|
||||
[0x71E094F3] = typeof(Messages_DialogsSlice),
|
||||
[0xF0E3E596] = typeof(Messages_DialogsNotModified),
|
||||
[0x8C718E87] = typeof(Messages_Messages),
|
||||
[0x3A54685E] = typeof(Messages_MessagesSlice),
|
||||
[0x1D73E7EA] = typeof(Messages_Messages),
|
||||
[0x5F206716] = typeof(Messages_MessagesSlice),
|
||||
[0xC776BA4E] = typeof(Messages_ChannelMessages),
|
||||
[0x74535F21] = typeof(Messages_MessagesNotModified),
|
||||
[0x64FF9FD5] = typeof(Messages_Chats),
|
||||
|
|
@ -285,7 +301,7 @@ namespace TL
|
|||
[0x1F2B0AFD] = typeof(UpdateNewMessage),
|
||||
[0x4E90BFD6] = typeof(UpdateMessageID),
|
||||
[0xA20DB0E5] = typeof(UpdateDeleteMessages),
|
||||
[0xC01E857F] = typeof(UpdateUserTyping),
|
||||
[0x2A17BF5C] = typeof(UpdateUserTyping),
|
||||
[0x83487AF0] = typeof(UpdateChatUserTyping),
|
||||
[0x07761198] = typeof(UpdateChatParticipants),
|
||||
[0xE5BDF8DE] = typeof(UpdateUserStatus),
|
||||
|
|
@ -302,7 +318,7 @@ namespace TL
|
|||
[0xEBE46819] = typeof(UpdateServiceNotification),
|
||||
[0xEE3B272A] = typeof(UpdatePrivacy),
|
||||
[0x05492A13] = typeof(UpdateUserPhone),
|
||||
[0x9C974FDF] = typeof(UpdateReadHistoryInbox),
|
||||
[0x9E84BC99] = typeof(UpdateReadHistoryInbox),
|
||||
[0x2F2F21BF] = typeof(UpdateReadHistoryOutbox),
|
||||
[0x7F891213] = typeof(UpdateWebPage),
|
||||
[0xF8227181] = typeof(UpdateReadMessagesContents),
|
||||
|
|
@ -324,7 +340,7 @@ namespace TL
|
|||
[0xE40370A3] = typeof(UpdateEditMessage),
|
||||
[0x691E9052] = typeof(UpdateInlineBotCallbackQuery),
|
||||
[0xB75F99A9] = typeof(UpdateReadChannelOutbox),
|
||||
[0x1B49EC6D] = typeof(UpdateDraftMessage),
|
||||
[0xEDFC111E] = typeof(UpdateDraftMessage),
|
||||
[0x571D2742] = typeof(UpdateReadFeaturedStickers),
|
||||
[0x9A422C20] = typeof(UpdateRecentStickers),
|
||||
[0xA229DD06] = typeof(UpdateConfig),
|
||||
|
|
@ -340,10 +356,10 @@ namespace TL
|
|||
[0x46560264] = typeof(UpdateLangPackTooLong),
|
||||
[0x56022F4D] = typeof(UpdateLangPack),
|
||||
[0xE511996D] = typeof(UpdateFavedStickers),
|
||||
[0xEA29055D] = typeof(UpdateChannelReadMessagesContents),
|
||||
[0x25F324F7] = typeof(UpdateChannelReadMessagesContents),
|
||||
[0x7084A7BE] = typeof(UpdateContactsReset),
|
||||
[0xB23FC698] = typeof(UpdateChannelAvailableMessages),
|
||||
[0xE16459C3] = typeof(UpdateDialogUnreadMark),
|
||||
[0xB658F23E] = typeof(UpdateDialogUnreadMark),
|
||||
[0xACA1657B] = typeof(UpdateMessagePoll),
|
||||
[0x54C01850] = typeof(UpdateChatDefaultBannedRights),
|
||||
[0x19360DC0] = typeof(UpdateFolderPeers),
|
||||
|
|
@ -368,7 +384,7 @@ namespace TL
|
|||
[0x5BB98608] = typeof(UpdatePinnedChannelMessages),
|
||||
[0xF89A6A4E] = typeof(UpdateChat),
|
||||
[0xF2EBDB4E] = typeof(UpdateGroupCallParticipants),
|
||||
[0x97D64341] = typeof(UpdateGroupCall),
|
||||
[0x9D2216E0] = typeof(UpdateGroupCall),
|
||||
[0xBB9BB9A5] = typeof(UpdatePeerHistoryTTL),
|
||||
[0xD087663A] = typeof(UpdateChatParticipant),
|
||||
[0x985D3ABB] = typeof(UpdateChannelParticipant),
|
||||
|
|
@ -377,7 +393,7 @@ namespace TL
|
|||
[0x4D712F2E] = typeof(UpdateBotCommands),
|
||||
[0x7063C3DB] = typeof(UpdatePendingJoinRequests),
|
||||
[0x11DFA986] = typeof(UpdateBotChatInviteRequester),
|
||||
[0x5E1B3CB8] = typeof(UpdateMessageReactions),
|
||||
[0x1E297BFA] = typeof(UpdateMessageReactions),
|
||||
[0x17B7A20B] = typeof(UpdateAttachMenuBots),
|
||||
[0x1592B79D] = typeof(UpdateWebViewResultSent),
|
||||
[0x14B85813] = typeof(UpdateBotMenuButton),
|
||||
|
|
@ -389,8 +405,6 @@ namespace TL
|
|||
[0x6F7863F4] = typeof(UpdateRecentReactions),
|
||||
[0x86FCCF85] = typeof(UpdateMoveStickerSetToTop),
|
||||
[0xD5A41724] = typeof(UpdateMessageExtendedMedia),
|
||||
[0x192EFBE3] = typeof(UpdateChannelPinnedTopic),
|
||||
[0xFE198602] = typeof(UpdateChannelPinnedTopics),
|
||||
[0x20529438] = typeof(UpdateUser),
|
||||
[0xEC05B097] = typeof(UpdateAutoSaveSettings),
|
||||
[0x75B3B798] = typeof(UpdateStory),
|
||||
|
|
@ -417,13 +431,23 @@ namespace TL
|
|||
[0x07DF587C] = typeof(UpdateBotEditBusinessMessage),
|
||||
[0xA02A982E] = typeof(UpdateBotDeleteBusinessMessage),
|
||||
[0x1824E40B] = typeof(UpdateNewStoryReaction),
|
||||
[0xDFD961F5] = typeof(UpdateBroadcastRevenueTransactions),
|
||||
[0x4E80A379] = typeof(UpdateStarsBalance),
|
||||
[0x1EA2FDA7] = typeof(UpdateBusinessBotCallbackQuery),
|
||||
[0xA584B019] = typeof(UpdateStarsRevenueStatus),
|
||||
[0x283BD312] = typeof(UpdateBotPurchasedPaidMedia),
|
||||
[0x8B725FCE] = typeof(UpdatePaidReactionPrivacy),
|
||||
[0x504AA18F] = typeof(UpdateSentPhoneCode),
|
||||
[0xA477288F] = typeof(UpdateGroupCallChainBlocks),
|
||||
[0x77B0E372] = typeof(UpdateReadMonoForumInbox),
|
||||
[0xA4A79376] = typeof(UpdateReadMonoForumOutbox),
|
||||
[0x9F812B08] = typeof(UpdateMonoForumNoPaidException),
|
||||
[0xD8326F0D] = typeof(UpdateGroupCallMessage),
|
||||
[0xC957A766] = typeof(UpdateGroupCallEncryptedMessage),
|
||||
[0x683B2C52] = typeof(UpdatePinnedForumTopic),
|
||||
[0xDEF143D0] = typeof(UpdatePinnedForumTopics),
|
||||
[0x3E85E92C] = typeof(UpdateDeleteGroupCallMessages),
|
||||
[0x48E246C2] = typeof(UpdateStarGiftAuctionState),
|
||||
[0xDC58F31E] = typeof(UpdateStarGiftAuctionUserState),
|
||||
[0xA56C2A3E] = typeof(Updates_State),
|
||||
[0x5D75A138] = typeof(Updates_DifferenceEmpty),
|
||||
[0x00F49CA0] = typeof(Updates_Difference),
|
||||
|
|
@ -493,6 +517,7 @@ namespace TL
|
|||
[0xB05AC6B1] = typeof(SendMessageChooseStickerAction),
|
||||
[0x25972BCB] = typeof(SendMessageEmojiInteraction),
|
||||
[0xB665902E] = typeof(SendMessageEmojiInteractionSeen),
|
||||
[0x376D975C] = typeof(SendMessageTextDraftAction),
|
||||
[0xB3134D9D] = typeof(Contacts_Found),
|
||||
[0x0D09E07B] = typeof(InputPrivacyValueAllowContacts),
|
||||
[0x184B35CE] = typeof(InputPrivacyValueAllowAll),
|
||||
|
|
@ -561,6 +586,7 @@ namespace TL
|
|||
[0x29D0F5EE] = typeof(InputStickerSetEmojiDefaultStatuses),
|
||||
[0x44C1F8E9] = typeof(InputStickerSetEmojiDefaultTopicIcons),
|
||||
[0x49748553] = typeof(InputStickerSetEmojiChannelDefaultStatuses),
|
||||
[0x1CF671A0] = typeof(InputStickerSetTonGifts),
|
||||
[0x2DD14EDC] = typeof(StickerSet),
|
||||
[0x6E153F16] = typeof(Messages_StickerSet),
|
||||
[0xD3F924EB] = null,//Messages_StickerSetNotModified
|
||||
|
|
@ -687,7 +713,7 @@ namespace TL
|
|||
[0x70B772A8] = typeof(Contacts_TopPeers),
|
||||
[0xB52C939D] = typeof(Contacts_TopPeersDisabled),
|
||||
[0x1B0C841A] = typeof(DraftMessageEmpty),
|
||||
[0x2D65321F] = typeof(DraftMessage),
|
||||
[0x96EAA5EB] = typeof(DraftMessage),
|
||||
[0xC6DC0C66] = typeof(Messages_FeaturedStickersNotModified),
|
||||
[0xBE382906] = typeof(Messages_FeaturedStickers),
|
||||
[0x0B17F890] = null,//Messages_RecentStickersNotModified
|
||||
|
|
@ -756,7 +782,7 @@ namespace TL
|
|||
[0xE095C1A0] = typeof(PhoneCallDiscardReasonDisconnect),
|
||||
[0x57ADC690] = typeof(PhoneCallDiscardReasonHangup),
|
||||
[0xFAF7E8C9] = typeof(PhoneCallDiscardReasonBusy),
|
||||
[0xAFE2B839] = typeof(PhoneCallDiscardReasonAllowGroupCall),
|
||||
[0x9FBBF1F7] = typeof(PhoneCallDiscardReasonMigrateConferenceCall),
|
||||
[0x7D748D04] = typeof(DataJSON),
|
||||
[0xCB296BF8] = typeof(LabeledPrice),
|
||||
[0x049EE584] = typeof(Invoice),
|
||||
|
|
@ -789,11 +815,11 @@ namespace TL
|
|||
[0x32DA9E9C] = typeof(InputStickerSetItem),
|
||||
[0x1E36FDED] = typeof(InputPhoneCall),
|
||||
[0x5366C915] = typeof(PhoneCallEmpty),
|
||||
[0xEED42858] = typeof(PhoneCallWaiting),
|
||||
[0x45361C63] = typeof(PhoneCallRequested),
|
||||
[0x22FD7181] = typeof(PhoneCallAccepted),
|
||||
[0x3BA5940C] = typeof(PhoneCall),
|
||||
[0xF9D25503] = typeof(PhoneCallDiscarded),
|
||||
[0xC5226F17] = typeof(PhoneCallWaiting),
|
||||
[0x14B0ED0C] = typeof(PhoneCallRequested),
|
||||
[0x3660C311] = typeof(PhoneCallAccepted),
|
||||
[0x30535AF5] = typeof(PhoneCall),
|
||||
[0x50CA4DE1] = typeof(PhoneCallDiscarded),
|
||||
[0x9CC123C7] = typeof(PhoneConnection),
|
||||
[0x635FE375] = typeof(PhoneConnectionWebrtc),
|
||||
[0xFC878FC8] = typeof(PhoneCallProtocol),
|
||||
|
|
@ -857,6 +883,7 @@ namespace TL
|
|||
[0x46D840AB] = typeof(ChannelAdminLogEventActionChangeEmojiStickerSet),
|
||||
[0x60A79C79] = typeof(ChannelAdminLogEventActionToggleSignatureProfiles),
|
||||
[0x64642DB3] = typeof(ChannelAdminLogEventActionParticipantSubExtend),
|
||||
[0xC517F77E] = typeof(ChannelAdminLogEventActionToggleAutotranslation),
|
||||
[0x1FAD68CD] = typeof(ChannelAdminLogEvent),
|
||||
[0xED8AF74D] = typeof(Channels_AdminLogResults),
|
||||
[0xEA107AE4] = typeof(ChannelAdminLogEventsFilter),
|
||||
|
|
@ -994,6 +1021,8 @@ namespace TL
|
|||
[0x2E94C3E7] = typeof(WebPageAttributeStory),
|
||||
[0x50CC03D3] = typeof(WebPageAttributeStickerSet),
|
||||
[0xCF6F6DB8] = typeof(WebPageAttributeUniqueStarGift),
|
||||
[0x31CAD303] = typeof(WebPageAttributeStarGiftCollection),
|
||||
[0x01C641C2] = typeof(WebPageAttributeStarGiftAuction),
|
||||
[0x4899484E] = typeof(Messages_VotesList),
|
||||
[0xF568028A] = typeof(BankCardOpenUrl),
|
||||
[0x3E24E573] = typeof(Payments_BankCardData),
|
||||
|
|
@ -1009,7 +1038,7 @@ namespace TL
|
|||
[0x8EA464B6] = typeof(StatsGraph),
|
||||
[0x396CA5FC] = typeof(Stats_BroadcastStats),
|
||||
[0x98F6AC75] = typeof(Help_PromoDataEmpty),
|
||||
[0x8C39793F] = typeof(Help_PromoData),
|
||||
[0x08A4D87A] = typeof(Help_PromoData),
|
||||
[0xDE33B094] = typeof(VideoSize),
|
||||
[0xF85C413C] = typeof(VideoSizeEmojiMarkup),
|
||||
[0x0DA082FE] = typeof(VideoSizeStickerMarkup),
|
||||
|
|
@ -1025,15 +1054,17 @@ namespace TL
|
|||
[0x455B853D] = typeof(MessageViews),
|
||||
[0xB6C4F543] = typeof(Messages_MessageViews),
|
||||
[0xA6341782] = typeof(Messages_DiscussionMessage),
|
||||
[0xAFBC09DB] = typeof(MessageReplyHeader),
|
||||
[0x6917560B] = typeof(MessageReplyHeader),
|
||||
[0x0E5AF939] = typeof(MessageReplyStoryHeader),
|
||||
[0x83D60FC2] = typeof(MessageReplies),
|
||||
[0xE8FD8014] = typeof(PeerBlocked),
|
||||
[0x7FE91C14] = typeof(Stats_MessageStats),
|
||||
[0x7780BCB4] = typeof(GroupCallDiscarded),
|
||||
[0xCDF8D3E3] = typeof(GroupCall),
|
||||
[0xEFB2B617] = typeof(GroupCall),
|
||||
[0xD8AA840F] = typeof(InputGroupCall),
|
||||
[0xEBA636FE] = typeof(GroupCallParticipant),
|
||||
[0xFE06823F] = typeof(InputGroupCallSlug),
|
||||
[0x8C10603F] = typeof(InputGroupCallInviteMessage),
|
||||
[0x2A3DC7AC] = typeof(GroupCallParticipant),
|
||||
[0x9E727AAD] = typeof(Phone_GroupCall),
|
||||
[0xF47751B6] = typeof(Phone_GroupParticipants),
|
||||
[0x1662AF0B] = typeof(Messages_HistoryImport),
|
||||
|
|
@ -1062,8 +1093,12 @@ namespace TL
|
|||
[0xE3779861] = typeof(Account_ResetPasswordFailedWait),
|
||||
[0xE9EFFC7D] = typeof(Account_ResetPasswordRequestedWait),
|
||||
[0xE926D63E] = typeof(Account_ResetPasswordOk),
|
||||
[0x4D93A990] = typeof(SponsoredMessage),
|
||||
[0xC9EE1D87] = typeof(Messages_SponsoredMessages),
|
||||
[0xC3DFFC04] = typeof(ChatTheme),
|
||||
[0x3458F9C8] = typeof(ChatThemeUniqueGift),
|
||||
[0xE011E1C4] = null,//Account_ChatThemesNotModified
|
||||
[0xBE098173] = typeof(Account_ChatThemes),
|
||||
[0x7DBF8673] = typeof(SponsoredMessage),
|
||||
[0xFFDA656D] = typeof(Messages_SponsoredMessages),
|
||||
[0x1839490F] = null,//Messages_SponsoredMessagesEmpty
|
||||
[0xC9B0539F] = typeof(SearchResultsCalendarPeriod),
|
||||
[0x147EE23C] = typeof(Messages_SearchResultsCalendar),
|
||||
|
|
@ -1111,6 +1146,12 @@ namespace TL
|
|||
[0x4D818D5D] = typeof(InputInvoiceStarGiftUpgrade),
|
||||
[0x4A5F5BD9] = typeof(InputInvoiceStarGiftTransfer),
|
||||
[0xDABAB2EF] = typeof(InputInvoicePremiumGiftStars),
|
||||
[0xF4997E42] = typeof(InputInvoiceBusinessBotTransferStars),
|
||||
[0xC39F5324] = typeof(InputInvoiceStarGiftResale),
|
||||
[0x9A0B48B8] = typeof(InputInvoiceStarGiftPrepaidUpgrade),
|
||||
[0x3E77F614] = typeof(InputInvoicePremiumAuthCode),
|
||||
[0x0923D8D1] = typeof(InputInvoiceStarGiftDropOriginalDetails),
|
||||
[0x1ECAFA10] = typeof(InputInvoiceStarGiftAuctionBid),
|
||||
[0xAED0CBD9] = typeof(Payments_ExportedInvoice),
|
||||
[0xCFB9D957] = typeof(Messages_TranscribedAudio),
|
||||
[0x5334759C] = typeof(Help_PremiumPromo),
|
||||
|
|
@ -1118,7 +1159,7 @@ namespace TL
|
|||
[0x616F7FE8] = typeof(InputStorePaymentGiftPremium),
|
||||
[0xFB790393] = typeof(InputStorePaymentPremiumGiftCode),
|
||||
[0x160544CA] = typeof(InputStorePaymentPremiumGiveaway),
|
||||
[0xDDDD0F56] = typeof(InputStorePaymentStarsTopup),
|
||||
[0xF9A2A6CB] = typeof(InputStorePaymentStarsTopup),
|
||||
[0x1D741EF7] = typeof(InputStorePaymentStarsGift),
|
||||
[0x751F08FA] = typeof(InputStorePaymentStarsGiveaway),
|
||||
[0x9BB2636D] = typeof(InputStorePaymentAuthCode),
|
||||
|
|
@ -1153,7 +1194,7 @@ namespace TL
|
|||
[0xFCFEB29C] = typeof(StickerKeyword),
|
||||
[0xB4073647] = typeof(Username),
|
||||
[0x023F109B] = typeof(ForumTopicDeleted),
|
||||
[0x71701DA9] = typeof(ForumTopic),
|
||||
[0xCDFF0ECA] = typeof(ForumTopic),
|
||||
[0x367617D3] = typeof(Messages_ForumTopics),
|
||||
[0x43B46B20] = typeof(DefaultHistoryTTL),
|
||||
[0x41BF109B] = typeof(ExportedContactToken),
|
||||
|
|
@ -1195,7 +1236,7 @@ namespace TL
|
|||
[0x8D595CD6] = typeof(StoryViews),
|
||||
[0x51E6EE4F] = typeof(StoryItemDeleted),
|
||||
[0xFFADC913] = typeof(StoryItemSkipped),
|
||||
[0x79B26A24] = typeof(StoryItem),
|
||||
[0xEDF164F1] = typeof(StoryItem),
|
||||
[0x1158FE3E] = typeof(Stories_AllStoriesNotModified),
|
||||
[0x6EFC5E81] = typeof(Stories_AllStories),
|
||||
[0x63C3DD0A] = typeof(Stories_Stories),
|
||||
|
|
@ -1204,8 +1245,9 @@ namespace TL
|
|||
[0xBD74CF49] = typeof(StoryViewPublicRepost),
|
||||
[0x59D78FC5] = typeof(Stories_StoryViewsList),
|
||||
[0xDE9EED1D] = typeof(Stories_StoryViews),
|
||||
[0x22C0F6D5] = typeof(InputReplyToMessage),
|
||||
[0x869FBE10] = typeof(InputReplyToMessage),
|
||||
[0x5881323A] = typeof(InputReplyToStory),
|
||||
[0x69D66C45] = typeof(InputReplyToMonoForum),
|
||||
[0x3FC9053B] = typeof(ExportedStoryLink),
|
||||
[0x712E27FD] = typeof(StoriesStealthMode),
|
||||
[0xCFC9E002] = typeof(MediaAreaCoordinates),
|
||||
|
|
@ -1222,7 +1264,7 @@ namespace TL
|
|||
[0xCAE68768] = typeof(Stories_PeerStories),
|
||||
[0xFD5E12BD] = typeof(Messages_WebPage),
|
||||
[0x257E962B] = typeof(PremiumGiftCodeOption),
|
||||
[0x284A1096] = typeof(Payments_CheckedGiftCode),
|
||||
[0xEB983F8F] = typeof(Payments_CheckedGiftCode),
|
||||
[0x4367DAA0] = typeof(Payments_GiveawayInfo),
|
||||
[0xE175E66F] = typeof(Payments_GiveawayInfoResults),
|
||||
[0xB2539D54] = typeof(PrepaidGiveaway),
|
||||
|
|
@ -1240,6 +1282,8 @@ namespace TL
|
|||
[0xEDF3ADD0] = typeof(PublicForwardStory),
|
||||
[0x93037E20] = typeof(Stats_PublicForwards),
|
||||
[0xB54B5ACF] = typeof(PeerColor),
|
||||
[0xB9C0639A] = typeof(PeerColorCollectible),
|
||||
[0xB8EA86A9] = typeof(InputPeerColorCollectible),
|
||||
[0x26219A58] = typeof(Help_PeerColorSet),
|
||||
[0x767D61EB] = typeof(Help_PeerColorProfileSet),
|
||||
[0xADEC6EBE] = typeof(Help_PeerColorOption),
|
||||
|
|
@ -1250,6 +1294,7 @@ namespace TL
|
|||
[0xCFCD0F13] = typeof(StoryReactionPublicRepost),
|
||||
[0xAA5F789C] = typeof(Stories_StoryReactionsList),
|
||||
[0xBD87CB6C] = typeof(SavedDialog),
|
||||
[0x64407EA7] = typeof(MonoForumDialog),
|
||||
[0xF83AE221] = typeof(Messages_SavedDialogs),
|
||||
[0x44BA9DD9] = typeof(Messages_SavedDialogsSlice),
|
||||
[0xC01F6FE8] = typeof(Messages_SavedDialogsNotModified),
|
||||
|
|
@ -1308,14 +1353,7 @@ namespace TL
|
|||
[0x846F9E42] = typeof(Channels_SponsoredMessageReportResultChooseOption),
|
||||
[0x3E3BCF2F] = typeof(Channels_SponsoredMessageReportResultAdsHidden),
|
||||
[0xAD798849] = typeof(Channels_SponsoredMessageReportResultReported),
|
||||
[0x5407E297] = typeof(Stats_BroadcastRevenueStats),
|
||||
[0xEC659737] = typeof(Stats_BroadcastRevenueWithdrawalUrl),
|
||||
[0x557E2CC4] = typeof(BroadcastRevenueTransactionProceeds),
|
||||
[0x5A590978] = typeof(BroadcastRevenueTransactionWithdrawal),
|
||||
[0x42D30D2E] = typeof(BroadcastRevenueTransactionRefund),
|
||||
[0x87158466] = typeof(Stats_BroadcastRevenueTransactions),
|
||||
[0x56E34970] = typeof(ReactionsNotifySettings),
|
||||
[0xC3FF71E7] = typeof(BroadcastRevenueBalances),
|
||||
[0x93C3E27E] = typeof(AvailableEffect),
|
||||
[0xD1ED9A5B] = null,//Messages_AvailableEffectsNotModified
|
||||
[0xBDDB616E] = typeof(Messages_AvailableEffects),
|
||||
|
|
@ -1329,13 +1367,13 @@ namespace TL
|
|||
[0x60682812] = typeof(StarsTransactionPeerAds),
|
||||
[0xF9677AAD] = typeof(StarsTransactionPeerAPI),
|
||||
[0x0BD915C0] = typeof(StarsTopupOption),
|
||||
[0xA39FD94A] = typeof(StarsTransaction),
|
||||
[0x13659EB0] = typeof(StarsTransaction),
|
||||
[0x6C9CE8ED] = typeof(Payments_StarsStatus),
|
||||
[0xE87ACBC0] = typeof(FoundStory),
|
||||
[0xE2DE7737] = typeof(Stories_FoundStories),
|
||||
[0xDE4C5D93] = typeof(GeoPointAddress),
|
||||
[0xFEBE5491] = typeof(StarsRevenueStatus),
|
||||
[0xC92BB73B] = typeof(Payments_StarsRevenueStats),
|
||||
[0x6C207376] = typeof(Payments_StarsRevenueStats),
|
||||
[0x1DAB80B7] = typeof(Payments_StarsRevenueWithdrawalUrl),
|
||||
[0x394E7F21] = typeof(Payments_StarsRevenueAdsAccountUrl),
|
||||
[0x206AE6D1] = typeof(InputStarsTransaction),
|
||||
|
|
@ -1348,10 +1386,10 @@ namespace TL
|
|||
[0x4BA3A95A] = typeof(MessageReactor),
|
||||
[0x94CE852A] = typeof(StarsGiveawayOption),
|
||||
[0x54236209] = typeof(StarsGiveawayWinnersOption),
|
||||
[0x02CC73C8] = typeof(StarGift),
|
||||
[0x5C62D151] = typeof(StarGiftUnique),
|
||||
[0x313A9547] = typeof(StarGift),
|
||||
[0x569D64C9] = typeof(StarGiftUnique),
|
||||
[0xA388A368] = null,//Payments_StarGiftsNotModified
|
||||
[0x901689EA] = typeof(Payments_StarGifts),
|
||||
[0x2ED82995] = typeof(Payments_StarGifts),
|
||||
[0x7903E3D9] = typeof(MessageReportOption),
|
||||
[0xF0E4E0B6] = typeof(ReportResultChooseOption),
|
||||
[0x6F09AC31] = typeof(ReportResultAddComment),
|
||||
|
|
@ -1364,23 +1402,25 @@ namespace TL
|
|||
[0x98D5EA1D] = typeof(Payments_ConnectedStarRefBots),
|
||||
[0xB4D5D859] = typeof(Payments_SuggestedStarRefBots),
|
||||
[0xBBB6B4A3] = typeof(StarsAmount),
|
||||
[0x74AEE3E0] = typeof(StarsTonAmount),
|
||||
[0x6010C534] = typeof(Messages_FoundStickersNotModified),
|
||||
[0x82C9E290] = typeof(Messages_FoundStickers),
|
||||
[0xB0CD6617] = typeof(BotVerifierSettings),
|
||||
[0xF93CD45C] = typeof(BotVerification),
|
||||
[0x39D99013] = typeof(StarGiftAttributeModel),
|
||||
[0x13ACFF19] = typeof(StarGiftAttributePattern),
|
||||
[0x94271762] = typeof(StarGiftAttributeBackdrop),
|
||||
[0xD93D859C] = typeof(StarGiftAttributeBackdrop),
|
||||
[0xE0BFF26C] = typeof(StarGiftAttributeOriginalDetails),
|
||||
[0x167BD90B] = typeof(Payments_StarGiftUpgradePreview),
|
||||
[0x3DE1DFED] = typeof(Payments_StarGiftUpgradePreview),
|
||||
[0x62D706B8] = typeof(Users_Users),
|
||||
[0x315A4974] = typeof(Users_UsersSlice),
|
||||
[0xCAA2F60B] = typeof(Payments_UniqueStarGift),
|
||||
[0xB53E8B21] = typeof(Messages_WebPagePreview),
|
||||
[0x6056DBA5] = typeof(SavedStarGift),
|
||||
[0x416C56E8] = typeof(Payments_UniqueStarGift),
|
||||
[0x8C9A88AC] = typeof(Messages_WebPagePreview),
|
||||
[0xEAD6805E] = typeof(SavedStarGift),
|
||||
[0x95F389B1] = typeof(Payments_SavedStarGifts),
|
||||
[0x69279795] = typeof(InputSavedStarGiftUser),
|
||||
[0xF101AA7F] = typeof(InputSavedStarGiftChat),
|
||||
[0x2085C238] = typeof(InputSavedStarGiftSlug),
|
||||
[0x84AA3A9C] = typeof(Payments_StarGiftWithdrawalUrl),
|
||||
[0x206AD49E] = null,//PaidReactionPrivacyDefault
|
||||
[0x1F0C1AD9] = typeof(PaidReactionPrivacyAnonymous),
|
||||
|
|
@ -1394,6 +1434,64 @@ namespace TL
|
|||
[0xC69708D3] = typeof(SponsoredPeer),
|
||||
[0xEA32B4B1] = null,//Contacts_SponsoredPeersEmpty
|
||||
[0xEB032884] = typeof(Contacts_SponsoredPeers),
|
||||
[0x48AAAE3C] = typeof(StarGiftAttributeIdModel),
|
||||
[0x4A162433] = typeof(StarGiftAttributeIdPattern),
|
||||
[0x1F01C757] = typeof(StarGiftAttributeIdBackdrop),
|
||||
[0x2EB1B658] = typeof(StarGiftAttributeCounter),
|
||||
[0x947A12DF] = typeof(Payments_ResaleStarGifts),
|
||||
[0xC387C04E] = typeof(Stories_CanSendStoryCount),
|
||||
[0xE7E82E12] = typeof(PendingSuggestion),
|
||||
[0xCBA9A52F] = typeof(TodoItem),
|
||||
[0x49B92A26] = typeof(TodoList),
|
||||
[0x221BB5E4] = typeof(TodoCompletion),
|
||||
[0x0E8E37E5] = typeof(SuggestedPost),
|
||||
[0x1B0E4F07] = typeof(StarsRating),
|
||||
[0x9D6B13B0] = typeof(StarGiftCollection),
|
||||
[0xA0BA4F17] = null,//Payments_StarGiftCollectionsNotModified
|
||||
[0x8A2932F3] = typeof(Payments_StarGiftCollections),
|
||||
[0x9325705A] = typeof(StoryAlbum),
|
||||
[0x564EDAEB] = null,//Stories_AlbumsNotModified
|
||||
[0xC3987A3A] = typeof(Stories_Albums),
|
||||
[0x3E0B5B6A] = typeof(SearchPostsFlood),
|
||||
[0x512FE446] = typeof(Payments_UniqueStarGiftValueInfo),
|
||||
[0xE3878AA4] = typeof(Users_SavedMusicNotModified),
|
||||
[0x34A2F297] = typeof(Users_SavedMusic),
|
||||
[0x4FC81D6E] = null,//Account_SavedMusicIdsNotModified
|
||||
[0x998D6636] = typeof(Account_SavedMusicIds),
|
||||
[0x374FA7AD] = typeof(Payments_CheckCanSendGiftResultOk),
|
||||
[0xD5E58274] = typeof(Payments_CheckCanSendGiftResultFail),
|
||||
[0x83268483] = null,//InputChatThemeEmpty
|
||||
[0xC93DE95C] = typeof(InputChatTheme),
|
||||
[0x87E5DFE4] = typeof(InputChatThemeUniqueGift),
|
||||
[0x99EA331D] = typeof(StarGiftUpgradePrice),
|
||||
[0x1A8AFC7E] = typeof(GroupCallMessage),
|
||||
[0xEE430C85] = typeof(GroupCallDonor),
|
||||
[0x9D1DBD26] = typeof(Phone_GroupCallStars),
|
||||
[0x711D692D] = typeof(RecentStory),
|
||||
[0x310240CC] = typeof(AuctionBidLevel),
|
||||
[0xFE333952] = null,//StarGiftAuctionStateNotModified
|
||||
[0x771A4E66] = typeof(StarGiftAuctionState),
|
||||
[0x972DABBF] = typeof(StarGiftAuctionStateFinished),
|
||||
[0x2EEED1C4] = typeof(StarGiftAuctionUserState),
|
||||
[0x6B39F4EC] = typeof(Payments_StarGiftAuctionState),
|
||||
[0x42B00348] = typeof(StarGiftAuctionAcquiredGift),
|
||||
[0x7D5BD1F0] = typeof(Payments_StarGiftAuctionAcquiredGifts),
|
||||
[0xD31BC45D] = typeof(StarGiftActiveAuctionState),
|
||||
[0xDB33DAD0] = null,//Payments_StarGiftActiveAuctionsNotModified
|
||||
[0xAEF6ABBC] = typeof(Payments_StarGiftActiveAuctions),
|
||||
[0x02E16C98] = typeof(InputStarGiftAuction),
|
||||
[0x7AB58308] = typeof(InputStarGiftAuctionSlug),
|
||||
[0x98613EBF] = typeof(Passkey),
|
||||
[0xF8E0AA1C] = typeof(Account_Passkeys),
|
||||
[0xE16B5CE1] = typeof(Account_PasskeyRegistrationOptions),
|
||||
[0xE2037789] = typeof(Auth_PasskeyLoginOptions),
|
||||
[0x3E63935C] = typeof(InputPasskeyResponseRegister),
|
||||
[0xC31FC14A] = typeof(InputPasskeyResponseLogin),
|
||||
[0x3C27B78F] = typeof(InputPasskeyCredentialPublicKey),
|
||||
[0xAFF56398] = typeof(StarGiftBackground),
|
||||
[0x3AAE0528] = typeof(StarGiftAuctionRound),
|
||||
[0x0AA021E5] = typeof(StarGiftAuctionRoundExtendable),
|
||||
[0x46C6E36F] = typeof(Payments_StarGiftUpgradeAttributes),
|
||||
// from TL.Secret:
|
||||
[0x6ABD9782] = typeof(Layer143.DecryptedMessageMediaDocument),
|
||||
[0x020DF5D0] = typeof(Layer101.MessageEntityBlockquote),
|
||||
|
|
@ -1513,6 +1611,7 @@ namespace TL
|
|||
[typeof(ChatReactions)] = 0xEAFC32BC, //chatReactionsNone
|
||||
[typeof(Messages_Reactions)] = 0xB06FDBDF, //messages.reactionsNotModified
|
||||
// from TL.Secret:
|
||||
[typeof(Account_ChatThemes)] = 0xE011E1C4, //account.chatThemesNotModified
|
||||
[typeof(EmojiStatusBase)] = 0x2DE11AAE, //emojiStatusEmpty
|
||||
[typeof(EmojiList)] = 0x481EADFA, //emojiListNotModified
|
||||
[typeof(Messages_EmojiGroups)] = 0x6FB4AD87, //messages.emojiGroupsNotModified
|
||||
|
|
@ -1527,6 +1626,12 @@ namespace TL
|
|||
[typeof(PaidReactionPrivacy)] = 0x206AD49E, //paidReactionPrivacyDefault
|
||||
[typeof(RequirementToContact)] = 0x050A9839, //requirementToContactEmpty
|
||||
[typeof(Contacts_SponsoredPeers)] = 0xEA32B4B1, //contacts.sponsoredPeersEmpty
|
||||
[typeof(Payments_StarGiftCollections)] = 0xA0BA4F17, //payments.starGiftCollectionsNotModified
|
||||
[typeof(Stories_Albums)] = 0x564EDAEB, //stories.albumsNotModified
|
||||
[typeof(Account_SavedMusicIds)] = 0x4FC81D6E, //account.savedMusicIdsNotModified
|
||||
[typeof(InputChatThemeBase)] = 0x83268483, //inputChatThemeEmpty
|
||||
[typeof(StarGiftAuctionStateBase)] = 0xFE333952, //starGiftAuctionStateNotModified
|
||||
[typeof(Payments_StarGiftActiveAuctions)]= 0xDB33DAD0, //payments.starGiftActiveAuctionsNotModified
|
||||
[typeof(DecryptedMessageMedia)] = 0x089F5C4A, //decryptedMessageMediaEmpty
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,7 +148,6 @@ namespace TL
|
|||
{
|
||||
public abstract long ID { get; }
|
||||
protected internal abstract IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats);
|
||||
public static implicit operator long(Peer peer) => peer.ID;
|
||||
}
|
||||
partial class PeerUser
|
||||
{
|
||||
|
|
@ -216,7 +215,7 @@ namespace TL
|
|||
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/userStatusEmpty">userStatusEmpty</a> = last seen a long time ago, more than a month (or blocked/deleted users)</remarks>
|
||||
partial class UserStatus { internal abstract TimeSpan LastSeenAgo { get; } }
|
||||
partial class UserStatusOnline { internal override TimeSpan LastSeenAgo => TimeSpan.Zero; }
|
||||
partial class UserStatusOffline { internal override TimeSpan LastSeenAgo => DateTime.UtcNow - new DateTime((was_online + 62135596800L) * 10000000, DateTimeKind.Utc); }
|
||||
partial class UserStatusOffline { internal override TimeSpan LastSeenAgo => DateTime.UtcNow - was_online; }
|
||||
/// <remarks>covers anything between 1 second and 2-3 days</remarks>
|
||||
partial class UserStatusRecently { internal override TimeSpan LastSeenAgo => TimeSpan.FromDays(1); }
|
||||
/// <remarks>between 2-3 and seven days</remarks>
|
||||
|
|
@ -348,8 +347,9 @@ namespace TL
|
|||
protected override InputPhoto ToInputPhoto() => new() { id = id, access_hash = access_hash, file_reference = file_reference };
|
||||
public InputPhotoFileLocation ToFileLocation() => ToFileLocation(LargestPhotoSize);
|
||||
public InputPhotoFileLocation ToFileLocation(PhotoSizeBase photoSize) => new() { id = id, access_hash = access_hash, file_reference = file_reference, thumb_size = photoSize.Type };
|
||||
public InputDocumentFileLocation ToFileLocation(VideoSize videoSize) => new() { id = id, access_hash = access_hash, file_reference = file_reference, thumb_size = videoSize.type };
|
||||
public InputPhotoFileLocation ToFileLocation(VideoSize videoSize) => new() { id = id, access_hash = access_hash, file_reference = file_reference, thumb_size = videoSize.type };
|
||||
public PhotoSizeBase LargestPhotoSize => sizes.Aggregate((agg, next) => (long)next.Width * next.Height > (long)agg.Width * agg.Height ? next : agg);
|
||||
public VideoSize LargestVideoSize => video_sizes?.OfType<VideoSize>().DefaultIfEmpty().Aggregate((agg, next) => (long)next.w * next.h > (long)agg.w * agg.h ? next : agg);
|
||||
}
|
||||
|
||||
partial class PhotoSizeBase
|
||||
|
|
@ -689,8 +689,8 @@ namespace TL
|
|||
{
|
||||
System.Text.Json.JsonValueKind.True or
|
||||
System.Text.Json.JsonValueKind.False => new JsonBool { value = elem.GetBoolean() },
|
||||
System.Text.Json.JsonValueKind.Object => new JsonObject { value = elem.EnumerateObject().Select(FromJsonProperty).ToArray() },
|
||||
System.Text.Json.JsonValueKind.Array => new JsonArray { value = elem.EnumerateArray().Select(FromJsonElement).ToArray() },
|
||||
System.Text.Json.JsonValueKind.Object => new JsonObject { value = [.. elem.EnumerateObject().Select(FromJsonProperty)] },
|
||||
System.Text.Json.JsonValueKind.Array => new JsonArray { value = [.. elem.EnumerateArray().Select(FromJsonElement)] },
|
||||
System.Text.Json.JsonValueKind.String => new JsonString { value = elem.GetString() },
|
||||
System.Text.Json.JsonValueKind.Number => new JsonNumber { value = elem.GetDouble() },
|
||||
_ => new JsonNull(),
|
||||
|
|
@ -709,7 +709,7 @@ namespace TL
|
|||
sb.Append(i == 0 ? "" : ",").Append(value[i]);
|
||||
return sb.Append(']').ToString();
|
||||
}
|
||||
public object[] ToNativeArray() => value.Select(v => v.ToNative()).ToArray();
|
||||
public object[] ToNativeArray() => [.. value.Select(v => v.ToNative())];
|
||||
public override object ToNative()
|
||||
{
|
||||
if (value.Length == 0) return Array.Empty<object>();
|
||||
|
|
|
|||
73
src/TL.cs
73
src/TL.cs
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
|
@ -16,7 +17,7 @@ namespace TL
|
|||
#else
|
||||
public interface IObject { }
|
||||
#endif
|
||||
public interface IMethod<ReturnType> : IObject { }
|
||||
public interface IMethod<out ReturnType> : IObject { }
|
||||
public interface IPeerResolver { IPeerInfo UserOrChat(Peer peer); }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
|
|
@ -48,6 +49,15 @@ namespace TL
|
|||
|
||||
public static class Serialization
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static byte[] ToBytes<T>(this T obj) where T : IObject
|
||||
{
|
||||
using var ms = new MemoryStream(384);
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.WriteTLObject(obj);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public static void WriteTLObject<T>(this BinaryWriter writer, T obj) where T : IObject
|
||||
{
|
||||
if (obj == null) { writer.WriteTLNull(typeof(T)); return; }
|
||||
|
|
@ -105,6 +115,17 @@ namespace TL
|
|||
#endif
|
||||
}
|
||||
|
||||
public static IMethod<X> ReadTLMethod<X>(this BinaryReader reader)
|
||||
{
|
||||
uint ctorNb = reader.ReadUInt32();
|
||||
if (!Layer.Methods.TryGetValue(ctorNb, out var ctor))
|
||||
throw new WTelegram.WTException($"Cannot find method for ctor #{ctorNb:x}");
|
||||
var method = ctor?.Invoke(reader);
|
||||
if (method is IMethod<bool> && typeof(X) == typeof(object))
|
||||
method = new BoolMethod { query = method };
|
||||
return (IMethod<X>)method;
|
||||
}
|
||||
|
||||
internal static void WriteTLValue(this BinaryWriter writer, object value, Type valueType)
|
||||
{
|
||||
if (value == null)
|
||||
|
|
@ -197,13 +218,13 @@ namespace TL
|
|||
foreach (var msg in messages)
|
||||
{
|
||||
writer.Write(msg.msg_id);
|
||||
writer.Write(msg.seq_no);
|
||||
writer.Write(msg.seqno);
|
||||
var patchPos = writer.BaseStream.Position;
|
||||
writer.Write(0); // patched below
|
||||
if ((msg.seq_no & 1) != 0)
|
||||
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38} #{(short)msg.msg_id.GetHashCode():X4}");
|
||||
if ((msg.seqno & 1) != 0)
|
||||
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38} #{(short)msg.msg_id.GetHashCode():X4}");
|
||||
else
|
||||
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38}");
|
||||
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38}");
|
||||
writer.WriteTLObject(msg.body);
|
||||
writer.BaseStream.Position = patchPos;
|
||||
writer.Write((int)(writer.BaseStream.Length - patchPos - 4)); // patch bytes field
|
||||
|
|
@ -222,6 +243,21 @@ namespace TL
|
|||
writer.WriteTLValue(array.GetValue(i), elementType);
|
||||
}
|
||||
|
||||
internal static void WriteTLRawVector(this BinaryWriter writer, Array array, int elementSize)
|
||||
{
|
||||
var startPos = writer.BaseStream.Position;
|
||||
int count = array.Length;
|
||||
var elementType = array.GetType().GetElementType();
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
{
|
||||
writer.BaseStream.Position = startPos + i * elementSize;
|
||||
writer.WriteTLValue(array.GetValue(i), elementType);
|
||||
}
|
||||
writer.BaseStream.Position = startPos;
|
||||
writer.Write(count);
|
||||
writer.BaseStream.Position = startPos + count * elementSize + 4;
|
||||
}
|
||||
|
||||
internal static List<T> ReadTLRawVector<T>(this BinaryReader reader, uint ctorNb)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
|
|
@ -289,13 +325,14 @@ namespace TL
|
|||
}
|
||||
|
||||
internal static void WriteTLStamp(this BinaryWriter writer, DateTime datetime)
|
||||
=> writer.Write(datetime == DateTime.MaxValue ? int.MaxValue : (int)(datetime.ToUniversalTime().Ticks / 10000000 - 62135596800L));
|
||||
=> writer.Write((int)Math.Min(Math.Max(datetime.ToUniversalTime().Ticks / 10000000 - 62135596800L, 0), int.MaxValue));
|
||||
|
||||
internal static DateTime ReadTLStamp(this BinaryReader reader)
|
||||
internal static DateTime ReadTLStamp(this BinaryReader reader) => reader.ReadInt32() switch
|
||||
{
|
||||
int unixstamp = reader.ReadInt32();
|
||||
return unixstamp == int.MaxValue ? DateTime.MaxValue : new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc);
|
||||
}
|
||||
<= 0 => default,
|
||||
int.MaxValue => DateTime.MaxValue,
|
||||
int unixstamp => new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc)
|
||||
};
|
||||
|
||||
internal static void WriteTLString(this BinaryWriter writer, string str)
|
||||
{
|
||||
|
|
@ -428,10 +465,10 @@ namespace TL
|
|||
}
|
||||
|
||||
[TLDef(0x5BB8E511)] //message#5bb8e511 msg_id:long seqno:int bytes:int body:Object = Message
|
||||
public sealed partial class _Message(long msgId, int seqNo, IObject obj) : IObject
|
||||
public sealed partial class _Message(long msgId, int seqno, IObject obj) : IObject
|
||||
{
|
||||
public long msg_id = msgId;
|
||||
public int seq_no = seqNo;
|
||||
public int seqno = seqno;
|
||||
public int bytes;
|
||||
public IObject body = obj;
|
||||
}
|
||||
|
|
@ -443,4 +480,16 @@ namespace TL
|
|||
|
||||
[TLDef(0x3072CFA1)] //gzip_packed#3072cfa1 packed_data:bytes = Object
|
||||
public sealed partial class GzipPacked : IObject { public byte[] packed_data; }
|
||||
|
||||
public sealed class Null<X> : IObject
|
||||
{
|
||||
public readonly static Null<X> Instance = new();
|
||||
public void WriteTL(BinaryWriter writer) => writer.WriteTLNull(typeof(X));
|
||||
}
|
||||
|
||||
public sealed class BoolMethod : IMethod<object>
|
||||
{
|
||||
public IObject query;
|
||||
public void WriteTL(BinaryWriter writer) => query.WriteTL(writer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ namespace WTelegram
|
|||
|
||||
/// <summary>Save the current state of the manager to JSON file</summary>
|
||||
/// <param name="statePath">File path to write</param>
|
||||
/// <remarks>Note: This does not save the the content of collected Users/Chats dictionaries</remarks>
|
||||
/// <remarks>Note: This does not save the content of collected Users/Chats dictionaries</remarks>
|
||||
public void SaveState(string statePath)
|
||||
=> System.IO.File.WriteAllText(statePath, System.Text.Json.JsonSerializer.Serialize(State, Helpers.JsonOptions));
|
||||
public static Dictionary<long, MBoxState> LoadState(string statePath) => !System.IO.File.Exists(statePath) ? null
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@
|
|||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<PackageId>WTelegramClient</PackageId>
|
||||
<Version>0.0.0</Version>
|
||||
<Authors>Wizou</Authors>
|
||||
<Description>Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 201
|
||||
<VersionPrefix>0.0.0</VersionPrefix>
|
||||
<VersionSuffix>layer.220</VersionSuffix>
|
||||
<Description>Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 220
|
||||
|
||||
Release Notes:
|
||||
$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</Description>
|
||||
$(ReleaseNotes)</Description>
|
||||
<Copyright>Copyright © Olivier Marcoux 2021-2025</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://wiz0u.github.io/WTelegramClient</PackageProjectUrl>
|
||||
|
|
@ -26,7 +27,7 @@ $(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%
|
|||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>Telegram;MTProto;Client;Api;UserBot</PackageTags>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageReleaseNotes>$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>$(ReleaseNotes)</PackageReleaseNotes>
|
||||
<NoWarn>NETSDK1138;CS0419;CS1573;CS1591</NoWarn>
|
||||
<DefineConstants>TRACE;OBFUSCATION;MTPG</DefineConstants>
|
||||
<!--<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>-->
|
||||
|
|
|
|||
Loading…
Reference in a new issue