Compare commits

...

57 commits

Author SHA1 Message Date
Wizou 3f531f4966 Collect: updated list of merged flags
Some checks failed
Dev build / build (push) Has been cancelled
2026-01-03 13:07:04 +01:00
Wizou 1912632722 Immediate remigrate to MainDC in case reconnection is only possible through a different DcID 2025-12-22 02:34:29 +01:00
Wizou 9c839128bb Update to Telegram API Layer 220
- Added support for passkey-based authentication with new classes (`Passkey`, `Account_Passkeys`, etc.) and methods (`Auth_InitPasskeyLogin`, `Account_RegisterPasskey`, etc.).
- Added new methods for handling star gift offers and upgrades (`Payments_ResolveStarGiftOffer`, `Payments_SendStarGiftOffer`, etc.).
- Enhanced star gift functionality with new fields (`gift_num`, `upgrade_variants`, etc.), flags, and classes (`MessageActionStarGiftPurchaseOffer`, `StarGiftBackground`, etc.).
- Updated `Messages_ForwardMessages` to include a new `effect` parameter.
2025-12-14 20:24:02 +01:00
Wizou d3ad4789a1 WTelegram.Helpers.JsonOptions can now serialize polymorph TL types (useful for logs).
Some checks failed
Dev build / build (push) Has been cancelled
Deserialization is also possible in non-trimmed apps, but not recommended as structures can change.
2025-12-14 20:23:45 +01:00
Wizou 208ab626c1 API Layer 218: Stargift auctions, Story Live broadcast, Paid messages in Live/group calls, Message schedule_repeat_period, Saved Music privacy key, and more... 2025-11-15 22:45:36 +01:00
Wizou bfc8e0e1b5 .NET 10 compatibility
Some checks failed
Dev build / build (push) Has been cancelled
2025-11-15 19:39:10 +01:00
Wizou e923d65d53 Clamp TL stamps to 0..int.MaxValue, mapping edge to DateTime.Min/MaxValueaa 2025-11-15 18:21:15 +01:00
Wizou 4ad2f0a212 Fix incorrect bare type for DestroyAuthKey, RpcDropAnswer, DestroySession
Some checks failed
Dev build / build (push) Has been cancelled
2025-11-13 07:45:57 +01:00
Wizou 30bc536ebc Removed annoying Peer implicit long operator (reverts #229)
Some checks failed
Dev build / build (push) Has been cancelled
2025-11-09 22:32:47 +01:00
Wizou d6fdcab440 ToBytes TL.Serialization helper.
Warning: do not use for long-term storage because TL structures can change in future layers and may not be deserializable
2025-11-05 15:00:48 +01:00
Wizou 9ec2f31f72 Delete alt-session on AUTH_KEY_UNREGISTERED for renegociation 2025-10-31 18:21:11 +01:00
Wizou 4ccfddd22e Collect: Fix Channel losing participants_count 2025-10-31 00:36:24 +01:00
Wizou 9693037ef2 Added missing DownloadFileAsync(doc, videoSize) 2025-10-31 00:31:30 +01:00
Wizou 40bcf69bfb Change UserStatusOffline.was_online to a DateTime 2025-10-31 00:30:02 +01:00
Wizou 4875f75774 API Layer 216: topics methods/updates moved to prefix Messages_* (to support topics for bots), contact notes, groupcall comments, profile color, stargifts stuff 2025-10-31 00:27:32 +01:00
Wizou 2e95576be5 Encryption class public + TL Methods/Helpers 2025-10-31 00:14:48 +01:00
Wizou 48d005b605 Encryption class public + Methods table
Some checks failed
Dev build / build (push) Has been cancelled
2025-10-10 20:27:42 +02:00
Wizou a5323eaa86 api doc 2025-10-06 18:34:25 +02:00
Wizou 610d059b4c Fix: Messages_Search helper parameter name
Some checks failed
Dev build / build (push) Has been cancelled
2025-10-03 13:09:33 +02:00
Wizou 3f1036a559 Fix #335 GetAllDialogs infinite loop when last dialogs' messages are unavailable
Some checks failed
Dev build / build (push) Has been cancelled
2025-09-26 13:34:58 +02:00
Wizou 4578dea3a3 HtmlToEntities tolerate unclosed &.. html entities 2025-09-21 01:55:11 +02:00
Wizou 253249e06a API Layer 214: ChatTheme, main ProfileTab, user saved music, some StarGift & Store Payment stuff...
(that might not be the most recent API layer. check https://patreon.com/wizou for the latest layers)
2025-09-01 13:37:41 +02:00
Wizou eb52dccfa7 Helper OpenChat to monitor a group/channel without joining (#333)
Some checks failed
Dev build / build (push) Has been cancelled
2025-08-27 00:18:34 +02:00
Wizou a9bbdb9fc4 Try to improve diagnostics/handling of weird email login case (#331) 2025-08-27 00:06:30 +02:00
Wizou 5f411d45f9 API Layer 211.2: StarsTransaction flag posts_search 2025-08-16 13:01:43 +02:00
Wizou e16e39bfba Avoid using obsolete DataCenter info on alt-DC connect 2025-08-09 02:43:43 +02:00
Wizou 7faa3873f8 API Layer 211: Stories Albums, user's pending Stars Rating, SearchPosts + Check Flood, ...
(that might not be the most recent API layer. check https://patreon.com/wizou for the latest layers)
2025-08-02 02:05:49 +02:00
Wizou d9e4b7cc0f CollectUsersChats: don't update usernames from min info
Some checks failed
Dev build / build (push) Has been cancelled
(thx @riniba)
2025-08-01 00:17:12 +02:00
Wizou 30a982b0ac API Layer 210: user's Stars Rating, StarGift collections management & more characteristics...
(that might not be the most recent layer. check https://patreon.com/wizou for the latest layers)
2025-07-25 17:01:58 +02:00
Wizou e9543a690b Examples for download abort, and uploading streamable video (fix #325, thx @patelriki13)
Some checks failed
Dev build / build (push) Has been cancelled
2025-07-25 01:03:30 +02:00
Wizou a8fa32dfd5 Support single-quote arguments in HTML 2025-07-18 02:28:43 +02:00
Wizou 56ba15bc13 API Layer 209: Reply-to specific ToDo item
(that might not be the most recent layer. check https://patreon.com/wizou for the latest layers)
2025-07-14 22:38:51 +02:00
Wizou a3f41330b5 API Layer 207: StarGift.ReleaseBy
Some checks failed
Dev build / build (push) Has been cancelled
(that might not be the most recent layer. check https://patreon.com/wizou for the latest layers)
2025-07-10 01:50:09 +02:00
Wizou 52d948af2a Fix DateTime type of *_at fields
Some checks failed
Dev build / build (push) Has been cancelled
2025-07-10 01:49:25 +02:00
Wizou bdcf389ed2 API Layer 206: ToDo lists, suggested posts, choose TON vs Stars for gifts/transactions, ...
(for the very latest layers, go to https://patreon.com/wizou)
2025-07-08 19:47:44 +02:00
Wizou 4f7954db61 API Layer 204: Channel DMs (MonoForum), Forum Tabs, Saved peer/dialog stuff...
(for the very latest layers, go to https://patreon.com/wizou)
2025-07-08 19:47:35 +02:00
Wizou fa90e236e7 Helpers to download animated photos (DownloadFileAsync + photo.LargestVideoSize) 2025-06-29 16:42:27 +02:00
Wizou 25990a8477 Fix Salts management 2025-06-29 16:01:05 +02:00
Wizou 3ff1200068 Use signed DcId values (improved logs) 2025-06-26 22:02:26 +02:00
Wizou 04e043222e ParallelTransfers property can configure how many parallel download/upload of file chunks can occur at the same time. Default is 2 (optimal for non-premium accounts), was 10 in previous versions. 2025-06-24 19:11:14 +02:00
Wizou d49d620edd Fixed possible concurrency issue on msgId/seqno, that could freeze protocol or cause BadMsgNotification (during downloads for example)
Thanks goes to @Deeps00009
2025-06-24 19:01:52 +02:00
Wizou 5358471574 Fix fields serialization order on KeyboardButtonSimpleWebView 2025-06-02 02:41:06 +02:00
Wizou 8836f8372b API Layer 203: Stargift resale, auto-translation... 2025-05-14 18:18:20 +02:00
Wizou 6fb59286bd API Layer 202: E2E group calls
https://core.telegram.org/api/end-to-end/group-calls
2025-05-01 12:17:06 +02:00
Wizou eaea2d051a API Layer 201.2: business bot stars 2025-04-20 03:23:43 +02:00
Wizou 6d238dc528 Store less stuff in session data and reduce save frequency for better performance. 2025-04-06 19:48:43 +02:00
Wizou f495f59bc8 Use media DC for uploads 2025-04-02 05:58:04 +02:00
Wizou 3f1d4eba92 API Layer 201: Paid msg service messages, Gifts settings, Business bot rights, sentCodePayment, sponsored peers ... 2025-03-27 00:59:57 +01:00
Wizou e6a4b802e7 Html/Markdown: prune entities of length=0 (fix wiz0u/WTelegramBot#6) 2025-03-23 03:10:04 +01:00
Wizou 0867c044fa Handle CONNECTION_NOT_INITED 2025-03-12 02:17:36 +01:00
Wizou 1ecd7047ef API Layer 200: Paid messages, details on chat partner, ... 2025-03-08 00:21:50 +01:00
Wizou e67a688baa Building with Github Actions 2025-03-03 02:02:06 +01:00
Wizou b626c6c644 API Layer 199: reCAPTCHA, PaidReactionPrivacy 2025-02-13 14:12:04 +01:00
Wizou 1fab219ef6 Added EmojiStatusBase.DocumentId helper 2025-01-31 20:39:42 +01:00
Wizou edc6019f2e Better support for HTML &entities; in HtmlText 2025-01-30 02:22:05 +01:00
Wizou e6dde32538 Fix Messages_GetAllChats to include only your chats 2025-01-28 13:46:06 +01:00
Wizou e5953994a7 property Title on ForumTopicBase 2025-01-24 00:41:48 +01:00
26 changed files with 5320 additions and 1313 deletions

9
.github/dev.yml vendored
View file

@ -1,7 +1,11 @@
pr: none pr: none
trigger: [ master ] trigger:
branches:
include: [ master ]
paths:
exclude: [ '.github', '*.md', 'Examples' ]
name: 4.2.7-dev.$(Rev:r) name: 4.3.2-dev.$(Rev:r)
pool: pool:
vmImage: ubuntu-latest vmImage: ubuntu-latest
@ -57,4 +61,3 @@ stages:
"message": "{ \"commitId\": \"$(Build.SourceVersion)\", \"buildNumber\": \"$(Build.BuildNumber)\", \"teamProjectName\": \"$(System.TeamProject)\", \"commitMessage\": \"$(Release_Notes)\" }" "message": "{ \"commitId\": \"$(Build.SourceVersion)\", \"buildNumber\": \"$(Build.BuildNumber)\", \"teamProjectName\": \"$(System.TeamProject)\", \"commitMessage\": \"$(Release_Notes)\" }"
} }
waitForCompletion: 'false' waitForCompletion: 'false'

2
.github/release.yml vendored
View file

@ -1,7 +1,7 @@
pr: none pr: none
trigger: none trigger: none
name: 4.2.$(Rev:r) name: 4.3.$(Rev:r)
pool: pool:
vmImage: ubuntu-latest vmImage: ubuntu-latest

View file

@ -2,7 +2,7 @@ name: 'Auto-Lock Issues'
on: on:
schedule: schedule:
- cron: '17 2 * * *' - cron: '17 2 * * 1'
workflow_dispatch: workflow_dispatch:
permissions: permissions:

68
.github/workflows/dev.yml vendored Normal file
View file

@ -0,0 +1,68 @@
name: Dev build
on:
push:
branches: [ master ]
paths-ignore: [ '.**', 'Examples/**', '**.md' ]
env:
PROJECT_PATH: src/WTelegramClient.csproj
CONFIGURATION: Release
RELEASE_NOTES: ${{ github.event.head_commit.message }}
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: 30
- name: Determine version
run: |
git fetch --depth=30 --tags
DESCR_TAG=$(git describe --tags)
COMMITS=${DESCR_TAG#*-}
COMMITS=${COMMITS%-*}
LAST_TAG=${DESCR_TAG%%-*}
NEXT_VERSION=${LAST_TAG%.*}.$((${LAST_TAG##*.} + 1))-dev.$COMMITS
RELEASE_VERSION=${{vars.RELEASE_VERSION}}-dev.$COMMITS
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: |
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 ${{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(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 }}

82
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,82 @@
name: Release build
on:
workflow_dispatch:
inputs:
release_notes:
description: 'Release notes'
required: true
version:
description: "Release version (leave empty for automatic versioning)"
run-name: '📌 Release build ${{ inputs.version }}'
env:
PROJECT_PATH: src/WTelegramClient.csproj
CONFIGURATION: Release
RELEASE_NOTES: ${{ inputs.release_notes }}
VERSION: ${{ inputs.version }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write # For git tag
id-token: write # enable GitHub OIDC token issuance for this job
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 100
- name: Determine version
if: ${{ env.VERSION == '' }}
run: |
git fetch --depth=100 --tags
DESCR_TAG=$(git describe --tags)
LAST_TAG=${DESCR_TAG%%-*}
NEXT_VERSION=${LAST_TAG%.*}.$((${LAST_TAG##*.} + 1))
RELEASE_VERSION=${{vars.RELEASE_VERSION}}
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: |
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 ${{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 }}

View file

@ -12,7 +12,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, 'telegram api') if: contains(github.event.issue.labels.*.name, 'telegram api')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/support-requests@v3.0.0 - uses: dessant/support-requests@v4
with: with:
support-label: 'telegram api' support-label: 'telegram api'
issue-comment: > 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. 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 close-issue: true
issue-close-reason: 'not planned'

View file

@ -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) 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> <a name="upload"></a>
## Upload a media file and post it with caption to a chat ## 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); 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> <a name="album"></a>
## Send a grouped media album using photos from various sources ## Send a grouped media album using photos from various sources
```csharp ```csharp

View file

@ -62,10 +62,8 @@ namespace WTelegramClientTest
{ {
private readonly NpgsqlConnection _sql; private readonly NpgsqlConnection _sql;
private readonly string _sessionName; private readonly string _sessionName;
private byte[] _data; private readonly byte[] _data;
private int _dataLen; private readonly int _dataLen;
private DateTime _lastWrite;
private Task _delayedWrite;
/// <param name="databaseUrl">Heroku DB URL of the form "postgres://user:password@host:port/database"</param> /// <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> /// <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) protected override void Dispose(bool disposing)
{ {
_delayedWrite?.Wait();
_sql.Dispose(); _sql.Dispose();
} }
@ -96,19 +93,10 @@ namespace WTelegramClientTest
} }
public override void Write(byte[] buffer, int offset, int count) // Write call and buffer modifications are done within a lock() 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); 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.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
cmd.ExecuteNonQuery(); 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); } });
} }
public override long Length => _dataLen; public override long Length => _dataLen;

View file

@ -1,4 +1,4 @@
[![API Layer](https://img.shields.io/badge/API_Layer-198-blueviolet)](https://corefork.telegram.org/methods) [![API Layer](https://img.shields.io/badge/API_Layer-220-blueviolet)](https://corefork.telegram.org/methods)
[![NuGet version](https://img.shields.io/nuget/v/WTelegramClient?color=00508F)](https://www.nuget.org/packages/WTelegramClient/) [![NuGet version](https://img.shields.io/nuget/v/WTelegramClient?color=00508F)](https://www.nuget.org/packages/WTelegramClient/)
[![NuGet prerelease](https://img.shields.io/nuget/vpre/WTelegramClient?color=C09030&label=dev+nuget)](https://www.nuget.org/packages/WTelegramClient/absoluteLatest) [![NuGet prerelease](https://img.shields.io/nuget/vpre/WTelegramClient?color=C09030&label=dev+nuget)](https://www.nuget.org/packages/WTelegramClient/absoluteLatest)
[![Donate](https://img.shields.io/badge/Help_this_project:-Donate-ff4444)](https://buymeacoffee.com/wizou) [![Donate](https://img.shields.io/badge/Help_this_project:-Donate-ff4444)](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). 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. 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. This ReadMe is a **quick but important tutorial** to learn the fundamentals about this library. Please read it all.
@ -206,4 +206,4 @@ the [Examples codes](https://wiz0u.github.io/WTelegramClient/EXAMPLES) and still
If you like this library, you can [buy me a coffee](https://buymeacoffee.com/wizou) ❤ This will help the project keep going. If you like this library, you can [buy me a coffee](https://buymeacoffee.com/wizou) ❤ This will help the project keep going.
© 2024 Olivier Marcoux © 2021-2025 Olivier Marcoux

View file

@ -32,6 +32,7 @@ public class MTProtoGenerator : IIncrementalGenerator
var nullables = LoadNullables(layer); var nullables = LoadNullables(layer);
var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods
var tableTL = new StringBuilder(); var tableTL = new StringBuilder();
var methodsTL = new StringBuilder();
var source = new StringBuilder(); var source = new StringBuilder();
source source
.AppendLine("using System;") .AppendLine("using System;")
@ -46,6 +47,9 @@ public class MTProtoGenerator : IIncrementalGenerator
tableTL tableTL
.AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Table = new()") .AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Table = new()")
.AppendLine("\t\t{"); .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) foreach (var classDecl in unit.classes)
{ {
@ -54,7 +58,6 @@ public class MTProtoGenerator : IIncrementalGenerator
var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute); var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
if (tldef == null) continue; if (tldef == null) continue;
var id = (uint)tldef.ConstructorArguments[0].Value; var id = (uint)tldef.ConstructorArguments[0].Value;
var inheritBefore = (bool?)tldef.NamedArguments.FirstOrDefault(k => k.Key == "inheritBefore").Value.Value ?? false;
StringBuilder writeTl = new(), readTL = new(); StringBuilder writeTl = new(), readTL = new();
var ns = symbol.BaseType.ContainingNamespace.ToString(); var ns = symbol.BaseType.ContainingNamespace.ToString();
var name = symbol.BaseType.Name; var name = symbol.BaseType.Name;
@ -80,15 +83,20 @@ public class MTProtoGenerator : IIncrementalGenerator
ns = symbol.ContainingNamespace.ToString(); ns = symbol.ContainingNamespace.ToString();
name = symbol.Name; name = symbol.Name;
if (!namespaces.TryGetValue(ns, out var classes)) namespaces[ns] = classes = []; 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();"; classes[name] = "\t\tpublic void WriteTL(BinaryWriter writer) => throw new NotSupportedException();";
continue; continue;
} }
if (id == 0x3072CFA1) // GzipPacked if (id == 0x3072CFA1) // GzipPacked
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = reader => (IObject)reader.ReadTLGzipped(typeof(IObject)),"); tableTL.AppendLine($"\t\t\t[0x{id:X8}] = reader => (IObject)reader.ReadTLGzipped(typeof(IObject)),");
else if (name != "Null" && (ns != "TL.Methods" || name == "Ping")) 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,"); tableTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}.ReadTL,");
}
var override_ = symbol.BaseType == object_ ? "" : "override "; var override_ = symbol.BaseType == object_ ? "" : "override ";
if (name == "Messages_AffectedMessages") override_ = "virtual "; if (name == "Messages_AffectedMessages") override_ = "virtual ";
//if (symbol.Constructors[0].IsImplicitlyDeclared) //if (symbol.Constructors[0].IsImplicitlyDeclared)
@ -105,8 +113,12 @@ public class MTProtoGenerator : IIncrementalGenerator
.AppendLine($"\t\t\twriter.Write(0x{id:X8});"); .AppendLine($"\t\t\twriter.Write(0x{id:X8});");
var members = symbol.GetMembers().ToList(); var members = symbol.GetMembers().ToList();
for (var parent = symbol.BaseType; parent != object_; parent = parent.BaseType) 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()); if (inheritBefore) members.InsertRange(0, parent.GetMembers());
else members.AddRange(parent.GetMembers()); else members.AddRange(parent.GetMembers());
tldef = parent.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
}
foreach (var member in members.OfType<IFieldSymbol>()) foreach (var member in members.OfType<IFieldSymbol>())
{ {
if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue; if (member.DeclaredAccessibility != Accessibility.Public || member.IsStatic) continue;
@ -164,26 +176,35 @@ public class MTProtoGenerator : IIncrementalGenerator
writeTl.AppendLine($"writer.WriteTLMessages({member.Name});"); writeTl.AppendLine($"writer.WriteTLMessages({member.Name});");
break; break;
case "TL.IObject": case "TL.IMethod<X>": case "TL.IObject": case "TL.IMethod<X>":
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);"); writeTl.AppendLine($"{member.Name}.WriteTL(writer);");
break; break;
case "System.Collections.Generic.Dictionary<long, TL.User>": case "System.Collections.Generic.Dictionary<long, TL.User>":
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<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; break;
case "System.Collections.Generic.Dictionary<long, TL.ChatBase>": case "System.Collections.Generic.Dictionary<long, TL.ChatBase>":
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<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; break;
default: default:
if (member.Type is IArrayTypeSymbol arrayType) if (member.Type is IArrayTypeSymbol arrayType)
{ {
if (name is "FutureSalts") if (name is "FutureSalts")
{
readTL.AppendLine($"r.{member.Name} = reader.ReadTLRawVector<{memberType.Substring(0, memberType.Length - 2)}>(0x0949D9DC).ToArray();"); readTL.AppendLine($"r.{member.Name} = reader.ReadTLRawVector<{memberType.Substring(0, memberType.Length - 2)}>(0x0949D9DC).ToArray();");
writeTl.AppendLine($"writer.WriteTLRawVector({member.Name}, 16);");
}
else else
{
readTL.AppendLine($"r.{member.Name} = reader.ReadTLVector<{memberType.Substring(0, memberType.Length - 2)}>();"); 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) else if (member.Type.BaseType.SpecialType == SpecialType.System_Enum)
{ {
readTL.AppendLine($"r.{member.Name} = ({memberType})reader.ReadUInt32();"); readTL.AppendLine($"r.{member.Name} = ({memberType})reader.ReadUInt32();");
@ -210,7 +231,8 @@ public class MTProtoGenerator : IIncrementalGenerator
foreach (var nullable in nullables) foreach (var nullable in nullables)
tableTL.AppendLine($"\t\t\t[0x{nullable.Value:X8}] = null,"); tableTL.AppendLine($"\t\t\t[0x{nullable.Value:X8}] = null,");
tableTL.AppendLine("\t\t};"); 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) foreach (var namesp in namespaces)
{ {
source.Append("namespace ").AppendLine(namesp.Key).Append('{'); source.Append("namespace ").AppendLine(namesp.Key).Append('{');

View file

@ -13,7 +13,6 @@ namespace WTelegram
{ {
partial class Client partial class Client
{ {
#region Client TL Helpers
/// <summary>Used to indicate progression of file download/upload</summary> /// <summary>Used to indicate progression of file download/upload</summary>
/// <param name="transmitted">transmitted bytes</param> /// <param name="transmitted">transmitted bytes</param>
/// <param name="totalSize">total size of file in bytes, or 0 if unknown</param> /// <param name="totalSize">total size of file in bytes, or 0 if unknown</param>
@ -33,11 +32,13 @@ namespace WTelegram
/// <returns>an <see cref="InputFile"/> or <see cref="InputFileBig"/> than can be used in various requests</returns> /// <returns>an <see cref="InputFile"/> or <see cref="InputFileBig"/> than can be used in various requests</returns>
public async Task<InputFileBase> UploadFileAsync(Stream stream, string filename, ProgressCallback progress = null) public async Task<InputFileBase> UploadFileAsync(Stream stream, string filename, ProgressCallback progress = null)
{ {
var client = await GetClientForDC(-_dcSession.DcID, true);
using (stream) using (stream)
{ {
const long SMALL_FILE_MAX_SIZE = 10 << 20;
bool hasLength = stream.CanSeek; bool hasLength = stream.CanSeek;
long transmitted = 0, length = hasLength ? stream.Length : -1; 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; int file_total_parts = hasLength ? (int)((length - 1) / FilePartSize) + 1 : -1;
long file_id = Helpers.RandomLong(); long file_id = Helpers.RandomLong();
int file_part = 0, read; int file_part = 0, read;
@ -65,9 +66,9 @@ namespace WTelegram
try try
{ {
if (isBig) if (isBig)
await this.Upload_SaveBigFilePart(file_id, file_part, file_total_parts, bytes); await client.Upload_SaveBigFilePart(file_id, file_part, file_total_parts, bytes);
else else
await this.Upload_SaveFilePart(file_id, file_part, bytes); await client.Upload_SaveFilePart(file_id, file_part, bytes);
lock (tasks) { transmitted += bytes.Length; tasks.Remove(file_part); } lock (tasks) { transmitted += bytes.Length; tasks.Remove(file_part); }
progress?.Invoke(transmitted, length); progress?.Invoke(transmitted, length);
} }
@ -93,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> /// <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> /// <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="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="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> /// <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() 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, text, new T(), offset_id: offset_id, limit: limit); => 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> /// <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> /// <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="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> /// <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() 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(text, new T(), offset_id: offset_id, limit: limit); => this.Messages_SearchGlobal(q, new T(), offset_id: offset_id, limit: limit);
/// <summary>Helper method to send a media message more easily</summary> /// <summary>Helper method to send a media message more easily</summary>
/// <param name="peer">Destination of message (chat group, channel, user chat, etc..) </param> /// <param name="peer">Destination of message (chat group, channel, user chat, etc..) </param>
@ -333,6 +334,18 @@ namespace WTelegram
return await DownloadFileAsync(fileLocation, outputStream, photo.dc_id, photoSize.FileSize, progress); 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> /// <summary>Download a document from Telegram into the outputStream</summary>
/// <param name="document">The document to download</param> /// <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="outputStream">Stream to write the file content to. This method does not close/dispose the stream</param>
@ -348,6 +361,18 @@ namespace WTelegram
return thumbSize == null ? document.mime_type : "image/" + fileType; 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> /// <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="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> /// <param name="outputStream">Stream to write file content to. This method does not close/dispose the stream</param>
@ -504,7 +529,11 @@ namespace WTelegram
public async Task<Messages_Chats> Messages_GetAllChats() public async Task<Messages_Chats> Messages_GetAllChats()
{ {
var dialogs = await Messages_GetAllDialogs(); var dialogs = await Messages_GetAllDialogs();
return new Messages_Chats { chats = dialogs.chats }; var result = new Messages_Chats { chats = [] };
foreach (var dialog in dialogs.dialogs)
if (dialog.Peer is (PeerChat or PeerChannel) and { ID: var id })
result.chats[id] = dialogs.chats[id];
return result;
} }
/// <summary>Returns the current user dialog list. <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.getDialogs#possible-errors">details</a>)</para></summary> /// <summary>Returns the current user dialog list. <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/messages.getDialogs#possible-errors">details</a>)</para></summary>
@ -518,18 +547,20 @@ namespace WTelegram
case Messages_DialogsSlice mds: case Messages_DialogsSlice mds:
var dialogList = new List<DialogBase>(); var dialogList = new List<DialogBase>();
var messageList = new List<MessageBase>(); 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); messageList.AddRange(dialogs.Messages);
skip = 0;
int last = dialogs.Dialogs.Length - 1; int last = dialogs.Dialogs.Length - 1;
var lastDialog = dialogs.Dialogs[last]; var lastDialog = dialogs.Dialogs[last];
retryDate:
var lastPeer = dialogs.UserOrChat(lastDialog).ToInputPeer(); var lastPeer = dialogs.UserOrChat(lastDialog).ToInputPeer();
var lastMsgId = lastDialog.TopMessage; var lastMsgId = lastDialog.TopMessage;
retryDate:
var lastDate = dialogs.Messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage)?.Date ?? default; var lastDate = dialogs.Messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage)?.Date ?? default;
if (lastDate == 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); dialogs = await this.Messages_GetDialogs(lastDate, lastMsgId, lastPeer, folder_id: folder_id);
if (dialogs is not Messages_Dialogs md) break; if (dialogs is not Messages_Dialogs md) break;
foreach (var (key, value) in md.chats) mds.chats[key] = value; foreach (var (key, value) in md.chats) mds.chats[key] = value;
@ -621,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> /// <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> /// <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) if (result.topics.Length < result.count)
{ {
var topics = result.topics.ToList(); var topics = result.topics.ToList();
var messages = result.messages.ToList(); var messages = result.messages.ToList();
while (true) 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; if (more_topics.topics.Length == 0) break;
topics.AddRange(more_topics.topics); topics.AddRange(more_topics.topics);
messages.AddRange(more_topics.messages); messages.AddRange(more_topics.messages);
@ -892,6 +923,28 @@ namespace WTelegram
} }
return chat; 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}");
}
}
} }
} }

View file

@ -59,6 +59,18 @@ namespace WTelegram
public long UserId => _session.UserId; 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> /// <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; } 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 Func<string, string> _config;
private readonly Session _session; private readonly Session _session;
@ -83,7 +95,7 @@ namespace WTelegram
private int _reactorReconnects = 0; private int _reactorReconnects = 0;
private const string ConnectionShutDown = "Could not read payload length : Connection shut down"; private const string ConnectionShutDown = "Could not read payload length : Connection shut down";
private const long Ticks5Secs = 5 * TimeSpan.TicksPerSecond; 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 _sha256 = SHA256.Create();
private readonly SHA256 _sha256Recv = SHA256.Create(); private readonly SHA256 _sha256Recv = SHA256.Create();
#if OBFUSCATION #if OBFUSCATION
@ -115,7 +127,7 @@ namespace WTelegram
_session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key)); _session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key));
if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id")); if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id"));
if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession); if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession);
_dcSession ??= new() { Id = Helpers.RandomLong() }; _dcSession ??= new();
_dcSession.Client = this; _dcSession.Client = this;
var version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; var version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
Helpers.Log(1, $"WTelegramClient {version} running under {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}"); Helpers.Log(1, $"WTelegramClient {version} running under {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
@ -128,6 +140,7 @@ namespace WTelegram
TcpHandler = cloneOf.TcpHandler; TcpHandler = cloneOf.TcpHandler;
MTProxyUrl = cloneOf.MTProxyUrl; MTProxyUrl = cloneOf.MTProxyUrl;
PingInterval = cloneOf.PingInterval; PingInterval = cloneOf.PingInterval;
MaxAutoReconnects = cloneOf.MaxAutoReconnects;
TLConfig = cloneOf.TLConfig; TLConfig = cloneOf.TLConfig;
_dcSession = dcSession; _dcSession = dcSession;
} }
@ -172,10 +185,6 @@ namespace WTelegram
return Console.ReadLine(); 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> /// <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="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> /// <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"); if (_tcpClient != null) throw new InvalidOperationException("Cannot switch to HTTP after TCP connection");
_httpClient = httpClient ?? new(); _httpClient = httpClient ?? new();
_httpWait = defaultHttpWait; _httpWait = defaultHttpWait;
while (_parallelTransfers.CurrentCount > 1) _parallelTransfers.Wait(); ParallelTransfers = 1;
} }
/// <summary>Disconnect from Telegram <i>(shouldn't be needed in normal usage)</i></summary> /// <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) private Session.DCSession GetOrCreateDCSession(int dcId, DcOption.Flags flags)
{ {
if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.AuthKey != null) if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.Client != null)
if (dcSession.Client != null || dcSession.DataCenter.flags == flags) return dcSession; // we have already a connected session with this DC, use it
return dcSession; // if we have already a session with this DC and we are connected or it is a perfect match, use it
if (dcSession == null && _session.DCSessions.TryGetValue(-dcId, out dcSession) && dcSession.AuthKey != null) if (dcSession == null && _session.DCSessions.TryGetValue(-dcId, out dcSession) && dcSession.AuthKey != null)
{ {
// we have already negociated an AuthKey with this DC // we have already negociated an AuthKey with this DC
if (dcSession.DataCenter.flags == flags && _session.DCSessions.Remove(-dcId)) if (dcSession.DataCenter.flags == flags && _session.DCSessions.Remove(-dcId))
return _session.DCSessions[dcId] = dcSession; // we found a misclassed DC, change its sign 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 dcSession = new Session.DCSession { // clone AuthKey for a session on the matching media_only DC
AuthKeyID = dcSession.AuthKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId }; authKeyID = dcSession.authKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId };
} }
// try to find the most appropriate DcOption for this DC // 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 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); dcId = Math.Abs(dcId);
} }
var dcOptions = GetDcOptions(Math.Abs(dcId), flags); var dcOptions = GetDcOptions(Math.Abs(dcId), flags);
var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}"); var dcOption = dcOptions.FirstOrDefault();
dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing dcSession ??= new(); // create new session only if not already existing
dcSession.DataCenter = dcOption; 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; return _session.DCSessions[dcId] = dcSession;
} }
@ -292,6 +301,7 @@ namespace WTelegram
/// <param name="dcId">ID of the Data Center (use negative values for media_only)</param> /// <param name="dcId">ID of the Data Center (use negative values for media_only)</param>
/// <param name="connect">Connect immediately</param> /// <param name="connect">Connect immediately</param>
/// <returns>Client connected to the selected DC</returns> /// <returns>Client connected to the selected DC</returns>
/// <remarks>⚠️ You shouldn't have to use this method unless you know what you're doing</remarks>
public async Task<Client> GetClientForDC(int dcId, bool connect = true) public async Task<Client> GetClientForDC(int dcId, bool connect = true)
{ {
if (_dcSession.DataCenter?.id == dcId) return this; if (_dcSession.DataCenter?.id == dcId) return this;
@ -301,6 +311,7 @@ namespace WTelegram
var flags = _dcSession.DataCenter.flags; var flags = _dcSession.DataCenter.flags;
if (dcId < 0) flags = (flags & DcOption.Flags.ipv6) | DcOption.Flags.media_only; if (dcId < 0) flags = (flags & DcOption.Flags.ipv6) | DcOption.Flags.media_only;
altSession = GetOrCreateDCSession(dcId, flags); altSession = GetOrCreateDCSession(dcId, flags);
_session.Save();
if (altSession.Client?.Disconnected ?? false) { altSession.Client.Dispose(); altSession.Client = null; } if (altSession.Client?.Disconnected ?? false) { altSession.Client.Dispose(); altSession.Client = null; }
altSession.Client ??= new Client(this, altSession); altSession.Client ??= new Client(this, altSession);
} }
@ -311,7 +322,7 @@ namespace WTelegram
try try
{ {
Auth_ExportedAuthorization exported = null; Auth_ExportedAuthorization exported = null;
if (_session.UserId != 0 && IsMainDC && altSession.UserId != _session.UserId) 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)); exported = await this.Auth_ExportAuthorization(Math.Abs(dcId));
await altSession.Client.ConnectAsync(); await altSession.Client.ConnectAsync();
if (exported != null) if (exported != null)
@ -320,6 +331,7 @@ namespace WTelegram
if (authorization is not Auth_Authorization { user: User user }) if (authorization is not Auth_Authorization { user: User user })
throw new WTException("Failed to get Authorization: " + authorization.GetType().Name); throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
altSession.UserId = user.id; altSession.UserId = user.id;
lock (_session) _session.Save();
} }
} }
finally finally
@ -330,19 +342,19 @@ namespace WTelegram
return altSession.Client; return altSession.Client;
} }
private async Task Reactor(Stream stream, CancellationTokenSource cts) private async Task Reactor(Stream stream, CancellationToken ct)
{ {
const int MinBufferSize = 1024; const int MinBufferSize = 1024;
var data = new byte[MinBufferSize]; var data = new byte[MinBufferSize];
while (!cts.IsCancellationRequested) while (!ct.IsCancellationRequested)
{ {
IObject obj = null; IObject obj = null;
try try
{ {
if (await stream.FullReadAsync(data, 4, cts.Token) != 4) if (await stream.FullReadAsync(data, 4, ct) != 4)
throw new WTException(ConnectionShutDown); throw new WTException(ConnectionShutDown);
#if OBFUSCATION #if OBFUSCATION
_recvCtr.EncryptDecrypt(data, 4); _recvCtr.EncryptDecrypt(data.AsSpan(0, 4));
#endif #endif
int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data); int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data);
if (payloadLen <= 0) if (payloadLen <= 0)
@ -351,23 +363,23 @@ namespace WTelegram
data = new byte[payloadLen]; data = new byte[payloadLen];
else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4) else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4)
data = new byte[Math.Max(payloadLen, MinBufferSize)]; 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"); throw new WTException("Could not read frame data : Connection shut down");
#if OBFUSCATION #if OBFUSCATION
_recvCtr.EncryptDecrypt(data, payloadLen); _recvCtr.EncryptDecrypt(data.AsSpan(0, payloadLen));
#endif #endif
obj = ReadFrame(data, payloadLen); obj = ReadFrame(data, payloadLen);
} }
catch (Exception ex) // an exception in RecvAsync is always fatal 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 }; bool disconnectedAltDC = !IsMainDC && ex is WTException { Message: ConnectionShutDown } or IOException { InnerException: SocketException };
if (disconnectedAltDC) if (disconnectedAltDC)
Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}"); Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}");
else else
Helpers.Log(5, $"{_dcSession.DcID}>An exception occured in the reactor: {ex}"); Helpers.Log(5, $"{_dcSession.DcID}>An exception occured in the reactor: {ex}");
var oldSemaphore = _sendSemaphore; 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 }; var reactorError = new ReactorError { Exception = ex };
try try
{ {
@ -418,7 +430,7 @@ namespace WTelegram
} }
internal DateTime MsgIdToStamp(long serverMsgId) 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) internal IObject ReadFrame(byte[] data, int dataLen)
{ {
@ -431,7 +443,7 @@ namespace WTelegram
throw new WTException($"Packet payload too small: {dataLen}"); throw new WTException($"Packet payload too small: {dataLen}");
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); 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}"); throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}");
if (authKeyId == 0) // Unencrypted message if (authKeyId == 0) // Unencrypted message
{ {
@ -467,7 +479,7 @@ namespace WTelegram
if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}"); 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 (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 ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}");
if (!_dcSession.CheckNewMsgId(msgId)) if (!_dcSession.CheckNewMsgId(msgId))
{ {
@ -476,19 +488,20 @@ namespace WTelegram
} }
var utcNow = DateTime.UtcNow; var utcNow = DateTime.UtcNow;
if (_lastRecvMsgId == 0) // resync ServerTicksOffset on first message 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); var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId);
long deltaTicks = (msgStamp - utcNow).Ticks; long deltaTicks = (msgStamp - utcNow).Ticks;
if (deltaTicks is > 0) if (deltaTicks is > 0)
if (deltaTicks < Ticks5Secs) // resync if next message is less than 5 seconds in the future if (deltaTicks < Ticks5Secs) // resync if next message is less than 5 seconds in the future
_dcSession.ServerTicksOffset += deltaTicks; _dcSession.serverTicksOffset += deltaTicks;
else if (_dcSession.ServerTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.ServerTicksOffset < 0) else if (_dcSession.serverTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.serverTicksOffset < 0)
_dcSession.ServerTicksOffset += deltaTicks; _dcSession.serverTicksOffset += deltaTicks;
if (serverSalt != _dcSession.Salt && serverSalt != _dcSession.OldSalt && serverSalt != _dcSession.Salts?.Values.ElementAtOrDefault(1)) 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}"); Helpers.Log(3, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}");
_dcSession.OldSalt = _dcSession.Salt; _dcSession.OldSalt = _dcSession.Salt;
_dcSession.Salt = serverSalt; _dcSession.Salt = serverSalt;
lock (_session) _session.Save();
if (++_saltChangeCounter >= 10) if (++_saltChangeCounter >= 10)
throw new WTException("Server salt changed too often! Security issue?"); throw new WTException("Server salt changed too often! Security issue?");
CheckSalt(); CheckSalt();
@ -498,7 +511,7 @@ namespace WTelegram
var ctorNb = reader.ReadUInt32(); var ctorNb = reader.ReadUInt32();
if (ctorNb != Layer.BadMsgCtor && deltaTicks / TimeSpan.TicksPerSecond is > 30 or < -300) 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. { // 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; return null;
} }
try try
@ -545,9 +558,11 @@ namespace WTelegram
{ {
var keys = _dcSession.Salts.Keys; var keys = _dcSession.Salts.Keys;
if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing
var now = DateTime.UtcNow.AddTicks(_dcSession.ServerTicksOffset); var now = DateTime.UtcNow.AddTicks(_dcSession.serverTicksOffset - TimeSpan.TicksPerMinute);
for (; keys.Count > 1 && keys[1] < now; _dcSession.OldSalt = _dcSession.Salt, _dcSession.Salt = _dcSession.Salts.Values[0]) 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); _dcSession.Salts.RemoveAt(0);
if (removed) _session.Save();
if (_dcSession.Salts.Count > 48) return; if (_dcSession.Salts.Count > 48) return;
} }
_dcSession.Salts[DateTime.MaxValue] = 0; _dcSession.Salts[DateTime.MaxValue] = 0;
@ -574,7 +589,7 @@ namespace WTelegram
{ {
var msg = new _Message(reader.ReadInt64(), reader.ReadInt32(), null) { bytes = reader.ReadInt32() }; var msg = new _Message(reader.ReadInt64(), reader.ReadInt32(), null) { bytes = reader.ReadInt32() };
messages.Add(msg); 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; var pos = reader.BaseStream.Position;
try try
{ {
@ -587,7 +602,7 @@ namespace WTelegram
else else
{ {
var obj = msg.body = reader.ReadTLObject(ctorNb); 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) catch (Exception ex)
@ -693,7 +708,7 @@ namespace WTelegram
rpc.tcs.SetResult(obj); rpc.tcs.SetResult(obj);
return; return;
} }
else if (_dcSession.AuthKeyID == 0) else if (_dcSession.authKeyID == 0)
throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}");
lock (_pendingRpcs) lock (_pendingRpcs)
_pendingRpcs[_bareRpc.msgId] = _bareRpc; _pendingRpcs[_bareRpc.msgId] = _bareRpc;
@ -724,25 +739,22 @@ namespace WTelegram
case MsgsAck msgsAck: case MsgsAck msgsAck:
break; // we don't do anything with these, for now break; // we don't do anything with these, for now
case BadMsgNotification badMsgNotification: case BadMsgNotification badMsgNotification:
await _sendSemaphore.WaitAsync(); bool retryRpcs = true;
bool retryLast = badMsgNotification.bad_msg_id == _dcSession.LastSentMsgId;
var lastSentMsg = _lastSentMsg;
_sendSemaphore.Release();
var logLevel = badMsgNotification.error_code == 48 ? 2 : 4; var logLevel = badMsgNotification.error_code == 48 ? 2 : 4;
Helpers.Log(logLevel, $"BadMsgNotification {badMsgNotification.error_code} for msg #{(short)badMsgNotification.bad_msg_id.GetHashCode():X4}"); Helpers.Log(logLevel, $"BadMsgNotification {badMsgNotification.error_code} for msg #{(short)badMsgNotification.bad_msg_id.GetHashCode():X4}");
switch (badMsgNotification.error_code) 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 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) 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; var localTime = DateTime.UtcNow;
_dcSession.ServerTicksOffset = (_lastRecvMsgId >> 32) * 10000000 - localTime.Ticks + 621355968000000000L; _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"); Helpers.Log(1, $"Time offset: {_dcSession.serverTicksOffset} | Server: {MsgIdToStamp(_lastRecvMsgId).AddTicks(_dcSession.serverTicksOffset).TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
break; 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 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) 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) if (_dcSession.seqno <= 1)
retryLast = false; retryRpcs = false;
else else
{ {
await ResetAsync(false, false); await ResetAsync(false, false);
@ -753,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) 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.OldSalt = _dcSession.Salt;
_dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt; _dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt;
lock (_session) _session.Save();
CheckSalt(); CheckSalt();
break; break;
default: default:
retryLast = false; retryRpcs = false;
break; break;
} }
if (retryLast) if (retryRpcs)
{ {
Rpc prevRequest;
lock (_pendingRpcs) 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; foreach (var rpc in _pendingRpcs.Values)
rpc.tcs.SetException(new WTException($"BadMsgNotification {badMsgNotification.error_code}")); rpc.tcs.TrySetResult(new RpcError { error_code = -503, error_message = $"BadMsgNotification {badMsgNotification.error_code}" });
_pendingRpcs.Clear();
} }
else
RaiseUpdates(badMsgNotification); RaiseUpdates(badMsgNotification);
}
break; break;
default: default:
RaiseUpdates(obj); RaiseUpdates(obj);
@ -864,14 +871,14 @@ namespace WTelegram
{ {
_cts = new(); _cts = new();
IPEndPoint endpoint = null; IPEndPoint endpoint = null;
bool needMigrate = false;
byte[] preamble, secret = null; byte[] preamble, secret = null;
int dcId = _dcSession?.DcID ?? 0; int dcId = _dcSession?.DcID ?? 0;
if (dcId == 0) dcId = 2; if (dcId == 0) dcId = 2;
if (MTProxyUrl != null) if (MTProxyUrl != null)
{ {
#if OBFUSCATION #if OBFUSCATION
if (TLConfig?.test_mode == true) dcId += 10000; if (TLConfig?.test_mode == true) dcId += dcId < 0 ? -10000 : 10000;
if (_dcSession.DataCenter?.flags.HasFlag(DcOption.Flags.media_only) == true) dcId = -dcId;
var parms = HttpUtility.ParseQueryString(MTProxyUrl[MTProxyUrl.IndexOf('?')..]); var parms = HttpUtility.ParseQueryString(MTProxyUrl[MTProxyUrl.IndexOf('?')..]);
var server = parms["server"]; var server = parms["server"];
int port = int.Parse(parms["port"]); int port = int.Parse(parms["port"]);
@ -937,11 +944,12 @@ namespace WTelegram
{ {
endpoint = GetDefaultEndpoint(out defaultDc); // re-ask callback for an address endpoint = GetDefaultEndpoint(out defaultDc); // re-ask callback for an address
if (!triedEndpoints.Add(endpoint)) throw; if (!triedEndpoints.Add(endpoint)) throw;
needMigrate = _dcSession.DataCenter.id == _session.MainDC && defaultDc != _session.MainDC;
_dcSession.Client = null; _dcSession.Client = null;
// is it address for a known DCSession? // is it address for a known DCSession?
_dcSession = _session.DCSessions.Values.FirstOrDefault(dcs => dcs.EndPoint.Equals(endpoint)); _dcSession = _session.DCSessions.Values.FirstOrDefault(dcs => dcs.EndPoint.Equals(endpoint));
if (defaultDc != 0) _dcSession ??= _session.DCSessions.GetValueOrDefault(defaultDc); if (defaultDc != 0) _dcSession ??= _session.DCSessions.GetValueOrDefault(defaultDc);
_dcSession ??= new() { Id = Helpers.RandomLong() }; _dcSession ??= new();
_dcSession.Client = this; _dcSession.Client = this;
_dcSession.DataCenter = null; _dcSession.DataCenter = null;
Helpers.Log(2, $"Connecting to {endpoint}..."); Helpers.Log(2, $"Connecting to {endpoint}...");
@ -969,13 +977,13 @@ namespace WTelegram
#endif #endif
await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token); await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token);
_reactorTask = Reactor(_networkStream, _cts); _reactorTask = Reactor(_networkStream, _cts.Token);
} }
_sendSemaphore.Release(); _sendSemaphore.Release();
try try
{ {
if (_dcSession.AuthKeyID == 0) if (_dcSession.authKeyID == 0)
await CreateAuthorizationKey(this, _dcSession); await CreateAuthorizationKey(this, _dcSession);
if (_networkStream != null) _ = KeepAlive(_cts.Token); if (_networkStream != null) _ = KeepAlive(_cts.Token);
@ -984,6 +992,30 @@ namespace WTelegram
else else
{ {
if (_dcSession.Layer != 0 && _dcSession.Layer != Layer.Version) _dcSession.Renew(); if (_dcSession.Layer != 0 && _dcSession.Layer != Layer.Version) _dcSession.Renew();
await InitConnection();
if (_dcSession.DataCenter == null)
{
_dcSession.DataCenter = _session.DcOptions.Where(dc => dc.id == TLConfig.this_dc)
.OrderByDescending(dc => dc.ip_address == endpoint?.Address.ToString())
.ThenByDescending(dc => dc.port == endpoint?.Port)
.ThenByDescending(dc => dc.flags == (endpoint?.AddressFamily == AddressFamily.InterNetworkV6 ? DcOption.Flags.ipv6 : 0))
.First();
_session.DCSessions[TLConfig.this_dc] = _dcSession;
}
if (_session.MainDC == 0) _session.MainDC = TLConfig.this_dc;
else if (needMigrate) await MigrateToDC(_session.MainDC);
}
}
finally
{
if (_reactorTask != null) // client not disposed
lock (_session) _session.Save();
}
Helpers.Log(2, $"Connected to {(TLConfig.test_mode ? "Test DC" : "DC")} {TLConfig.this_dc}... {TLConfig.flags & (Config.Flags)~0x18E00U}");
}
private async Task InitConnection()
{
var initParams = JSONValue.FromJsonElement(System.Text.Json.JsonDocument.Parse(Config("init_params")).RootElement); var initParams = JSONValue.FromJsonElement(System.Text.Json.JsonDocument.Parse(Config("init_params")).RootElement);
TLConfig = await this.InvokeWithLayer(Layer.Version, TLConfig = await this.InvokeWithLayer(Layer.Version,
new TL.Methods.InitConnection<Config> new TL.Methods.InitConnection<Config>
@ -1001,24 +1033,6 @@ namespace WTelegram
}); });
_dcSession.Layer = Layer.Version; _dcSession.Layer = Layer.Version;
_session.DcOptions = TLConfig.dc_options; _session.DcOptions = TLConfig.dc_options;
if (_dcSession.DataCenter == null)
{
_dcSession.DataCenter = _session.DcOptions.Where(dc => dc.id == TLConfig.this_dc)
.OrderByDescending(dc => dc.ip_address == endpoint?.Address.ToString())
.ThenByDescending(dc => dc.port == endpoint?.Port)
.ThenByDescending(dc => dc.flags == (endpoint?.AddressFamily == AddressFamily.InterNetworkV6 ? DcOption.Flags.ipv6 : 0))
.First();
_session.DCSessions[TLConfig.this_dc] = _dcSession;
}
if (_session.MainDC == 0) _session.MainDC = TLConfig.this_dc;
}
}
finally
{
if (_reactorTask != null) // client not disposed
lock (_session) _session.Save();
}
Helpers.Log(2, $"Connected to {(TLConfig.test_mode ? "Test DC" : "DC")} {TLConfig.this_dc}... {TLConfig.flags & (Config.Flags)~0x18E00U}");
} }
private async Task KeepAlive(CancellationToken ct) private async Task KeepAlive(CancellationToken ct)
@ -1114,11 +1128,12 @@ namespace WTelegram
{ {
try 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; var self = users[0] as User;
if (self.id == long.Parse(botToken.Split(':')[0])) if (self.id == long.Parse(botToken.Split(':')[0]))
{ {
_session.UserId = _dcSession.UserId = self.id; _session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save();
RaiseUpdates(self); RaiseUpdates(self);
return User = self; return User = self;
} }
@ -1158,6 +1173,7 @@ namespace WTelegram
self.phone == string.Concat((phone_number = Config("phone_number")).Where(char.IsDigit))) self.phone == string.Concat((phone_number = Config("phone_number")).Where(char.IsDigit)))
{ {
_session.UserId = _dcSession.UserId = self.id; _session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save();
RaiseUpdates(self); RaiseUpdates(self);
return User = self; return User = self;
} }
@ -1221,10 +1237,13 @@ namespace WTelegram
if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be) if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be)
sentCodeBase = verifiedLogin.sent_code; sentCodeBase = verifiedLogin.sent_code;
} }
RaiseUpdates(sentCodeBase);
} }
resent: resent:
if (sentCodeBase is Auth_SentCodeSuccess success) if (sentCodeBase is Auth_SentCodeSuccess success)
authorization = success.authorization; authorization = success.authorization;
else if (sentCodeBase is Auth_SentCodePaymentRequired paymentRequired)
throw new WTException("Auth_SentCodePaymentRequired unsupported");
else if (sentCodeBase is Auth_SentCode sentCode) else if (sentCodeBase is Auth_SentCode sentCode)
{ {
phone_code_hash = sentCode.phone_code_hash; phone_code_hash = sentCode.phone_code_hash;
@ -1393,7 +1412,7 @@ namespace WTelegram
public User LoginAlreadyDone(Auth_AuthorizationBase authorization) public User LoginAlreadyDone(Auth_AuthorizationBase authorization)
{ {
if (authorization is not Auth_Authorization { user: User self }) 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; _session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save(); lock (_session) _session.Save();
RaiseUpdates(self); RaiseUpdates(self);
@ -1414,13 +1433,12 @@ namespace WTelegram
internal (long msgId, int seqno) NewMsgId(bool isContent) internal (long msgId, int seqno) NewMsgId(bool isContent)
{ {
int seqno; 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 msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4
lock (_session) lock (_session)
{ {
if (msgId <= _dcSession.LastSentMsgId) msgId = _dcSession.LastSentMsgId += 4; else _dcSession.LastSentMsgId = msgId; if (msgId <= _dcSession.lastSentMsgId) msgId = _dcSession.lastSentMsgId += 4; else _dcSession.lastSentMsgId = msgId;
seqno = isContent ? _dcSession.Seqno++ * 2 + 1 : _dcSession.Seqno * 2; seqno = isContent ? _dcSession.seqno++ * 2 + 1 : _dcSession.seqno * 2;
_session.Save();
} }
return (msgId, seqno); return (msgId, seqno);
} }
@ -1428,7 +1446,7 @@ namespace WTelegram
private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null) private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null)
{ {
if (_reactorTask == null) throw new WTException("You must connect to Telegram first"); 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); var (msgId, seqno) = NewMsgId(isContent);
if (rpc != null) if (rpc != null)
lock (_pendingRpcs) lock (_pendingRpcs)
@ -1456,13 +1474,13 @@ namespace WTelegram
using var writer = new BinaryWriter(memStream); using var writer = new BinaryWriter(memStream);
writer.Write(0); // int32 payload_len (to be patched with payload length) 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"); 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(0L); // int64 auth_key_id = 0 (Unencrypted)
writer.Write(msgId); // int64 message_id writer.Write(msgId); // int64 message_id
writer.Write(0); // int32 message_data_length (to be patched) 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 writer.WriteTLObject(msg); // bytes message_data
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(20), (int)memStream.Length - 24); // patch message_data_length BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(20), (int)memStream.Length - 24); // patch message_data_length
} }
@ -1473,7 +1491,7 @@ namespace WTelegram
using var clearWriter = new BinaryWriter(clearStream); using var clearWriter = new BinaryWriter(clearStream);
clearWriter.Write(_dcSession.AuthKey, 88, 32); clearWriter.Write(_dcSession.AuthKey, 88, 32);
clearWriter.Write(_dcSession.Salt); // int64 salt 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(msgId); // int64 message_id
clearWriter.Write(seqno); // int32 msg_seqno clearWriter.Write(seqno); // int32 msg_seqno
clearWriter.Write(0); // int32 message_data_length (to be patched) clearWriter.Write(0); // int32 message_data_length (to be patched)
@ -1493,13 +1511,13 @@ namespace WTelegram
const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding) 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); 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(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key
writer.Write(encrypted_data); // bytes encrypted_data writer.Write(encrypted_data); // bytes encrypted_data
} }
if (_paddedMode) // Padded intermediate mode => append random padding 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); RNG.GetBytes(padding);
writer.Write(padding); writer.Write(padding);
} }
@ -1507,7 +1525,7 @@ namespace WTelegram
int frameLength = (int)memStream.Length; int frameLength = (int)memStream.Length;
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
#if OBFUSCATION #if OBFUSCATION
_sendCtr?.EncryptDecrypt(buffer, frameLength); _sendCtr?.EncryptDecrypt(buffer.AsSpan(0, frameLength));
#endif #endif
if (_networkStream != null) if (_networkStream != null)
await _networkStream.WriteAsync(buffer, 0, frameLength); await _networkStream.WriteAsync(buffer, 0, frameLength);
@ -1566,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> /// <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) 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 }; query = new TL.Methods.InvokeWithoutUpdates<T> { query = query };
bool got503 = false; bool got503 = false;
retry: retry:
@ -1614,11 +1632,28 @@ namespace WTelegram
got503 = true; got503 = true;
goto retry; 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") else if (code == 500 && message == "AUTH_RESTART")
lock (_session)
{ {
_session.UserId = 0; // force a full login authorization flow, next time _session.UserId = 0; // force a full login authorization flow, next time
User = null; User = null;
lock (_session) _session.Save(); _session.Save();
} }
throw new RpcException(code, message, x); throw new RpcException(code, message, x);
case ReactorError: case ReactorError:

