Compare commits

...

28 commits

Author SHA1 Message Date
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
19 changed files with 2683 additions and 756 deletions

View file

@ -12,14 +12,16 @@ env:
jobs:
build:
permissions:
id-token: write # enable GitHub OIDC token issuance for this job
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 100
fetch-depth: 30
- name: Determine version
run: |
git fetch --depth=100 --tags
git fetch --depth=30 --tags
DESCR_TAG=$(git describe --tags)
COMMITS=${DESCR_TAG#*-}
COMMITS=${COMMITS%-*}
@ -29,24 +31,37 @@ jobs:
if [[ "$RELEASE_VERSION" > "$NEXT_VERSION" ]] then VERSION=$RELEASE_VERSION; else VERSION=$NEXT_VERSION; fi
echo Last tag: $LAST_TAG · Next version: $NEXT_VERSION · Release version: $RELEASE_VERSION · Build version: $VERSION
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# - name: Setup .NET
# uses: actions/setup-dotnet@v4
# with:
# dotnet-version: 8.0.x
- name: Pack
run: dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION "-p:ReleaseNotes=\"$RELEASE_NOTES\"" --output packages
run: |
RELEASE_NOTES=${RELEASE_NOTES//$'\n'/%0A}
RELEASE_NOTES=${RELEASE_NOTES//\"/%22}
RELEASE_NOTES=${RELEASE_NOTES//,/%2C}
RELEASE_NOTES=${RELEASE_NOTES//;/%3B}
dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION -p:ReleaseNotes="$RELEASE_NOTES" --output packages
# - name: Upload artifact
# uses: actions/upload-artifact@v4
# with:
# name: packages
# path: packages/*.nupkg
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Nuget push
run: dotnet nuget push packages/*.nupkg --api-key ${{secrets.NUGETAPIKEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
run: dotnet nuget push packages/*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
- name: Deployment Notification
env:
JSON: |
{
"status": "success", "complete": true, "commitMessage": ${{ toJSON(github.event.head_commit.message) }},
"status": "success", "complete": true, "commitMessage": ${{ toJSON(env.RELEASE_NOTES) }},
"message": "{ \"commitId\": \"${{ github.sha }}\", \"buildNumber\": \"${{ env.VERSION }}\", \"repoName\": \"${{ github.repository }}\"}"
}
run: |

View file

@ -22,6 +22,7 @@ jobs:
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:
@ -37,19 +38,35 @@ jobs:
if [[ "$RELEASE_VERSION" > "$NEXT_VERSION" ]] then VERSION=$RELEASE_VERSION; else VERSION=$NEXT_VERSION; fi
echo Last tag: $LAST_TAG · Next version: $NEXT_VERSION · Release version: $RELEASE_VERSION · Build version: $VERSION
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Pack
run: dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION "-p:ReleaseNotes=\"$RELEASE_NOTES\"" --output packages
run: |
RELEASE_NOTES=${RELEASE_NOTES//|/%0A}
RELEASE_NOTES=${RELEASE_NOTES// - /%0A- }
RELEASE_NOTES=${RELEASE_NOTES// /%0A%0A}
RELEASE_NOTES=${RELEASE_NOTES//$'\n'/%0A}
RELEASE_NOTES=${RELEASE_NOTES//\"/%22}
RELEASE_NOTES=${RELEASE_NOTES//,/%2C}
RELEASE_NOTES=${RELEASE_NOTES//;/%3B}
dotnet pack $PROJECT_PATH --configuration $CONFIGURATION -p:Version=$VERSION -p:ReleaseNotes="$RELEASE_NOTES" --output packages
# - name: Upload artifact
# uses: actions/upload-artifact@v4
# with:
# name: packages
# path: packages/*.nupkg
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Nuget push
run: dotnet nuget push packages/*.nupkg --api-key ${{secrets.NUGETAPIKEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
run: dotnet nuget push packages/*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
- name: Git tag
run: |
git tag $VERSION
@ -58,7 +75,7 @@ jobs:
env:
JSON: |
{
"status": "success", "complete": true, "commitMessage": ${{ toJSON(github.event.head_commit.message) }},
"status": "success", "complete": true, "commitMessage": ${{ toJSON(env.RELEASE_NOTES) }},
"message": "{ \"commitId\": \"${{ github.sha }}\", \"buildNumber\": \"${{ env.VERSION }}\", \"repoName\": \"${{ github.repository }}\"}"
}
run: |

View file

@ -12,7 +12,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, 'telegram api')
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v3.0.0
- uses: dessant/support-requests@v4
with:
support-label: 'telegram api'
issue-comment: >
@ -26,3 +26,4 @@ jobs:
If the above links didn't answer your problem, [click here to ask your question on **StackOverflow**](https://stackoverflow.com/questions/ask?tags=c%23+wtelegramclient+telegram-api) so the whole community can help and benefit.
close-issue: true
issue-close-reason: 'not planned'

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)
_Note: To abort an ongoing download, you can throw an exception via the `progress` callback argument._
_Note: To abort an ongoing download, you can throw an exception via the `progress` callback argument. Example: `(t,s) => ct.ThrowIfCancellationRequested()`_
<a name="upload"></a>
## Upload a media file and post it with caption to a chat
@ -224,6 +224,41 @@ var inputFile = await client.UploadFileAsync(Filepath);
await client.SendMediaAsync(peer, "Here is the photo", inputFile);
```
<a name="upload-video"></a>
## Upload a streamable video with optional custom thumbnail
```csharp
var chats = await client.Messages_GetAllChats();
InputPeer peer = chats.chats[1234567890]; // the chat we want
const string videoPath = @"C:\...\video.mp4";
const string thumbnailPath = @"C:\...\thumbnail.jpg";
// Extract video information using FFMpegCore or similar library
var mediaInfo = await FFmpeg.GetMediaInfo(videoPath);
var videoStream = mediaInfo.VideoStreams.FirstOrDefault();
int width = videoStream?.Width ?? 0;
int height = videoStream?.Height ?? 0;
int duration = (int)mediaInfo.Duration.TotalSeconds;
// Upload video file
var inputFile = await Client.UploadFileAsync(videoPath);
// Prepare InputMedia structure with video attributes
var media = new InputMediaUploadedDocument(inputFile, "video/mp4",
new DocumentAttributeVideo { w = width, h = height, duration = duration,
flags = DocumentAttributeVideo.Flags.supports_streaming });
if (thumbnailPath != null)
{
// upload custom thumbnail and complete InputMedia structure
var inputThumb = await client.UploadFileAsync(thumbnailPath);
media.thumb = inputThumb;
media.flags |= InputMediaUploadedDocument.Flags.has_thumb;
}
// Send the media message
await client.SendMessageAsync(peer, "caption", media);
```
*Note: This example requires FFMpegCore NuGet package for video metadata extraction. You can also manually set width, height, and duration if you know the video properties.*
<a name="album"></a>
## Send a grouped media album using photos from various sources
```csharp

View file

@ -1,4 +1,4 @@
[![API Layer](https://img.shields.io/badge/API_Layer-206-blueviolet)](https://corefork.telegram.org/methods)
[![API Layer](https://img.shields.io/badge/API_Layer-216-blueviolet)](https://corefork.telegram.org/methods)
[![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)
[![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).
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.

View file

@ -32,6 +32,7 @@ public class MTProtoGenerator : IIncrementalGenerator
var nullables = LoadNullables(layer);
var namespaces = new Dictionary<string, Dictionary<string, string>>(); // namespace,class,methods
var tableTL = new StringBuilder();
var methodsTL = new StringBuilder();
var source = new StringBuilder();
source
.AppendLine("using System;")
@ -46,6 +47,9 @@ public class MTProtoGenerator : IIncrementalGenerator
tableTL
.AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Table = new()")
.AppendLine("\t\t{");
methodsTL
.AppendLine("\t\tpublic static readonly Dictionary<uint, Func<BinaryReader, IObject>> Methods = new()")
.AppendLine("\t\t{");
foreach (var classDecl in unit.classes)
{
@ -79,15 +83,20 @@ public class MTProtoGenerator : IIncrementalGenerator
ns = symbol.ContainingNamespace.ToString();
name = symbol.Name;
if (!namespaces.TryGetValue(ns, out var classes)) namespaces[ns] = classes = [];
if (name is "_Message" or "RpcResult" or "MsgCopy")
if (name is "_Message" or "MsgCopy")
{
classes[name] = "\t\tpublic void WriteTL(BinaryWriter writer) => throw new NotSupportedException();";
continue;
}
if (id == 0x3072CFA1) // GzipPacked
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = reader => (IObject)reader.ReadTLGzipped(typeof(IObject)),");
else if (name != "Null" && (ns != "TL.Methods" || name == "Ping"))
else if (name != "Null")
{
if (ns == "TL.Methods")
methodsTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}{(symbol.IsGenericType ? "<object>" : "")}.ReadTL,");
if (ns != "TL.Methods" || name == "Ping")
tableTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}.ReadTL,");
}
var override_ = symbol.BaseType == object_ ? "" : "override ";
if (name == "Messages_AffectedMessages") override_ = "virtual ";
//if (symbol.Constructors[0].IsImplicitlyDeclared)
@ -167,26 +176,35 @@ public class MTProtoGenerator : IIncrementalGenerator
writeTl.AppendLine($"writer.WriteTLMessages({member.Name});");
break;
case "TL.IObject": case "TL.IMethod<X>":
readTL.AppendLine($"r.{member.Name} = {(memberType == "TL.IObject" ? "" : $"({memberType})")}reader.ReadTLObject();");
readTL.AppendLine($"r.{member.Name} = {(memberType == "TL.IObject" ? "reader.ReadTLObject()" : "reader.ReadTLMethod<X>()")};");
writeTl.AppendLine($"{member.Name}.WriteTL(writer);");
break;
case "System.Collections.Generic.Dictionary<long, TL.User>":
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<User>();");
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
writeTl.AppendLine($"writer.WriteTLVector({member.Name}?.Values.ToArray());");
break;
case "System.Collections.Generic.Dictionary<long, TL.ChatBase>":
readTL.AppendLine($"r.{member.Name} = reader.ReadTLDictionary<ChatBase>();");
writeTl.AppendLine($"writer.WriteTLVector({member.Name}.Values.ToArray());");
writeTl.AppendLine($"writer.WriteTLVector({member.Name}?.Values.ToArray());");
break;
case "object":
readTL.AppendLine($"r.{member.Name} = reader.ReadTLObject();");
writeTl.AppendLine($"writer.WriteTLValue({member.Name}, {member.Name}.GetType());");
break;
default:
if (member.Type is IArrayTypeSymbol arrayType)
{
if (name is "FutureSalts")
{
readTL.AppendLine($"r.{member.Name} = reader.ReadTLRawVector<{memberType.Substring(0, memberType.Length - 2)}>(0x0949D9DC).ToArray();");
writeTl.AppendLine($"writer.WriteTLRawVector({member.Name}, 16);");
}
else
{
readTL.AppendLine($"r.{member.Name} = reader.ReadTLVector<{memberType.Substring(0, memberType.Length - 2)}>();");
writeTl.AppendLine($"writer.WriteTLVector({member.Name});");
}
}
else if (member.Type.BaseType.SpecialType == SpecialType.System_Enum)
{
readTL.AppendLine($"r.{member.Name} = ({memberType})reader.ReadUInt32();");
@ -213,7 +231,8 @@ public class MTProtoGenerator : IIncrementalGenerator
foreach (var nullable in nullables)
tableTL.AppendLine($"\t\t\t[0x{nullable.Value:X8}] = null,");
tableTL.AppendLine("\t\t};");
namespaces["TL"]["Layer"] = tableTL.ToString();
methodsTL.AppendLine("\t\t};");
namespaces["TL"]["Layer"] = tableTL.ToString() + methodsTL.ToString();
foreach (var namesp in namespaces)
{
source.Append("namespace ").AppendLine(namesp.Key).Append('{');

View file

@ -13,7 +13,6 @@ namespace WTelegram
{
partial class Client
{
#region Client TL Helpers
/// <summary>Used to indicate progression of file download/upload</summary>
/// <param name="transmitted">transmitted bytes</param>
/// <param name="totalSize">total size of file in bytes, or 0 if unknown</param>
@ -36,9 +35,10 @@ namespace WTelegram
var client = await GetClientForDC(-_dcSession.DcID, true);
using (stream)
{
const long SMALL_FILE_MAX_SIZE = 10 << 20;
bool hasLength = stream.CanSeek;
long transmitted = 0, length = hasLength ? stream.Length : -1;
bool isBig = !hasLength || length >= 10 * 1024 * 1024;
bool isBig = !hasLength || length > SMALL_FILE_MAX_SIZE;
int file_total_parts = hasLength ? (int)((length - 1) / FilePartSize) + 1 : -1;
long file_id = Helpers.RandomLong();
int file_part = 0, read;
@ -94,19 +94,19 @@ namespace WTelegram
/// <summary>Search messages in chat with <see href="https://corefork.telegram.org/type/MessagesFilter">filter</see> and text <para>See <a href="https://corefork.telegram.org/method/messages.search"/></para></summary>
/// <typeparam name="T">See <see cref="MessagesFilter"/> for a list of possible filter types</typeparam>
/// <param name="peer">User or chat, histories with which are searched, or <see langword="null"/> constructor for global search</param>
/// <param name="text">Text search request</param>
/// <param name="q">Text search request</param>
/// <param name="offset_id">Only return messages starting from the specified message ID</param>
/// <param name="limit"><a href="https://corefork.telegram.org/api/offsets">Number of results to return</a></param>
public Task<Messages_MessagesBase> Messages_Search<T>(InputPeer peer, string text = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
=> this.Messages_Search(peer, text, new T(), offset_id: offset_id, limit: limit);
public Task<Messages_MessagesBase> Messages_Search<T>(InputPeer peer, string q = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
=> this.Messages_Search(peer, q, new T(), offset_id: offset_id, limit: limit);
/// <summary>Search messages globally with <see href="https://corefork.telegram.org/type/MessagesFilter">filter</see> and text <para>See <a href="https://corefork.telegram.org/method/messages.searchGlobal"/></para></summary>
/// <typeparam name="T">See <see cref="MessagesFilter"/> for a list of possible filter types</typeparam>
/// <param name="text">Text search request</param>
/// <param name="q">Query</param>
/// <param name="offset_id">Only return messages starting from the specified message ID</param>
/// <param name="limit"><a href="https://corefork.telegram.org/api/offsets">Number of results to return</a></param>
public Task<Messages_MessagesBase> Messages_SearchGlobal<T>(string text = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
=> this.Messages_SearchGlobal(text, new T(), offset_id: offset_id, limit: limit);
public Task<Messages_MessagesBase> Messages_SearchGlobal<T>(string q = null, int offset_id = 0, int limit = int.MaxValue) where T : MessagesFilter, new()
=> this.Messages_SearchGlobal(q, new T(), offset_id: offset_id, limit: limit);
/// <summary>Helper method to send a media message more easily</summary>
/// <param name="peer">Destination of message (chat group, channel, user chat, etc..) </param>
@ -361,6 +361,18 @@ namespace WTelegram
return thumbSize == null ? document.mime_type : "image/" + fileType;
}
/// <summary>Download a document from Telegram into the outputStream</summary>
/// <param name="document">The document to download</param>
/// <param name="outputStream">Stream to write the file content to. This method does not close/dispose the stream</param>
/// <param name="videoSize">A specific size/version of the animated photo. Use <c>photo.LargestVideoSize</c> to download the largest version of the animated photo</param>
/// <param name="progress">(optional) Callback for tracking the progression of the transfer</param>
/// <returns>MIME type of the document/thumbnail</returns>
public async Task<Storage_FileType> DownloadFileAsync(Document document, Stream outputStream, VideoSize videoSize, ProgressCallback progress = null)
{
var fileLocation = document.ToFileLocation(videoSize);
return await DownloadFileAsync(fileLocation, outputStream, document.dc_id, videoSize.size, progress);
}
/// <summary>Download a file from Telegram into the outputStream</summary>
/// <param name="fileLocation">Telegram file identifier, typically obtained with a .ToFileLocation() call</param>
/// <param name="outputStream">Stream to write file content to. This method does not close/dispose the stream</param>
@ -535,18 +547,20 @@ namespace WTelegram
case Messages_DialogsSlice mds:
var dialogList = new List<DialogBase>();
var messageList = new List<MessageBase>();
while (dialogs.Dialogs.Length != 0)
int skip = 0;
while (dialogs.Dialogs.Length > skip)
{
dialogList.AddRange(dialogs.Dialogs);
dialogList.AddRange(skip == 0 ? dialogs.Dialogs : dialogs.Dialogs[skip..]);
messageList.AddRange(dialogs.Messages);
skip = 0;
int last = dialogs.Dialogs.Length - 1;
var lastDialog = dialogs.Dialogs[last];
retryDate:
var lastPeer = dialogs.UserOrChat(lastDialog).ToInputPeer();
var lastMsgId = lastDialog.TopMessage;
retryDate:
var lastDate = dialogs.Messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage)?.Date ?? default;
if (lastDate == default)
if (--last < 0) break; else { lastDialog = dialogs.Dialogs[last]; goto retryDate; }
if (--last < 0) break; else { ++skip; lastDialog = dialogs.Dialogs[last]; goto retryDate; }
dialogs = await this.Messages_GetDialogs(lastDate, lastMsgId, lastPeer, folder_id: folder_id);
if (dialogs is not Messages_Dialogs md) break;
foreach (var (key, value) in md.chats) mds.chats[key] = value;
@ -638,18 +652,18 @@ namespace WTelegram
}
/// <summary>Helper simplified method: Get all <a href="https://corefork.telegram.org/api/forum">topics of a forum</a> <para>See <a href="https://corefork.telegram.org/method/channels.getForumTopics"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.getForumTopics#possible-errors">details</a>)</para></summary>
/// <param name="channel">Supergroup</param>
/// <param name="peer">Supergroup or Bot peer</param>
/// <param name="q">Search query</param>
public async Task<Messages_ForumTopics> Channels_GetAllForumTopics(InputChannelBase channel, string q = null)
public async Task<Messages_ForumTopics> Channels_GetAllForumTopics(InputPeer peer, string q = null)
{
var result = await this.Channels_GetForumTopics(channel, offset_date: DateTime.MaxValue, q: q);
var result = await this.Messages_GetForumTopics(peer, offset_date: DateTime.MaxValue, q: q);
if (result.topics.Length < result.count)
{
var topics = result.topics.ToList();
var messages = result.messages.ToList();
while (true)
{
var more_topics = await this.Channels_GetForumTopics(channel, messages[^1].Date, messages[^1].ID, topics[^1].ID);
var more_topics = await this.Messages_GetForumTopics(peer, messages[^1].Date, messages[^1].ID, topics[^1].ID);
if (more_topics.topics.Length == 0) break;
topics.AddRange(more_topics.topics);
messages.AddRange(more_topics.messages);
@ -909,6 +923,28 @@ namespace WTelegram
}
return chat;
}
#endregion
/// <summary>Receive updates for a given group/channel until cancellation is requested.</summary>
/// <param name="channel">Group/channel to monitor without joining</param>
/// <param name="ct">Cancel token to stop the monitoring</param>
/// <remarks>After cancelling, you may still receive updates for a few more seconds</remarks>
public async void OpenChat(InputChannel channel, CancellationToken ct)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, ct);
try
{
while (!cts.IsCancellationRequested)
{
var diff = await this.Updates_GetChannelDifference(channel, null, 1, 1, true);
var timeout = diff.Timeout * 1000;
await Task.Delay(timeout != 0 ? timeout : 30000, cts.Token);
}
}
catch (Exception ex)
{
if (!cts.IsCancellationRequested)
Console.WriteLine($"An exception occured for OpenChat {channel.channel_id}: {ex.Message}");
}
}
}
}

View file

@ -185,10 +185,6 @@ namespace WTelegram
return Console.ReadLine();
}
/// <summary>Load a specific Telegram server public key</summary>
/// <param name="pem">A string starting with <c>-----BEGIN RSA PUBLIC KEY-----</c></param>
public static void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem);
/// <summary>Builds a structure that is used to validate a 2FA password</summary>
/// <param name="accountPassword">Password validation configuration. You can obtain this via <c>Account_GetPassword</c> or through OnOther as part of the login process</param>
/// <param name="password">The password to validate</param>
@ -277,9 +273,8 @@ namespace WTelegram
private Session.DCSession GetOrCreateDCSession(int dcId, DcOption.Flags flags)
{
if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.AuthKey != null)
if (dcSession.Client != null || dcSession.DataCenter.flags == flags)
return dcSession; // if we have already a session with this DC and we are connected or it is a perfect match, use it
if (_session.DCSessions.TryGetValue(dcId, out var dcSession) && dcSession.Client != null)
return dcSession; // we have already a connected session with this DC, use it
if (dcSession == null && _session.DCSessions.TryGetValue(-dcId, out dcSession) && dcSession.AuthKey != null)
{
// we have already negociated an AuthKey with this DC
@ -295,9 +290,10 @@ namespace WTelegram
dcId = Math.Abs(dcId);
}
var dcOptions = GetDcOptions(Math.Abs(dcId), flags);
var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}");
var dcOption = dcOptions.FirstOrDefault();
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;
}
@ -358,7 +354,7 @@ namespace WTelegram
if (await stream.FullReadAsync(data, 4, ct) != 4)
throw new WTException(ConnectionShutDown);
#if OBFUSCATION
_recvCtr.EncryptDecrypt(data, 4);
_recvCtr.EncryptDecrypt(data.AsSpan(0, 4));
#endif
int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data);
if (payloadLen <= 0)
@ -370,7 +366,7 @@ namespace WTelegram
if (await stream.FullReadAsync(data, payloadLen, ct) != payloadLen)
throw new WTException("Could not read frame data : Connection shut down");
#if OBFUSCATION
_recvCtr.EncryptDecrypt(data, payloadLen);
_recvCtr.EncryptDecrypt(data.AsSpan(0, payloadLen));
#endif
obj = ReadFrame(data, payloadLen);
}
@ -593,7 +589,7 @@ namespace WTelegram
{
var msg = new _Message(reader.ReadInt64(), reader.ReadInt32(), null) { bytes = reader.ReadInt32() };
messages.Add(msg);
if ((msg.seq_no & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
if ((msg.seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
var pos = reader.BaseStream.Position;
try
{
@ -606,7 +602,7 @@ namespace WTelegram
else
{
var obj = msg.body = reader.ReadTLObject(ctorNb);
Helpers.Log(1, $" → {obj.GetType().Name,-38} {MsgIdToStamp(msg.msg_id):u} {((msg.seq_no & 1) != 0 ? "" : "(svc)")} {((msg.msg_id & 2) == 0 ? "" : "NAR")}");
Helpers.Log(1, $" → {obj.GetType().Name,-38} {MsgIdToStamp(msg.msg_id):u} {((msg.seqno & 1) != 0 ? "" : "(svc)")} {((msg.msg_id & 2) == 0 ? "" : "NAR")}");
}
}
catch (Exception ex)
@ -1129,7 +1125,7 @@ namespace WTelegram
{
try
{
var users = await this.Users_GetUsers(InputUser.Self); // this calls also reenable incoming Updates
var users = await this.Users_GetUsers(InputUser.Self); // this call also reenable incoming Updates
var self = users[0] as User;
if (self.id == long.Parse(botToken.Split(':')[0]))
{
@ -1238,10 +1234,13 @@ namespace WTelegram
if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be)
sentCodeBase = verifiedLogin.sent_code;
}
RaiseUpdates(sentCodeBase);
}
resent:
if (sentCodeBase is Auth_SentCodeSuccess success)
authorization = success.authorization;
else if (sentCodeBase is Auth_SentCodePaymentRequired paymentRequired)
throw new WTException("Auth_SentCodePaymentRequired unsupported");
else if (sentCodeBase is Auth_SentCode sentCode)
{
phone_code_hash = sentCode.phone_code_hash;
@ -1410,7 +1409,7 @@ namespace WTelegram
public User LoginAlreadyDone(Auth_AuthorizationBase authorization)
{
if (authorization is not Auth_Authorization { user: User self })
throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
throw new WTException("Failed to get Authorization: " + authorization?.GetType().Name);
_session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save();
RaiseUpdates(self);
@ -1478,7 +1477,7 @@ namespace WTelegram
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
writer.Write(msgId); // int64 message_id
writer.Write(0); // int32 message_data_length (to be patched)
Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_')}...");
Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_')}");
writer.WriteTLObject(msg); // bytes message_data
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(20), (int)memStream.Length - 24); // patch message_data_length
}
@ -1523,7 +1522,7 @@ namespace WTelegram
int frameLength = (int)memStream.Length;
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
#if OBFUSCATION
_sendCtr?.EncryptDecrypt(buffer, frameLength);
_sendCtr?.EncryptDecrypt(buffer.AsSpan(0, frameLength));
#endif
if (_networkStream != null)
await _networkStream.WriteAsync(buffer, 0, frameLength);
@ -1630,6 +1629,16 @@ namespace WTelegram
got503 = true;
goto retry;
}
else if (code == 401 && !IsMainDC && message is "SESSION_REVOKED" or "AUTH_KEY_UNREGISTERED") // need to renegociate alt-DC auth
{
lock (_session)
{
_session.DCSessions.Remove(_dcSession.DcID);
if (_session.MainDC != -_dcSession.DcID) _session.DCSessions.Remove(-_dcSession.DcID);
_session.Save();
}
await DisposeAsync();
}
else if (code == 400 && message == "CONNECTION_NOT_INITED")
{
await InitConnection();

View file

@ -13,7 +13,7 @@ using static WTelegram.Compat;
namespace WTelegram
{
internal static class Encryption
public static class Encryption
{
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = [];
internal static readonly RandomNumberGenerator RNG = RandomNumberGenerator.Create();
@ -94,7 +94,7 @@ namespace WTelegram
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok");
if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch");
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce);
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(sha1, resPQ.server_nonce, pqInnerData.new_nonce);
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
using var answerReader = new BinaryReader(new MemoryStream(answer));
@ -163,8 +163,9 @@ namespace WTelegram
session.AuthKey = authKey;
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
session.OldSalt = session.Salt;
}
(byte[] key, byte[] iv) ConstructTmpAESKeyIV(TL.Int128 server_nonce, Int256 new_nonce)
public static (byte[] key, byte[] iv) ConstructTmpAESKeyIV(SHA1 sha1, TL.Int128 server_nonce, Int256 new_nonce)
{
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
@ -183,7 +184,6 @@ namespace WTelegram
sha1.Initialize();
return (tmp_aes_key, tmp_aes_iv);
}
}
internal static void CheckGoodPrime(BigInteger p, int g)
{
@ -237,6 +237,8 @@ namespace WTelegram
throw new WTException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
}
/// <summary>Load a specific Telegram server public key</summary>
/// <param name="pem">A string starting with <c>-----BEGIN RSA PUBLIC KEY-----</c></param>
public static void LoadPublicKey(string pem)
{
using var rsa = RSA.Create();
@ -245,10 +247,7 @@ namespace WTelegram
var rsaParam = rsa.ExportParameters(false);
if (rsaParam.Modulus[0] == 0) rsaParam.Modulus = rsaParam.Modulus[1..];
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
using var memStream = new MemoryStream(280);
using (var writer = new BinaryWriter(memStream))
writer.WriteTLObject(publicKey);
var bareData = memStream.ToArray();
var bareData = publicKey.ToBytes();
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1
PublicKeys[fingerprint] = publicKey;
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
@ -276,7 +275,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
-----END RSA PUBLIC KEY-----");
}
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, int x, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
public static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, int x, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
{
// first, construct AES key & IV
byte[] aes_key = new byte[32], aes_iv = new byte[32];
@ -297,7 +296,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
}
internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
public static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
{
if (input.Length % 16 != 0) throw new WTException("AES_IGE input size not divisible by 16");
@ -305,8 +304,8 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
var output = new byte[input.Length];
var prevBytes = (byte[])aes_iv.Clone();
var span = MemoryMarshal.Cast<byte, long>(input);
var sout = MemoryMarshal.Cast<byte, long>(output);
var prev = MemoryMarshal.Cast<byte, long>(prevBytes);
var sout = MemoryMarshal.Cast<byte, long>(output.AsSpan());
var prev = MemoryMarshal.Cast<byte, long>(prevBytes.AsSpan());
if (!encrypt) { (prev[2], prev[0]) = (prev[0], prev[2]); (prev[3], prev[1]) = (prev[1], prev[3]); }
for (int i = 0, count = input.Length / 8; i < count;)
{
@ -319,7 +318,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
}
#if OBFUSCATION
internal sealed class AesCtr(byte[] key, byte[] ivec) : IDisposable
public sealed class AesCtr(byte[] key, byte[] ivec) : IDisposable
{
readonly ICryptoTransform _encryptor = AesECB.CreateEncryptor(key, null);
readonly byte[] _ecount = new byte[16];
@ -327,9 +326,9 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
public void Dispose() => _encryptor.Dispose();
public void EncryptDecrypt(byte[] buffer, int length)
public void EncryptDecrypt(Span<byte> buffer)
{
for (int i = 0; i < length; i++)
for (int i = 0; i < buffer.Length; i++)
{
if (_num == 0)
{
@ -373,7 +372,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
var sendCtr = new AesCtr(sendKey, sendIV);
var recvCtr = new AesCtr(recvKey, recvIV);
var encrypted = (byte[])preamble.Clone();
sendCtr.EncryptDecrypt(encrypted, 64);
sendCtr.EncryptDecrypt(encrypted);
for (int i = 56; i < 64; i++)
preamble[i] = encrypted[i];
return (sendCtr, recvCtr, preamble);
@ -557,7 +556,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
{
count = count + 15 & ~15;
var span = MemoryMarshal.Cast<byte, long>(buffer.AsSpan(offset, count));
var prev = MemoryMarshal.Cast<byte, long>(_prevBytes);
var prev = MemoryMarshal.Cast<byte, long>(_prevBytes.AsSpan());
for (offset = 0, count /= 8; offset < count;)
{
prev[0] ^= span[offset]; prev[1] ^= span[offset + 1];

View file

@ -30,6 +30,8 @@ namespace TL
_users[user.id] = user;
else
{ // update previously full user from min user:
// see https://github.com/tdlib/td/blob/master/td/telegram/UserManager.cpp#L2689
// and https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/data/data_session.cpp#L515
const User.Flags updated_flags = (User.Flags)0x5DAFE000;
const User.Flags2 updated_flags2 = (User.Flags2)0x711;
// tdlib updated flags: deleted | bot | bot_chat_history | bot_nochats | verified | bot_inline_geo
@ -53,7 +55,7 @@ namespace TL
if (user.lang_code != null)
prevUser.lang_code = user.lang_code; // tdlib: updated if present ; tdesktop: ignored
prevUser.emoji_status = user.emoji_status; // tdlib/tdesktop: updated
prevUser.usernames = user.usernames; // tdlib: not updated ; tdesktop: updated
//prevUser.usernames = user.usernames; // tdlib/tdesktop: not updated
if (user.stories_max_id > 0)
prevUser.stories_max_id = user.stories_max_id; // tdlib: updated if > 0 ; tdesktop: not updated
prevUser.color = user.color; // tdlib/tdesktop: updated
@ -68,8 +70,13 @@ namespace TL
foreach (var chat in chats)
if (chat is not Channel channel)
_chats[chat.ID] = chat;
else if (!channel.flags.HasFlag(Channel.Flags.min) || !_chats.TryGetValue(channel.id, out var prevChat) || prevChat is not Channel prevChannel || prevChannel.flags.HasFlag(Channel.Flags.min))
else if (!_chats.TryGetValue(channel.id, out var prevChat) || prevChat is not Channel prevChannel)
_chats[channel.id] = channel;
else if (!channel.flags.HasFlag(Channel.Flags.min) || prevChannel.flags.HasFlag(Channel.Flags.min))
{
if (channel.participants_count == 0) channel.participants_count = prevChannel.participants_count; // non-min channel can lack this info
_chats[channel.id] = channel;
}
else
{ // update previously full channel from min channel:
const Channel.Flags updated_flags = (Channel.Flags)0x7FDC0BE0;
@ -376,15 +383,15 @@ namespace TL
end = offset + 1;
if (end < sb.Length && sb[end] == '#') end++;
while (end < sb.Length && sb[end] is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9') end++;
if (end >= sb.Length || sb[end] != ';') break;
var html = HttpUtility.HtmlDecode(sb.ToString(offset, end - offset + 1));
var html = HttpUtility.HtmlDecode(end >= sb.Length || sb[end] != ';'
? sb.ToString(offset, end - offset) + ";" : sb.ToString(offset, ++end - offset));
if (html.Length == 1)
{
sb[offset] = html[0];
sb.Remove(++offset, end - offset + 1);
sb.Remove(++offset, end - offset);
}
else
offset = end + 1;
offset = end;
}
else if (c == '<')
{
@ -401,6 +408,7 @@ namespace TL
case "u": case "ins": ProcessEntity<MessageEntityUnderline>(); break;
case "s": case "strike": case "del": ProcessEntity<MessageEntityStrike>(); break;
case "span class=\"tg-spoiler\"":
case "span class='tg-spoiler'":
case "span" when closing:
case "tg-spoiler": ProcessEntity<MessageEntitySpoiler>(); break;
case "code": ProcessEntity<MessageEntityCode>(); break;
@ -420,7 +428,8 @@ namespace TL
prevEntity.length = offset - prevEntity.offset;
}
}
else if (tag.StartsWith("a href=\"") && tag[^1] == '"')
else if ((tag[^1] == '"' && tag.StartsWith("a href=\""))
|| (tag[^1] == '\'' && tag.StartsWith("a href='")))
{
tag = HttpUtility.HtmlDecode(tag[8..^1]);
if (tag.StartsWith("tg://user?id=") && long.TryParse(tag[13..], out var user_id) && users?.GetValueOrDefault(user_id)?.access_hash is long hash)
@ -428,12 +437,13 @@ namespace TL
else
entities.Add(new MessageEntityTextUrl { offset = offset, length = -1, url = tag });
}
else if (tag.StartsWith("code class=\"language-") && tag[^1] == '"')
else if ((tag[^1] == '"' && tag.StartsWith("code class=\"language-"))
|| (tag[^1] == '\'' && tag.StartsWith("code class='language-")))
{
if (entities.LastOrDefault(e => e.length == -1) is MessageEntityPre prevEntity)
prevEntity.language = tag[21..^1];
}
else if (premium && (tag.StartsWith("tg-emoji emoji-id=\"") || tag.StartsWith("tg-emoji id=\"")))
else if (premium && (tag.StartsWith("tg-emoji emoji-id=\"") || tag.StartsWith("tg-emoji emoji-id='")))
entities.Add(new MessageEntityCustomEmoji { offset = offset, length = -1, document_id = long.Parse(tag[(tag.IndexOf('=') + 2)..^1]) });
break;
}

View file

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

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
{
#pragma warning disable IDE1006, CS1574
/// <summary>Object describes the contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessage"/></para></summary>
/// <summary>Object describes the contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessage"/></para> <para>Derived classes: <see cref="DecryptedMessage"/>, <see cref="DecryptedMessageService"/></para></summary>
public abstract partial class DecryptedMessageBase : IObject
{
/// <summary>Flags, see <a href="https://corefork.telegram.org/mtproto/TL-combinators#conditional-fields">TL conditional fields</a> (added in layer 45)</summary>
@ -24,11 +24,12 @@ namespace TL
public virtual long ReplyToRandom => default;
/// <summary>Random group ID, assigned by the author of message.<br/>Multiple encrypted messages with a photo attached and with the same group ID indicate an <a href="https://corefork.telegram.org/api/files#albums-grouped-media">album or grouped media</a> (parameter added in layer 45)</summary>
public virtual long Grouped => default;
/// <summary>Random bytes, removed in layer 17.</summary>
public virtual byte[] RandomBytes => default;
public virtual DecryptedMessageAction Action => default;
}
/// <summary>Object describes media contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageMedia"/></para></summary>
/// <summary>Object describes media contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageMedia"/></para> <para>Derived classes: <see cref="DecryptedMessageMediaPhoto"/>, <see cref="DecryptedMessageMediaVideo"/>, <see cref="DecryptedMessageMediaGeoPoint"/>, <see cref="DecryptedMessageMediaContact"/>, <see cref="DecryptedMessageMediaDocument"/>, <see cref="DecryptedMessageMediaAudio"/>, <see cref="DecryptedMessageMediaExternalDocument"/>, <see cref="DecryptedMessageMediaVenue"/>, <see cref="DecryptedMessageMediaWebPage"/></para></summary>
/// <remarks>a <see langword="null"/> value means <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaEmpty">decryptedMessageMediaEmpty</a></remarks>
public abstract partial class DecryptedMessageMedia : IObject
{
@ -36,14 +37,17 @@ namespace TL
internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new WTelegram.WTException("Incompatible DecryptedMessageMedia"); }
}
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para></summary>
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para> <para>Derived classes: <see cref="DecryptedMessageActionSetMessageTTL"/>, <see cref="DecryptedMessageActionReadMessages"/>, <see cref="DecryptedMessageActionDeleteMessages"/>, <see cref="DecryptedMessageActionScreenshotMessages"/>, <see cref="DecryptedMessageActionFlushHistory"/>, <see cref="DecryptedMessageActionResend"/>, <see cref="DecryptedMessageActionNotifyLayer"/>, <see cref="DecryptedMessageActionTyping"/>, <see cref="DecryptedMessageActionRequestKey"/>, <see cref="DecryptedMessageActionAcceptKey"/>, <see cref="DecryptedMessageActionAbortKey"/>, <see cref="DecryptedMessageActionCommitKey"/>, <see cref="DecryptedMessageActionNoop"/></para></summary>
public abstract partial class DecryptedMessageAction : IObject { }
/// <summary>Indicates the location of a photo, will be deprecated soon <para>See <a href="https://corefork.telegram.org/type/FileLocation"/></para></summary>
/// <summary>Indicates the location of a photo, will be deprecated soon <para>See <a href="https://corefork.telegram.org/type/FileLocation"/></para> <para>Derived classes: <see cref="FileLocationUnavailable"/>, <see cref="FileLocation"/></para></summary>
public abstract partial class FileLocationBase : IObject
{
/// <summary>Volume ID</summary>
public virtual long VolumeId => default;
/// <summary>Local ID</summary>
public virtual int LocalId => default;
/// <summary>Secret</summary>
public virtual long Secret => default;
}
@ -55,6 +59,7 @@ namespace TL
{
/// <summary>Random message ID, assigned by the author of message.<br/>Must be equal to the ID passed to sending method.</summary>
public long random_id;
/// <summary>Random bytes, removed in layer 17.</summary>
public byte[] random_bytes;
/// <summary>Message text</summary>
public string message;
@ -67,6 +72,7 @@ namespace TL
public override string Message => message;
/// <summary>Media content</summary>
public override DecryptedMessageMedia Media => media;
/// <summary>Random bytes, removed in layer 17.</summary>
public override byte[] RandomBytes => random_bytes;
}
/// <summary>Contents of an encrypted service message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageService"/></para></summary>
@ -75,12 +81,14 @@ namespace TL
{
/// <summary>Random message ID, assigned by the message author.<br/>Must be equal to the ID passed to the sending method.</summary>
public long random_id;
/// <summary>Random bytes, removed in Layer 17.</summary>
public byte[] random_bytes;
/// <summary>Action relevant to the service message</summary>
public DecryptedMessageAction action;
/// <summary>Random message ID, assigned by the message author.<br/>Must be equal to the ID passed to the sending method.</summary>
public override long RandomId => random_id;
/// <summary>Random bytes, removed in Layer 17.</summary>
public override byte[] RandomBytes => random_bytes;
/// <summary>Action relevant to the service message</summary>
public override DecryptedMessageAction Action => action;
@ -167,6 +175,7 @@ namespace TL
public int thumb_w;
/// <summary>Thumbnail height</summary>
public int thumb_h;
/// <summary>File name, moved to <c>attributes</c> in Layer 45.</summary>
public string file_name;
/// <summary>File MIME-type</summary>
public string mime_type;
@ -498,25 +507,38 @@ namespace TL
[TLDef(0x7C596B46)]
public sealed partial class FileLocationUnavailable : FileLocationBase
{
/// <summary>Volume ID</summary>
public long volume_id;
/// <summary>Local ID</summary>
public int local_id;
/// <summary>Secret</summary>
public long secret;
/// <summary>Volume ID</summary>
public override long VolumeId => volume_id;
/// <summary>Local ID</summary>
public override int LocalId => local_id;
/// <summary>Secret</summary>
public override long Secret => secret;
}
/// <summary>File location. <para>See <a href="https://corefork.telegram.org/constructor/fileLocation"/></para></summary>
[TLDef(0x53D69076)]
public sealed partial class FileLocation : FileLocationBase
{
/// <summary>DC ID</summary>
public int dc_id;
/// <summary>Volume ID</summary>
public long volume_id;
/// <summary>Local ID</summary>
public int local_id;
/// <summary>Secret</summary>
public long secret;
/// <summary>Volume ID</summary>
public override long VolumeId => volume_id;
/// <summary>Local ID</summary>
public override int LocalId => local_id;
/// <summary>Secret</summary>
public override long Secret => secret;
}
}
@ -775,7 +797,6 @@ namespace TL
{
/// <summary>Field <see cref="reply_to_random_id"/> has a value</summary>
has_reply_to_random_id = 0x8,
/// <summary>Whether this is a silent message (no notification triggered)</summary>
silent = 0x20,
/// <summary>Field <see cref="entities"/> has a value</summary>
has_entities = 0x80,

View file

@ -6,7 +6,7 @@ namespace TL
{
public static partial class Layer
{
public const int Version = 206; // fetched 02/07/2025 12:40:29
public const int Version = 216; // fetched 10/10/2025 20:01:17
internal const int SecretChats = 144;
internal const int MTProto2 = 73;
internal const uint VectorCtor = 0x1CB5C415;
@ -41,7 +41,9 @@ namespace TL
[0x3BCBF734] = typeof(DhGenOk),
[0x46DC1FB9] = typeof(DhGenRetry),
[0xA69DAE02] = typeof(DhGenFail),
[0x7ABE77EC] = typeof(Methods.Ping),
[0xF660E1D4] = typeof(DestroyAuthKeyOk),
[0x0A9F2259] = typeof(DestroyAuthKeyNone),
[0xEA109B13] = typeof(DestroyAuthKeyFail),
[0x62D6B459] = typeof(MsgsAck),
[0xA7EFF811] = typeof(BadMsgNotification),
[0xEDAB447B] = typeof(BadServerSalt),
@ -66,6 +68,7 @@ namespace TL
[0x37982646] = typeof(IpPortSecret),
[0x4679B65F] = typeof(AccessPointRule),
[0x5A592A6C] = typeof(Help_ConfigSimple),
[0x7ABE77EC] = typeof(Methods.Ping),
// from TL.SchemaExtensions:
[0x3FEDD339] = typeof(True),
[0xC4B9F9BB] = typeof(Error),
@ -140,7 +143,7 @@ namespace TL
[0xFE685355] = typeof(Channel),
[0x17D493D5] = typeof(ChannelForbidden),
[0x2633421B] = typeof(ChatFull),
[0xE07429DE] = typeof(ChannelFull),
[0xE4E0B29D] = typeof(ChannelFull),
[0xC02D4007] = typeof(ChatParticipant),
[0xE46BCEE4] = typeof(ChatParticipantCreator),
[0xA0933F5B] = typeof(ChatParticipantAdmin),
@ -197,7 +200,7 @@ namespace TL
[0x502F92F7] = typeof(MessageActionInviteToGroupCall),
[0x3C134D7B] = typeof(MessageActionSetMessagesTTL),
[0xB3A07661] = typeof(MessageActionGroupCallScheduled),
[0xAA786345] = typeof(MessageActionSetChatTheme),
[0xB91BBD3A] = typeof(MessageActionSetChatTheme),
[0xEBBCA3CB] = typeof(MessageActionChatJoinedByRequest),
[0x47DD8079] = typeof(MessageActionWebViewDataSentMe),
[0xB4C38CB5] = typeof(MessageActionWebViewDataSent),
@ -215,8 +218,8 @@ namespace TL
[0x41B3E202] = typeof(MessageActionPaymentRefunded),
[0x45D5B021] = typeof(MessageActionGiftStars),
[0xB00C47A2] = typeof(MessageActionPrizeStars),
[0x4717E8A4] = typeof(MessageActionStarGift),
[0x2E3AE60E] = typeof(MessageActionStarGiftUnique),
[0xF24DE7FA] = typeof(MessageActionStarGift),
[0x95728543] = typeof(MessageActionStarGiftUnique),
[0xAC1F1FCD] = typeof(MessageActionPaidMessagesRefunded),
[0x84B88578] = typeof(MessageActionPaidMessagesPrice),
[0x2FFE2F7A] = typeof(MessageActionConferenceCall),
@ -226,6 +229,7 @@ namespace TL
[0x95DDCF69] = typeof(MessageActionSuggestedPostSuccess),
[0x69F916F8] = typeof(MessageActionSuggestedPostRefund),
[0xA8A3C699] = typeof(MessageActionGiftTon),
[0x2C8F2A25] = typeof(MessageActionSuggestBirthday),
[0xD58A08C6] = typeof(Dialog),
[0x71BD134C] = typeof(DialogFolder),
[0x2331B22D] = typeof(PhotoEmpty),
@ -240,7 +244,7 @@ namespace TL
[0xB2A2F663] = typeof(GeoPoint),
[0x5E002502] = typeof(Auth_SentCode),
[0x2390FE44] = typeof(Auth_SentCodeSuccess),
[0xD7CEF980] = typeof(Auth_SentCodePaymentRequired),
[0xE0955A3C] = typeof(Auth_SentCodePaymentRequired),
[0x2EA2C0D4] = typeof(Auth_Authorization),
[0x44747E9A] = typeof(Auth_AuthorizationSignUpRequired),
[0xB434E2B8] = typeof(Auth_ExportedAuthorization),
@ -254,7 +258,7 @@ namespace TL
[0xF47741F7] = typeof(PeerSettings),
[0xA437C3ED] = typeof(WallPaper),
[0xE0804116] = typeof(WallPaperNoFile),
[0x99E78045] = typeof(UserFull),
[0xA02BC13E] = typeof(UserFull),
[0x145ADE0B] = typeof(Contact),
[0xC13E3C50] = typeof(ImportedContact),
[0x16D9703B] = typeof(ContactStatus),
@ -266,8 +270,8 @@ namespace TL
[0x15BA6C40] = typeof(Messages_Dialogs),
[0x71E094F3] = typeof(Messages_DialogsSlice),
[0xF0E3E596] = typeof(Messages_DialogsNotModified),
[0x8C718E87] = typeof(Messages_Messages),
[0x3A54685E] = typeof(Messages_MessagesSlice),
[0x1D73E7EA] = typeof(Messages_Messages),
[0x5F206716] = typeof(Messages_MessagesSlice),
[0xC776BA4E] = typeof(Messages_ChannelMessages),
[0x74535F21] = typeof(Messages_MessagesNotModified),
[0x64FF9FD5] = typeof(Messages_Chats),
@ -294,7 +298,7 @@ namespace TL
[0x1F2B0AFD] = typeof(UpdateNewMessage),
[0x4E90BFD6] = typeof(UpdateMessageID),
[0xA20DB0E5] = typeof(UpdateDeleteMessages),
[0xC01E857F] = typeof(UpdateUserTyping),
[0x2A17BF5C] = typeof(UpdateUserTyping),
[0x83487AF0] = typeof(UpdateChatUserTyping),
[0x07761198] = typeof(UpdateChatParticipants),
[0xE5BDF8DE] = typeof(UpdateUserStatus),
@ -311,7 +315,7 @@ namespace TL
[0xEBE46819] = typeof(UpdateServiceNotification),
[0xEE3B272A] = typeof(UpdatePrivacy),
[0x05492A13] = typeof(UpdateUserPhone),
[0x9C974FDF] = typeof(UpdateReadHistoryInbox),
[0x9E84BC99] = typeof(UpdateReadHistoryInbox),
[0x2F2F21BF] = typeof(UpdateReadHistoryOutbox),
[0x7F891213] = typeof(UpdateWebPage),
[0xF8227181] = typeof(UpdateReadMessagesContents),
@ -398,8 +402,6 @@ namespace TL
[0x6F7863F4] = typeof(UpdateRecentReactions),
[0x86FCCF85] = typeof(UpdateMoveStickerSetToTop),
[0xD5A41724] = typeof(UpdateMessageExtendedMedia),
[0x192EFBE3] = typeof(UpdateChannelPinnedTopic),
[0xFE198602] = typeof(UpdateChannelPinnedTopics),
[0x20529438] = typeof(UpdateUser),
[0xEC05B097] = typeof(UpdateAutoSaveSettings),
[0x75B3B798] = typeof(UpdateStory),
@ -436,6 +438,10 @@ namespace TL
[0x77B0E372] = typeof(UpdateReadMonoForumInbox),
[0xA4A79376] = typeof(UpdateReadMonoForumOutbox),
[0x9F812B08] = typeof(UpdateMonoForumNoPaidException),
[0x78C314E0] = typeof(UpdateGroupCallMessage),
[0xC957A766] = typeof(UpdateGroupCallEncryptedMessage),
[0x683B2C52] = typeof(UpdatePinnedForumTopic),
[0xDEF143D0] = typeof(UpdatePinnedForumTopics),
[0xA56C2A3E] = typeof(Updates_State),
[0x5D75A138] = typeof(Updates_DifferenceEmpty),
[0x00F49CA0] = typeof(Updates_Difference),
@ -505,6 +511,7 @@ namespace TL
[0xB05AC6B1] = typeof(SendMessageChooseStickerAction),
[0x25972BCB] = typeof(SendMessageEmojiInteraction),
[0xB665902E] = typeof(SendMessageEmojiInteractionSeen),
[0x376D975C] = typeof(SendMessageTextDraftAction),
[0xB3134D9D] = typeof(Contacts_Found),
[0x0D09E07B] = typeof(InputPrivacyValueAllowContacts),
[0x184B35CE] = typeof(InputPrivacyValueAllowAll),
@ -1008,6 +1015,7 @@ namespace TL
[0x2E94C3E7] = typeof(WebPageAttributeStory),
[0x50CC03D3] = typeof(WebPageAttributeStickerSet),
[0xCF6F6DB8] = typeof(WebPageAttributeUniqueStarGift),
[0x31CAD303] = typeof(WebPageAttributeStarGiftCollection),
[0x4899484E] = typeof(Messages_VotesList),
[0xF568028A] = typeof(BankCardOpenUrl),
[0x3E24E573] = typeof(Payments_BankCardData),
@ -1039,7 +1047,7 @@ namespace TL
[0x455B853D] = typeof(MessageViews),
[0xB6C4F543] = typeof(Messages_MessageViews),
[0xA6341782] = typeof(Messages_DiscussionMessage),
[0xAFBC09DB] = typeof(MessageReplyHeader),
[0x6917560B] = typeof(MessageReplyHeader),
[0x0E5AF939] = typeof(MessageReplyStoryHeader),
[0x83D60FC2] = typeof(MessageReplies),
[0xE8FD8014] = typeof(PeerBlocked),
@ -1078,6 +1086,10 @@ namespace TL
[0xE3779861] = typeof(Account_ResetPasswordFailedWait),
[0xE9EFFC7D] = typeof(Account_ResetPasswordRequestedWait),
[0xE926D63E] = typeof(Account_ResetPasswordOk),
[0xC3DFFC04] = typeof(ChatTheme),
[0x3458F9C8] = typeof(ChatThemeUniqueGift),
[0xE011E1C4] = null,//Account_ChatThemesNotModified
[0xBE098173] = typeof(Account_ChatThemes),
[0x7DBF8673] = typeof(SponsoredMessage),
[0xFFDA656D] = typeof(Messages_SponsoredMessages),
[0x1839490F] = null,//Messages_SponsoredMessagesEmpty
@ -1128,7 +1140,10 @@ namespace TL
[0x4A5F5BD9] = typeof(InputInvoiceStarGiftTransfer),
[0xDABAB2EF] = typeof(InputInvoicePremiumGiftStars),
[0xF4997E42] = typeof(InputInvoiceBusinessBotTransferStars),
[0x63CBC38C] = typeof(InputInvoiceStarGiftResale),
[0xC39F5324] = typeof(InputInvoiceStarGiftResale),
[0x9A0B48B8] = typeof(InputInvoiceStarGiftPrepaidUpgrade),
[0x3E77F614] = typeof(InputInvoicePremiumAuthCode),
[0x0923D8D1] = typeof(InputInvoiceStarGiftDropOriginalDetails),
[0xAED0CBD9] = typeof(Payments_ExportedInvoice),
[0xCFB9D957] = typeof(Messages_TranscribedAudio),
[0x5334759C] = typeof(Help_PremiumPromo),
@ -1136,7 +1151,7 @@ namespace TL
[0x616F7FE8] = typeof(InputStorePaymentGiftPremium),
[0xFB790393] = typeof(InputStorePaymentPremiumGiftCode),
[0x160544CA] = typeof(InputStorePaymentPremiumGiveaway),
[0xDDDD0F56] = typeof(InputStorePaymentStarsTopup),
[0xF9A2A6CB] = typeof(InputStorePaymentStarsTopup),
[0x1D741EF7] = typeof(InputStorePaymentStarsGift),
[0x751F08FA] = typeof(InputStorePaymentStarsGiveaway),
[0x9BB2636D] = typeof(InputStorePaymentAuthCode),
@ -1171,7 +1186,7 @@ namespace TL
[0xFCFEB29C] = typeof(StickerKeyword),
[0xB4073647] = typeof(Username),
[0x023F109B] = typeof(ForumTopicDeleted),
[0x71701DA9] = typeof(ForumTopic),
[0xCDFF0ECA] = typeof(ForumTopic),
[0x367617D3] = typeof(Messages_ForumTopics),
[0x43B46B20] = typeof(DefaultHistoryTTL),
[0x41BF109B] = typeof(ExportedContactToken),
@ -1213,7 +1228,7 @@ namespace TL
[0x8D595CD6] = typeof(StoryViews),
[0x51E6EE4F] = typeof(StoryItemDeleted),
[0xFFADC913] = typeof(StoryItemSkipped),
[0x79B26A24] = typeof(StoryItem),
[0xEDF164F1] = typeof(StoryItem),
[0x1158FE3E] = typeof(Stories_AllStoriesNotModified),
[0x6EFC5E81] = typeof(Stories_AllStories),
[0x63C3DD0A] = typeof(Stories_Stories),
@ -1222,7 +1237,7 @@ namespace TL
[0xBD74CF49] = typeof(StoryViewPublicRepost),
[0x59D78FC5] = typeof(Stories_StoryViewsList),
[0xDE9EED1D] = typeof(Stories_StoryViews),
[0xB07038B0] = typeof(InputReplyToMessage),
[0x869FBE10] = typeof(InputReplyToMessage),
[0x5881323A] = typeof(InputReplyToStory),
[0x69D66C45] = typeof(InputReplyToMonoForum),
[0x3FC9053B] = typeof(ExportedStoryLink),
@ -1259,6 +1274,8 @@ namespace TL
[0xEDF3ADD0] = typeof(PublicForwardStory),
[0x93037E20] = typeof(Stats_PublicForwards),
[0xB54B5ACF] = typeof(PeerColor),
[0xB9C0639A] = typeof(PeerColorCollectible),
[0xB8EA86A9] = typeof(InputPeerColorCollectible),
[0x26219A58] = typeof(Help_PeerColorSet),
[0x767D61EB] = typeof(Help_PeerColorProfileSet),
[0xADEC6EBE] = typeof(Help_PeerColorOption),
@ -1361,10 +1378,10 @@ namespace TL
[0x4BA3A95A] = typeof(MessageReactor),
[0x94CE852A] = typeof(StarsGiveawayOption),
[0x54236209] = typeof(StarsGiveawayWinnersOption),
[0xC62ACA28] = typeof(StarGift),
[0x6411DB89] = typeof(StarGiftUnique),
[0x80AC53C3] = typeof(StarGift),
[0xB0BF741B] = typeof(StarGiftUnique),
[0xA388A368] = null,//Payments_StarGiftsNotModified
[0x901689EA] = typeof(Payments_StarGifts),
[0x2ED82995] = typeof(Payments_StarGifts),
[0x7903E3D9] = typeof(MessageReportOption),
[0xF0E4E0B6] = typeof(ReportResultChooseOption),
[0x6F09AC31] = typeof(ReportResultAddComment),
@ -1386,12 +1403,12 @@ namespace TL
[0x13ACFF19] = typeof(StarGiftAttributePattern),
[0xD93D859C] = typeof(StarGiftAttributeBackdrop),
[0xE0BFF26C] = typeof(StarGiftAttributeOriginalDetails),
[0x167BD90B] = typeof(Payments_StarGiftUpgradePreview),
[0x3DE1DFED] = typeof(Payments_StarGiftUpgradePreview),
[0x62D706B8] = typeof(Users_Users),
[0x315A4974] = typeof(Users_UsersSlice),
[0xCAA2F60B] = typeof(Payments_UniqueStarGift),
[0xB53E8B21] = typeof(Messages_WebPagePreview),
[0xDFDA0499] = typeof(SavedStarGift),
[0x416C56E8] = typeof(Payments_UniqueStarGift),
[0x8C9A88AC] = typeof(Messages_WebPagePreview),
[0x8983A452] = typeof(SavedStarGift),
[0x95F389B1] = typeof(Payments_SavedStarGifts),
[0x69279795] = typeof(InputSavedStarGiftUser),
[0xF101AA7F] = typeof(InputSavedStarGiftChat),
@ -1420,6 +1437,25 @@ namespace TL
[0x49B92A26] = typeof(TodoList),
[0x4CC120B7] = 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),
// from TL.Secret:
[0x6ABD9782] = typeof(Layer143.DecryptedMessageMediaDocument),
[0x020DF5D0] = typeof(Layer101.MessageEntityBlockquote),
@ -1539,6 +1575,7 @@ namespace TL
[typeof(ChatReactions)] = 0xEAFC32BC, //chatReactionsNone
[typeof(Messages_Reactions)] = 0xB06FDBDF, //messages.reactionsNotModified
// from TL.Secret:
[typeof(Account_ChatThemes)] = 0xE011E1C4, //account.chatThemesNotModified
[typeof(EmojiStatusBase)] = 0x2DE11AAE, //emojiStatusEmpty
[typeof(EmojiList)] = 0x481EADFA, //emojiListNotModified
[typeof(Messages_EmojiGroups)] = 0x6FB4AD87, //messages.emojiGroupsNotModified
@ -1553,6 +1590,10 @@ namespace TL
[typeof(PaidReactionPrivacy)] = 0x206AD49E, //paidReactionPrivacyDefault
[typeof(RequirementToContact)] = 0x050A9839, //requirementToContactEmpty
[typeof(Contacts_SponsoredPeers)] = 0xEA32B4B1, //contacts.sponsoredPeersEmpty
[typeof(Payments_StarGiftCollections)] = 0xA0BA4F17, //payments.starGiftCollectionsNotModified
[typeof(Stories_Albums)] = 0x564EDAEB, //stories.albumsNotModified
[typeof(Account_SavedMusicIds)] = 0x4FC81D6E, //account.savedMusicIdsNotModified
[typeof(InputChatThemeBase)] = 0x83268483, //inputChatThemeEmpty
[typeof(DecryptedMessageMedia)] = 0x089F5C4A, //decryptedMessageMediaEmpty
};
}

View file

@ -148,7 +148,6 @@ namespace TL
{
public abstract long ID { get; }
protected internal abstract IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats);
public static implicit operator long(Peer peer) => peer.ID;
}
partial class PeerUser
{
@ -216,7 +215,7 @@ namespace TL
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/userStatusEmpty">userStatusEmpty</a> = last seen a long time ago, more than a month (or blocked/deleted users)</remarks>
partial class UserStatus { internal abstract TimeSpan LastSeenAgo { get; } }
partial class UserStatusOnline { internal override TimeSpan LastSeenAgo => TimeSpan.Zero; }
partial class UserStatusOffline { internal override TimeSpan LastSeenAgo => DateTime.UtcNow - new DateTime((was_online + 62135596800L) * 10000000, DateTimeKind.Utc); }
partial class UserStatusOffline { internal override TimeSpan LastSeenAgo => DateTime.UtcNow - was_online; }
/// <remarks>covers anything between 1 second and 2-3 days</remarks>
partial class UserStatusRecently { internal override TimeSpan LastSeenAgo => TimeSpan.FromDays(1); }
/// <remarks>between 2-3 and seven days</remarks>
@ -690,8 +689,8 @@ namespace TL
{
System.Text.Json.JsonValueKind.True or
System.Text.Json.JsonValueKind.False => new JsonBool { value = elem.GetBoolean() },
System.Text.Json.JsonValueKind.Object => new JsonObject { value = elem.EnumerateObject().Select(FromJsonProperty).ToArray() },
System.Text.Json.JsonValueKind.Array => new JsonArray { value = elem.EnumerateArray().Select(FromJsonElement).ToArray() },
System.Text.Json.JsonValueKind.Object => new JsonObject { value = [.. elem.EnumerateObject().Select(FromJsonProperty)] },
System.Text.Json.JsonValueKind.Array => new JsonArray { value = [.. elem.EnumerateArray().Select(FromJsonElement)] },
System.Text.Json.JsonValueKind.String => new JsonString { value = elem.GetString() },
System.Text.Json.JsonValueKind.Number => new JsonNumber { value = elem.GetDouble() },
_ => new JsonNull(),
@ -710,7 +709,7 @@ namespace TL
sb.Append(i == 0 ? "" : ",").Append(value[i]);
return sb.Append(']').ToString();
}
public object[] ToNativeArray() => value.Select(v => v.ToNative()).ToArray();
public object[] ToNativeArray() => [.. value.Select(v => v.ToNative())];
public override object ToNative()
{
if (value.Length == 0) return Array.Empty<object>();

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
@ -16,7 +17,7 @@ namespace TL
#else
public interface IObject { }
#endif
public interface IMethod<ReturnType> : IObject { }
public interface IMethod<out ReturnType> : IObject { }
public interface IPeerResolver { IPeerInfo UserOrChat(Peer peer); }
[AttributeUsage(AttributeTargets.Class)]
@ -48,6 +49,15 @@ namespace TL
public static class Serialization
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static byte[] ToBytes<T>(this T obj) where T : IObject
{
using var ms = new MemoryStream(384);
using var writer = new BinaryWriter(ms);
writer.WriteTLObject(obj);
return ms.ToArray();
}
public static void WriteTLObject<T>(this BinaryWriter writer, T obj) where T : IObject
{
if (obj == null) { writer.WriteTLNull(typeof(T)); return; }
@ -105,6 +115,17 @@ namespace TL
#endif
}
public static IMethod<X> ReadTLMethod<X>(this BinaryReader reader)
{
uint ctorNb = reader.ReadUInt32();
if (!Layer.Methods.TryGetValue(ctorNb, out var ctor))
throw new WTelegram.WTException($"Cannot find method for ctor #{ctorNb:x}");
var method = ctor?.Invoke(reader);
if (method is IMethod<bool> && typeof(X) == typeof(object))
method = new BoolMethod { query = method };
return (IMethod<X>)method;
}
internal static void WriteTLValue(this BinaryWriter writer, object value, Type valueType)
{
if (value == null)
@ -197,10 +218,10 @@ namespace TL
foreach (var msg in messages)
{
writer.Write(msg.msg_id);
writer.Write(msg.seq_no);
writer.Write(msg.seqno);
var patchPos = writer.BaseStream.Position;
writer.Write(0); // patched below
if ((msg.seq_no & 1) != 0)
if ((msg.seqno & 1) != 0)
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38} #{(short)msg.msg_id.GetHashCode():X4}");
else
WTelegram.Helpers.Log(1, $" → {msg.body.GetType().Name.TrimEnd('_'),-38}");
@ -222,6 +243,21 @@ namespace TL
writer.WriteTLValue(array.GetValue(i), elementType);
}
internal static void WriteTLRawVector(this BinaryWriter writer, Array array, int elementSize)
{
var startPos = writer.BaseStream.Position;
int count = array.Length;
var elementType = array.GetType().GetElementType();
for (int i = count - 1; i >= 0; i--)
{
writer.BaseStream.Position = startPos + i * elementSize;
writer.WriteTLValue(array.GetValue(i), elementType);
}
writer.BaseStream.Position = startPos;
writer.Write(count);
writer.BaseStream.Position = startPos + count * elementSize + 4;
}
internal static List<T> ReadTLRawVector<T>(this BinaryReader reader, uint ctorNb)
{
int count = reader.ReadInt32();
@ -289,13 +325,14 @@ namespace TL
}
internal static void WriteTLStamp(this BinaryWriter writer, DateTime datetime)
=> writer.Write(datetime == DateTime.MaxValue ? int.MaxValue : (int)(datetime.ToUniversalTime().Ticks / 10000000 - 62135596800L));
=> writer.Write((int)Math.Min(Math.Max(datetime.ToUniversalTime().Ticks / 10000000 - 62135596800L, 0), int.MaxValue));
internal static DateTime ReadTLStamp(this BinaryReader reader)
internal static DateTime ReadTLStamp(this BinaryReader reader) => reader.ReadInt32() switch
{
int unixstamp = reader.ReadInt32();
return unixstamp == int.MaxValue ? DateTime.MaxValue : new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc);
}
<= 0 => default,
int.MaxValue => DateTime.MaxValue,
int unixstamp => new((unixstamp + 62135596800L) * 10000000, DateTimeKind.Utc)
};
internal static void WriteTLString(this BinaryWriter writer, string str)
{
@ -428,10 +465,10 @@ namespace TL
}
[TLDef(0x5BB8E511)] //message#5bb8e511 msg_id:long seqno:int bytes:int body:Object = Message
public sealed partial class _Message(long msgId, int seqNo, IObject obj) : IObject
public sealed partial class _Message(long msgId, int seqno, IObject obj) : IObject
{
public long msg_id = msgId;
public int seq_no = seqNo;
public int seqno = seqno;
public int bytes;
public IObject body = obj;
}
@ -443,4 +480,16 @@ namespace TL
[TLDef(0x3072CFA1)] //gzip_packed#3072cfa1 packed_data:bytes = Object
public sealed partial class GzipPacked : IObject { public byte[] packed_data; }
public sealed class Null<X> : IObject
{
public readonly static Null<X> Instance = new();
public void WriteTL(BinaryWriter writer) => writer.WriteTLNull(typeof(X));
}
public sealed class BoolMethod : IMethod<object>
{
public IObject query;
public void WriteTL(BinaryWriter writer) => query.WriteTL(writer);
}
}

View file

@ -566,7 +566,7 @@ namespace WTelegram
/// <summary>Save the current state of the manager to JSON file</summary>
/// <param name="statePath">File path to write</param>
/// <remarks>Note: This does not save the the content of collected Users/Chats dictionaries</remarks>
/// <remarks>Note: This does not save the content of collected Users/Chats dictionaries</remarks>
public void SaveState(string statePath)
=> System.IO.File.WriteAllText(statePath, System.Text.Json.JsonSerializer.Serialize(State, Helpers.JsonOptions));
public static Dictionary<long, MBoxState> LoadState(string statePath) => !System.IO.File.Exists(statePath) ? null

View file

@ -13,11 +13,11 @@
<PackageId>WTelegramClient</PackageId>
<Authors>Wizou</Authors>
<VersionPrefix>0.0.0</VersionPrefix>
<VersionSuffix>layer.206</VersionSuffix>
<Description>Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 206
<VersionSuffix>layer.216</VersionSuffix>
<Description>Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 216
Release Notes:
$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</Description>
$(ReleaseNotes)</Description>
<Copyright>Copyright © Olivier Marcoux 2021-2025</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://wiz0u.github.io/WTelegramClient</PackageProjectUrl>
@ -27,7 +27,7 @@ $(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%
<RepositoryType>git</RepositoryType>
<PackageTags>Telegram;MTProto;Client;Api;UserBot</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</PackageReleaseNotes>
<PackageReleaseNotes>$(ReleaseNotes)</PackageReleaseNotes>
<NoWarn>NETSDK1138;CS0419;CS1573;CS1591</NoWarn>
<DefineConstants>TRACE;OBFUSCATION;MTPG</DefineConstants>
<!--<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>-->