View file

@ -13,7 +13,7 @@ using static WTelegram.Compat;
namespace WTelegram namespace WTelegram
{ {
internal static class Encryption public static class Encryption
{ {
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = []; private static readonly Dictionary<long, RSAPublicKey> PublicKeys = [];
internal static readonly RandomNumberGenerator RNG = RandomNumberGenerator.Create(); 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 (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok");
if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch"); if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch");
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server 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); var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
using var answerReader = new BinaryReader(new MemoryStream(answer)); using var answerReader = new BinaryReader(new MemoryStream(answer));
@ -110,9 +110,9 @@ namespace WTelegram
var g_a = BigEndianInteger(serverDHinnerData.g_a); var g_a = BigEndianInteger(serverDHinnerData.g_a);
var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime); var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime);
CheckGoodPrime(dh_prime, serverDHinnerData.g); CheckGoodPrime(dh_prime, serverDHinnerData.g);
session.LastSentMsgId = 0; session.lastSentMsgId = 0;
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks; session.serverTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
Helpers.Log(1, $"Time offset: {session.ServerTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC"); Helpers.Log(1, $"Time offset: {session.serverTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
//6) //6)
var salt = new byte[256]; var salt = new byte[256];
RNG.GetBytes(salt); RNG.GetBytes(salt);
@ -159,12 +159,13 @@ namespace WTelegram
if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4))) if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4)))
throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); 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.AuthKey = authKey;
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw); session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
session.OldSalt = session.Salt; session.OldSalt = session.Salt;
}
(byte[] key, byte[] iv) ConstructTmpAESKeyIV(TL.Int128 server_nonce, Int256 new_nonce) 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]; byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
sha1.TransformBlock(new_nonce, 0, 32, null, 0); sha1.TransformBlock(new_nonce, 0, 32, null, 0);
@ -183,7 +184,6 @@ namespace WTelegram
sha1.Initialize(); sha1.Initialize();
return (tmp_aes_key, tmp_aes_iv); return (tmp_aes_key, tmp_aes_iv);
} }
}
internal static void CheckGoodPrime(BigInteger p, int g) 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}"); 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) public static void LoadPublicKey(string pem)
{ {
using var rsa = RSA.Create(); using var rsa = RSA.Create();
@ -245,10 +247,7 @@ namespace WTelegram
var rsaParam = rsa.ExportParameters(false); var rsaParam = rsa.ExportParameters(false);
if (rsaParam.Modulus[0] == 0) rsaParam.Modulus = rsaParam.Modulus[1..]; if (rsaParam.Modulus[0] == 0) rsaParam.Modulus = rsaParam.Modulus[1..];
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent }; var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
using var memStream = new MemoryStream(280); var bareData = publicKey.ToBytes();
using (var writer = new BinaryWriter(memStream))
writer.WriteTLObject(publicKey);
var bareData = memStream.ToArray();
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1 var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1
PublicKeys[fingerprint] = publicKey; PublicKeys[fingerprint] = publicKey;
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}"); Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
@ -276,7 +275,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
-----END RSA PUBLIC KEY-----"); -----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 // first, construct AES key & IV
byte[] aes_key = new byte[32], aes_iv = new byte[32]; 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); 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"); 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 output = new byte[input.Length];
var prevBytes = (byte[])aes_iv.Clone(); var prevBytes = (byte[])aes_iv.Clone();
var span = MemoryMarshal.Cast<byte, long>(input); var span = MemoryMarshal.Cast<byte, long>(input);
var sout = MemoryMarshal.Cast<byte, long>(output); var sout = MemoryMarshal.Cast<byte, long>(output.AsSpan());
var prev = MemoryMarshal.Cast<byte, long>(prevBytes); 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]); } 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;) for (int i = 0, count = input.Length / 8; i < count;)
{ {
@ -319,7 +318,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
} }
#if OBFUSCATION #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 ICryptoTransform _encryptor = AesECB.CreateEncryptor(key, null);
readonly byte[] _ecount = new byte[16]; readonly byte[] _ecount = new byte[16];
@ -327,9 +326,9 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
public void Dispose() => _encryptor.Dispose(); 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) if (_num == 0)
{ {
@ -373,7 +372,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
var sendCtr = new AesCtr(sendKey, sendIV); var sendCtr = new AesCtr(sendKey, sendIV);
var recvCtr = new AesCtr(recvKey, recvIV); var recvCtr = new AesCtr(recvKey, recvIV);
var encrypted = (byte[])preamble.Clone(); var encrypted = (byte[])preamble.Clone();
sendCtr.EncryptDecrypt(encrypted, 64); sendCtr.EncryptDecrypt(encrypted);
for (int i = 56; i < 64; i++) for (int i = 56; i < 64; i++)
preamble[i] = encrypted[i]; preamble[i] = encrypted[i];
return (sendCtr, recvCtr, preamble); return (sendCtr, recvCtr, preamble);
@ -557,7 +556,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
{ {
count = count + 15 & ~15; count = count + 15 & ~15;
var span = MemoryMarshal.Cast<byte, long>(buffer.AsSpan(offset, count)); 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;) for (offset = 0, count /= 8; offset < count;)
{ {
prev[0] ^= span[offset]; prev[1] ^= span[offset + 1]; prev[0] ^= span[offset]; prev[1] ^= span[offset + 1];

View file

@ -27,8 +27,88 @@ namespace WTelegram
public static readonly JsonSerializerOptions JsonOptions = new() { IncludeFields = true, WriteIndented = true, public static readonly JsonSerializerOptions JsonOptions = new() { IncludeFields = true, WriteIndented = true,
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault ? null : WTelegramContext.Default, 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 #endif
IgnoreReadOnlyProperties = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
private static readonly ConsoleColor[] LogLevelToColor = [ ConsoleColor.DarkGray, ConsoleColor.DarkCyan, private static readonly ConsoleColor[] LogLevelToColor = [ ConsoleColor.DarkGray, ConsoleColor.DarkCyan,
ConsoleColor.Cyan, ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue ]; ConsoleColor.Cyan, ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue ];

View file

@ -30,12 +30,19 @@ namespace TL
_users[user.id] = user; _users[user.id] = user;
else else
{ // update previously full user from min user: { // update previously full user from min user:
const User.Flags updated_flags = (User.Flags)0x5DAFE000; // see https://github.com/tdlib/td/blob/master/td/telegram/UserManager.cpp#L2689
const User.Flags2 updated_flags2 = (User.Flags2)0x711; // 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 // tdlib updated flags: deleted | bot | bot_chat_history | bot_nochats | verified | bot_inline_geo
// | support | scam | fake | bot_attach_menu | premium // | support | scam | fake | bot_attach_menu | premium
// tdesktop non-updated flags: bot | bot_chat_history | bot_nochats | bot_attach_menu // 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.flags = (prevUser.flags & ~updated_flags) | (user.flags & updated_flags);
prevUser.flags2 = (prevUser.flags2 & ~updated_flags2) | (user.flags2 & updated_flags2); prevUser.flags2 = (prevUser.flags2 & ~updated_flags2) | (user.flags2 & updated_flags2);
prevUser.first_name ??= user.first_name; // tdlib: not updated ; tdesktop: updated only if unknown prevUser.first_name ??= user.first_name; // tdlib: not updated ; tdesktop: updated only if unknown
@ -53,8 +60,8 @@ namespace TL
if (user.lang_code != null) if (user.lang_code != null)
prevUser.lang_code = user.lang_code; // tdlib: updated if present ; tdesktop: ignored prevUser.lang_code = user.lang_code; // tdlib: updated if present ; tdesktop: ignored
prevUser.emoji_status = user.emoji_status; // tdlib/tdesktop: updated prevUser.emoji_status = user.emoji_status; // tdlib/tdesktop: updated
prevUser.usernames = user.usernames; // tdlib: not updated ; tdesktop: updated //prevUser.usernames = user.usernames; // tdlib/tdesktop: not updated
if (user.stories_max_id > 0) if (user.stories_max_id != null)
prevUser.stories_max_id = user.stories_max_id; // tdlib: updated if > 0 ; tdesktop: not updated prevUser.stories_max_id = user.stories_max_id; // tdlib: updated if > 0 ; tdesktop: not updated
prevUser.color = user.color; // tdlib/tdesktop: updated prevUser.color = user.color; // tdlib/tdesktop: updated
prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet prevUser.profile_color = user.profile_color; // tdlib/tdesktop: unimplemented yet
@ -68,8 +75,13 @@ namespace TL
foreach (var chat in chats) foreach (var chat in chats)
if (chat is not Channel channel) if (chat is not Channel channel)
_chats[chat.ID] = chat; _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; _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 else
{ // update previously full channel from min channel: { // update previously full channel from min channel:
const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0; const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0;
@ -373,17 +385,18 @@ namespace TL
char c = sb[offset]; char c = sb[offset];
if (c == '&') if (c == '&')
{ {
for (end = offset + 1; end < sb.Length; end++) end = offset + 1;
if (sb[end] == ';') break; if (end < sb.Length && sb[end] == '#') end++;
if (end >= sb.Length) break; while (end < sb.Length && sb[end] is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9') end++;
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) if (html.Length == 1)
{ {
sb[offset] = html[0]; sb[offset] = html[0];
sb.Remove(++offset, end - offset + 1); sb.Remove(++offset, end - offset);
} }
else else
offset = end + 1; offset = end;
} }
else if (c == '<') else if (c == '<')
{ {
@ -400,6 +413,7 @@ namespace TL
case "u": case "ins": ProcessEntity<MessageEntityUnderline>(); break; case "u": case "ins": ProcessEntity<MessageEntityUnderline>(); break;
case "s": case "strike": case "del": ProcessEntity<MessageEntityStrike>(); break; case "s": case "strike": case "del": ProcessEntity<MessageEntityStrike>(); break;
case "span class=\"tg-spoiler\"": case "span class=\"tg-spoiler\"":
case "span class='tg-spoiler'":
case "span" when closing: case "span" when closing:
case "tg-spoiler": ProcessEntity<MessageEntitySpoiler>(); break; case "tg-spoiler": ProcessEntity<MessageEntitySpoiler>(); break;
case "code": ProcessEntity<MessageEntityCode>(); break; case "code": ProcessEntity<MessageEntityCode>(); break;
@ -419,20 +433,22 @@ namespace TL
prevEntity.length = offset - prevEntity.offset; 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 = tag[8..^1]; 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) if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && users?.GetValueOrDefault(user_id)?.access_hash is long hash)
entities.Add(new InputMessageEntityMentionName { offset = offset, length = -1, user_id = new InputUser(user_id, hash) }); entities.Add(new InputMessageEntityMentionName { offset = offset, length = -1, user_id = new InputUser(user_id, hash) });
else else
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag }); 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) if (entities.LastOrDefault(e => e.length == -1) is MessageEntityPre prevEntity)
prevEntity.language = tag[21..^1]; 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]) }); entities.Add(new MessageEntityCustomEmoji { offset = offset, length = -1, document_id = long.Parse(tag[(tag.IndexOf('=') + 2)..^1]) });
break; break;
} }
@ -457,11 +473,13 @@ namespace TL
{ {
int newlen = sb.Length; int newlen = sb.Length;
while (--newlen >= 0 && char.IsWhiteSpace(sb[newlen])); while (--newlen >= 0 && char.IsWhiteSpace(sb[newlen]));
if (++newlen == sb.Length) return; if (++newlen != sb.Length) sb.Length = newlen;
sb.Length = newlen; for (int i = 0; i < entities.Count; i++)
foreach (var entity in entities) {
if (entity.offset + entity.length > newlen) var entity = entities[i];
entity.length = newlen - entity.offset; if (entity.offset + entity.length > newlen) entity.length = newlen - entity.offset;
if (entity.length == 0) entities.RemoveAt(i--);
}
} }
/// <summary>Converts the (plain text + entities) format used by Telegram messages into an <a href="https://core.telegram.org/bots/api/#html-style">HTML-formatted text</a></summary> /// <summary>Converts the (plain text + entities) format used by Telegram messages into an <a href="https://core.telegram.org/bots/api/#html-style">HTML-formatted text</a></summary>
@ -494,7 +512,7 @@ namespace TL
if (tag[0] == 'a') if (tag[0] == 'a')
{ {
if (nextEntity is MessageEntityTextUrl metu) if (nextEntity is MessageEntityTextUrl metu)
tag = $"<a href=\"{metu.url}\">"; tag = $"<a href=\"{Escape(metu.url)}\">";
else if (nextEntity is MessageEntityMentionName memn) else if (nextEntity is MessageEntityMentionName memn)
tag = $"<a href=\"tg://user?id={memn.user_id}\">"; tag = $"<a href=\"tg://user?id={memn.user_id}\">";
else if (nextEntity is InputMessageEntityMentionName imemn) else if (nextEntity is InputMessageEntityMentionName imemn)

View file

@ -21,25 +21,25 @@ namespace WTelegram
public sealed class DCSession public sealed class DCSession
{ {
public long Id;
public long AuthKeyID;
public byte[] AuthKey; // 2048-bit = 256 bytes public byte[] AuthKey; // 2048-bit = 256 bytes
public long UserId; public long UserId;
public long OldSalt; // still accepted for a further 1800 seconds public long OldSalt; // still accepted for a further 1800 seconds
public long Salt; public long Salt;
public SortedList<DateTime, long> Salts; public SortedList<DateTime, long> Salts;
public int Seqno;
public long ServerTicksOffset;
public long LastSentMsgId;
public TL.DcOption DataCenter; public TL.DcOption DataCenter;
public bool WithoutUpdates;
public int Layer; 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 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 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; } 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(); } } public void DisableUpdates(bool disable = true) { if (withoutUpdates != disable) { withoutUpdates = disable; Renew(); } }
const int MsgIdsN = 512; const int MsgIdsN = 512;
private long[] _msgIds; private long[] _msgIds;
@ -117,6 +117,9 @@ namespace WTelegram
throw new WTException("Integrity check failed in session loading"); throw new WTException("Integrity check failed in session loading");
session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions); session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
Helpers.Log(2, "Loaded previous session"); 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 ??= new Session();
session._store = store; session._store = store;

View file

@ -109,15 +109,13 @@ namespace TL
public Int128 new_nonce_hash3; public Int128 new_nonce_hash3;
} }
public enum DestroyAuthKeyRes : uint public abstract partial class DestroyAuthKeyRes : IObject { }
{ [TLDef(0xF660E1D4)] //destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_ok"/></summary> public sealed partial class DestroyAuthKeyOk : DestroyAuthKeyRes { }
Ok = 0xF660E1D4, [TLDef(0x0A9F2259)] //destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_none"/></summary> public sealed partial class DestroyAuthKeyNone : DestroyAuthKeyRes { }
None = 0x0A9F2259, [TLDef(0xEA109B13)] //destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes
///<summary>See <a href="https://corefork.telegram.org/constructor/destroy_auth_key_fail"/></summary> public sealed partial class DestroyAuthKeyFail : DestroyAuthKeyRes { }
Fail = 0xEA109B13,
}
[TLDef(0x62D6B459)] //msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck [TLDef(0x62D6B459)] //msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck
public sealed partial class MsgsAck : IObject public sealed partial class MsgsAck : IObject
@ -327,12 +325,12 @@ namespace TL
}); });
public static Task<DestroyAuthKeyRes> DestroyAuthKey(this Client client) 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) 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, req_msg_id = req_msg_id,
}); });
@ -357,7 +355,7 @@ namespace TL
}); });
public static Task<DestroySessionRes> DestroySession(this Client client, long session_id) public static Task<DestroySessionRes> DestroySession(this Client client, long session_id)
=> client.InvokeBare(new DestroySession => client.Invoke(new DestroySession
{ {
session_id = session_id, session_id = session_id,
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
namespace TL namespace TL
{ {
#pragma warning disable IDE1006, CS1574 #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 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> /// <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; 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> /// <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; public virtual long Grouped => default;
/// <summary>Random bytes, removed in layer 17.</summary>
public virtual byte[] RandomBytes => default; public virtual byte[] RandomBytes => default;
public virtual DecryptedMessageAction Action => 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> /// <remarks>a <see langword="null"/> value means <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaEmpty">decryptedMessageMediaEmpty</a></remarks>
public abstract partial class DecryptedMessageMedia : IObject 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"); } 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 { } 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 public abstract partial class FileLocationBase : IObject
{ {
/// <summary>Volume ID</summary>
public virtual long VolumeId => default; public virtual long VolumeId => default;
/// <summary>Local ID</summary>
public virtual int LocalId => default; public virtual int LocalId => default;
/// <summary>Secret</summary>
public virtual long Secret => default; 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> /// <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; public long random_id;
/// <summary>Random bytes, removed in layer 17.</summary>
public byte[] random_bytes; public byte[] random_bytes;
/// <summary>Message text</summary> /// <summary>Message text</summary>
public string message; public string message;
@ -67,6 +72,7 @@ namespace TL
public override string Message => message; public override string Message => message;
/// <summary>Media content</summary> /// <summary>Media content</summary>
public override DecryptedMessageMedia Media => media; public override DecryptedMessageMedia Media => media;
/// <summary>Random bytes, removed in layer 17.</summary>
public override byte[] RandomBytes => random_bytes; 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> /// <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> /// <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; public long random_id;
/// <summary>Random bytes, removed in Layer 17.</summary>
public byte[] random_bytes; public byte[] random_bytes;
/// <summary>Action relevant to the service message</summary> /// <summary>Action relevant to the service message</summary>
public DecryptedMessageAction action; 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> /// <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; public override long RandomId => random_id;
/// <summary>Random bytes, removed in Layer 17.</summary>
public override byte[] RandomBytes => random_bytes; public override byte[] RandomBytes => random_bytes;
/// <summary>Action relevant to the service message</summary> /// <summary>Action relevant to the service message</summary>
public override DecryptedMessageAction Action => action; public override DecryptedMessageAction Action => action;
@ -167,6 +175,7 @@ namespace TL
public int thumb_w; public int thumb_w;
/// <summary>Thumbnail height</summary> /// <summary>Thumbnail height</summary>
public int thumb_h; public int thumb_h;
/// <summary>File name, moved to <c>attributes</c> in Layer 45.</summary>
public string file_name; public string file_name;
/// <summary>File MIME-type</summary> /// <summary>File MIME-type</summary>
public string mime_type; public string mime_type;
@ -498,25 +507,38 @@ namespace TL
[TLDef(0x7C596B46)] [TLDef(0x7C596B46)]
public sealed partial class FileLocationUnavailable : FileLocationBase public sealed partial class FileLocationUnavailable : FileLocationBase
{ {
/// <summary>Volume ID</summary>
public long volume_id; public long volume_id;
/// <summary>Local ID</summary>
public int local_id; public int local_id;
/// <summary>Secret</summary>
public long secret; public long secret;
/// <summary>Volume ID</summary>
public override long VolumeId => volume_id; public override long VolumeId => volume_id;
/// <summary>Local ID</summary>
public override int LocalId => local_id; public override int LocalId => local_id;
/// <summary>Secret</summary>
public override long Secret => secret; public override long Secret => secret;
} }
/// <summary>File location. <para>See <a href="https://corefork.telegram.org/constructor/fileLocation"/></para></summary> /// <summary>File location. <para>See <a href="https://corefork.telegram.org/constructor/fileLocation"/></para></summary>
[TLDef(0x53D69076)] [TLDef(0x53D69076)]
public sealed partial class FileLocation : FileLocationBase public sealed partial class FileLocation : FileLocationBase
{ {
/// <summary>DC ID</summary>
public int dc_id; public int dc_id;
/// <summary>Volume ID</summary>
public long volume_id; public long volume_id;
/// <summary>Local ID</summary>
public int local_id; public int local_id;
/// <summary>Secret</summary>
public long secret; public long secret;
/// <summary>Volume ID</summary>
public override long VolumeId => volume_id; public override long VolumeId => volume_id;
/// <summary>Local ID</summary>
public override int LocalId => local_id; public override int LocalId => local_id;
/// <summary>Secret</summary>
public override long Secret => secret; public override long Secret => secret;
} }
} }
@ -775,7 +797,6 @@ namespace TL
{ {
/// <summary>Field <see cref="reply_to_random_id"/> has a value</summary> /// <summary>Field <see cref="reply_to_random_id"/> has a value</summary>
has_reply_to_random_id = 0x8, has_reply_to_random_id = 0x8,
/// <summary>Whether this is a silent message (no notification triggered)</summary>
silent = 0x20, silent = 0x20,
/// <summary>Field <see cref="entities"/> has a value</summary> /// <summary>Field <see cref="entities"/> has a value</summary>
has_entities = 0x80, has_entities = 0x80,

View file

@ -6,7 +6,7 @@ namespace TL
{ {
public static partial class Layer public static partial class Layer
{ {
public const int Version = 198; // fetched 22/01/2025 22:22:20 public const int Version = 220; // fetched 12/06/2025 13:11:05
internal const int SecretChats = 144; internal const int SecretChats = 144;
internal const int MTProto2 = 73; internal const int MTProto2 = 73;
internal const uint VectorCtor = 0x1CB5C415; internal const uint VectorCtor = 0x1CB5C415;
@ -41,7 +41,9 @@ namespace TL
[0x3BCBF734] = typeof(DhGenOk), [0x3BCBF734] = typeof(DhGenOk),
[0x46DC1FB9] = typeof(DhGenRetry), [0x46DC1FB9] = typeof(DhGenRetry),
[0xA69DAE02] = typeof(DhGenFail), [0xA69DAE02] = typeof(DhGenFail),
[0x7ABE77EC] = typeof(Methods.Ping), [0xF660E1D4] = typeof(DestroyAuthKeyOk),
[0x0A9F2259] = typeof(DestroyAuthKeyNone),
[0xEA109B13] = typeof(DestroyAuthKeyFail),
[0x62D6B459] = typeof(MsgsAck), [0x62D6B459] = typeof(MsgsAck),
[0xA7EFF811] = typeof(BadMsgNotification), [0xA7EFF811] = typeof(BadMsgNotification),
[0xEDAB447B] = typeof(BadServerSalt), [0xEDAB447B] = typeof(BadServerSalt),
@ -66,6 +68,7 @@ namespace TL
[0x37982646] = typeof(IpPortSecret), [0x37982646] = typeof(IpPortSecret),
[0x4679B65F] = typeof(AccessPointRule), [0x4679B65F] = typeof(AccessPointRule),
[0x5A592A6C] = typeof(Help_ConfigSimple), [0x5A592A6C] = typeof(Help_ConfigSimple),
[0x7ABE77EC] = typeof(Methods.Ping),
// from TL.SchemaExtensions: // from TL.SchemaExtensions:
[0x3FEDD339] = typeof(True), [0x3FEDD339] = typeof(True),
[0xC4B9F9BB] = typeof(Error), [0xC4B9F9BB] = typeof(Error),
@ -81,7 +84,7 @@ namespace TL
[0xF7C1B13F] = typeof(InputUserSelf), [0xF7C1B13F] = typeof(InputUserSelf),
[0xF21158C6] = typeof(InputUser), [0xF21158C6] = typeof(InputUser),
[0x1DA448E2] = typeof(InputUserFromMessage), [0x1DA448E2] = typeof(InputUserFromMessage),
[0xF392B7F4] = typeof(InputPhoneContact), [0x6A1DC4BE] = typeof(InputPhoneContact),
[0xF52FF27F] = typeof(InputFile), [0xF52FF27F] = typeof(InputFile),
[0xFA4F0BB5] = typeof(InputFileBig), [0xFA4F0BB5] = typeof(InputFileBig),
[0x62DC8B48] = typeof(InputFileStoryDocument), [0x62DC8B48] = typeof(InputFileStoryDocument),
@ -103,6 +106,7 @@ namespace TL
[0x89FDD778] = typeof(InputMediaStory), [0x89FDD778] = typeof(InputMediaStory),
[0xC21B8849] = typeof(InputMediaWebPage), [0xC21B8849] = typeof(InputMediaWebPage),
[0xC4103386] = typeof(InputMediaPaidMedia), [0xC4103386] = typeof(InputMediaPaidMedia),
[0x9FC55FDE] = typeof(InputMediaTodo),
[0x1CA48F57] = null,//InputChatPhotoEmpty [0x1CA48F57] = null,//InputChatPhotoEmpty
[0xBDCDAEC0] = typeof(InputChatUploadedPhoto), [0xBDCDAEC0] = typeof(InputChatUploadedPhoto),
[0x8953AD37] = typeof(InputChatPhoto), [0x8953AD37] = typeof(InputChatPhoto),
@ -124,7 +128,7 @@ namespace TL
[0x36C6019A] = typeof(PeerChat), [0x36C6019A] = typeof(PeerChat),
[0xA2A5371E] = typeof(PeerChannel), [0xA2A5371E] = typeof(PeerChannel),
[0xD3BC4B7A] = typeof(UserEmpty), [0xD3BC4B7A] = typeof(UserEmpty),
[0x4B46C37E] = typeof(User), [0x31774388] = typeof(User),
[0x4F11BAE1] = null,//UserProfilePhotoEmpty [0x4F11BAE1] = null,//UserProfilePhotoEmpty
[0x82D1F706] = typeof(UserProfilePhoto), [0x82D1F706] = typeof(UserProfilePhoto),
[0x09D05049] = null,//UserStatusEmpty [0x09D05049] = null,//UserStatusEmpty
@ -136,10 +140,10 @@ namespace TL
[0x29562865] = typeof(ChatEmpty), [0x29562865] = typeof(ChatEmpty),
[0x41CBF256] = typeof(Chat), [0x41CBF256] = typeof(Chat),
[0x6592A1A7] = typeof(ChatForbidden), [0x6592A1A7] = typeof(ChatForbidden),
[0xE00998B7] = typeof(Channel), [0x1C32B11C] = typeof(Channel),
[0x17D493D5] = typeof(ChannelForbidden), [0x17D493D5] = typeof(ChannelForbidden),
[0x2633421B] = typeof(ChatFull), [0x2633421B] = typeof(ChatFull),
[0x52D6806B] = typeof(ChannelFull), [0xE4E0B29D] = typeof(ChannelFull),
[0xC02D4007] = typeof(ChatParticipant), [0xC02D4007] = typeof(ChatParticipant),
[0xE46BCEE4] = typeof(ChatParticipantCreator), [0xE46BCEE4] = typeof(ChatParticipantCreator),
[0xA0933F5B] = typeof(ChatParticipantAdmin), [0xA0933F5B] = typeof(ChatParticipantAdmin),
@ -148,8 +152,8 @@ namespace TL
[0x37C1011C] = null,//ChatPhotoEmpty [0x37C1011C] = null,//ChatPhotoEmpty
[0x1C6E1C11] = typeof(ChatPhoto), [0x1C6E1C11] = typeof(ChatPhoto),
[0x90A6CA84] = typeof(MessageEmpty), [0x90A6CA84] = typeof(MessageEmpty),
[0x96FDBBE9] = typeof(Message), [0xB92F76CF] = typeof(Message),
[0xD3D28540] = typeof(MessageService), [0x7A800E0A] = typeof(MessageService),
[0x3DED6320] = null,//MessageMediaEmpty [0x3DED6320] = null,//MessageMediaEmpty
[0x695150D7] = typeof(MessageMediaPhoto), [0x695150D7] = typeof(MessageMediaPhoto),
[0x56E0D474] = typeof(MessageMediaGeo), [0x56E0D474] = typeof(MessageMediaGeo),
@ -167,6 +171,8 @@ namespace TL
[0xAA073BEB] = typeof(MessageMediaGiveaway), [0xAA073BEB] = typeof(MessageMediaGiveaway),
[0xCEAA3EA1] = typeof(MessageMediaGiveawayResults), [0xCEAA3EA1] = typeof(MessageMediaGiveawayResults),
[0xA8852491] = typeof(MessageMediaPaidMedia), [0xA8852491] = typeof(MessageMediaPaidMedia),
[0x8A53B014] = typeof(MessageMediaToDo),
[0xCA5CAB89] = typeof(MessageMediaVideoStream),
[0xB6AEF7B0] = null,//MessageActionEmpty [0xB6AEF7B0] = null,//MessageActionEmpty
[0xBD47CBAD] = typeof(MessageActionChatCreate), [0xBD47CBAD] = typeof(MessageActionChatCreate),
[0xB5A1CE5A] = typeof(MessageActionChatEditTitle), [0xB5A1CE5A] = typeof(MessageActionChatEditTitle),
@ -195,17 +201,17 @@ namespace TL
[0x502F92F7] = typeof(MessageActionInviteToGroupCall), [0x502F92F7] = typeof(MessageActionInviteToGroupCall),
[0x3C134D7B] = typeof(MessageActionSetMessagesTTL), [0x3C134D7B] = typeof(MessageActionSetMessagesTTL),
[0xB3A07661] = typeof(MessageActionGroupCallScheduled), [0xB3A07661] = typeof(MessageActionGroupCallScheduled),
[0xAA786345] = typeof(MessageActionSetChatTheme), [0xB91BBD3A] = typeof(MessageActionSetChatTheme),
[0xEBBCA3CB] = typeof(MessageActionChatJoinedByRequest), [0xEBBCA3CB] = typeof(MessageActionChatJoinedByRequest),
[0x47DD8079] = typeof(MessageActionWebViewDataSentMe), [0x47DD8079] = typeof(MessageActionWebViewDataSentMe),
[0xB4C38CB5] = typeof(MessageActionWebViewDataSent), [0xB4C38CB5] = typeof(MessageActionWebViewDataSent),
[0x6C6274FA] = typeof(MessageActionGiftPremium), [0x48E91302] = typeof(MessageActionGiftPremium),
[0x0D999256] = typeof(MessageActionTopicCreate), [0x0D999256] = typeof(MessageActionTopicCreate),
[0xC0944820] = typeof(MessageActionTopicEdit), [0xC0944820] = typeof(MessageActionTopicEdit),
[0x57DE635E] = typeof(MessageActionSuggestProfilePhoto), [0x57DE635E] = typeof(MessageActionSuggestProfilePhoto),
[0x31518E9B] = typeof(MessageActionRequestedPeer), [0x31518E9B] = typeof(MessageActionRequestedPeer),
[0x5060A3F4] = typeof(MessageActionSetChatWallPaper), [0x5060A3F4] = typeof(MessageActionSetChatWallPaper),
[0x56D03994] = typeof(MessageActionGiftCode), [0x31C48347] = typeof(MessageActionGiftCode),
[0xA80F51E4] = typeof(MessageActionGiveawayLaunch), [0xA80F51E4] = typeof(MessageActionGiveawayLaunch),
[0x87E2F155] = typeof(MessageActionGiveawayResults), [0x87E2F155] = typeof(MessageActionGiveawayResults),
[0xCC02AA6D] = typeof(MessageActionBoostApply), [0xCC02AA6D] = typeof(MessageActionBoostApply),
@ -213,8 +219,20 @@ namespace TL
[0x41B3E202] = typeof(MessageActionPaymentRefunded), [0x41B3E202] = typeof(MessageActionPaymentRefunded),
[0x45D5B021] = typeof(MessageActionGiftStars), [0x45D5B021] = typeof(MessageActionGiftStars),
[0xB00C47A2] = typeof(MessageActionPrizeStars), [0xB00C47A2] = typeof(MessageActionPrizeStars),
[0x4717E8A4] = typeof(MessageActionStarGift), [0xEA2C31D3] = typeof(MessageActionStarGift),
[0xACDFCB81] = typeof(MessageActionStarGiftUnique), [0x95728543] = typeof(MessageActionStarGiftUnique),
[0xAC1F1FCD] = typeof(MessageActionPaidMessagesRefunded),
[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), [0xD58A08C6] = typeof(Dialog),
[0x71BD134C] = typeof(DialogFolder), [0x71BD134C] = typeof(DialogFolder),
[0x2331B22D] = typeof(PhotoEmpty), [0x2331B22D] = typeof(PhotoEmpty),
@ -229,6 +247,7 @@ namespace TL
[0xB2A2F663] = typeof(GeoPoint), [0xB2A2F663] = typeof(GeoPoint),
[0x5E002502] = typeof(Auth_SentCode), [0x5E002502] = typeof(Auth_SentCode),
[0x2390FE44] = typeof(Auth_SentCodeSuccess), [0x2390FE44] = typeof(Auth_SentCodeSuccess),
[0xE0955A3C] = typeof(Auth_SentCodePaymentRequired),
[0x2EA2C0D4] = typeof(Auth_Authorization), [0x2EA2C0D4] = typeof(Auth_Authorization),
[0x44747E9A] = typeof(Auth_AuthorizationSignUpRequired), [0x44747E9A] = typeof(Auth_AuthorizationSignUpRequired),
[0xB434E2B8] = typeof(Auth_ExportedAuthorization), [0xB434E2B8] = typeof(Auth_ExportedAuthorization),
@ -239,10 +258,10 @@ namespace TL
[0x5C467992] = typeof(InputNotifyForumTopic), [0x5C467992] = typeof(InputNotifyForumTopic),
[0xCACB6AE2] = typeof(InputPeerNotifySettings), [0xCACB6AE2] = typeof(InputPeerNotifySettings),
[0x99622C0C] = typeof(PeerNotifySettings), [0x99622C0C] = typeof(PeerNotifySettings),
[0xACD66C5E] = typeof(PeerSettings), [0xF47741F7] = typeof(PeerSettings),
[0xA437C3ED] = typeof(WallPaper), [0xA437C3ED] = typeof(WallPaper),
[0xE0804116] = typeof(WallPaperNoFile), [0xE0804116] = typeof(WallPaperNoFile),
[0x4D975BBC] = typeof(UserFull), [0xA02BC13E] = typeof(UserFull),
[0x145ADE0B] = typeof(Contact), [0x145ADE0B] = typeof(Contact),
[0xC13E3C50] = typeof(ImportedContact), [0xC13E3C50] = typeof(ImportedContact),
[0x16D9703B] = typeof(ContactStatus), [0x16D9703B] = typeof(ContactStatus),
@ -254,8 +273,8 @@ namespace TL
[0x15BA6C40] = typeof(Messages_Dialogs), [0x15BA6C40] = typeof(Messages_Dialogs),
[0x71E094F3] = typeof(Messages_DialogsSlice), [0x71E094F3] = typeof(Messages_DialogsSlice),
[0xF0E3E596] = typeof(Messages_DialogsNotModified), [0xF0E3E596] = typeof(Messages_DialogsNotModified),
[0x8C718E87] = typeof(Messages_Messages), [0x1D73E7EA] = typeof(Messages_Messages),
[0x3A54685E] = typeof(Messages_MessagesSlice), [0x5F206716] = typeof(Messages_MessagesSlice),
[0xC776BA4E] = typeof(Messages_ChannelMessages), [0xC776BA4E] = typeof(Messages_ChannelMessages),
[0x74535F21] = typeof(Messages_MessagesNotModified), [0x74535F21] = typeof(Messages_MessagesNotModified),
[0x64FF9FD5] = typeof(Messages_Chats), [0x64FF9FD5] = typeof(Messages_Chats),
@ -282,7 +301,7 @@ namespace TL
[0x1F2B0AFD] = typeof(UpdateNewMessage), [0x1F2B0AFD] = typeof(UpdateNewMessage),
[0x4E90BFD6] = typeof(UpdateMessageID), [0x4E90BFD6] = typeof(UpdateMessageID),
[0xA20DB0E5] = typeof(UpdateDeleteMessages), [0xA20DB0E5] = typeof(UpdateDeleteMessages),
[0xC01E857F] = typeof(UpdateUserTyping), [0x2A17BF5C] = typeof(UpdateUserTyping),
[0x83487AF0] = typeof(UpdateChatUserTyping), [0x83487AF0] = typeof(UpdateChatUserTyping),
[0x07761198] = typeof(UpdateChatParticipants), [0x07761198] = typeof(UpdateChatParticipants),
[0xE5BDF8DE] = typeof(UpdateUserStatus), [0xE5BDF8DE] = typeof(UpdateUserStatus),
@ -299,7 +318,7 @@ namespace TL
[0xEBE46819] = typeof(UpdateServiceNotification), [0xEBE46819] = typeof(UpdateServiceNotification),
[0xEE3B272A] = typeof(UpdatePrivacy), [0xEE3B272A] = typeof(UpdatePrivacy),
[0x05492A13] = typeof(UpdateUserPhone), [0x05492A13] = typeof(UpdateUserPhone),
[0x9C974FDF] = typeof(UpdateReadHistoryInbox), [0x9E84BC99] = typeof(UpdateReadHistoryInbox),
[0x2F2F21BF] = typeof(UpdateReadHistoryOutbox), [0x2F2F21BF] = typeof(UpdateReadHistoryOutbox),
[0x7F891213] = typeof(UpdateWebPage), [0x7F891213] = typeof(UpdateWebPage),
[0xF8227181] = typeof(UpdateReadMessagesContents), [0xF8227181] = typeof(UpdateReadMessagesContents),
@ -321,7 +340,7 @@ namespace TL
[0xE40370A3] = typeof(UpdateEditMessage), [0xE40370A3] = typeof(UpdateEditMessage),
[0x691E9052] = typeof(UpdateInlineBotCallbackQuery), [0x691E9052] = typeof(UpdateInlineBotCallbackQuery),
[0xB75F99A9] = typeof(UpdateReadChannelOutbox), [0xB75F99A9] = typeof(UpdateReadChannelOutbox),
[0x1B49EC6D] = typeof(UpdateDraftMessage), [0xEDFC111E] = typeof(UpdateDraftMessage),
[0x571D2742] = typeof(UpdateReadFeaturedStickers), [0x571D2742] = typeof(UpdateReadFeaturedStickers),
[0x9A422C20] = typeof(UpdateRecentStickers), [0x9A422C20] = typeof(UpdateRecentStickers),
[0xA229DD06] = typeof(UpdateConfig), [0xA229DD06] = typeof(UpdateConfig),
@ -337,10 +356,10 @@ namespace TL
[0x46560264] = typeof(UpdateLangPackTooLong), [0x46560264] = typeof(UpdateLangPackTooLong),
[0x56022F4D] = typeof(UpdateLangPack), [0x56022F4D] = typeof(UpdateLangPack),
[0xE511996D] = typeof(UpdateFavedStickers), [0xE511996D] = typeof(UpdateFavedStickers),
[0xEA29055D] = typeof(UpdateChannelReadMessagesContents), [0x25F324F7] = typeof(UpdateChannelReadMessagesContents),
[0x7084A7BE] = typeof(UpdateContactsReset), [0x7084A7BE] = typeof(UpdateContactsReset),
[0xB23FC698] = typeof(UpdateChannelAvailableMessages), [0xB23FC698] = typeof(UpdateChannelAvailableMessages),
[0xE16459C3] = typeof(UpdateDialogUnreadMark), [0xB658F23E] = typeof(UpdateDialogUnreadMark),
[0xACA1657B] = typeof(UpdateMessagePoll), [0xACA1657B] = typeof(UpdateMessagePoll),
[0x54C01850] = typeof(UpdateChatDefaultBannedRights), [0x54C01850] = typeof(UpdateChatDefaultBannedRights),
[0x19360DC0] = typeof(UpdateFolderPeers), [0x19360DC0] = typeof(UpdateFolderPeers),
@ -365,7 +384,7 @@ namespace TL
[0x5BB98608] = typeof(UpdatePinnedChannelMessages), [0x5BB98608] = typeof(UpdatePinnedChannelMessages),
[0xF89A6A4E] = typeof(UpdateChat), [0xF89A6A4E] = typeof(UpdateChat),
[0xF2EBDB4E] = typeof(UpdateGroupCallParticipants), [0xF2EBDB4E] = typeof(UpdateGroupCallParticipants),
[0x97D64341] = typeof(UpdateGroupCall), [0x9D2216E0] = typeof(UpdateGroupCall),
[0xBB9BB9A5] = typeof(UpdatePeerHistoryTTL), [0xBB9BB9A5] = typeof(UpdatePeerHistoryTTL),
[0xD087663A] = typeof(UpdateChatParticipant), [0xD087663A] = typeof(UpdateChatParticipant),
[0x985D3ABB] = typeof(UpdateChannelParticipant), [0x985D3ABB] = typeof(UpdateChannelParticipant),
@ -374,7 +393,7 @@ namespace TL
[0x4D712F2E] = typeof(UpdateBotCommands), [0x4D712F2E] = typeof(UpdateBotCommands),
[0x7063C3DB] = typeof(UpdatePendingJoinRequests), [0x7063C3DB] = typeof(UpdatePendingJoinRequests),
[0x11DFA986] = typeof(UpdateBotChatInviteRequester), [0x11DFA986] = typeof(UpdateBotChatInviteRequester),
[0x5E1B3CB8] = typeof(UpdateMessageReactions), [0x1E297BFA] = typeof(UpdateMessageReactions),
[0x17B7A20B] = typeof(UpdateAttachMenuBots), [0x17B7A20B] = typeof(UpdateAttachMenuBots),
[0x1592B79D] = typeof(UpdateWebViewResultSent), [0x1592B79D] = typeof(UpdateWebViewResultSent),
[0x14B85813] = typeof(UpdateBotMenuButton), [0x14B85813] = typeof(UpdateBotMenuButton),
@ -386,8 +405,6 @@ namespace TL
[0x6F7863F4] = typeof(UpdateRecentReactions), [0x6F7863F4] = typeof(UpdateRecentReactions),
[0x86FCCF85] = typeof(UpdateMoveStickerSetToTop), [0x86FCCF85] = typeof(UpdateMoveStickerSetToTop),
[0xD5A41724] = typeof(UpdateMessageExtendedMedia), [0xD5A41724] = typeof(UpdateMessageExtendedMedia),
[0x192EFBE3] = typeof(UpdateChannelPinnedTopic),
[0xFE198602] = typeof(UpdateChannelPinnedTopics),
[0x20529438] = typeof(UpdateUser), [0x20529438] = typeof(UpdateUser),
[0xEC05B097] = typeof(UpdateAutoSaveSettings), [0xEC05B097] = typeof(UpdateAutoSaveSettings),
[0x75B3B798] = typeof(UpdateStory), [0x75B3B798] = typeof(UpdateStory),
@ -414,12 +431,23 @@ namespace TL
[0x07DF587C] = typeof(UpdateBotEditBusinessMessage), [0x07DF587C] = typeof(UpdateBotEditBusinessMessage),
[0xA02A982E] = typeof(UpdateBotDeleteBusinessMessage), [0xA02A982E] = typeof(UpdateBotDeleteBusinessMessage),
[0x1824E40B] = typeof(UpdateNewStoryReaction), [0x1824E40B] = typeof(UpdateNewStoryReaction),
[0xDFD961F5] = typeof(UpdateBroadcastRevenueTransactions),
[0x4E80A379] = typeof(UpdateStarsBalance), [0x4E80A379] = typeof(UpdateStarsBalance),
[0x1EA2FDA7] = typeof(UpdateBusinessBotCallbackQuery), [0x1EA2FDA7] = typeof(UpdateBusinessBotCallbackQuery),
[0xA584B019] = typeof(UpdateStarsRevenueStatus), [0xA584B019] = typeof(UpdateStarsRevenueStatus),
[0x283BD312] = typeof(UpdateBotPurchasedPaidMedia), [0x283BD312] = typeof(UpdateBotPurchasedPaidMedia),
[0x51CA7AEC] = typeof(UpdatePaidReactionPrivacy), [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), [0xA56C2A3E] = typeof(Updates_State),
[0x5D75A138] = typeof(Updates_DifferenceEmpty), [0x5D75A138] = typeof(Updates_DifferenceEmpty),
[0x00F49CA0] = typeof(Updates_Difference), [0x00F49CA0] = typeof(Updates_Difference),
@ -489,6 +517,7 @@ namespace TL
[0xB05AC6B1] = typeof(SendMessageChooseStickerAction), [0xB05AC6B1] = typeof(SendMessageChooseStickerAction),
[0x25972BCB] = typeof(SendMessageEmojiInteraction), [0x25972BCB] = typeof(SendMessageEmojiInteraction),
[0xB665902E] = typeof(SendMessageEmojiInteractionSeen), [0xB665902E] = typeof(SendMessageEmojiInteractionSeen),
[0x376D975C] = typeof(SendMessageTextDraftAction),
[0xB3134D9D] = typeof(Contacts_Found), [0xB3134D9D] = typeof(Contacts_Found),
[0x0D09E07B] = typeof(InputPrivacyValueAllowContacts), [0x0D09E07B] = typeof(InputPrivacyValueAllowContacts),
[0x184B35CE] = typeof(InputPrivacyValueAllowAll), [0x184B35CE] = typeof(InputPrivacyValueAllowAll),
@ -557,6 +586,7 @@ namespace TL
[0x29D0F5EE] = typeof(InputStickerSetEmojiDefaultStatuses), [0x29D0F5EE] = typeof(InputStickerSetEmojiDefaultStatuses),
[0x44C1F8E9] = typeof(InputStickerSetEmojiDefaultTopicIcons), [0x44C1F8E9] = typeof(InputStickerSetEmojiDefaultTopicIcons),
[0x49748553] = typeof(InputStickerSetEmojiChannelDefaultStatuses), [0x49748553] = typeof(InputStickerSetEmojiChannelDefaultStatuses),
[0x1CF671A0] = typeof(InputStickerSetTonGifts),
[0x2DD14EDC] = typeof(StickerSet), [0x2DD14EDC] = typeof(StickerSet),
[0x6E153F16] = typeof(Messages_StickerSet), [0x6E153F16] = typeof(Messages_StickerSet),
[0xD3F924EB] = null,//Messages_StickerSetNotModified [0xD3F924EB] = null,//Messages_StickerSetNotModified
@ -683,7 +713,7 @@ namespace TL
[0x70B772A8] = typeof(Contacts_TopPeers), [0x70B772A8] = typeof(Contacts_TopPeers),
[0xB52C939D] = typeof(Contacts_TopPeersDisabled), [0xB52C939D] = typeof(Contacts_TopPeersDisabled),
[0x1B0C841A] = typeof(DraftMessageEmpty), [0x1B0C841A] = typeof(DraftMessageEmpty),
[0x2D65321F] = typeof(DraftMessage), [0x96EAA5EB] = typeof(DraftMessage),
[0xC6DC0C66] = typeof(Messages_FeaturedStickersNotModified), [0xC6DC0C66] = typeof(Messages_FeaturedStickersNotModified),
[0xBE382906] = typeof(Messages_FeaturedStickers), [0xBE382906] = typeof(Messages_FeaturedStickers),
[0x0B17F890] = null,//Messages_RecentStickersNotModified [0x0B17F890] = null,//Messages_RecentStickersNotModified
@ -752,7 +782,7 @@ namespace TL
[0xE095C1A0] = typeof(PhoneCallDiscardReasonDisconnect), [0xE095C1A0] = typeof(PhoneCallDiscardReasonDisconnect),
[0x57ADC690] = typeof(PhoneCallDiscardReasonHangup), [0x57ADC690] = typeof(PhoneCallDiscardReasonHangup),
[0xFAF7E8C9] = typeof(PhoneCallDiscardReasonBusy), [0xFAF7E8C9] = typeof(PhoneCallDiscardReasonBusy),
[0xAFE2B839] = typeof(PhoneCallDiscardReasonAllowGroupCall), [0x9FBBF1F7] = typeof(PhoneCallDiscardReasonMigrateConferenceCall),
[0x7D748D04] = typeof(DataJSON), [0x7D748D04] = typeof(DataJSON),
[0xCB296BF8] = typeof(LabeledPrice), [0xCB296BF8] = typeof(LabeledPrice),
[0x049EE584] = typeof(Invoice), [0x049EE584] = typeof(Invoice),
@ -785,11 +815,11 @@ namespace TL
[0x32DA9E9C] = typeof(InputStickerSetItem), [0x32DA9E9C] = typeof(InputStickerSetItem),
[0x1E36FDED] = typeof(InputPhoneCall), [0x1E36FDED] = typeof(InputPhoneCall),
[0x5366C915] = typeof(PhoneCallEmpty), [0x5366C915] = typeof(PhoneCallEmpty),
[0xEED42858] = typeof(PhoneCallWaiting), [0xC5226F17] = typeof(PhoneCallWaiting),
[0x45361C63] = typeof(PhoneCallRequested), [0x14B0ED0C] = typeof(PhoneCallRequested),
[0x22FD7181] = typeof(PhoneCallAccepted), [0x3660C311] = typeof(PhoneCallAccepted),
[0x3BA5940C] = typeof(PhoneCall), [0x30535AF5] = typeof(PhoneCall),
[0xF9D25503] = typeof(PhoneCallDiscarded), [0x50CA4DE1] = typeof(PhoneCallDiscarded),
[0x9CC123C7] = typeof(PhoneConnection), [0x9CC123C7] = typeof(PhoneConnection),
[0x635FE375] = typeof(PhoneConnectionWebrtc), [0x635FE375] = typeof(PhoneConnectionWebrtc),
[0xFC878FC8] = typeof(PhoneCallProtocol), [0xFC878FC8] = typeof(PhoneCallProtocol),
@ -853,6 +883,7 @@ namespace TL
[0x46D840AB] = typeof(ChannelAdminLogEventActionChangeEmojiStickerSet), [0x46D840AB] = typeof(ChannelAdminLogEventActionChangeEmojiStickerSet),
[0x60A79C79] = typeof(ChannelAdminLogEventActionToggleSignatureProfiles), [0x60A79C79] = typeof(ChannelAdminLogEventActionToggleSignatureProfiles),
[0x64642DB3] = typeof(ChannelAdminLogEventActionParticipantSubExtend), [0x64642DB3] = typeof(ChannelAdminLogEventActionParticipantSubExtend),
[0xC517F77E] = typeof(ChannelAdminLogEventActionToggleAutotranslation),
[0x1FAD68CD] = typeof(ChannelAdminLogEvent), [0x1FAD68CD] = typeof(ChannelAdminLogEvent),
[0xED8AF74D] = typeof(Channels_AdminLogResults), [0xED8AF74D] = typeof(Channels_AdminLogResults),
[0xEA107AE4] = typeof(ChannelAdminLogEventsFilter), [0xEA107AE4] = typeof(ChannelAdminLogEventsFilter),
@ -990,6 +1021,8 @@ namespace TL
[0x2E94C3E7] = typeof(WebPageAttributeStory), [0x2E94C3E7] = typeof(WebPageAttributeStory),
[0x50CC03D3] = typeof(WebPageAttributeStickerSet), [0x50CC03D3] = typeof(WebPageAttributeStickerSet),
[0xCF6F6DB8] = typeof(WebPageAttributeUniqueStarGift), [0xCF6F6DB8] = typeof(WebPageAttributeUniqueStarGift),
[0x31CAD303] = typeof(WebPageAttributeStarGiftCollection),
[0x01C641C2] = typeof(WebPageAttributeStarGiftAuction),
[0x4899484E] = typeof(Messages_VotesList), [0x4899484E] = typeof(Messages_VotesList),
[0xF568028A] = typeof(BankCardOpenUrl), [0xF568028A] = typeof(BankCardOpenUrl),
[0x3E24E573] = typeof(Payments_BankCardData), [0x3E24E573] = typeof(Payments_BankCardData),
@ -1005,7 +1038,7 @@ namespace TL
[0x8EA464B6] = typeof(StatsGraph), [0x8EA464B6] = typeof(StatsGraph),
[0x396CA5FC] = typeof(Stats_BroadcastStats), [0x396CA5FC] = typeof(Stats_BroadcastStats),
[0x98F6AC75] = typeof(Help_PromoDataEmpty), [0x98F6AC75] = typeof(Help_PromoDataEmpty),
[0x8C39793F] = typeof(Help_PromoData), [0x08A4D87A] = typeof(Help_PromoData),
[0xDE33B094] = typeof(VideoSize), [0xDE33B094] = typeof(VideoSize),
[0xF85C413C] = typeof(VideoSizeEmojiMarkup), [0xF85C413C] = typeof(VideoSizeEmojiMarkup),
[0x0DA082FE] = typeof(VideoSizeStickerMarkup), [0x0DA082FE] = typeof(VideoSizeStickerMarkup),
@ -1013,7 +1046,7 @@ namespace TL
[0xD7584C87] = typeof(StatsGroupTopAdmin), [0xD7584C87] = typeof(StatsGroupTopAdmin),
[0x535F779D] = typeof(StatsGroupTopInviter), [0x535F779D] = typeof(StatsGroupTopInviter),
[0xEF7FF916] = typeof(Stats_MegagroupStats), [0xEF7FF916] = typeof(Stats_MegagroupStats),
[0x734C4CCB] = typeof(GlobalPrivacySettings), [0xFE41B34F] = typeof(GlobalPrivacySettings),
[0x4203C5EF] = typeof(Help_CountryCode), [0x4203C5EF] = typeof(Help_CountryCode),
[0xC3878E23] = typeof(Help_Country), [0xC3878E23] = typeof(Help_Country),
[0x93CC1F32] = null,//Help_CountriesListNotModified [0x93CC1F32] = null,//Help_CountriesListNotModified
@ -1021,15 +1054,17 @@ namespace TL
[0x455B853D] = typeof(MessageViews), [0x455B853D] = typeof(MessageViews),
[0xB6C4F543] = typeof(Messages_MessageViews), [0xB6C4F543] = typeof(Messages_MessageViews),
[0xA6341782] = typeof(Messages_DiscussionMessage), [0xA6341782] = typeof(Messages_DiscussionMessage),
[0xAFBC09DB] = typeof(MessageReplyHeader), [0x6917560B] = typeof(MessageReplyHeader),
[0x0E5AF939] = typeof(MessageReplyStoryHeader), [0x0E5AF939] = typeof(MessageReplyStoryHeader),
[0x83D60FC2] = typeof(MessageReplies), [0x83D60FC2] = typeof(MessageReplies),
[0xE8FD8014] = typeof(PeerBlocked), [0xE8FD8014] = typeof(PeerBlocked),
[0x7FE91C14] = typeof(Stats_MessageStats), [0x7FE91C14] = typeof(Stats_MessageStats),
[0x7780BCB4] = typeof(GroupCallDiscarded), [0x7780BCB4] = typeof(GroupCallDiscarded),
[0xCDF8D3E3] = typeof(GroupCall), [0xEFB2B617] = typeof(GroupCall),
[0xD8AA840F] = typeof(InputGroupCall), [0xD8AA840F] = typeof(InputGroupCall),
[0xEBA636FE] = typeof(GroupCallParticipant), [0xFE06823F] = typeof(InputGroupCallSlug),
[0x8C10603F] = typeof(InputGroupCallInviteMessage),
[0x2A3DC7AC] = typeof(GroupCallParticipant),
[0x9E727AAD] = typeof(Phone_GroupCall), [0x9E727AAD] = typeof(Phone_GroupCall),
[0xF47751B6] = typeof(Phone_GroupParticipants), [0xF47751B6] = typeof(Phone_GroupParticipants),
[0x1662AF0B] = typeof(Messages_HistoryImport), [0x1662AF0B] = typeof(Messages_HistoryImport),
@ -1058,8 +1093,12 @@ namespace TL
[0xE3779861] = typeof(Account_ResetPasswordFailedWait), [0xE3779861] = typeof(Account_ResetPasswordFailedWait),
[0xE9EFFC7D] = typeof(Account_ResetPasswordRequestedWait), [0xE9EFFC7D] = typeof(Account_ResetPasswordRequestedWait),
[0xE926D63E] = typeof(Account_ResetPasswordOk), [0xE926D63E] = typeof(Account_ResetPasswordOk),
[0x4D93A990] = typeof(SponsoredMessage), [0xC3DFFC04] = typeof(ChatTheme),
[0xC9EE1D87] = typeof(Messages_SponsoredMessages), [0x3458F9C8] = typeof(ChatThemeUniqueGift),
[0xE011E1C4] = null,//Account_ChatThemesNotModified
[0xBE098173] = typeof(Account_ChatThemes),
[0x7DBF8673] = typeof(SponsoredMessage),
[0xFFDA656D] = typeof(Messages_SponsoredMessages),
[0x1839490F] = null,//Messages_SponsoredMessagesEmpty [0x1839490F] = null,//Messages_SponsoredMessagesEmpty
[0xC9B0539F] = typeof(SearchResultsCalendarPeriod), [0xC9B0539F] = typeof(SearchResultsCalendarPeriod),
[0x147EE23C] = typeof(Messages_SearchResultsCalendar), [0x147EE23C] = typeof(Messages_SearchResultsCalendar),
@ -1106,6 +1145,13 @@ namespace TL
[0xE8625E92] = typeof(InputInvoiceStarGift), [0xE8625E92] = typeof(InputInvoiceStarGift),
[0x4D818D5D] = typeof(InputInvoiceStarGiftUpgrade), [0x4D818D5D] = typeof(InputInvoiceStarGiftUpgrade),
[0x4A5F5BD9] = typeof(InputInvoiceStarGiftTransfer), [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), [0xAED0CBD9] = typeof(Payments_ExportedInvoice),
[0xCFB9D957] = typeof(Messages_TranscribedAudio), [0xCFB9D957] = typeof(Messages_TranscribedAudio),
[0x5334759C] = typeof(Help_PremiumPromo), [0x5334759C] = typeof(Help_PremiumPromo),
@ -1113,10 +1159,10 @@ namespace TL
[0x616F7FE8] = typeof(InputStorePaymentGiftPremium), [0x616F7FE8] = typeof(InputStorePaymentGiftPremium),
[0xFB790393] = typeof(InputStorePaymentPremiumGiftCode), [0xFB790393] = typeof(InputStorePaymentPremiumGiftCode),
[0x160544CA] = typeof(InputStorePaymentPremiumGiveaway), [0x160544CA] = typeof(InputStorePaymentPremiumGiveaway),
[0xDDDD0F56] = typeof(InputStorePaymentStarsTopup), [0xF9A2A6CB] = typeof(InputStorePaymentStarsTopup),
[0x1D741EF7] = typeof(InputStorePaymentStarsGift), [0x1D741EF7] = typeof(InputStorePaymentStarsGift),
[0x751F08FA] = typeof(InputStorePaymentStarsGiveaway), [0x751F08FA] = typeof(InputStorePaymentStarsGiveaway),
[0x74C34319] = typeof(PremiumGiftOption), [0x9BB2636D] = typeof(InputStorePaymentAuthCode),
[0x88F8F21B] = typeof(PaymentFormMethod), [0x88F8F21B] = typeof(PaymentFormMethod),
[0x2DE11AAE] = null,//EmojiStatusEmpty [0x2DE11AAE] = null,//EmojiStatusEmpty
[0xE7FF068A] = typeof(EmojiStatus), [0xE7FF068A] = typeof(EmojiStatus),
@ -1148,7 +1194,7 @@ namespace TL
[0xFCFEB29C] = typeof(StickerKeyword), [0xFCFEB29C] = typeof(StickerKeyword),
[0xB4073647] = typeof(Username), [0xB4073647] = typeof(Username),
[0x023F109B] = typeof(ForumTopicDeleted), [0x023F109B] = typeof(ForumTopicDeleted),
[0x71701DA9] = typeof(ForumTopic), [0xCDFF0ECA] = typeof(ForumTopic),
[0x367617D3] = typeof(Messages_ForumTopics), [0x367617D3] = typeof(Messages_ForumTopics),
[0x43B46B20] = typeof(DefaultHistoryTTL), [0x43B46B20] = typeof(DefaultHistoryTTL),
[0x41BF109B] = typeof(ExportedContactToken), [0x41BF109B] = typeof(ExportedContactToken),
@ -1190,7 +1236,7 @@ namespace TL
[0x8D595CD6] = typeof(StoryViews), [0x8D595CD6] = typeof(StoryViews),
[0x51E6EE4F] = typeof(StoryItemDeleted), [0x51E6EE4F] = typeof(StoryItemDeleted),
[0xFFADC913] = typeof(StoryItemSkipped), [0xFFADC913] = typeof(StoryItemSkipped),
[0x79B26A24] = typeof(StoryItem), [0xEDF164F1] = typeof(StoryItem),
[0x1158FE3E] = typeof(Stories_AllStoriesNotModified), [0x1158FE3E] = typeof(Stories_AllStoriesNotModified),
[0x6EFC5E81] = typeof(Stories_AllStories), [0x6EFC5E81] = typeof(Stories_AllStories),
[0x63C3DD0A] = typeof(Stories_Stories), [0x63C3DD0A] = typeof(Stories_Stories),
@ -1199,8 +1245,9 @@ namespace TL
[0xBD74CF49] = typeof(StoryViewPublicRepost), [0xBD74CF49] = typeof(StoryViewPublicRepost),
[0x59D78FC5] = typeof(Stories_StoryViewsList), [0x59D78FC5] = typeof(Stories_StoryViewsList),
[0xDE9EED1D] = typeof(Stories_StoryViews), [0xDE9EED1D] = typeof(Stories_StoryViews),
[0x22C0F6D5] = typeof(InputReplyToMessage), [0x869FBE10] = typeof(InputReplyToMessage),
[0x5881323A] = typeof(InputReplyToStory), [0x5881323A] = typeof(InputReplyToStory),
[0x69D66C45] = typeof(InputReplyToMonoForum),
[0x3FC9053B] = typeof(ExportedStoryLink), [0x3FC9053B] = typeof(ExportedStoryLink),
[0x712E27FD] = typeof(StoriesStealthMode), [0x712E27FD] = typeof(StoriesStealthMode),
[0xCFC9E002] = typeof(MediaAreaCoordinates), [0xCFC9E002] = typeof(MediaAreaCoordinates),
@ -1217,7 +1264,7 @@ namespace TL
[0xCAE68768] = typeof(Stories_PeerStories), [0xCAE68768] = typeof(Stories_PeerStories),
[0xFD5E12BD] = typeof(Messages_WebPage), [0xFD5E12BD] = typeof(Messages_WebPage),
[0x257E962B] = typeof(PremiumGiftCodeOption), [0x257E962B] = typeof(PremiumGiftCodeOption),
[0x284A1096] = typeof(Payments_CheckedGiftCode), [0xEB983F8F] = typeof(Payments_CheckedGiftCode),
[0x4367DAA0] = typeof(Payments_GiveawayInfo), [0x4367DAA0] = typeof(Payments_GiveawayInfo),
[0xE175E66F] = typeof(Payments_GiveawayInfoResults), [0xE175E66F] = typeof(Payments_GiveawayInfoResults),
[0xB2539D54] = typeof(PrepaidGiveaway), [0xB2539D54] = typeof(PrepaidGiveaway),
@ -1235,6 +1282,8 @@ namespace TL
[0xEDF3ADD0] = typeof(PublicForwardStory), [0xEDF3ADD0] = typeof(PublicForwardStory),
[0x93037E20] = typeof(Stats_PublicForwards), [0x93037E20] = typeof(Stats_PublicForwards),
[0xB54B5ACF] = typeof(PeerColor), [0xB54B5ACF] = typeof(PeerColor),
[0xB9C0639A] = typeof(PeerColorCollectible),
[0xB8EA86A9] = typeof(InputPeerColorCollectible),
[0x26219A58] = typeof(Help_PeerColorSet), [0x26219A58] = typeof(Help_PeerColorSet),
[0x767D61EB] = typeof(Help_PeerColorProfileSet), [0x767D61EB] = typeof(Help_PeerColorProfileSet),
[0xADEC6EBE] = typeof(Help_PeerColorOption), [0xADEC6EBE] = typeof(Help_PeerColorOption),
@ -1245,6 +1294,7 @@ namespace TL
[0xCFCD0F13] = typeof(StoryReactionPublicRepost), [0xCFCD0F13] = typeof(StoryReactionPublicRepost),
[0xAA5F789C] = typeof(Stories_StoryReactionsList), [0xAA5F789C] = typeof(Stories_StoryReactionsList),
[0xBD87CB6C] = typeof(SavedDialog), [0xBD87CB6C] = typeof(SavedDialog),
[0x64407EA7] = typeof(MonoForumDialog),
[0xF83AE221] = typeof(Messages_SavedDialogs), [0xF83AE221] = typeof(Messages_SavedDialogs),
[0x44BA9DD9] = typeof(Messages_SavedDialogsSlice), [0x44BA9DD9] = typeof(Messages_SavedDialogsSlice),
[0xC01F6FE8] = typeof(Messages_SavedDialogsNotModified), [0xC01F6FE8] = typeof(Messages_SavedDialogsNotModified),
@ -1275,11 +1325,11 @@ namespace TL
[0x01190CF1] = typeof(InputQuickReplyShortcutId), [0x01190CF1] = typeof(InputQuickReplyShortcutId),
[0xC68D6695] = typeof(Messages_QuickReplies), [0xC68D6695] = typeof(Messages_QuickReplies),
[0x5F91EB5B] = null,//Messages_QuickRepliesNotModified [0x5F91EB5B] = null,//Messages_QuickRepliesNotModified
[0xBD068601] = typeof(ConnectedBot), [0xCD64636C] = typeof(ConnectedBot),
[0x17D7F87B] = typeof(Account_ConnectedBots), [0x17D7F87B] = typeof(Account_ConnectedBots),
[0x2AD93719] = typeof(Messages_DialogFilters), [0x2AD93719] = typeof(Messages_DialogFilters),
[0x6C8E1E06] = typeof(Birthday), [0x6C8E1E06] = typeof(Birthday),
[0x896433B4] = typeof(BotBusinessConnection), [0x8F34B2F5] = typeof(BotBusinessConnection),
[0x09C469CD] = typeof(InputBusinessIntro), [0x09C469CD] = typeof(InputBusinessIntro),
[0x5A0A066D] = typeof(BusinessIntro), [0x5A0A066D] = typeof(BusinessIntro),
[0xFAFF629D] = typeof(Messages_MyStickers), [0xFAFF629D] = typeof(Messages_MyStickers),
@ -1303,14 +1353,7 @@ namespace TL
[0x846F9E42] = typeof(Channels_SponsoredMessageReportResultChooseOption), [0x846F9E42] = typeof(Channels_SponsoredMessageReportResultChooseOption),
[0x3E3BCF2F] = typeof(Channels_SponsoredMessageReportResultAdsHidden), [0x3E3BCF2F] = typeof(Channels_SponsoredMessageReportResultAdsHidden),
[0xAD798849] = typeof(Channels_SponsoredMessageReportResultReported), [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), [0x56E34970] = typeof(ReactionsNotifySettings),
[0xC3FF71E7] = typeof(BroadcastRevenueBalances),
[0x93C3E27E] = typeof(AvailableEffect), [0x93C3E27E] = typeof(AvailableEffect),
[0xD1ED9A5B] = null,//Messages_AvailableEffectsNotModified [0xD1ED9A5B] = null,//Messages_AvailableEffectsNotModified
[0xBDDB616E] = typeof(Messages_AvailableEffects), [0xBDDB616E] = typeof(Messages_AvailableEffects),
@ -1324,13 +1367,13 @@ namespace TL
[0x60682812] = typeof(StarsTransactionPeerAds), [0x60682812] = typeof(StarsTransactionPeerAds),
[0xF9677AAD] = typeof(StarsTransactionPeerAPI), [0xF9677AAD] = typeof(StarsTransactionPeerAPI),
[0x0BD915C0] = typeof(StarsTopupOption), [0x0BD915C0] = typeof(StarsTopupOption),
[0x64DFC926] = typeof(StarsTransaction), [0x13659EB0] = typeof(StarsTransaction),
[0x6C9CE8ED] = typeof(Payments_StarsStatus), [0x6C9CE8ED] = typeof(Payments_StarsStatus),
[0xE87ACBC0] = typeof(FoundStory), [0xE87ACBC0] = typeof(FoundStory),
[0xE2DE7737] = typeof(Stories_FoundStories), [0xE2DE7737] = typeof(Stories_FoundStories),
[0xDE4C5D93] = typeof(GeoPointAddress), [0xDE4C5D93] = typeof(GeoPointAddress),
[0xFEBE5491] = typeof(StarsRevenueStatus), [0xFEBE5491] = typeof(StarsRevenueStatus),
[0xC92BB73B] = typeof(Payments_StarsRevenueStats), [0x6C207376] = typeof(Payments_StarsRevenueStats),
[0x1DAB80B7] = typeof(Payments_StarsRevenueWithdrawalUrl), [0x1DAB80B7] = typeof(Payments_StarsRevenueWithdrawalUrl),
[0x394E7F21] = typeof(Payments_StarsRevenueAdsAccountUrl), [0x394E7F21] = typeof(Payments_StarsRevenueAdsAccountUrl),
[0x206AE6D1] = typeof(InputStarsTransaction), [0x206AE6D1] = typeof(InputStarsTransaction),
@ -1343,10 +1386,10 @@ namespace TL
[0x4BA3A95A] = typeof(MessageReactor), [0x4BA3A95A] = typeof(MessageReactor),
[0x94CE852A] = typeof(StarsGiveawayOption), [0x94CE852A] = typeof(StarsGiveawayOption),
[0x54236209] = typeof(StarsGiveawayWinnersOption), [0x54236209] = typeof(StarsGiveawayWinnersOption),
[0x02CC73C8] = typeof(StarGift), [0x313A9547] = typeof(StarGift),
[0xF2FE7E4A] = typeof(StarGiftUnique), [0x569D64C9] = typeof(StarGiftUnique),
[0xA388A368] = null,//Payments_StarGiftsNotModified [0xA388A368] = null,//Payments_StarGiftsNotModified
[0x901689EA] = typeof(Payments_StarGifts), [0x2ED82995] = typeof(Payments_StarGifts),
[0x7903E3D9] = typeof(MessageReportOption), [0x7903E3D9] = typeof(MessageReportOption),
[0xF0E4E0B6] = typeof(ReportResultChooseOption), [0xF0E4E0B6] = typeof(ReportResultChooseOption),
[0x6F09AC31] = typeof(ReportResultAddComment), [0x6F09AC31] = typeof(ReportResultAddComment),
@ -1359,24 +1402,96 @@ namespace TL
[0x98D5EA1D] = typeof(Payments_ConnectedStarRefBots), [0x98D5EA1D] = typeof(Payments_ConnectedStarRefBots),
[0xB4D5D859] = typeof(Payments_SuggestedStarRefBots), [0xB4D5D859] = typeof(Payments_SuggestedStarRefBots),
[0xBBB6B4A3] = typeof(StarsAmount), [0xBBB6B4A3] = typeof(StarsAmount),
[0x74AEE3E0] = typeof(StarsTonAmount),
[0x6010C534] = typeof(Messages_FoundStickersNotModified), [0x6010C534] = typeof(Messages_FoundStickersNotModified),
[0x82C9E290] = typeof(Messages_FoundStickers), [0x82C9E290] = typeof(Messages_FoundStickers),
[0xB0CD6617] = typeof(BotVerifierSettings), [0xB0CD6617] = typeof(BotVerifierSettings),
[0xF93CD45C] = typeof(BotVerification), [0xF93CD45C] = typeof(BotVerification),
[0x39D99013] = typeof(StarGiftAttributeModel), [0x39D99013] = typeof(StarGiftAttributeModel),
[0x13ACFF19] = typeof(StarGiftAttributePattern), [0x13ACFF19] = typeof(StarGiftAttributePattern),
[0x94271762] = typeof(StarGiftAttributeBackdrop), [0xD93D859C] = typeof(StarGiftAttributeBackdrop),
[0xE0BFF26C] = typeof(StarGiftAttributeOriginalDetails), [0xE0BFF26C] = typeof(StarGiftAttributeOriginalDetails),
[0x167BD90B] = typeof(Payments_StarGiftUpgradePreview), [0x3DE1DFED] = typeof(Payments_StarGiftUpgradePreview),
[0x62D706B8] = typeof(Users_Users), [0x62D706B8] = typeof(Users_Users),
[0x315A4974] = typeof(Users_UsersSlice), [0x315A4974] = typeof(Users_UsersSlice),
[0xCAA2F60B] = typeof(Payments_UniqueStarGift), [0x416C56E8] = typeof(Payments_UniqueStarGift),
[0xB53E8B21] = typeof(Messages_WebPagePreview), [0x8C9A88AC] = typeof(Messages_WebPagePreview),
[0x6056DBA5] = typeof(SavedStarGift), [0xEAD6805E] = typeof(SavedStarGift),
[0x95F389B1] = typeof(Payments_SavedStarGifts), [0x95F389B1] = typeof(Payments_SavedStarGifts),
[0x69279795] = typeof(InputSavedStarGiftUser), [0x69279795] = typeof(InputSavedStarGiftUser),
[0xF101AA7F] = typeof(InputSavedStarGiftChat), [0xF101AA7F] = typeof(InputSavedStarGiftChat),
[0x2085C238] = typeof(InputSavedStarGiftSlug),
[0x84AA3A9C] = typeof(Payments_StarGiftWithdrawalUrl), [0x84AA3A9C] = typeof(Payments_StarGiftWithdrawalUrl),
[0x206AD49E] = null,//PaidReactionPrivacyDefault
[0x1F0C1AD9] = typeof(PaidReactionPrivacyAnonymous),
[0xDC6CFCF0] = typeof(PaidReactionPrivacyPeer),
[0x1E109708] = typeof(Account_PaidMessagesRevenue),
[0x050A9839] = null,//RequirementToContactEmpty
[0xE581E4E9] = typeof(RequirementToContactPremium),
[0xB4F67E93] = typeof(RequirementToContactPaidMessages),
[0xA0624CF7] = typeof(BusinessBotRights),
[0x71F276C4] = typeof(DisallowedGiftsSettings),
[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: // from TL.Secret:
[0x6ABD9782] = typeof(Layer143.DecryptedMessageMediaDocument), [0x6ABD9782] = typeof(Layer143.DecryptedMessageMediaDocument),
[0x020DF5D0] = typeof(Layer101.MessageEntityBlockquote), [0x020DF5D0] = typeof(Layer101.MessageEntityBlockquote),
@ -1496,6 +1611,7 @@ namespace TL
[typeof(ChatReactions)] = 0xEAFC32BC, //chatReactionsNone [typeof(ChatReactions)] = 0xEAFC32BC, //chatReactionsNone
[typeof(Messages_Reactions)] = 0xB06FDBDF, //messages.reactionsNotModified [typeof(Messages_Reactions)] = 0xB06FDBDF, //messages.reactionsNotModified
// from TL.Secret: // from TL.Secret:
[typeof(Account_ChatThemes)] = 0xE011E1C4, //account.chatThemesNotModified
[typeof(EmojiStatusBase)] = 0x2DE11AAE, //emojiStatusEmpty [typeof(EmojiStatusBase)] = 0x2DE11AAE, //emojiStatusEmpty
[typeof(EmojiList)] = 0x481EADFA, //emojiListNotModified [typeof(EmojiList)] = 0x481EADFA, //emojiListNotModified
[typeof(Messages_EmojiGroups)] = 0x6FB4AD87, //messages.emojiGroupsNotModified [typeof(Messages_EmojiGroups)] = 0x6FB4AD87, //messages.emojiGroupsNotModified
@ -1507,6 +1623,15 @@ namespace TL
[typeof(Messages_QuickReplies)] = 0x5F91EB5B, //messages.quickRepliesNotModified [typeof(Messages_QuickReplies)] = 0x5F91EB5B, //messages.quickRepliesNotModified
[typeof(Messages_AvailableEffects)] = 0xD1ED9A5B, //messages.availableEffectsNotModified [typeof(Messages_AvailableEffects)] = 0xD1ED9A5B, //messages.availableEffectsNotModified
[typeof(Payments_StarGifts)] = 0xA388A368, //payments.starGiftsNotModified [typeof(Payments_StarGifts)] = 0xA388A368, //payments.starGiftsNotModified
[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 [typeof(DecryptedMessageMedia)] = 0x089F5C4A, //decryptedMessageMediaEmpty
}; };
} }

View file

@ -148,7 +148,6 @@ namespace TL
{ {
public abstract long ID { get; } public abstract long ID { get; }
protected internal abstract IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats); 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 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> /// <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 UserStatus { internal abstract TimeSpan LastSeenAgo { get; } }
partial class UserStatusOnline { internal override TimeSpan LastSeenAgo => TimeSpan.Zero; } 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> /// <remarks>covers anything between 1 second and 2-3 days</remarks>
partial class UserStatusRecently { internal override TimeSpan LastSeenAgo => TimeSpan.FromDays(1); } partial class UserStatusRecently { internal override TimeSpan LastSeenAgo => TimeSpan.FromDays(1); }
/// <remarks>between 2-3 and seven days</remarks> /// <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 }; protected override InputPhoto ToInputPhoto() => new() { id = id, access_hash = access_hash, file_reference = file_reference };
public InputPhotoFileLocation ToFileLocation() => ToFileLocation(LargestPhotoSize); 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 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 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 partial class PhotoSizeBase
@ -525,7 +525,7 @@ namespace TL
partial class Document partial class Document
{ {
public override long ID => id; public override long ID => id;
public override string ToString() => Filename is string filename ? base.ToString() + ": " + filename : base.ToString(); public override string ToString() => $"{Filename ?? $"Document {mime_type}"} {size:N0} bytes";
public string Filename => GetAttribute<DocumentAttributeFilename>()?.file_name; public string Filename => GetAttribute<DocumentAttributeFilename>()?.file_name;
protected override InputDocument ToInputDocument() => new() { id = id, access_hash = access_hash, file_reference = file_reference }; protected override InputDocument ToInputDocument() => new() { id = id, access_hash = access_hash, file_reference = file_reference };
public InputDocumentFileLocation ToFileLocation(PhotoSizeBase thumbSize = null) => new() { id = id, access_hash = access_hash, file_reference = file_reference, thumb_size = thumbSize?.Type }; public InputDocumentFileLocation ToFileLocation(PhotoSizeBase thumbSize = null) => new() { id = id, access_hash = access_hash, file_reference = file_reference, thumb_size = thumbSize?.Type };
@ -689,8 +689,8 @@ namespace TL
{ {
System.Text.Json.JsonValueKind.True or System.Text.Json.JsonValueKind.True or
System.Text.Json.JsonValueKind.False => new JsonBool { value = elem.GetBoolean() }, 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.Object => new JsonObject { value = [.. elem.EnumerateObject().Select(FromJsonProperty)] },
System.Text.Json.JsonValueKind.Array => new JsonArray { value = elem.EnumerateArray().Select(FromJsonElement).ToArray() }, 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.String => new JsonString { value = elem.GetString() },
System.Text.Json.JsonValueKind.Number => new JsonNumber { value = elem.GetDouble() }, System.Text.Json.JsonValueKind.Number => new JsonNumber { value = elem.GetDouble() },
_ => new JsonNull(), _ => new JsonNull(),
@ -709,7 +709,7 @@ namespace TL
sb.Append(i == 0 ? "" : ",").Append(value[i]); sb.Append(i == 0 ? "" : ",").Append(value[i]);
return sb.Append(']').ToString(); 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() public override object ToNative()
{ {
if (value.Length == 0) return Array.Empty<object>(); if (value.Length == 0) return Array.Empty<object>();
@ -761,6 +761,13 @@ namespace TL
partial class MessageReplyHeader { public int TopicID => flags.HasFlag(Flags.forum_topic) ? flags.HasFlag(Flags.has_reply_to_top_id) ? reply_to_top_id : reply_to_msg_id : 0; } partial class MessageReplyHeader { public int TopicID => flags.HasFlag(Flags.forum_topic) ? flags.HasFlag(Flags.has_reply_to_top_id) ? reply_to_top_id : reply_to_msg_id : 0; }
partial class GroupCallBase { public static implicit operator InputGroupCall(GroupCallBase call) => new() { id = call.ID, access_hash = call.AccessHash }; } partial class GroupCallBase { public static implicit operator InputGroupCall(GroupCallBase call) => new() { id = call.ID, access_hash = call.AccessHash }; }
partial class EmojiStatusBase { public virtual long DocumentId => 0; }
partial class EmojiStatus { public override long DocumentId => document_id; }
partial class EmojiStatusCollectible{ public override long DocumentId => document_id; }
partial class ForumTopicBase { public virtual string Title => null; }
partial class ForumTopic { public override string Title => title; }
partial class RequestedPeer { public abstract long ID { get; } } partial class RequestedPeer { public abstract long ID { get; } }
partial class RequestedPeerUser { public override long ID => user_id; } partial class RequestedPeerUser { public override long ID => user_id; }
partial class RequestedPeerChat { public override long ID => chat_id; } partial class RequestedPeerChat { public override long ID => chat_id; }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@ -16,7 +17,7 @@ namespace TL
#else #else
public interface IObject { } public interface IObject { }
#endif #endif
public interface IMethod<ReturnType> : IObject { } public interface IMethod<out ReturnType> : IObject { }
public interface IPeerResolver { IPeerInfo UserOrChat(Peer peer); } public interface IPeerResolver { IPeerInfo UserOrChat(Peer peer); }
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
@ -48,6 +49,15 @@ namespace TL
public static class Serialization 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 public static void WriteTLObject<T>(this BinaryWriter writer, T obj) where T : IObject
{ {
if (obj == null) { writer.WriteTLNull(typeof(T)); return; } if (obj == null) { writer.WriteTLNull(typeof(T)); return; }
@ -105,6 +115,17 @@ namespace TL
#endif #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) internal static void WriteTLValue(this BinaryWriter writer, object value, Type valueType)
{ {
if (value == null) if (value == null)
@ -197,10 +218,10 @@ namespace TL
foreach (var msg in messages) foreach (var msg in messages)
{ {
writer.Write(msg.msg_id); writer.Write(msg.msg_id);
writer.Write(msg.seq_no); writer.Write(msg.seqno);
var patchPos = writer.BaseStream.Position; var patchPos = writer.BaseStream.Position;
writer.Write(0); // patched below writer.Write(0); // patched below
if ((msg.seq_no & 1) != 0) if ((msg.seqno & 1) != 0)
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38} #{(short)msg.msg_id.GetHashCode():X4}"); WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38} #{(short)msg.msg_id.GetHashCode():X4}");
else else
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38}"); WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38}");
@ -222,6 +243,21 @@ namespace TL
writer.WriteTLValue(array.GetValue(i), elementType); 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) internal static List<T> ReadTLRawVector<T>(this BinaryReader reader, uint ctorNb)
{ {
int count = reader.ReadInt32(); int count = reader.ReadInt32();
@ -289,13 +325,14 @@ namespace TL
} }
internal static void WriteTLStamp(this BinaryWriter writer, DateTime datetime) 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(); <= 0 => default,
return unixstamp == int.MaxValue ? DateTime.MaxValue : new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc); int.MaxValue => DateTime.MaxValue,
} int unixstamp => new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc)
};
internal static void WriteTLString(this BinaryWriter writer, string str) 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 [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 long msg_id = msgId;
public int seq_no = seqNo; public int seqno = seqno;
public int bytes; public int bytes;
public IObject body = obj; public IObject body = obj;
} }
@ -443,4 +480,16 @@ namespace TL
[TLDef(0x3072CFA1)] //gzip_packed#3072cfa1 packed_data:bytes = Object [TLDef(0x3072CFA1)] //gzip_packed#3072cfa1 packed_data:bytes = Object
public sealed partial class GzipPacked : IObject { public byte[] packed_data; } 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);
}
} }

View file

@ -100,7 +100,7 @@ namespace WTelegram
static readonly byte[] TlsClientHello3 = [ static readonly byte[] TlsClientHello3 = [
// 0x00, 0x00, len { len { 0x00 len { domain } } } len is 16-bit big-endian length of the following block of data // 0x00, 0x00, len { len { 0x00 len { domain } } } len is 16-bit big-endian length of the following block of data
0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x4A, 0x4A,/*=grease(4)*/ 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x4A, 0x4A/*=grease(4)*/, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01,
0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31,
@ -108,9 +108,9 @@ namespace WTelegram
0x00, 0x17, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00,
0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02,
0x00, 0x23, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x2b, 0x00, 0x07, 0x06, 0x6A, 0x6A,/*=grease(6) */ 0x03, 0x04, 0x03, 0x03, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x6A, 0x6A/*=grease(6)*/, 0x03, 0x04, 0x03, 0x03,
0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01,
0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x4A, 0x4A,/*=grease(4) */ 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, /* random[32] */ 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x4A, 0x4A/*=grease(4)*/, 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, /* random[32] */
0x44, 0x69, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32, 0x44, 0x69, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32,
0xff, 0x01, 0x00, 0x01, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00,
]; ];

View file

@ -566,7 +566,7 @@ namespace WTelegram
/// <summary>Save the current state of the manager to JSON file</summary> /// <summary>Save the current state of the manager to JSON file</summary>
/// <param name="statePath">File path to write</param> /// <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) public void SaveState(string statePath)
=> System.IO.File.WriteAllText(statePath, System.Text.Json.JsonSerializer.Serialize(State, Helpers.JsonOptions)); => 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 public static Dictionary<long, MBoxState> LoadState(string statePath) => !System.IO.File.Exists(statePath) ? null

View file

@ -11,13 +11,14 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<PackageId>WTelegramClient</PackageId> <PackageId>WTelegramClient</PackageId>
<Version>0.0.0</Version>
<Authors>Wizou</Authors> <Authors>Wizou</Authors>
<Description>Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 198 <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: Release Notes:
$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</Description> $(ReleaseNotes)</Description>
<Copyright>Copyright © Olivier Marcoux 2021-2024</Copyright> <Copyright>Copyright © Olivier Marcoux 2021-2025</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://wiz0u.github.io/WTelegramClient</PackageProjectUrl> <PackageProjectUrl>https://wiz0u.github.io/WTelegramClient</PackageProjectUrl>
<PackageIcon>logo.png</PackageIcon> <PackageIcon>logo.png</PackageIcon>
@ -26,15 +27,15 @@ $(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageTags>Telegram;MTProto;Client;Api;UserBot</PackageTags> <PackageTags>Telegram;MTProto;Client;Api;UserBot</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile> <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> <NoWarn>NETSDK1138;CS0419;CS1573;CS1591</NoWarn>
<DefineConstants>TRACE;OBFUSCATION;MTPG</DefineConstants> <DefineConstants>TRACE;OBFUSCATION;MTPG</DefineConstants>
<!--<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>--> <!--<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>-->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\.github\dev.yml" Link="Data\dev.yml" /> <None Include="..\.github\workflows\dev.yml" Link="Data\dev.yml" />
<None Include="..\.github\release.yml" Link="Data\release.yml" /> <None Include="..\.github\workflows\release.yml" Link="Data\release.yml" />
<None Include="..\EXAMPLES.md" Link="Data\EXAMPLES.md" /> <None Include="..\EXAMPLES.md" Link="Data\EXAMPLES.md" />
<None Include="..\FAQ.md" Link="Data\FAQ.md" /> <None Include="..\FAQ.md" Link="Data\FAQ.md" />
<None Include="..\README.md" Link="Data\README.md" Pack="true" PackagePath="\" /> <None Include="..\README.md" Link="Data\README.md" Pack="true" PackagePath="\" />