diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 51bae9d..fca0a89 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1 @@
-github: wiz0u
-custom: ["https://www.buymeacoffee.com/wizou", "http://t.me/WTelegramClientBot?start=donate"]
+custom: ["https://www.paypal.me/wizou"]
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 5efff01..0000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-contact_links:
- - name: You have a question about the Telegram API or how to do something with WTelegramClient?
- url: https://stackoverflow.com/questions/ask?tags=c%23+wtelegramclient+telegram-api
- about: The answer to your question can be helpful to the community so it's better to ask them on StackOverflow --->
diff --git a/.github/ci.yml b/.github/ci.yml
new file mode 100644
index 0000000..35ac36c
--- /dev/null
+++ b/.github/ci.yml
@@ -0,0 +1,32 @@
+pr: none
+trigger:
+- master
+
+name: 1.0.0-ci.$(Rev:r)
+
+pool:
+ vmImage: ubuntu-latest
+
+variables:
+ buildConfiguration: 'Release'
+
+steps:
+- task: DotNetCoreCLI@2
+ inputs:
+ command: 'pack'
+ packagesToPack: '**/*.csproj'
+ includesymbols: true
+ versioningScheme: 'byEnvVar'
+ versionEnvVar: 'Build.BuildNumber'
+ buildProperties: 'NoWarn="0419;1573;1591";Version=$(Build.BuildNumber);ContinuousIntegrationBuild=true'
+# buildProperties: 'NoWarn="0419;1573;1591";AllowedOutputExtensionsInPackageBuildOutputFolder=".dll;.xml;.pdb"'
+
+- task: NuGetCommand@2
+ inputs:
+ command: 'push'
+ packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.*upkg'
+ publishPackageMetadata: true
+ nuGetFeedType: 'internal'
+ publishVstsFeed: 'WTelegramClient/WTelegramClient'
+
+
diff --git a/.github/dev.yml b/.github/dev.yml
deleted file mode 100644
index 6206e8e..0000000
--- a/.github/dev.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-pr: none
-trigger:
- branches:
- include: [ master ]
- paths:
- exclude: [ '.github', '*.md', 'Examples' ]
-
-name: 4.3.2-dev.$(Rev:r)
-
-pool:
- vmImage: ubuntu-latest
-
-variables:
- buildConfiguration: 'Release'
- Release_Notes: $[replace(variables['Build.SourceVersionMessage'], '"', '''''')]
-
-stages:
- - stage: publish
- jobs:
- - job: publish
- steps:
- - task: UseDotNet@2
- displayName: 'Use .NET Core sdk'
- inputs:
- packageType: 'sdk'
- version: '9.x'
- includePreviewVersions: true
-
- - task: DotNetCoreCLI@2
- inputs:
- command: 'pack'
- packagesToPack: 'src/WTelegramClient.csproj'
- includesymbols: true
- versioningScheme: 'byEnvVar'
- versionEnvVar: 'Build.BuildNumber'
- buildProperties: NoWarn="0419;1573;1591";ContinuousIntegrationBuild=true;Version=$(Build.BuildNumber);"ReleaseNotes=$(Release_Notes)"
-
- - task: NuGetCommand@2
- inputs:
- command: 'push'
- packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
- publishPackageMetadata: true
- nuGetFeedType: 'external'
- publishFeedCredentials: 'nuget.org'
-
- - stage: notify
- jobs:
- - job: notify
- pool:
- server
- steps:
- - task: InvokeRESTAPI@1
- inputs:
- connectionType: 'connectedServiceName'
- serviceConnection: 'Telegram Deploy Notice'
- method: 'POST'
- body: |
- {
- "status": "success",
- "complete": true,
- "message": "{ \"commitId\": \"$(Build.SourceVersion)\", \"buildNumber\": \"$(Build.BuildNumber)\", \"teamProjectName\": \"$(System.TeamProject)\", \"commitMessage\": \"$(Release_Notes)\" }"
- }
- waitForCompletion: 'false'
diff --git a/.github/release.yml b/.github/release.yml
index e4ca17f..edfd937 100644
--- a/.github/release.yml
+++ b/.github/release.yml
@@ -1,38 +1,27 @@
pr: none
trigger: none
-name: 4.3.$(Rev:r)
+name: 1.0.0
pool:
vmImage: ubuntu-latest
variables:
buildConfiguration: 'Release'
- Release_Notes: $[replace(variables['releaseNotes'], '"', '''''')]
stages:
- stage: publish
jobs:
- job: publish
steps:
- - checkout: self
- persistCredentials: true
-
- - task: UseDotNet@2
- displayName: 'Use .NET Core sdk'
- inputs:
- packageType: 'sdk'
- version: '9.x'
- includePreviewVersions: true
-
- task: DotNetCoreCLI@2
inputs:
command: 'pack'
- packagesToPack: 'src/WTelegramClient.csproj'
+ packagesToPack: '**/*.csproj'
includesymbols: true
versioningScheme: 'byEnvVar'
versionEnvVar: 'Build.BuildNumber'
- buildProperties: NoWarn="0419;1573;1591";ContinuousIntegrationBuild=true;Version=$(Build.BuildNumber);"ReleaseNotes=$(Release_Notes)"
+ buildProperties: 'NoWarn="0419;1573;1591";Version=$(Build.BuildNumber);ContinuousIntegrationBuild=true'
- task: NuGetCommand@2
inputs:
@@ -41,12 +30,6 @@ stages:
nuGetFeedType: 'external'
publishFeedCredentials: 'nuget.org'
- - script: |
- git tag $(Build.BuildNumber)
- git push --tags
- workingDirectory: $(Build.SourcesDirectory)
- displayName: Git Tag
-
- stage: notify
jobs:
- job: notify
@@ -59,9 +42,13 @@ stages:
serviceConnection: 'Telegram Deploy Notice'
method: 'POST'
body: |
- {
+ {
"status": "success",
"complete": true,
- "message": "{ \"commitId\": \"$(Build.SourceVersion)\", \"buildNumber\": \"$(Build.BuildNumber)\", \"teamProjectName\": \"$(System.TeamProject)\"}"
+ "message": "{
+ \"commitId\": \"$(Build.SourceVersion)\",
+ \"buildNumber\": \"$(Build.BuildNumber)\",
+ \"teamProjectName\": \"$(system.TeamProject)\"
+ }"
}
waitForCompletion: 'false'
diff --git a/.github/workflows/autolock.yml b/.github/workflows/autolock.yml
deleted file mode 100644
index 9a676bc..0000000
--- a/.github/workflows/autolock.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: 'Auto-Lock Issues'
-
-on:
- schedule:
- - cron: '17 2 * * 1'
- workflow_dispatch:
-
-permissions:
- issues: write
- pull-requests: write
- discussions: write
-
-concurrency:
- group: lock-threads
-
-jobs:
- action:
- runs-on: ubuntu-latest
- steps:
- - uses: dessant/lock-threads@v5
- with:
- issue-inactive-days: '60'
- pr-inactive-days: '60'
- discussion-inactive-days: '60'
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
deleted file mode 100644
index de5e145..0000000
--- a/.github/workflows/dev.yml
+++ /dev/null
@@ -1,68 +0,0 @@
-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 }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 7bdbcd1..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-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 }}
diff --git a/.github/workflows/telegram-api.yml b/.github/workflows/telegram-api.yml
deleted file mode 100644
index efd921a..0000000
--- a/.github/workflows/telegram-api.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-name: 'Telegram API issues'
-
-on:
- issues:
- types: [labeled]
-
-permissions:
- issues: write
-
-jobs:
- action:
- if: contains(github.event.issue.labels.*.name, 'telegram api')
- runs-on: ubuntu-latest
- steps:
- - uses: dessant/support-requests@v4
- with:
- support-label: 'telegram api'
- issue-comment: >
- Please note that **Github issues** should be used only for problems with the library code itself.
-
-
- For questions about Telegram API usage, you can search the [API official documentation](https://core.telegram.org/api#getting-started) and the [full list of methods](https://core.telegram.org/methods).
-
- WTelegramClient covers 100% of the API and let you do anything you can do in an official client.
-
-
- 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'
diff --git a/.gitignore b/.gitignore
index 0c768ca..9491a2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,6 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
-launchSettings.json
-
# User-specific files
*.rsuser
*.suo
diff --git a/EXAMPLES.md b/EXAMPLES.md
deleted file mode 100644
index d00c4b5..0000000
--- a/EXAMPLES.md
+++ /dev/null
@@ -1,624 +0,0 @@
-# Example programs using WTelegramClient
-
-For these examples to work as a fully-functional Program.cs, be sure to start with these lines:
-```csharp
-using System;
-using System.Linq;
-using TL;
-
-using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
-await client.LoginUserIfNeeded();
-```
-
-In this case, environment variables are used for configuration so make sure to
-go to your **Project Properties > Debug > Launch Profiles > Environment variables**
-and add at least these variables with adequate values: **api_id, api_hash, phone_number**
-
-Remember that these are just simple example codes that you should adjust to your needs.
-In real production code, you might want to properly test the success of each operation or handle exceptions,
-and avoid calling the same methods (like `Messages_GetAllChats`) repetitively.
-
-➡️ Use Ctrl-F to search this page for the example matching your needs
-WTelegramClient covers 100% of Telegram Client API, much more than the examples below: check the [full API methods list](https://corefork.telegram.org/methods)!
-More examples can also be found in the [Examples folder](https://github.com/wiz0u/WTelegramClient/tree/master/Examples) and in answers to [StackOverflow questions](https://stackoverflow.com/questions/tagged/wtelegramclient).
-
-
-## Change logging settings
-By default, WTelegramClient logs are displayed on the Console screen.
-If you are not in a Console app or don't want the logs on screen, you can redirect them as you prefer:
-
-```csharp
-// • Log to file in replacement of default Console screen logging, using this static variable:
-static StreamWriter WTelegramLogs = new StreamWriter("WTelegram.log", true, Encoding.UTF8) { AutoFlush = true };
-...
-WTelegram.Helpers.Log = (lvl, str) => WTelegramLogs.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{"TDIWE!"[lvl]}] {str}");
-
-// • Log to VS Output debugging pane in addition (+=) to the default Console screen logging:
-WTelegram.Helpers.Log += (lvl, str) => System.Diagnostics.Debug.WriteLine(str);
-
-// • In ASP.NET service, you will typically send logs to an ILogger:
-WTelegram.Helpers.Log = (lvl, str) => _logger.Log((LogLevel)lvl, str);
-
-// • Disable logging (⛔️𝗗𝗢𝗡'𝗧 𝗗𝗢 𝗧𝗛𝗜𝗦 as you won't be able to diagnose any upcoming problem):
-WTelegram.Helpers.Log = (lvl, str) => { };
-```
-
-The `lvl` argument correspond to standard [LogLevel values](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel#fields)
-
-
-## Send a message to someone by @username
-```csharp
-var resolved = await client.Contacts_ResolveUsername("JsonDumpBot"); // username without the @
-await client.SendMessageAsync(resolved, "/start");
-```
-*Note: This also works if the @username points to a channel/group, but you must already have joined that channel before sending a message to it.
-If the username is invalid/unused, the API call raises an RpcException.*
-
-
-
-## Convert message to/from HTML or Markdown, and send it to ourself (Saved Messages)
-```csharp
-// HTML-formatted text:
-var text = $"Hello dear {HtmlText.Escape(client.User.first_name)}\n" +
- "Enjoy this userbot written with WTelegramClient";
-var entities = client.HtmlToEntities(ref text);
-var sent = await client.SendMessageAsync(InputPeer.Self, text, entities: entities);
-// if you need to convert a sent/received Message to HTML: (easier to store)
-text = client.EntitiesToHtml(sent.message, sent.entities);
-```
-```csharp
-// Markdown-style text:
-var text2 = $"Hello __dear *{Markdown.Escape(client.User.first_name)}*__\n" +
- "Enjoy this `userbot` written with [WTelegramClient](https://github.com/wiz0u/WTelegramClient)";
-var entities2 = client.MarkdownToEntities(ref text2);
-var sent2 = await client.SendMessageAsync(InputPeer.Self, text2, entities: entities2);
-// if you need to convert a sent/received Message to Markdown: (easier to store)
-text2 = client.EntitiesToMarkdown(sent2.message, sent2.entities);
-```
-See [HTML formatting style](https://core.telegram.org/bots/api/#html-style) and [MarkdownV2 formatting style](https://core.telegram.org/bots/api/#markdownv2-style) for details.
-*Note: For the `tg://user?id=` notation to work, you need to pass the _users dictionary in arguments ([see below](#collect-users-chats))*
-
-
-## List all dialogs (chats/groups/channels/user chat) we are currently in
-```csharp
-var dialogs = await client.Messages_GetAllDialogs();
-foreach (Dialog dialog in dialogs.dialogs)
-{
- switch (dialogs.UserOrChat(dialog))
- {
- case User user when user.IsActive: Console.WriteLine("User " + user); break;
- case ChatBase chat when chat.IsActive: Console.WriteLine(chat); break;
- }
- //var latestMsg = dialogs.messages.FirstOrDefault(m => m.Peer.ID == dialog.Peer.ID && m.ID == dialog.TopMessage);
-}
-```
-
-Notes:
-- The lists returned by Messages_GetAllDialogs contains the `access_hash` for those chats and users.
-- See also the `Main` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L18).
-- To retrieve the dialog information about a specific [peer](README.md#terminology), use `client.Messages_GetPeerDialogs(inputPeer)`
-
-
-## List all chats (groups/channels NOT users) that we joined and send a message to one
-```csharp
-var chats = await client.Messages_GetAllChats();
-foreach (var (id, chat) in chats.chats)
- if (chat.IsActive)
- Console.WriteLine($"{id} : {chat}");
-Console.Write("Choose a chat ID to send a message to: ");
-long chatId = long.Parse(Console.ReadLine());
-await client.SendMessageAsync(chats.chats[chatId], "Hello, World");
-```
-Notes:
-- This list does not include discussions with other users. For this, you need to use [Messages_GetAllDialogs](#list-dialogs).
-- The list returned by Messages_GetAllChats contains the `access_hash` for those chats. Read [FAQ #4](FAQ.md#access-hash) about this.
-- If a basic chat group has been migrated to a supergroup, you may find both the old `Chat` and a `Channel` with different IDs in the `chats.chats` result,
-but the old `Chat` will be marked with flag [deactivated] and should not be used anymore. See [Terminology in ReadMe](README.md#terminology).
-- You can find a longer version of this method call in [Examples/Program_GetAllChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_GetAllChats.cs?ts=4#L31)
-
-
-## List the members from a chat
-For a basic Chat: *(see Terminology in [ReadMe](README.md#terminology))*
-```csharp
-var chatFull = await client.Messages_GetFullChat(1234567890); // the chat we want
-foreach (var (id, user) in chatFull.users)
- Console.WriteLine(user);
-```
-
-For a Channel/Group:
-```csharp
-var chats = await client.Messages_GetAllChats();
-var channel = (Channel)chats.chats[1234567890]; // the channel we want
-for (int offset = 0; ;)
-{
- var participants = await client.Channels_GetParticipants(channel, null, offset);
- foreach (var (id, user) in participants.users)
- Console.WriteLine(user);
- offset += participants.participants.Length;
- if (offset >= participants.count || participants.participants.Length == 0) break;
-}
-```
-
-For big Channel/Group, Telegram servers might limit the number of members you can obtain with the normal above method.
-In this case, you can use the following helper method, but it can take several minutes to complete:
-```csharp
-var chats = await client.Messages_GetAllChats();
-var channel = (Channel)chats.chats[1234567890]; // the channel we want
-var participants = await client.Channels_GetAllParticipants(channel);
-```
-
-You can use specific filters, for example to list only the channel owner/admins:
-```csharp
-var participants = await client.Channels_GetParticipants(channel, filter: new ChannelParticipantsAdmins());
-foreach (var participant in participants.participants) // This is the better way to enumerate the result
-{
- var user = participants.users[participant.UserID];
- if (participant is ChannelParticipantCreator cpc) Console.WriteLine($"{user} is the owner '{cpc.rank}'");
- else if (participant is ChannelParticipantAdmin cpa) Console.WriteLine($"{user} is admin '{cpa.rank}'");
-}
-```
-*Note: It is not possible to list only the Deleted Accounts. Those will be automatically removed by Telegram from your group after a while*
-
-
-## Fetch all messages (history) from a chat/user
-```csharp
-var chats = await client.Messages_GetAllChats();
-InputPeer peer = chats.chats[1234567890]; // the chat (or User) we want
-for (int offset_id = 0; ;)
-{
- var messages = await client.Messages_GetHistory(peer, offset_id);
- if (messages.Messages.Length == 0) break;
- foreach (var msgBase in messages.Messages)
- {
- var from = messages.UserOrChat(msgBase.From ?? msgBase.Peer); // from can be User/Chat/Channel
- if (msgBase is Message msg)
- Console.WriteLine($"{from}> {msg.message} {msg.media}");
- else if (msgBase is MessageService ms)
- Console.WriteLine($"{from} [{ms.action.GetType().Name[13..]}]");
- }
- offset_id = messages.Messages[^1].ID;
-}
-```
-Notes:
-- `peer` can also be a User, obtained through methods like [`Messages_GetAllDialogs`](#list-dialogs)
-- To stop at a specific msg ID, use Messages_GetHistory `min_id` argument. For example, `min_id: dialog.read_inbox_max_id`
-- To mark the message history as read, use: `await client.ReadHistory(peer);`
-
-
-## Monitor all Telegram events happening for the user
-
-This is done through the `client.OnUpdates` callback event, or via the [UpdateManager class](FAQ.md#manager) that simplifies the handling of updates.
-
-See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21).
-
-
-## Monitor new messages being posted in chats in real-time
-
-You have to handle update events containing an `UpdateNewMessage`.
-This can be done through the `client.OnUpdates` callback event, or via the [UpdateManager class](FAQ.md#manager) that simplifies the handling of updates.
-
-See the `HandleMessage` method in [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21).
-
-You can filter specific chats the message are posted in, by looking at the `Message.peer_id` field.
-See also [explanation below](#message-user) to extract user/chat info from messages.
-
-
-## Downloading photos, medias, files
-
-This is done using the helper method `client.DownloadFileAsync(file, outputStream)`
-that simplifies the download of a photo/document/file once you get a reference to its location *(through updates or API calls)*.
-
-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. Example: `(t,s) => ct.ThrowIfCancellationRequested()`_
-
-
-## Upload a media file and post it with caption to a chat
-```csharp
-const int ChatId = 1234567890; // the chat we want
-const string Filepath = @"C:\...\photo.jpg";
-
-var chats = await client.Messages_GetAllChats();
-InputPeer peer = chats.chats[ChatId];
-var inputFile = await client.UploadFileAsync(Filepath);
-await client.SendMediaAsync(peer, "Here is the photo", inputFile);
-```
-
-
-## 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.*
-
-
-## Send a grouped media album using photos from various sources
-```csharp
-// Photo 1 already on Telegram: latest photo found in the user's Saved Messages
-var history = await client.Messages_GetHistory(InputPeer.Self);
-PhotoBase photoFromTelegram = history.Messages.OfType().Select(m => m.media).OfType().First().photo;
-// Photo 2 uploaded now from our computer:
-var uploadedFile = await client.UploadFileAsync(@"C:\Pictures\flower.jpg");
-// Photo 3 specified by external url:
-const string photoUrl = "https://picsum.photos/310/200.jpg";
-
-var inputMedias = new List
-{
- photoFromTelegram, // PhotoBase has implicit conversion to InputMediaPhoto
- new InputMediaUploadedPhoto { file = uploadedFile },
- new InputMediaPhotoExternal { url = photoUrl },
- //or Document, InputMediaDocument, InputMediaUploadedDocument, InputMediaDocumentExternal...
-};
-await client.SendAlbumAsync(InputPeer.Self, inputMedias, "My first album");
-```
-*Note: Don't mix Photos and file Documents in your album, it doesn't work well*
-
-
-## Forward or copy a message to another chat
-```csharp
-// Determine which chats and message to forward/copy
-var chats = await client.Messages_GetAllChats();
-var from_chat = chats.chats[1234567890]; // source chat
-var to_chat = chats.chats[1234567891]; // destination chat
-var history = await client.Messages_GetHistory(from_chat, limit: 1);
-var msg = history.Messages[0] as Message; // last message of source chat
-
-// • Forward the message (only the source message id is necessary)
-await client.ForwardMessagesAsync(from_chat, [msg.ID], to_chat);
-
-// • Copy the message without the "Forwarded" header (only the source message id is necessary)
-await client.ForwardMessagesAsync(from_chat, [msg.ID], to_chat, drop_author: true);
-
-// • Alternative solution to copy the message (the full message is needed)
-await client.SendMessageAsync(to_chat, msg.message, msg.media?.ToInputMedia(), entities: msg.entities);
-```
-
-
-## Schedule a message to be sent to a chat
-```csharp
-var chats = await client.Messages_GetAllChats();
-InputPeer peer = chats.chats[1234567890]; // the chat we want
-DateTime when = DateTime.UtcNow.AddMinutes(3);
-await client.SendMessageAsync(peer, "This will be posted in 3 minutes", schedule_date: when);
-```
-*Note: Make sure your computer clock is synchronized with Internet time*
-
-
-## Fun with stickers, GIFs, dice, and animated emojies
-```csharp
-// • List all stickerSets the user has added to his account
-var allStickers = await client.Messages_GetAllStickers();
-foreach (var stickerSet in allStickers.sets)
- Console.WriteLine($"Pack {stickerSet.short_name} contains {stickerSet.count} stickers");
-//if you need details on each: var sticketSetDetails = await client.Messages_GetStickerSet(stickerSet);
-
-// • Send a random sticker from the user's favorites stickers
-var favedStickers = await client.Messages_GetFavedStickers();
-var stickerDoc = favedStickers.stickers[new Random().Next(favedStickers.stickers.Length)];
-await client.SendMessageAsync(InputPeer.Self, null, stickerDoc);
-
-// • Send a specific sticker given the stickerset shortname and emoticon
-var friendlyPanda = await client.Messages_GetStickerSet("Friendly_Panda");
-var laughId = friendlyPanda.packs.First(p => p.emoticon == "😂").documents[0];
-var laughDoc = friendlyPanda.documents.First(d => d.ID == laughId);
-await client.SendMessageAsync(InputPeer.Self, null, laughDoc);
-
-// • Send a GIF from an internet URL
-await client.SendMessageAsync(InputPeer.Self, null, new InputMediaDocumentExternal
- { url = "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" });
-
-// • Send a GIF stored on the computer (you can save inputFile for later reuse)
-var inputFile = await client.UploadFileAsync(@"C:\Pictures\Rotating_earth_(large).gif");
-await client.SendMediaAsync(InputPeer.Self, null, inputFile);
-
-// • Send a random dice/game-of-chance effect from the list of available "dices", see https://core.telegram.org/api/dice
-var appConfig = await client.Help_GetAppConfig();
-var emojies_send_dice = appConfig.config["emojies_send_dice"] as string[];
-var dice_emoji = emojies_send_dice[new Random().Next(emojies_send_dice.Length)];
-var diceMsg = await client.SendMessageAsync(InputPeer.Self, null, new InputMediaDice { emoticon = dice_emoji });
-Console.WriteLine("Dice result:" + ((MessageMediaDice)diceMsg.media).value);
-
-// • Send an animated emoji with full-screen animation, see https://core.telegram.org/api/animated-emojis#emoji-reactions
-// Please note that on Telegram Desktop, you won't view the effect from the sender user's point of view
-// You can view the effect if you're on Telegram Android, or if you're the receiving user (instead of Self)
-var msg = await client.SendMessageAsync(InputPeer.Self, "🎉");
-await Task.Delay(5000); // wait for classic animation to finish
-await client.SendMessageAsync(InputPeer.Self, "and now, full-screen animation on the above emoji");
-// trigger the full-screen animation,
-var typing = await client.Messages_SetTyping(InputPeer.Self, new SendMessageEmojiInteraction {
- emoticon = "🎉", msg_id = msg.id,
- interaction = new DataJSON { data = @"{""v"":1,""a"":[{""t"":0.0,""i"":1}]}" }
-});
-await Task.Delay(5000);
-```
-
-
-## Fun with custom emojies and reactions on pinned messages
-```csharp
-// • Sending a message with custom emojies in Markdown to ourself:
-var text = "Vicksy says Hi! ";
-var entities = client.MarkdownToEntities(ref text);
-await client.SendMessageAsync(InputPeer.Self, text, entities: entities);
-// also available in HTML: 👋
-
-// • Fetch all available standard emoji reactions
-var all_emoji = await client.Messages_GetAvailableReactions();
-
-var chats = await client.Messages_GetAllChats();
-var chat = chats.chats[1234567890]; // the chat we want
-
-// • Check reactions available in this chat
-var full = await client.GetFullChat(chat);
-Reaction reaction = full.full_chat.AvailableReactions switch
-{
- ChatReactionsSome some => some.reactions[0], // only some reactions are allowed => pick the first
- ChatReactionsAll all => // all reactions are allowed in this chat
- all.flags.HasFlag(ChatReactionsAll.Flags.allow_custom) && client.User.flags.HasFlag(TL.User.Flags.premium)
- ? new ReactionCustomEmoji { document_id = 5190875290439525089 } // we can use custom emoji reactions here
- : new ReactionEmoji { emoticon = all_emoji.reactions[0].reaction }, // else, pick the first standard emoji reaction
- _ => null // reactions are not allowed in this chat
-};
-if (reaction == null) return;
-
-// • Send the selected reaction on the last 2 pinned messages
-var messages = await client.Messages_Search(chat, limit: 2);
-foreach (var msg in messages.Messages)
- await client.Messages_SendReaction(chat, msg.ID, reaction: new[] { reaction });
-```
-*Note: you can find custom emoji document IDs via API methods like [Messages_GetFeaturedEmojiStickers](https://corefork.telegram.org/methods#working-with-custom-animated-emojis) or inspecting messages entities. Access hash is not required*
-
-
-
-## Join a channel/group by their public name or invite link
-* For a public channel/group `@channelname`
-If you have a link of the form `https://t.me/channelname`, you need to extract the `channelname` from the URL.
-You can resolve the channel/group username and join it like this:
-```csharp
-var resolved = await client.Contacts_ResolveUsername("channelname"); // without the @
-if (resolved.Chat is Channel channel)
- await client.Channels_JoinChannel(channel);
-```
-* For a private channel/group/chat, you need to have an invite link
-Telegram invite links can typically have two forms: `https://t․me/joinchat/HASH` or `https://t․me/+HASH` *(note the '+' prefix here)*
-To use them, you need to extract the `HASH` part from the URL and then you can use those two methods:
-```csharp
-var chatInvite = await client.Messages_CheckChatInvite("HASH"); // optional: get information before joining
-await client.Messages_ImportChatInvite("HASH"); // join the channel/group
-// Note: This works also with HASH invite links from public channel/group
-```
-`CheckChatInvite` can return [3 different types of invitation object](https://corefork.telegram.org/type/ChatInvite)
-
-You can also use helper methods `AnalyzeInviteLink` and `GetMessageByLink` to more easily fetch information from links.
-
-## Add/Invite/Remove someone in a chat
-```csharp
-var chats = await client.Messages_GetAllChats();
-var chat = chats.chats[1234567890]; // the target chat
-```
-After the above code, once you [have obtained](FAQ.md#access-hash) an `InputUser` or `User`, you can:
-```csharp
-// • Directly add the user to a Chat/Channel/group:
-var miu = await client.AddChatUser(chat, user);
-// You may get exception USER_NOT_MUTUAL_CONTACT if the user left the chat previously and you want to add him again
-// or a result with miu.missing_invitees listing users that denied the right to be added to a chat
-
-// • Obtain the main invite link for the chat, and send it to the user:
-var mcf = await client.GetFullChat(chat);
-var invite = (ChatInviteExported)mcf.full_chat.ExportedInvite;
-await client.SendMessageAsync(user, "Join our group with this link: " + invite.link);
-
-// • Create a new invite link for the chat/channel, and send it to the user
-var invite = (ChatInviteExported)await client.Messages_ExportChatInvite(chat, title: "MyLink");
-await client.SendMessageAsync(user, "Join our group with this link: " + invite.link);
-// • Revoke then delete that invite link (when you no longer need it)
-await client.Messages_EditExportedChatInvite(chat, invite.link, revoked: true);
-await client.Messages_DeleteExportedChatInvite(chat, invite.link);
-
-// • Remove the user from a Chat/Channel/Group:
-await client.DeleteChatUser(chat, user);
-```
-
-
-## Send a message to someone by phone number
-```csharp
-var contacts = await client.Contacts_ImportContacts(new[] { new InputPhoneContact { phone = "+PHONENUMBER" } });
-if (contacts.imported.Length > 0)
- await client.SendMessageAsync(contacts.users[contacts.imported[0].user_id], "Hello!");
-```
-*Note: Don't use this method too much. To prevent spam, Telegram may restrict your ability to add new phone numbers or ban your account.*
-
-
-## Retrieve the current user's contacts list
-There are two different methods. Here is the simpler one:
-```csharp
-var contacts = await client.Contacts_GetContacts();
-foreach (User contact in contacts.users.Values)
- Console.WriteLine($"{contact} {contact.phone}");
-```
-
-The second method uses the more complex GDPR export, **takeout session** system.
-Here is an example on how to implement it:
-```csharp
-using TL.Methods; // methods as structures, for Invoke* calls
-
-var takeout = await client.Account_InitTakeoutSession(contacts: true);
-var finishTakeout = new Account_FinishTakeoutSession();
-try
-{
- var savedContacts = await client.InvokeWithTakeout(takeout.id, new Contacts_GetSaved());
- foreach (SavedPhoneContact contact in savedContacts)
- Console.WriteLine($"{contact.first_name} {contact.last_name} {contact.phone}, added on {contact.date}");
- finishTakeout.flags = Account_FinishTakeoutSession.Flags.success;
-}
-finally
-{
- await client.InvokeWithTakeout(takeout.id, finishTakeout);
-}
-```
-
-
-
-
-## Collect Users/Chats description structures and access hash
-
-Many API calls return a structure with a `users` and a `chats` field at the root of the structure.
-This is also the case for updates passed to `client.OnUpdates`.
-
-These two dictionaries give details *(including access hash)* about the various users/chats that will be typically referenced in subobjects deeper in the structure,
-typically in the form of a `Peer` object or a `user_id`/`chat_id` field.
-
-In such case, the root structure inherits the `IPeerResolver` interface, and you can use the `UserOrChat(peer)` method to resolve a `Peer`
-into either a `User` or `ChatBase` (`Chat`,`Channel`...) description structure *(depending on the kind of peer it was describing)*
-
-You can also use the `CollectUsersChats` helper method to collect these 2 fields into 2 aggregate dictionaries to remember details
-*(including access hashes)* about all the users/chats you've encountered so far.
-This method also helps dealing with [incomplete `min` structures](https://core.telegram.org/api/min).
-
-Example of usage:
-```csharp
-private Dictionary _users = new();
-private Dictionary _chats = new();
-...
-var dialogs = await client.Messages_GetAllDialogs();
-dialogs.CollectUsersChats(_users, _chats);
-
-private async Task OnUpdates(UpdatesBase updates)
-{
- updates.CollectUsersChats(_users, _chats);
- ...
-}
-
-// example of UserOrChat usage:
-var firstPeer = dialogs.UserOrChat(dialogs.dialogs[0].Peer);
-if (firstPeer is User firstUser) Console.WriteLine($"First dialog is with user {firstUser}");
-else if (firstPeer is ChatBase firstChat) Console.WriteLine($"First dialog is {firstChat}");
-```
-
-*Note: If you need to save/restore those dictionaries between runs of your program, it's up to you to serialize their content to disk*
-
-
-## Get chat and user info from a message
-First, you should read the above [section about collecting users/chats](#collect-users-chats), and the [FAQ about dealing with IDs](FAQ.md#access-hash).
-
-A message contains those two fields/properties:
-- `peer_id`/`Peer` that identify WHERE the message was posted
-- `from_id`/`From` that identify WHO posted the message (it can be `null` in some case of anonymous posting)
-
-These two fields derive from class `Peer` and can be of type `PeerChat`, `PeerChannel` or `PeerUser` depending on the nature of WHERE & WHO
-(private chat with a user? message posted BY a channel IN a chat? ...)
-
-> ✳️ It is recommended that you use the [UpdateManager class](FAQ.md#manager), as it handles automatically all of the details below, and you just need to use `Manager.UserOrChat(peer)` or Manager.Users/Chats dictionaries
-
-The root structure where you obtained the message (typically `UpdatesBase` or `Messages_MessagesBase`) inherits from `IPeerResolver`.
-This allows you to call `.UserOrChat(peer)` on the root structure, in order to resolve those fields into a `User` class, or a `ChatBase`-derived class
-(typically `Chat` or `Channel`) which will give you details about the peer, instead of just the ID, and can be implicitly converted to `InputPeer`.
-
-However, in some case _(typically when dealing with updates)_, Telegram might choose to not include details about a peer
-because it expects you to already know about it (`UserOrChat` returns `null`).
-That's why you should collect users/chats details each time you're dealing with Updates or other API results inheriting from `IPeerResolver`,
-and use the collected dictionaries to find details about users/chats
-([see previous section](#collect-users-chats) and [Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21) example)
-
-And finally, it may happen that you receive updates of type `UpdateShortMessage` or `UpdateShortChatMessage` with totally unknown peers (even in your collected dictionaries).
-In this case, [Telegram recommends](https://core.telegram.org/api/updates#recovering-gaps) that you use the [`Updates_GetDifference`](https://corefork.telegram.org/method/updates.getDifference) method to retrieve the full information associated with the short message.
-Here is an example showing how to deal with `UpdateShortMessage`: (same for `UpdateShortChatMessage`)
-```csharp
-if (updates is UpdateShortMessage usm && !_users.ContainsKey(usm.user_id))
-{
- var fullDiff = await client.Updates_GetDifference(usm.pts - usm.pts_count, usm.date, 0)
- fullDiff.CollectUsersChats(_users, _chats);
-}
-```
-
-
-
-## Use a proxy or MTProxy to connect to Telegram
-SOCKS/HTTPS proxies can be used through the `client.TcpHandler` delegate and a proxy library like [StarkSoftProxy](https://www.nuget.org/packages/StarkSoftProxy/) or [xNetStandard](https://www.nuget.org/packages/xNetStandard/):
-```csharp
-using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
-client.TcpHandler = async (address, port) =>
-{
- var proxy = new Socks5ProxyClient(ProxyHost, ProxyPort, ProxyUsername, ProxyPassword);
- //var proxy = xNet.Socks5ProxyClient.Parse("host:port:username:password");
- return proxy.CreateConnection(address, port);
-};
-await client.LoginUserIfNeeded();
-```
-
-MTProxy (MTProto proxy) can be used to prevent ISP blocking Telegram servers, through the `client.MTProxyUrl` property:
-```csharp
-using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
-client.MTProxyUrl = "https://t.me/proxy?server=...&port=...&secret=...";
-await client.LoginUserIfNeeded();
-```
-You can find a list of working MTProxies in channels like [@ProxyMTProto](https://t.me/s/ProxyMTProto) or [@MTProxyT](https://t.me/s/MTProxyT) *(right-click the "Connect" buttons)*
-If your Telegram client is already connected to such MTPROTO proxy, you can also export its URL by clicking on the shield button  and then **⋮** > **Share**
-
-*Note: WTelegramClient always uses transport obfuscation when connecting to Telegram servers, even without MTProxy*
-
-
-## Change 2FA password
-```csharp
-const string old_password = "password"; // current password if any (unused otherwise)
-const string new_password = "new_password"; // or null to disable 2FA
-var accountPwd = await client.Account_GetPassword();
-var password = accountPwd.current_algo == null ? null : await WTelegram.Client.InputCheckPassword(accountPwd, old_password);
-accountPwd.current_algo = null; // makes InputCheckPassword generate a new password
-var new_password_hash = new_password == null ? null : await WTelegram.Client.InputCheckPassword(accountPwd, new_password);
-await client.Account_UpdatePasswordSettings(password, new Account_PasswordInputSettings
-{
- flags = Account_PasswordInputSettings.Flags.has_new_algo,
- new_password_hash = new_password_hash?.A,
- new_algo = accountPwd.new_algo,
- hint = "new password hint",
-});
-```
-
-
-## Store session data to database or elsewhere, instead of files
-
-If you don't want to store session data into files *(for example if your VPS Hosting doesn't allow that)*, or just for easier management,
-you can choose to store the session data somewhere else, like in a database.
-
-The WTelegram.Client constructor takes an optional `sessionStore` parameter to allow storing sessions in an alternate manner.
-Use it to pass a custom Stream-derived class that will **read** (first initial call to Length & Read) and **store** (subsequent Writes) session data to database.
-
-You can find an example for such custom session store in [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs?ts=4#L61)
-
-
-## Send/receive end-to-end encrypted messages & files in Secret Chats
-
-This can be done easily using the helper class `WTelegram.SecretChats` offering methods to manage/encrypt/decrypt secret chats & encrypted messages/files.
-
-You can view a full working example at [Examples/Program_SecretChats.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_SecretChats.cs?ts=4#L11).
-
-Secret Chats have been tested successfully with Telegram Android & iOS official clients.
-You can also check our [FAQ for more implementation details](FAQ.md#14-secret-chats-implementation-details).
diff --git a/Examples/ASPnet_webapp.zip b/Examples/ASPnet_webapp.zip
deleted file mode 100644
index f1183ce..0000000
Binary files a/Examples/ASPnet_webapp.zip and /dev/null differ
diff --git a/Examples/Program_DownloadSavedMedia.cs b/Examples/Program_DownloadSavedMedia.cs
deleted file mode 100644
index 00022aa..0000000
--- a/Examples/Program_DownloadSavedMedia.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using TL;
-
-namespace WTelegramClientTest
-{
- static class Program_DownloadSavedMedia
- {
- // go to Project Properties > Debug > Launch Profiles > Environment variables and add at least these: api_id, api_hash, phone_number
- static async Task Main(string[] _)
- {
- Console.WriteLine("The program will download photos/medias from messages you send/forward to yourself (Saved Messages)");
- var cts = new CancellationTokenSource();
- await using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
- var user = await client.LoginUserIfNeeded();
- client.OnUpdates += Client_OnUpdates;
- Console.ReadKey();
- cts.Cancel();
-
- async Task Client_OnUpdates(UpdatesBase updates)
- {
- foreach (var update in updates.UpdateList)
- {
- if (update is not UpdateNewMessage { message: Message message })
- continue; // if it's not about a new message, ignore the update
- if (message.peer_id.ID != user.ID)
- continue; // if it's not in the "Saved messages" chat, ignore it
-
- if (message.media is MessageMediaDocument { document: Document document })
- {
- var filename = document.Filename; // use document original filename, or build a name from document ID & MIME type:
- filename ??= $"{document.id}.{document.mime_type[(document.mime_type.IndexOf('/') + 1)..]}";
- Console.WriteLine("Downloading " + filename);
- using var fileStream = File.Create(filename);
- await client.DownloadFileAsync(document, fileStream, progress: (p, t) => cts.Token.ThrowIfCancellationRequested());
- Console.WriteLine("Download finished");
- }
- else if (message.media is MessageMediaPhoto { photo: Photo photo })
- {
- var filename = $"{photo.id}.jpg";
- Console.WriteLine("Downloading " + filename);
- using var fileStream = File.Create(filename);
- var type = await client.DownloadFileAsync(photo, fileStream);
- fileStream.Close(); // necessary for the renaming
- Console.WriteLine("Download finished");
- if (type is not Storage_FileType.unknown and not Storage_FileType.partial)
- File.Move(filename, $"{photo.id}.{type}", true); // rename extension
- }
- }
- }
- }
- }
-}
diff --git a/Examples/Program_GetAllChats.cs b/Examples/Program_GetAllChats.cs
deleted file mode 100644
index f9ce1ce..0000000
--- a/Examples/Program_GetAllChats.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using TL;
-
-namespace WTelegramClientTest
-{
- static class Program_GetAllChats
- {
- // This code is similar to what you should have obtained if you followed the README introduction
- // I've just added a few comments to explain further what's going on
-
- // go to Project Properties > Debug > Launch Profiles > Environment variables and add at least these: api_id, api_hash, phone_number
- static string Config(string what)
- {
- if (what == "api_id") return Environment.GetEnvironmentVariable("api_id");
- if (what == "api_hash") return Environment.GetEnvironmentVariable("api_hash");
- if (what == "phone_number") return Environment.GetEnvironmentVariable("phone_number");
- if (what == "verification_code") return null; // let WTelegramClient ask the user with a console prompt
- if (what == "first_name") return "John"; // if sign-up is required
- if (what == "last_name") return "Doe"; // if sign-up is required
- if (what == "password") return "secret!"; // if user has enabled 2FA
- return null;
- }
-
- static async Task Main(string[] _)
- {
- await using var client = new WTelegram.Client(Config);
- var user = await client.LoginUserIfNeeded();
- Console.WriteLine($"We are logged-in as {user.username ?? user.first_name + " " + user.last_name} (id {user.id})");
-
- var chats = await client.Messages_GetAllChats(); // chats = groups/channels (does not include users dialogs)
- Console.WriteLine("This user has joined the following:");
- foreach (var (id, chat) in chats.chats)
- switch (chat)
- {
- case Chat smallgroup when smallgroup.IsActive:
- Console.WriteLine($"{id}: Small group: {smallgroup.title} with {smallgroup.participants_count} members");
- break;
- case Channel channel when channel.IsChannel:
- Console.WriteLine($"{id}: Channel {channel.username}: {channel.title}");
- //Console.WriteLine($" → access_hash = {channel.access_hash:X}");
- break;
- case Channel group: // no broadcast flag => it's a big group, also called supergroup or megagroup
- Console.WriteLine($"{id}: Group {group.username}: {group.title}");
- //Console.WriteLine($" → access_hash = {group.access_hash:X}");
- break;
- }
-
- Console.Write("Type a chat ID to send a message: ");
- long chatId = long.Parse(Console.ReadLine());
- var target = chats.chats[chatId];
- Console.WriteLine($"Sending a message in chat {chatId}: {target.Title}");
- // Next line implicitely creates an adequate InputPeer from ChatBase: (with the access_hash if these is one)
- InputPeer peer = target;
- await client.SendMessageAsync(peer, "Hello, World");
- }
- }
-}
diff --git a/Examples/Program_Heroku.cs b/Examples/Program_Heroku.cs
deleted file mode 100644
index 4ad3740..0000000
--- a/Examples/Program_Heroku.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using Npgsql;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using TL;
-
-// This is an example userbot designed to run on a Heroku account with a PostgreSQL database for session storage
-// This userbot simply answer "Pong" when someone sends him a "Ping" private message (or in Saved Messages)
-// To use/install/deploy this userbot ➡️ follow the steps at the end of this file
-// When run locally, close the window or type ALT-F4 to exit cleanly and save session (similar to Heroku SIGTERM)
-
-namespace WTelegramClientTest
-{
- static class Program_Heroku
- {
- static WTelegram.Client Client;
- static User My;
- static readonly Dictionary Users = [];
- static readonly Dictionary Chats = [];
-
- // See steps at the end of this file to setup required Environment variables
- static async Task Main(string[] _)
- {
- var exit = new SemaphoreSlim(0);
- AppDomain.CurrentDomain.ProcessExit += (s, e) => exit.Release(); // detect SIGTERM to exit gracefully
- var store = new PostgreStore(Environment.GetEnvironmentVariable("DATABASE_URL"), Environment.GetEnvironmentVariable("SESSION_NAME"));
- // if DB does not contain a session yet, client will be run in interactive mode
- Client = new WTelegram.Client(store.Length == 0 ? null : Environment.GetEnvironmentVariable, store);
- await using (Client)
- {
- Client.OnUpdates += Client_OnUpdates;
- My = await Client.LoginUserIfNeeded();
- Console.WriteLine($"We are logged-in as {My.username ?? My.first_name + " " + My.last_name} (id {My.id})");
- var dialogs = await Client.Messages_GetAllDialogs();
- dialogs.CollectUsersChats(Users, Chats);
- await exit.WaitAsync();
- }
- }
-
- private static async Task Client_OnUpdates(UpdatesBase updates)
- {
- updates.CollectUsersChats(Users, Chats);
- foreach (var update in updates.UpdateList)
- {
- Console.WriteLine(update.GetType().Name);
- if (update is UpdateNewMessage { message: Message { peer_id: PeerUser { user_id: var user_id } } msg }) // private message
- if (!msg.flags.HasFlag(Message.Flags.out_)) // ignore our own outgoing messages
- if (Users.TryGetValue(user_id, out var user))
- {
- Console.WriteLine($"New message from {user}: {msg.message}");
- if (msg.message.Equals("Ping", StringComparison.OrdinalIgnoreCase))
- await Client.SendMessageAsync(user, "Pong");
- }
- }
- }
- }
-
- #region PostgreSQL session store
- class PostgreStore : Stream
- {
- private readonly NpgsqlConnection _sql;
- private readonly string _sessionName;
- private readonly byte[] _data;
- private readonly int _dataLen;
-
- /// Heroku DB URL of the form "postgres://user:password@host:port/database"
- /// Entry name for the session data in the WTelegram_sessions table (default: "Heroku")
- public PostgreStore(string databaseUrl, string sessionName = null)
- {
- _sessionName = sessionName ?? "Heroku";
- var parts = databaseUrl.Split(':', '/', '@');
- _sql = new NpgsqlConnection($"User ID={parts[3]};Password={parts[4]};Host={parts[5]};Port={parts[6]};Database={parts[7]};Pooling=true;SSL Mode=Require;Trust Server Certificate=True;");
- _sql.Open();
- using (var create = new NpgsqlCommand("CREATE TABLE IF NOT EXISTS WTelegram_sessions (name text NOT NULL PRIMARY KEY, data bytea)", _sql))
- create.ExecuteNonQuery();
- using var cmd = new NpgsqlCommand($"SELECT data FROM WTelegram_sessions WHERE name = '{_sessionName}'", _sql);
- using var rdr = cmd.ExecuteReader();
- if (rdr.Read())
- _dataLen = (_data = rdr[0] as byte[]).Length;
- }
-
- protected override void Dispose(bool disposing)
- {
- _sql.Dispose();
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- Array.Copy(_data, 0, buffer, offset, count);
- return count;
- }
-
- public override void Write(byte[] buffer, int offset, int count) // Write call and buffer modifications are done within a lock()
- {
- using var cmd = new NpgsqlCommand($"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data", _sql);
- cmd.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
- cmd.ExecuteNonQuery();
- }
-
- public override long Length => _dataLen;
- public override long Position { get => 0; set { } }
- public override bool CanSeek => false;
- public override bool CanRead => true;
- public override bool CanWrite => true;
- public override long Seek(long offset, SeekOrigin origin) => 0;
- public override void SetLength(long value) { }
- public override void Flush() { }
- }
- #endregion
-}
-
-/******************************************************************************************************************************
-HOW TO USE AND DEPLOY THIS EXAMPLE HEROKU USERBOT:
-- From your Heroku.com account dashboard, create a new app
-- Navigate to the app Resources and add the add-on "Heroku Postgres"
-- Navigate to the app Settings, click Reveal Config Vars and save the Heroku git URL and the value of DATABASE_URL
-- Add a new var named "api_hash" with your api hash obtained from https://my.telegram.org/apps
-- Add a new var named "phone_number" with the phone_number of the user this userbot will manage
-- Scroll down to Buildpacks and add this URL: https://github.com/jincod/dotnetcore-buildpack.git
-- In Visual Studio, Clone the Heroku git repository and add some standard .gitignore .gitattributes files
-- In this repository folder, create a new .NET Console project with this Program.cs file
-- Add these Nuget packages: WTelegramClient and Npgsql
-- In Project properties > Debug > Launch Profiles > Environment variables, configure the same values for DATABASE_URL, api_hash, phone_number
-- Run the project in Visual Studio. The first time, it should ask you interactively for elements to complete the connection
-- On the following runs, the PostgreSQL database contains the session data and it should connect automatically
-- You can test the userbot by sending him "Ping" in private message (or saved messages). It should respond with "Pong"
-- You can now commit & push your git sources to Heroku, they will be compiled and deployed/run as a web app
-- The userbot should work fine for 1 minute, but it will be taken down because it is not a web app. Let's fix that
-- Navigate to the app Resources, copy the line starting with: web cd $HOME/.......
-- Back in Visual Studio (or Explorer), at the root of the repository create a Procfile text file (without extension)
-- Paste inside the line you copied, and replace the initial "web" with "worker:" (don't forget the colon)
-- Commit and push the Git changes to Heroku. Wait until the deployment is complete.
-- Verify that the Resources "web" line has changed to "worker" and is enabled (use the pencil icon if necessary)
-- Now your userbot should be running 24/7.
-- To prevent AUTH_KEY_DUPLICATED issues, set a SESSION_NAME env variable in your local VS project with a value like "PC"
-DISCLAIMER: I'm not affiliated nor expert with Heroku, so if you have any problem with the above I might not be able to help
-******************************************************************************************************************************/
diff --git a/Examples/Program_ListenUpdates.cs b/Examples/Program_ListenUpdates.cs
index 59abe49..d4ebebb 100644
--- a/Examples/Program_ListenUpdates.cs
+++ b/Examples/Program_ListenUpdates.cs
@@ -1,72 +1,103 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using TL;
namespace WTelegramClientTest
{
- static class Program_ListenUpdates
+ class Program_ListenUpdates
{
- static WTelegram.Client Client;
- static WTelegram.UpdateManager Manager;
- static User My;
+ static string Config(string what)
+ {
+ // go to Project Properties > Debug > Environment variables and add at least these: api_id, api_hash, phone_number
+ if (what == "verification_code") { Console.Write("Code: "); return Console.ReadLine(); }
+ return Environment.GetEnvironmentVariable(what);
+ }
- // go to Project Properties > Debug > Launch Profiles > Environment variables and add at least these: api_id, api_hash, phone_number
static async Task Main(string[] _)
{
Console.WriteLine("The program will display updates received for the logged-in user. Press any key to terminate");
- WTelegram.Helpers.Log = (l, s) => System.Diagnostics.Debug.WriteLine(s);
- Client = new WTelegram.Client(Environment.GetEnvironmentVariable);
- await using (Client)
- {
- Manager = Client.WithUpdateManager(Client_OnUpdate/*, "Updates.state"*/);
- My = await Client.LoginUserIfNeeded();
- // Note: on login, Telegram may sends a bunch of updates/messages that happened in the past and were not acknowledged
- Console.WriteLine($"We are logged-in as {My.username ?? My.first_name + " " + My.last_name} (id {My.id})");
- // We collect all infos about the users/chats so that updates can be printed with their names
- var dialogs = await Client.Messages_GetAllDialogs(); // dialogs = groups/channels/users
- dialogs.CollectUsersChats(Manager.Users, Manager.Chats);
-
- Console.ReadKey();
- } // WTelegram.Client gets disposed when exiting this scope
-
- //Manager.SaveState("Updates.state"); // if you want to resume missed updates on the next run (see WithUpdateManager above)
+ WTelegram.Helpers.Log += (l, s) => System.Diagnostics.Debug.WriteLine(s);
+ using var client = new WTelegram.Client(Config) { CollectAccessHash = true };
+ client.Update += Client_Update;
+ await client.ConnectAsync();
+ var my = await client.LoginUserIfNeeded();
+ users[my.id] = my;
+ // note that on logging, Telegram may sends a bunch of updates/messages that happened in the past and were not acknowledged
+ Console.WriteLine($"We are logged-in as {my.username ?? my.first_name + " " + my.last_name} (id {my.id})");
+ var dialogsBase = await client.Messages_GetDialogs(default, 0, null, 0, 0);
+ if (dialogsBase is Messages_Dialogs dialogs)
+ while (dialogs.dialogs.Length != 0)
+ {
+ foreach (var user in dialogs.users) users[user.ID] = user;
+ foreach (var chat in dialogs.chats) chats[chat.ID] = chat;
+ var lastDialog = (Dialog)dialogs.dialogs[^1];
+ var lastMsg = dialogs.messages.LastOrDefault(m => m.Peer.ID == lastDialog.peer.ID && m.ID == lastDialog.top_message);
+ InputPeer offsetPeer = lastDialog.peer is PeerUser pu ? dialogs.users.First(u => u.ID == pu.ID)
+ : dialogs.chats.First(u => u.ID == lastDialog.peer.ID);
+ dialogs = (Messages_Dialogs)await client.Messages_GetDialogs(lastMsg?.Date ?? default, lastDialog.top_message, offsetPeer, 500, 0);
+ }
+ Console.ReadKey();
+ await client.Ping(43); // dummy API call.. this is used to force an acknowledge on this session's updates
}
- // if not using async/await, we could just return Task.CompletedTask
- private static async Task Client_OnUpdate(Update update)
+
+ private static readonly Dictionary users = new();
+ private static readonly Dictionary chats = new();
+ private static string AUser(long user_id) => users.TryGetValue(user_id, out var user) ? user.DisplayName : $"User {user_id}";
+ private static string AChat(long chat_id) => chats.TryGetValue(chat_id, out var chat) ? chat.Title : $"Chat {chat_id}";
+ private static string APeer(Peer peer) => peer is null ? null : peer is PeerUser user ? AUser(user.user_id)
+ : peer is PeerChat chat ? AChat(chat.chat_id) : peer is PeerChannel channel ? AChat(channel.channel_id) : $"Peer {peer.ID}";
+
+ private static void Client_Update(ITLObject arg)
+ {
+ switch (arg)
+ {
+ case UpdateShortMessage usm: Console.WriteLine($"{AUser(usm.user_id)}> {usm.message}"); break;
+ case UpdateShortChatMessage uscm: Console.WriteLine($"{AUser(uscm.from_id)} in {AChat(uscm.chat_id)}> {uscm.message}"); break;
+ case UpdateShortSentMessage: Console.WriteLine($"You sent a message"); break;
+ case UpdateShort updateShort: DisplayUpdate(updateShort.update); break;
+ case Updates u:
+ foreach (var user in u.users) users[user.ID] = user;
+ foreach (var chat in u.chats) chats[chat.ID] = chat;
+ foreach (var update in u.updates) DisplayUpdate(update);
+ break;
+ case UpdatesCombined uc:
+ foreach (var user in uc.users) users[user.ID] = user;
+ foreach (var chat in uc.chats) chats[chat.ID] = chat;
+ foreach (var update in uc.updates) DisplayUpdate(update);
+ break;
+ default: Console.WriteLine(arg.GetType().Name); break;
+ }
+ }
+
+ private static void DisplayUpdate(Update update)
{
switch (update)
{
- case UpdateNewMessage unm: await HandleMessage(unm.message); break;
- case UpdateEditMessage uem: await HandleMessage(uem.message, true); break;
- // Note: UpdateNewChannelMessage and UpdateEditChannelMessage are also handled by above cases
- case UpdateDeleteChannelMessages udcm: Console.WriteLine($"{udcm.messages.Length} message(s) deleted in {Chat(udcm.channel_id)}"); break;
+ case UpdateNewMessage unm: DisplayMessage(unm.message); break;
+ case UpdateEditMessage uem: Console.Write("(Edit): "); DisplayMessage(uem.message); break;
+ case UpdateDeleteChannelMessages udcm: Console.WriteLine($"{udcm.messages.Length} message(s) deleted in {AChat(udcm.channel_id)}"); break;
case UpdateDeleteMessages udm: Console.WriteLine($"{udm.messages.Length} message(s) deleted"); break;
- case UpdateUserTyping uut: Console.WriteLine($"{User(uut.user_id)} is {uut.action}"); break;
- case UpdateChatUserTyping ucut: Console.WriteLine($"{Peer(ucut.from_id)} is {ucut.action} in {Chat(ucut.chat_id)}"); break;
- case UpdateChannelUserTyping ucut2: Console.WriteLine($"{Peer(ucut2.from_id)} is {ucut2.action} in {Chat(ucut2.channel_id)}"); break;
- case UpdateChatParticipants { participants: ChatParticipants cp }: Console.WriteLine($"{cp.participants.Length} participants in {Chat(cp.chat_id)}"); break;
- case UpdateUserStatus uus: Console.WriteLine($"{User(uus.user_id)} is now {uus.status.GetType().Name[10..]}"); break;
- case UpdateUserName uun: Console.WriteLine($"{User(uun.user_id)} has changed profile name: {uun.first_name} {uun.last_name}"); break;
- case UpdateUser uu: Console.WriteLine($"{User(uu.user_id)} has changed infos/photo"); break;
- default: Console.WriteLine(update.GetType().Name); break; // there are much more update types than the above example cases
+ case UpdateUserTyping uut: Console.WriteLine($"{AUser(uut.user_id)} is {uut.action.GetType().Name[11..^6]}"); break;
+ case UpdateChatUserTyping ucut: Console.WriteLine($"{APeer(ucut.from_id)} is {ucut.action.GetType().Name[11..^6]} in {AChat(ucut.chat_id)}"); break;
+ case UpdateChannelUserTyping ucut2: Console.WriteLine($"{APeer(ucut2.from_id)} is {ucut2.action.GetType().Name[11..^6]} in {AChat(ucut2.channel_id)}"); break;
+ case UpdateChatParticipants { participants: ChatParticipants cp }: Console.WriteLine($"{cp.participants.Length} participants in {AChat(cp.chat_id)}"); break;
+ case UpdateUserStatus uus: Console.WriteLine($"{AUser(uus.user_id)} is now {uus.status.GetType().Name[10..]}"); break;
+ case UpdateUserName uun: Console.WriteLine($"{AUser(uun.user_id)} has changed profile name: @{uun.username} {uun.first_name} {uun.last_name}"); break;
+ case UpdateUserPhoto uup: Console.WriteLine($"{AUser(uup.user_id)} has changed profile photo"); break;
+ default: Console.WriteLine(update.GetType().Name); break;
}
}
- // in this example method, we're not using async/await, so we just return Task.CompletedTask
- private static Task HandleMessage(MessageBase messageBase, bool edit = false)
+ private static void DisplayMessage(MessageBase messageBase)
{
- if (edit) Console.Write("(Edit): ");
switch (messageBase)
{
- case Message m: Console.WriteLine($"{Peer(m.from_id) ?? m.post_author} in {Peer(m.peer_id)}> {m.message}"); break;
- case MessageService ms: Console.WriteLine($"{Peer(ms.from_id)} in {Peer(ms.peer_id)} [{ms.action.GetType().Name[13..]}]"); break;
+ case Message m: Console.WriteLine($"{APeer(m.from_id) ?? m.post_author} in {APeer(m.peer_id)}> {m.message}"); break;
+ case MessageService ms: Console.WriteLine($"{APeer(ms.from_id)} in {APeer(ms.peer_id)} [{ms.action.GetType().Name[13..]}]"); break;
}
- return Task.CompletedTask;
}
-
- private static string User(long id) => Manager.Users.TryGetValue(id, out var user) ? user.ToString() : $"User {id}";
- private static string Chat(long id) => Manager.Chats.TryGetValue(id, out var chat) ? chat.ToString() : $"Chat {id}";
- private static string Peer(Peer peer) => Manager.UserOrChat(peer)?.ToString() ?? $"Peer {peer?.ID}";
}
}
diff --git a/Examples/Program_ReactorError.cs b/Examples/Program_ReactorError.cs
deleted file mode 100644
index d84b8a0..0000000
--- a/Examples/Program_ReactorError.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using TL;
-
-namespace WTelegramClientTest
-{
- static class Program_ReactorError
- {
- static WTelegram.Client Client;
-
- // go to Project Properties > Debug > Launch Profiles > Environment variables and add at least these: api_id, api_hash, phone_number
- static async Task Main(string[] _)
- {
- Console.WriteLine("The program demonstrate how to handle ReactorError. Press any key to terminate");
- WTelegram.Helpers.Log = (l, s) => System.Diagnostics.Debug.WriteLine(s);
- try
- {
- await CreateAndConnect();
- Console.ReadKey();
- }
- finally
- {
- if (Client != null) await Client.DisposeAsync();
- }
- }
-
- private static async Task CreateAndConnect()
- {
- Client = new WTelegram.Client(Environment.GetEnvironmentVariable);
- Client.OnUpdates += Client_OnUpdates;
- Client.OnOther += Client_OnOther;
- var my = await Client.LoginUserIfNeeded();
- Console.WriteLine($"We are logged-in as " + my);
- }
-
- private static async Task Client_OnOther(IObject arg)
- {
- if (arg is ReactorError err)
- {
- // typically: network connection was totally lost
- Console.WriteLine($"Fatal reactor error: {err.Exception.Message}");
- while (true)
- {
- Console.WriteLine("Disposing the client and trying to reconnect in 5 seconds...");
- if (Client != null) await Client.DisposeAsync();
- Client = null;
- await Task.Delay(5000);
- try
- {
- await CreateAndConnect();
- break;
- }
- catch (Exception ex) when (ex is not ObjectDisposedException)
- {
- Console.WriteLine("Connection still failing: " + ex.Message);
- }
- }
- }
- else
- Console.WriteLine("Other: " + arg.GetType().Name);
- }
-
- private static Task Client_OnUpdates(UpdatesBase updates)
- {
- foreach (var update in updates.UpdateList)
- Console.WriteLine(update.GetType().Name);
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Examples/Program_SecretChats.cs b/Examples/Program_SecretChats.cs
deleted file mode 100644
index cff9092..0000000
--- a/Examples/Program_SecretChats.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using TL;
-using WTelegram;
-
-namespace WTelegramClientTest
-{
- static class Program_SecretChats
- {
- static Client Client;
- static SecretChats Secrets;
- static ISecretChat ActiveChat; // the secret chat currently selected
- static readonly Dictionary Users = [];
- static readonly Dictionary Chats = [];
-
- // go to Project Properties > Debug > Launch Profiles > Environment variables and add at least these: api_id, api_hash, phone_number
- static async Task Main()
- {
- Helpers.Log = (l, s) => System.Diagnostics.Debug.WriteLine(s);
- Client = new Client(Environment.GetEnvironmentVariable);
- Secrets = new SecretChats(Client, "Secrets.bin");
- AppDomain.CurrentDomain.ProcessExit += (s, e) => { Secrets.Dispose(); Client.Dispose(); };
- SelectActiveChat();
-
- Client.OnUpdates += Client_OnUpdates;
- var myself = await Client.LoginUserIfNeeded();
- Users[myself.id] = myself;
- Console.WriteLine($"We are logged-in as {myself}");
-
- var dialogs = await Client.Messages_GetAllDialogs(); // load the list of users/chats
- dialogs.CollectUsersChats(Users, Chats);
- Console.WriteLine(@"Available commands:
-/request Initiate Secret Chat with user (see /users)
-/discard [delete] Terminate active secret chat [and delete history]
-/select Select another Secret Chat
-/photo filename.jpg Send a JPEG photo
-/read Mark active discussion as read
-/users List collected users and their IDs
-Type a command, or a message to send to the active secret chat:");
- do
- {
- try
- {
- var line = Console.ReadLine();
- if (line.StartsWith('/'))
- {
- if (line == "/discard delete") { await Secrets.Discard(ActiveChat.ChatId, true); SelectActiveChat(); }
- else if (line == "/discard") { await Secrets.Discard(ActiveChat.ChatId, false); SelectActiveChat(); }
- else if (line == "/read") await Client.Messages_ReadEncryptedHistory(ActiveChat.Peer, DateTime.UtcNow);
- else if (line == "/users") foreach (var user in Users.Values) Console.WriteLine($"{user.id,-10} {user}");
- else if (line.StartsWith("/select ")) SelectActiveChat(int.Parse(line[8..]));
- else if (line.StartsWith("/request "))
- if (Users.TryGetValue(long.Parse(line[9..]), out var user))
- SelectActiveChat(await Secrets.Request(user));
- else
- Console.WriteLine("User not found");
- else if (line.StartsWith("/photo "))
- {
- var media = new TL.Layer46.DecryptedMessageMediaPhoto { caption = line[7..] };
- var file = await Secrets.UploadFile(File.OpenRead(line[7..]), media);
- var sent = await Secrets.SendMessage(ActiveChat.ChatId, new TL.Layer73.DecryptedMessage { random_id = Helpers.RandomLong(),
- media = media, flags = TL.Layer73.DecryptedMessage.Flags.has_media }, file: file);
- }
- else Console.WriteLine("Unrecognized command");
- }
- else if (ActiveChat == null) Console.WriteLine("No active secret chat");
- else await Secrets.SendMessage(ActiveChat.ChatId, new TL.Layer73.DecryptedMessage { message = line, random_id = Helpers.RandomLong() });
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- } while (true);
- }
-
- private static async Task Client_OnUpdates(UpdatesBase updates)
- {
- updates.CollectUsersChats(Users, Chats);
- foreach (var update in updates.UpdateList)
- switch (update)
- {
- case UpdateEncryption ue: // Change in Secret Chat status
- await Secrets.HandleUpdate(ue);
- break;
- case UpdateNewEncryptedMessage unem: // Encrypted message or service message:
- if (unem.message.ChatId != ActiveChat?.ChatId) SelectActiveChat(unem.message.ChatId);
- foreach (var msg in Secrets.DecryptMessage(unem.message))
- {
- if (msg.Media != null && unem.message is EncryptedMessage { file: EncryptedFile ef })
- {
- int slash = msg.Media.MimeType?.IndexOf('/') ?? 0; // quick & dirty conversion from MIME type to file extension
- var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.{(slash > 0 ? msg.Media.MimeType[(slash + 1)..] : "bin")}";
- Console.WriteLine($"{unem.message.ChatId}> {msg.Message} [attached file downloaded to {filename}]");
- using var output = File.Create(filename);
- await Secrets.DownloadFile(ef, msg.Media, output);
- }
- else if (msg.Action == null) Console.WriteLine($"{unem.message.ChatId}> {msg.Message}");
- else Console.WriteLine($"{unem.message.ChatId}> Service Message {msg.Action.GetType().Name[22..]}");
- }
- break;
- case UpdateEncryptedChatTyping:
- case UpdateEncryptedMessagesRead:
- //Console.WriteLine(update.GetType().Name);
- break;
- }
- }
-
- private static void SelectActiveChat(int newActiveChat = 0)
- {
- ActiveChat = Secrets.Chats.FirstOrDefault(sc => newActiveChat == 0 || sc.ChatId == newActiveChat);
- Console.WriteLine("Active secret chat ID: " + ActiveChat?.ChatId);
- }
- }
-}
diff --git a/Examples/WinForms_app.zip b/Examples/WinForms_app.zip
deleted file mode 100644
index 419b21b..0000000
Binary files a/Examples/WinForms_app.zip and /dev/null differ
diff --git a/FAQ.md b/FAQ.md
deleted file mode 100644
index 76a889d..0000000
--- a/FAQ.md
+++ /dev/null
@@ -1,362 +0,0 @@
-# FAQ
-
-Before asking questions, make sure to **[read through the ReadMe first](README.md)**,
-take a look at the [example programs](EXAMPLES.md) or [StackOverflow questions](https://stackoverflow.com/questions/tagged/wtelegramclient),
-and refer to the [API method list](https://corefork.telegram.org/methods) for the full range of Telegram services available in this library.
-
-➡️ Use Ctrl-F to search this page for the information you seek
-
-
-## 1. How to remove the Console logs?
-
-Writing the library logs to the Console is the default behavior of the `WTelegram.Helpers.Log` delegate.
-You can change the delegate with the `+=` operator to **also** write them somewhere else, or with the `=` operator to prevent them from being printed to screen and instead write them somewhere (file, logger, ...).
-In any case, it is not recommended to totally ignore those logs because you wouldn't be able to diagnose a problem after it happens.
-
-Read the [example about logging settings](EXAMPLES.md#logging) for how to write logs to a file.
-
-
-## 2. How to handle multiple user accounts
-
-The WTelegram.session file contains the authentication keys negociated for the current user.
-
-You could switch the current user via an `Auth_Logout` followed by a `LoginUserIfNeeded` but that would require the user to sign in with a verification_code each time.
-
-Instead, if you want to deal with multiple users from the same machine, the recommended solution is to have a different session file for each user.
-This can be done by having your Config callback reply with a different filename (or folder) for "**session_pathname**" for each user.
-This way, you can keep separate session files (each with their authentication keys) for each user.
-
-If you need to manage these user accounts in parallel, you can create multiple instances of WTelegram.Client,
-and give them a Config callback that will select a different session file ;
-for example: `new WTelegram.Client(what => Config(what, "session42"))`
-
-Also please note that the session files are encrypted with your api_hash (or session_key), so if you change it, the existing session files can't be read anymore.
-Your api_id/api_hash represents your application, and shouldn't change with each user account the application will manage.
-
-
-
-## 3. How to use the library in a WinForms, WPF or ASP.NET application
-
-The library should work without a problem in such applications.
-The difficulty might be in your Config callback when the user must enter the verification code or password, as you can't use `Console.ReadLine` here.
-
-For GUI apps, an easy solution is to call `Interaction.InputBox("Enter verification code")` instead.
-This might require adding a reference *(and `using`)* to the Microsoft.VisualBasic assembly.
-
-A more complex solution requires the use of a `ManualResetEventSlim` that you will wait for in Config callback,
-and when the user has provided the verification_code through your app, you "set" the event to release your Config callback so it can return the code.
-
-Another solution is to use the [alternative login method](README.md#alternative-simplified-configuration--login),
-calling `client.Login(...)` as the user provides the requested configuration elements.
-You can download such full example apps [for WinForms](Examples/WinForms_app.zip) and [for ASP.NET](Examples/ASPnet_webapp.zip)
-
-
-## 4. How to use IDs and access_hash? Why the error `CHANNEL_INVALID` or `USER_ID_INVALID`?
-
-⚠️ In Telegram Client API *(contrary to Bot API)*, you **cannot** interact with channels/users/etc. with only their IDs.
-
-You also need to obtain their `access_hash` which is specific to the resource you want to access AND to the currently logged-in user.
-This serves as a proof that the logged-in user is entitled to access that channel/user/photo/document/...
-(otherwise, anybody with the ID could access it)
-
-> A small private group `Chat` don't need an access_hash and can be queried using their `chat_id` only.
-However most common chat groups are not `Chat` but a `Channel` supergroup (without the `broadcast` flag). See [Terminology in ReadMe](README.md#terminology).
-Some TL methods only applies to private `Chat`, some only applies to `Channel` and some to both.
-
-The `access_hash` must usually be provided within the `Input...` structure you pass in argument to an API method (`InputPeer`, `InputChannel`, `InputUser`, etc...).
-
-You obtain the `access_hash` through TL **description structures** like `Channel`, `User`, `Photo`, `Document` that you receive through updates
-or when you query them through API methods like `Messages_GetAllChats`, `Messages_GetAllDialogs`, `Contacts_ResolveUsername`, etc...
-
-You can use the [`UserOrChat` and `CollectUsersChats` methods](EXAMPLES.md#collect-users-chats) to help you in obtaining/collecting
-the description structures you receive via API calls or updates.
-
-Once you obtained the description structure, there are 2 methods for building your `Input...` request structure:
-* **Recommended:** Just pass that description structure you already have, in place of the `Input...` argument, it will work!
-*The implicit conversion operators on base classes like `ChatBase/UserBase` will create the `Input...` structure for you automatically.*
-* Alternatively, you can manually create the `Input...` structure yourself by extracting the `access_hash` from the description structure
-
-*Note: An `access_hash` obtained from a User/Channel structure with flag `min` may not be usable for most requests. See [Min constructors](https://core.telegram.org/api/min).*
-
-
-## 5. I need to test a feature that has been recently developed but seems not available in my program
-
-The developmental versions of the library are now available as **pre-release** on Nuget (with `-dev` in the version number)
-
-So make sure you tick the checkbox "Include prerelease" in Nuget manager and/or navigate to the Versions list then select the highest `x.x.x-dev.x` version to install in your program.
-
-
-## 6. Telegram asks me to signup (firstname, lastname) even for an existing account
-This happens when you connect to Telegram Test servers instead of Production servers.
-On these separate test servers, all created accounts and chats are periodically deleted, so you shouldn't use them under normal circumstances.
-
-You can verify this is your issue by looking at [WTelegram logs](EXAMPLES.md#logging) on the line `Connected to (Test) DC x...`
-
-This wrong-server problem typically happens when you use WTelegramClient Github source project in your application in DEBUG builds.
-It is **not recommended** to use WTelegramClient in source code form.
-Instead, you should use the Nuget manager to **install package WTelegramClient** into your application.
-*And remember to delete the WTelegram.session file to force a new login on the correct server.*
-
-If you use the Github source project in an old .NET Framework 4.x or .NET Core x.x application, you may also experience the following error
-> System.TypeInitializationException (FileNotFoundException for "System.Text.Json Version=5.0.0.0 ...")
-
-To fix this, you should also switch to using the [WTelegramClient Nuget package](https://www.nuget.org/packages/WTelegramClient) as it will install the required dependencies for it to work.
-
-
-## 7. I get errors FLOOD_WAIT_X or PEER_FLOOD, PHONE_NUMBER_BANNED, USER_DEACTIVATED_BAN. I can't import phone numbers.
-
-You can get these kind of problems if you abuse Telegram [Terms of Service](https://telegram.org/tos), or the [API Terms of Service](https://core.telegram.org/api/terms), or make excessive requests.
-
-You can try to wait more between the requests, wait for a day or two to see if the requests become possible again.
-
->ℹ️ For FLOOD_WAIT_X with X < 60 seconds (see `client.FloodRetryThreshold`), WTelegramClient will automatically wait the specified delay and retry the request for you.
-For longer delays, you can catch the thrown `RpcException` and check the value of property X.
-
-An account that was restricted due to reported spam might receive PEER_FLOOD errors. Read [Telegram Spam FAQ](https://telegram.org/faq_spam) to learn more.
-
-If you think your phone number was banned from Telegram for a wrong reason, you may try to contact [recover@telegram.org](mailto:recover@telegram.org), explaining what you were doing.
-
-In any case, WTelegramClient is not responsible for the bad usage of the library and we are not affiliated to Telegram teams, so there is nothing we can do.
-
-
-## 8. How to NOT get banned from Telegram?
-
-**Do not share publicly your app's ID and hash!** They cannot be regenerated and are bound to your Telegram account.
-
-From the [official documentation](https://core.telegram.org/api/obtaining_api_id):
-
-> Note that all API client libraries are strictly monitored to prevent abuse.
-> If you use the Telegram API for flooding, spamming, faking subscriber and view counters of channels, you **will be banned forever**.
-> Due to excessive abuse of the Telegram API, **all accounts that sign up or log in using unofficial Telegram clients are automatically
-> put under observation** to avoid violations of the [Terms of Service](https://core.telegram.org/api/terms).
-
-Here are some advices from [another similar library](https://github.com/gotd/td/blob/main/.github/SUPPORT.md#how-to-not-get-banned):
-
-1. This client is unofficial, Telegram treats such clients suspiciously, especially fresh ones.
-2. Use regular bots instead of userbots whenever possible.
-3. If you still want to automate things with a user, use it passively (i.e. receive more than sending).
-4. When using it with a user:
- * Do not use QR code login, this will result in permaban.
- * Do it with extreme care.
- * Do not use VoIP numbers.
- * Do not abuse, spam or use it for other suspicious activities.
- * Implement a rate limiting system.
-
-Some additional advices from me:
-
-5. Avoid repetitive polling or repetitive sequence of actions/requests: Save the initial results of your queries, and update those results when you're informed of a change through `OnUpdates` events.
-6. Don't buy fake user accounts/sessions and don't extract api_id/hash/authkey/sessions from official clients, this is [specifically forbidden by API TOS](https://core.telegram.org/api/terms#2-transparency). You must use your own api_id and create your own sessions associated with it.
-7. If a phone number is brand new, it will be closely monitored by Telegram for abuse, and it can even already be considered a bad user due to bad behavior from the previous owner of that phone number (which may happen often with VoIP or other easy-to-buy-online numbers, so expect fast ban)
-8. You may want to use your new phone number account with an official Telegram client and act like a normal user for some time (some weeks/months), before using it for automation with WTelegramClient.
-9. When creating a new API ID/Hash, I recommend you use your own phone number with long history of normal Telegram usage, rather than a brand new phone number with short history.
-In particular, DON'T create an API ID/Hash for every phone numbers you will control. One API ID/Hash represents your application, which can be used to control several user accounts.
-10. If you actually do use the library to spam, scam, or other stuff annoying to everybody, GTFO and don't cry that you got banned using WTelegramClient. Some people don't seem to realize by themselves that what they plan to do with the library is actually negative for the community and are surprised that they got caught.
-We don't support such use of the library, and will not help people asking for support if we suspect them of mass-user manipulation.
-11. If your client displays Telegram channels to your users, you have to support and display [official sponsored messages](https://core.telegram.org/api/sponsored-messages).
-
-
-## 9. Why the error `CHAT_ID_INVALID`?
-
-Most chat groups you see are likely of type `Channel`, not `Chat`.
-This difference is important to understand. Please [read about the Terminology in ReadMe](README.md#terminology).
-
-You typically get the error `CHAT_ID_INVALID` when you try to call API methods designed specifically for a `Chat`, with the ID of a `Channel`.
-All API methods taking a `long chat_id` as a direct method parameter are for Chats and cannot be used with Channels.
-
-There is probably another method achieving the same result but specifically designed for Channels, and it will have a similar name, but beginning with `Channels_` ...
-
-However, note that those Channel-compatible methods will require an `InputChannel` or `InputPeerChannel` object as argument instead of a simple channel ID.
-That object must be created with both fields `channel_id` and `access_hash` correctly filled. You can read more about this in [FAQ #4](#access-hash).
-
-
-
-## 10. `chats.chats[id]` fails. My chats list is empty or does not contain the chat I'm looking for.
-
-There can be several reasons why `chats.chats` doesn't contain the chat you expect:
-- You're searching for a user instead of a chat ID.
-Private messages with a user are not called "chats". See [Terminology in ReadMe](README.md#terminology).
-To obtain the list of users (as well as chats and channels) the logged-in user is currenly engaged in a discussion with, you should [use the API method `Messages_GetAllDialogs`](EXAMPLES.md#list-dialogs)
-- The currently logged-in user account has not joined this particular chat.
-API method [`Messages_GetAllChats`](https://corefork.telegram.org/method/messages.getAllChats) will only return those chat groups/channels the user is in, not all Telegram chat groups.
-If you're looking for other Telegram groups/channels/users, try API methods [`Contacts_ResolveUsername`](EXAMPLES.md#msg-by-name) or `Contacts_Search`
-- You're trying to use a Bot API (or TDLib) numerical ID, like -1001234567890
-Telegram Client API don't use these kind of IDs for chats. Remove the -100 prefix and try again with the rest (1234567890).
-- the `chats.chats` dictionary is empty.
-This is the case if you are logged-in as a brand new user account (that hasn't join any chat groups/channels)
-or if you are connected to a Test DC (a Telegram datacenter server for tests) instead of Production DC
-([read FAQ #6](#wrong-server) for more)
-
-To help determine if `chats.chats` is empty or does not contain a certain chat, you should [dump the chat list to the screen](EXAMPLES.md#list-chats)
-or simply use a debugger: Place a breakpoint after the `Messages_GetAllChats` call, run the program up to there, and use a Watch pane to display the content of the chats.chats dictionary.
-
-
-## 11. I get "Connection shut down" errors in my logs
-
-There are various reasons why you may get this error. Here are the explanation and how to solve it:
-
-1) On secondary DCs *(DC used to download files)*, a Connection shut down is considered "normal"
-Your main DC is the one WTelegramClient connects to during login. Secondary DC connections are established and maintained when you download files.
-The DC number for an operation or error is indicated with a prefix like "2>" on the log line.
-If Telegram servers decide to shutdown this secondary connection, it's not an issue, WTelegramClient will re-establish the connection later if necessary.
-
-2) Occasional connection shutdowns on the main DC should be caught by WTelegramClient and the reactor should automatically reconnect to the DC
-*(up to `MaxAutoReconnects` times)*.
-This should be transparent and pending API calls should automatically be resent upon reconnection.
-You can choose to increase `MaxAutoReconnects` if it happens too often because your Internet connection is unstable.
-
-3) If you reach `MaxAutoReconnects` disconnections or a reconnection fails, then the **OnOther** event handler will receive a `ReactorError` object to notify you of the problem,
-and pending API calls throw the network IOException.
-In this case, the recommended action would be to dispose the client and recreate one (see example [Program_ReactorError.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ReactorError.cs))
-
-4) In case of slow Internet connection or if you break in the debugger for some time,
-you might also get Connection shutdown because your client couldn't send Pings to Telegram in the allotted time.
-In this case, you can use the `PingInterval` property to increase the delay between pings *(for example 300 seconds instead of 60)*.
-
-5) If you're using an [MTProxy](EXAMPLES.md#proxy), some of them are known to be quite unstable. You may want to try switching to another MTProxy that is more stable.
-
-
-## 12. How to migrate from TLSharp? How to sign-in/sign-up/register account properly?
-
-First, make sure you read the [ReadMe documentation](README.md) completely, it contains essential information and a quick tutorial to easily understand how to correctly use the library.
-
-WTelegramClient approach is much more simpler and secure than TLSharp.
-
-All client APIs have dedicated async methods that you can call like this: `await client.Method_Name(param1, param2, ...)`
-See the [full method list](https://core.telegram.org/methods) (just replace the dot with an underscore in the names)
-
-A session file is created or resumed automatically on startup, and maintained up-to-date automatically throughout the session.
-That session file is incompatible with TLSharp so you cannot reuse a TLSharp .dat file. You'll need to create a new session.
-To fight against the reselling of fake user accounts, we don't support the import/export of session files from external sources.
-
-**DON'T** call methods Auth_SendCode/SignIn/SignUp/... because all the login phase is handled automatically by calling `await client.LoginUserIfNeeded()` after creating the client.
-Your Config callback just need to provide the various login answers if they are needed (see [ReadMe](README.md) and [FAQ #4](#GUI)).
-In particular, it will detect and handle automatically and properly the various login cases/particularity like:
-* Login not necessary (when a session is resumed with an already logged-in user)
-* Logout required (if you want to change the logged-in user)
-* 2FA password required (your Config needs to provide "password")
-* Email registration procedure required (your Config needs to provide "email", "email_verification_code")
-* Account registration/sign-up required (your Config needs to provide "first_name", "last_name")
-* Request to resend the verification code through alternate ways (if your Config answer an empty "verification_code" initially)
-* Transient failures, slowness to respond, wrong code/password, checks for encryption key safety, etc..
-
-Contrary to TLSharp, WTelegramClient supports MTProto v2.0 (more secured), transport obfuscation, protocol security checks, MTProto [Proxy](EXAMPLES.md#proxy), real-time updates, multiple DC connections, API documentation in Intellisense...
-
-
-## 13. How to host my userbot online?
-
-If you need your userbot to run 24/7, you would typically design your userbot as a Console program, compiled for Linux or Windows,
-and hosted online on any [VPS Hosting](https://www.google.com/search?q=vps+hosting) (Virtual Private Server).
-Pure WebApp hosts might not be adequate as they will recycle (stop) your app if there is no incoming HTTP requests.
-
-There are many cheap VPS Hosting offers available, for example Heroku:
-See [Examples/Program_Heroku.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_Heroku.cs?ts=4#L9) for such an implementation and the steps to host/deploy it.
-
-
-## 14. Secret Chats implementation details
-
-The following choices were made while implementing Secret Chats in WTelegramClient:
-- It may not support remote antique Telegram clients *(prior to 2018, still using insecure MTProto 1.0)*
-- It doesn't store outgoing messages *(so if remote client was offline for a week and ask us to resend old messages, we will send void data)*
-- It doesn't store incoming messages on disk *(it's up to you if you want to store them)*
-- If you pass `DecryptMessage` parameter `fillGaps: true` *(default)*, incoming messages are ensured to be delivered to you in correct order.
-If for some reason, we received them in incorrect order, messages are kept in memory until the requested missing messages are obtained.
-If those missing messages are never obtained during the session, incoming messages might get stuck and lost.
-- SecretChats file data is only valid for the current user, so make sure to pick the right file *(or a new file name)* if you change logged-in user.
-- If you want to accept incoming Secret Chats request only from specific user, you must check it in OnUpdates before:
-`await Secrets.HandleUpdate(ue, ue.chat is EncryptedChatRequested ecr && ecr.admin_id == EXPECTED_USER_ID);`
-- As recommended, new encryption keys are negotiated every 100 sent/received messages or after one week.
-If remote client doesn't complete this negotiation before reaching 200 messages, the Secret Chat is aborted.
-
-
-## 15. The example codes don't compile on my machine
-
-The snippets of example codes found in the [ReadMe](README.md) or [Examples](EXAMPLES.md) pages were written for .NET 5 / C# 9 minimum.
-If you're having compiler problem on code constructs such as `using`, `foreach`, `[^1]` or about "Deconstruct",
-that typically means you're still using an obsolete version of .NET (Framework 4.x or Core)
-
-Here are the recommended actions to fix your problem:
-- Create a new project for .NET 6+ (in Visual Studio 2019 or more recent):
- - Select File > New > Project
- - Search for "C# Console"
- - Select the **Console App**, but NOT Console App (.NET Framework) !
- - On the framework selection page, choose .NET 6.0 or more recent
- - Now you can start developing for WTelegramClient 🙂
-- If you don't want to target a recent version of .NET, you can upgrade your existing project to use the latest version of the C# language:
- - Close Visual Studio
- - Edit your *.csproj file **with Notepad**
- - Within the first ``, add the following line:
- `latest`
- - Save, close Notepad and reopen your project in Visual Studio
- - If you still have issues on some `foreach` constructs, add this class somewhere in your project:
- ```csharp
- static class Extensions
- {
- public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value)
- {
- key = tuple.Key;
- value = tuple.Value;
- }
- }
- ```
-
-Also, remember to add a `using TL;` at the top of your files to have access to all the Telegram API methods.
-
-
-# Troubleshooting guide
-
-Here is a list of common issues and how to fix them so that your program work correctly:
-
-1) Are you using the Nuget package or the library source code?
-It is not recommended to copy/compile the source code of the library for a normal usage.
-When built in DEBUG mode, the source code connects to Telegram test servers (see also [FAQ #6](#wrong-server)).
-So you can either:
- - **Recommended:** Use the [official Nuget package](https://www.nuget.org/packages/WTelegramClient)
- - Build your code in RELEASE mode
- - Modify your config callback to reply to "server_address" with the IP address of Telegram production servers (as found on your API development tools)
-
-2) Did you call `Login` or `LoginUserIfNeeded` succesfully?
-If you don't complete authentication as a user (or bot), you have access to a very limited subset of Telegram APIs.
-Make sure your calls succeed and don't throw an exception.
-
-3) Did you use `await` with every Client methods?
-This library is completely Task-based. You should learn, understand and use the [asynchronous model of C# programming](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) before proceeding further.
-Using `.Result` or `.Wait()` can lead to deadlocks.
-
-4) Is your program ending immediately instead of waiting for Updates?
-Your program must be running/waiting continuously in order for the background Task to receive and process the Updates.
-So make sure your main program doesn't end immediately or dispose the client too soon (via `using`?).
-For a console program, this is typical done by waiting for a key or some close event.
-
-5) Is every Telegram API call rejected? (typically with an exception message like `AUTH_RESTART`)
-The user authentification might have failed at some point (or the user revoked the authorization).
-It is therefore necessary to go through the authentification again. This can be done by deleting the WTelegram.session file, or at runtime by calling `client.Reset()`
-
-
-# About the UpdateManager
-
-The UpdateManager does the following:
-- ensure the correct sequential order of receiving updates (Telegram may send them in wrong order)
-- fetch the missing updates if there was a gap (missing update) in the flow of incoming updates
-- resume the flow of updates where you left off if you stopped your program (with saved state)
-- collect the users & chats from updates automatically for you _(by default)_
-- simplifies the handling of the various containers of update (UpdatesBase)
-
-To use the UpdateManager, instead of setting `client.OnUpdates`, you call:
-```csharp
-// if you don't care about missed updates while your program was down:
-var manager = client.WithUpdateManager(OnUpdate);
-
-// if you want to recover missed updates using the state saved on the last run of your program
-var manager = client.WithUpdateManager(OnUpdate, "Updates.state");
-// to save the state later, preferably after disposing the client:
-manager.SaveState("Updates.state")
-```
-
-Your `OnUpdate` method will directly take a single `Update` as parameter, instead of a container of updates.
-The `manager.Users` and `manager.Chats` dictionaries will collect the users/chats data from updates.
-You can also feed them manually from result of your API calls by calling `result.CollectUsersChats(manager.Users, manager.Chats);` and resolve Peer fields via `manager.UserOrChat(peer)`
-See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21) for an example of implementation.
-
-Notes:
-- set `manager.Log` if you want different logger settings than the client
-- `WithUpdateManager()` has other parameters for advanced use
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index 3265a97..08900bc 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2024 Olivier Marcoux
+Copyright (c) 2021 Olivier Marcoux
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 05aecf3..0e1217d 100644
--- a/README.md
+++ b/README.md
@@ -1,209 +1,148 @@
-[](https://corefork.telegram.org/methods)
-[](https://www.nuget.org/packages/WTelegramClient/)
-[](https://www.nuget.org/packages/WTelegramClient/absoluteLatest)
-[](https://buymeacoffee.com/wizou)
+[](https://www.nuget.org/packages/WTelegramClient/)
+[](https://dev.azure.com/wiz0u/WTelegramClient/_packaging?_a=package&feed=WTelegramClient&package=WTelegramClient&protocolType=NuGet)
+[](https://dev.azure.com/wiz0u/WTelegramClient/_build?definitionId=7)
+[](https://schema.horner.tj)
+[](https://t.me/WTelegramClient)
-## *Telegram Client API library written 100% in C# and .NET*
+#
WTelegramClient
+### _Telegram client library written 100% in C# and .NET Standard_
-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.
+## How to use
-Library was developed solely by one unemployed guy. [Donations](https://buymeacoffee.com/wizou) or [Patreon memberships are welcome](https://patreon.com/wizou).
+⚠️ This library relies on asynchronous C# programming (`async/await`) so make sure you are familiar with this before proceeding.
-This ReadMe is a **quick but important tutorial** to learn the fundamentals about this library. Please read it all.
-
-> ⚠️ This library requires understanding advanced C# techniques such as **asynchronous programming** or **subclass pattern matching**...
-> If you are a beginner in C#, starting a project based on this library might not be a great idea.
-
-# How to use
-
-After installing WTelegramClient through [Nuget](https://www.nuget.org/packages/WTelegramClient/), your first Console program will be as simple as:
+After installing WTelegramClient through Nuget, your first Console program will be as simple as:
```csharp
static async Task Main(string[] _)
{
using var client = new WTelegram.Client();
- var myself = await client.LoginUserIfNeeded();
- Console.WriteLine($"We are logged-in as {myself} (id {myself.id})");
+ await client.ConnectAsync();
+ var user = await client.LoginUserIfNeeded();
+ Console.WriteLine($"We are logged-in as {user.username ?? user.first_name + " " + user.last_name} (id {user.id})");
}
```
-When run, this will prompt you interactively for your App **api_hash** and **api_id** (that you obtain through Telegram's
-[API development tools](https://my.telegram.org/apps) page) and try to connect to Telegram servers.
-Those api hash/id represent your application and one can be used for handling many user accounts.
+When run, this will prompt you interactively for your App **api_id** and **api_hash** (that you obtain through Telegram's [API development tools](https://my.telegram.org/apps) page) and try to connect to Telegram servers.
-Then it will attempt to sign-in *(login)* as a user for which you must enter the **phone_number** and the **verification_code**
-that will be sent to this user (for example through SMS, Email, or another Telegram client app the user is connected to).
+Then it will attempt to sign-in as a user for which you must enter the **phone_number** and the **verification_code** that will be sent to this user (for example through SMS or another Telegram client app the user is connected to).
-If the verification succeeds but the phone number is unknown to Telegram, the user might be prompted to sign-up
-*(register their account by accepting the Terms of Service)* and provide their **first_name** and **last_name**.
-If the account already exists and has enabled two-step verification (2FA) a **password** might be required.
-In some case, Telegram may request that you associate an **email** with your account for receiving login verification codes,
-you may skip this step by leaving **email** empty, otherwise the email address will first receive an **email_verification_code**.
-All these login scenarios are handled automatically within the call to `LoginUserIfNeeded`.
+If the verification succeeds but the phone number is unknown to Telegram, the user might be prompted to sign-up (accepting the Terms of Service) and enter their **first_name** and **last_name**.
-After login, you now have access to the **[full range of Telegram Client APIs](https://corefork.telegram.org/methods)**.
-All those API methods require `using TL;` namespace and are called with an underscore instead of a dot in the method name, like this: `await client.Method_Name(...)`
+And that's it, you now have access to the [full range of Telegram services](https://core.telegram.org/methods), mainly through calls to `await client.Some_TL_Method(...)`
# Saved session
-If you run this program again, you will notice that only **api_hash** is requested, the other prompts are gone and you are automatically logged-on and ready to go.
+If you run this program again, you will notice that only **api_id** and **api_hash** are requested, the other prompts are gone and you are automatically logged-on and ready to go.
-This is because WTelegramClient saves (typically in the encrypted file **bin\WTelegram.session**) its state
-and the authentication keys that were negotiated with Telegram so that you needn't sign-in again every time.
+This is because WTelegramClient saves (typically in the encrypted file **bin\WTelegram.session**) its state and the authentication keys that were negociated with Telegram so that you needn't sign-in again every time.
-That file path is configurable (**session_pathname**), and under various circumstances *(changing user or server address, write permissions)*
-you may want to change it or simply delete the existing session file in order to restart the authentification process.
+That file path is configurable, and under various circumstances (changing user or server address) you may want to change it or simply delete the existing session file in order to restart the authentification process.
# Non-interactive configuration
-Your next step will probably be to provide a configuration to the client so that the required elements are not prompted through the Console but answered by your program.
+Your next step will probably be to provide a configuration to the client so that the required elements (in bold above) are not prompted through the Console but answered by your program.
To do this, you need to write a method that will provide the answers, and pass it on the constructor:
```csharp
static string Config(string what)
{
- switch (what)
- {
- case "api_id": return "YOUR_API_ID";
- case "api_hash": return "YOUR_API_HASH";
- case "phone_number": return "+12025550156";
- case "verification_code": Console.Write("Code: "); return Console.ReadLine();
- case "first_name": return "John"; // if sign-up is required
- case "last_name": return "Doe"; // if sign-up is required
- case "password": return "secret!"; // if user has enabled 2FA
- default: return null; // let WTelegramClient decide the default config
- }
+ if (what == "api_id") return "YOUR_API_ID";
+ if (what == "api_hash") return "YOUR_API_HASH";
+ if (what == "phone_number") return "+12025550156";
+ if (what == "verification_code") { Console.Write("Code: "); return Console.ReadLine(); }
+ if (what == "first_name") return "John"; // if sign-up is required
+ if (what == "last_name") return "Doe"; // if sign-up is required
+ if (what == "password") return "secret!"; // if user has enabled 2FA
+ return null;
}
...
using var client = new WTelegram.Client(Config);
```
-There are other configuration items that are queried to your method but returning `null` let WTelegramClient choose a default adequate value.
-Those shown above are the only ones that have no default values and should be provided by your method.
-
-Returning `null` for verification_code or password will show a prompt for console apps, or an error otherwise
-*(see [FAQ #3](https://wiz0u.github.io/WTelegramClient/FAQ#GUI) for WinForms)*
-Returning `""` for verification_code requests the resending of the code through another system (SMS or Call).
-
-Another simple approach is to pass `Environment.GetEnvironmentVariable` as the config callback and define the configuration items as environment variables
-*(undefined variables get the default `null` behavior)*.
+There are other configuration items that are queried to your method but returning `null` let WTelegramClient choose a default adequate value. Those shown above are the only ones that have no default values and are required to be provided by your method.
Finally, if you want to redirect the library logs to your logger instead of the Console, you can install a delegate in the `WTelegram.Helpers.Log` static property.
-Its `int` argument is the log severity, compatible with the [LogLevel enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel).
-
-# Alternative simplified configuration & login
-Since version 3.0.0, a new approach to login/configuration has been added. Some people might find it easier to deal with:
-
-```csharp
-WTelegram.Client client = new WTelegram.Client(YOUR_API_ID, "YOUR_API_HASH"); // this constructor doesn't need a Config method
-await DoLogin("+12025550156"); // initial call with user's phone_number
-...
-//client.Dispose(); // the client must be disposed when you're done running your userbot.
-
-async Task DoLogin(string loginInfo) // (add this method to your code)
-{
- while (client.User == null)
- switch (await client.Login(loginInfo)) // returns which config is needed to continue login
- {
- case "verification_code": Console.Write("Code: "); loginInfo = Console.ReadLine(); break;
- case "name": loginInfo = "John Doe"; break; // if sign-up is required (first/last_name)
- case "password": loginInfo = "secret!"; break; // if user has enabled 2FA
- default: loginInfo = null; break;
- }
- Console.WriteLine($"We are logged-in as {client.User} (id {client.User.id})");
-}
-```
-
-With this method, you can choose in some cases to interrupt the login loop via a `return` instead of `break`, and resume it later
-by calling `DoLogin(requestedCode)` again once you've obtained the requested code/password/etc...
-See [WinForms example](https://wiz0u.github.io/WTelegramClient/Examples/WinForms_app.zip) and [ASP.NET example](https://wiz0u.github.io/WTelegramClient/Examples/ASPnet_webapp.zip)
+Its `int` argument is the log severity, compatible with the classic [LogLevel enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel)
# Example of API call
-> The Telegram API makes extensive usage of base and derived classes, so be ready to use the various C# syntaxes
-to check/cast base classes into the more useful derived classes (`is`, `as`, `case DerivedType` )
+ℹ️ The Telegram API makes extensive usage of base and derived classes, so be ready to use the various syntaxes C# offer to check/cast base classes into the more useful derived classes (`is`, `as`, `case DerivedType` )
-All the Telegram API classes/methods are fully documented through Intellisense: Place your mouse over a class/method name,
-or start typing the call arguments to see a tooltip displaying their description, the list of derived classes and a web link to the official API page.
+To find which derived classes are available for a given base class, the fastest is to check our [TL.Schema.cs](src/TL.Schema.cs) source file as they are listed in groups.
+Intellisense tooltips on API structures/methods will also display a web link to the adequate Telegram documentation page.
-The Telegram [API object classes](https://corefork.telegram.org/schema) are defined in the `TL` namespace,
-and the [API functions](https://corefork.telegram.org/methods) are available as async methods of `Client`.
+The Telegram [API object classes](https://core.telegram.org/schema) are defined in the `TL` namespace, and the [API functions](https://core.telegram.org/methods) are available as async methods of `Client`.
-Below is an example of calling the [messages.getAllChats](https://corefork.telegram.org/method/messages.getAllChats) API function,
-enumerating the various groups/channels the user is in, and then using `client.SendMessageAsync` helper function to easily send a message:
+Below is an example of calling the [messages.getAllChats](https://core.telegram.org/method/messages.getAllChats) API function and enumerating the various groups/channels the user is in, and then using `client.SendMessageAsync` helper function to easily send a message:
```csharp
using TL;
...
-var chats = await client.Messages_GetAllChats();
+var chats = await client.Messages_GetAllChats(null);
Console.WriteLine("This user has joined the following:");
-foreach (var (id, chat) in chats.chats)
- if (chat.IsActive)
- Console.WriteLine($"{id,10}: {chat}");
+foreach (var chat in chats.chats)
+ switch (chat)
+ {
+ case Chat smallgroup when (smallgroup.flags & Chat.Flags.deactivated) == 0:
+ Console.WriteLine($"{smallgroup.id}: Small group: {smallgroup.title} with {smallgroup.participants_count} members");
+ break;
+ case Channel channel when (channel.flags & Channel.Flags.broadcast) != 0:
+ Console.WriteLine($"{channel.id}: Channel {channel.username}: {channel.title}");
+ break;
+ case Channel group:
+ Console.WriteLine($"{group.id}: Group {group.username}: {group.title}");
+ break;
+ }
Console.Write("Type a chat ID to send a message: ");
-long chatId = long.Parse(Console.ReadLine());
-var target = chats.chats[chatId];
-Console.WriteLine($"Sending a message in chat {chatId}: {target.Title}");
+long id = long.Parse(Console.ReadLine());
+var target = chats.First(chat => chat.ID == id);
await client.SendMessageAsync(target, "Hello, World");
```
-➡️ You can find lots of useful code snippets in [EXAMPLES](https://wiz0u.github.io/WTelegramClient/EXAMPLES)
-and more detailed programs in the [Examples subdirectory](https://github.com/wiz0u/WTelegramClient/tree/master/Examples).
-➡️ Check [the FAQ](https://wiz0u.github.io/WTelegramClient/FAQ#compile) if example codes don't compile correctly on your machine, or other troubleshooting.
-
-
-# Terminology in Telegram Client API
-
-In the API, Telegram uses some terms/classnames that can be confusing as they differ from the terms shown to end-users:
-- `Channel`: A (large or public) chat group *(sometimes called [supergroup](https://corefork.telegram.org/api/channel#supergroups))*,
-or a [broadcast channel](https://corefork.telegram.org/api/channel#channels) (the `broadcast` flag differentiate those)
-- `Chat`: A private [basic chat group](https://corefork.telegram.org/api/channel#basic-groups) with less than 200 members
-(it may be migrated to a supergroup `Channel` with a new ID when it gets bigger or public, in which case the old `Chat` will still exist but will be `deactivated`)
-**⚠️ Most chat groups you see are really of type `Channel`, not `Chat`!**
-- **chats**: In plural or general meaning, it means either `Chat` or `Channel` *(therefore, no private user discussions)*
-- `Peer`: Either a `Chat`, a `Channel` or a `User`
-- **Dialog**: Status of chat with a `Peer` *(draft, last message, unread count, pinned...)*. It represents each line from your Telegram chat list.
-- **Access Hash**: Telegram requires you to provide a specific `access_hash` for users, channels, and other resources before interacting with them.
-See [FAQ #4](https://wiz0u.github.io/WTelegramClient/FAQ#access-hash) to learn more about it.
-- **DC** (DataCenter): There are a few datacenters depending on where in the world the user (or an uploaded media file) is from.
-- **Session** or **Authorization**: Pairing between a device and a phone number. You can have several active sessions for the same phone number.
-- **Participant**: A member/subscriber of a chat group or channel
-
# Other things to know
-The Client class offers `OnUpdates` and `OnOther` events that are triggered when Telegram servers sends Updates (like new messages or status) or other notifications, independently of your API requests.
-You can also use the [UpdateManager class](https://wiz0u.github.io/WTelegramClient/FAQ#manager) to simplify the handling of such updates.
-See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21) and [Examples/Program_ReactorError.cs](https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ReactorError.cs?ts=4#L30)
+The Client class also offers an `Update` event that is triggered when Telegram servers sends unsollicited Updates or notifications/information/status/service messages, independently of your API requests.
-An invalid API request can result in a `RpcException` being raised, reflecting the [error code and status text](https://revgram.github.io/errors.html) of the problem.
+An invalid API request can result in a `RpcException` being raised, reflecting the [error code and status text](https://core.telegram.org/api/errors) of the problem.
-To [prevent getting banned](https://wiz0u.github.io/WTelegramClient/FAQ#prevent-ban) during dev, you can connect to [test servers](https://docs.pyrogram.org/topics/test-servers), by adding this line in your Config callback:
-`case "server_address": return "2>149.154.167.40:443"; // test DC`
+The other configuration items that you can override include: **session_pathname, server_address, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, user_id**
-The other configuration items that you can provide include: **session_pathname, email, email_verification_code, session_key, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, firebase, user_id, bot_token**
+Optional API parameters have a default value of `null` when unset. Passing `null` for a required string/array is the same as *empty* (0-length). Required API parameters/fields can sometimes be set to 0 or `null` when unused (check API documentation).
-Optional API parameters have a default value of `null` when unset. Passing `null` for a required string/array is the same as *empty* (0-length).
-Required API parameters/fields can sometimes be set to 0 or `null` when unused (check API documentation or experiment).
+I've added several useful converters or implicit cast to various API object so that they are more easy to manipulate.
-I've added several useful converters, implicit cast or helper properties to various API objects so that they are more easy to manipulate.
+Beyond the TL async methods, the Client class offers a few other methods to simplify the sending of files, medias or messages.
-Beyond the TL async methods, the Client class offers a few other methods to simplify the sending/receiving of files, medias or messages,
-as well as generic handling of chats/channels.
+This library works best with **.NET 5.0+** and is also available for **.NET Standard 2.0** (.NET Framework 4.6.1+ & .NET Core 2.0+)
-This library works best with **.NET 5.0+** (faster, no dependencies) and is also available for **.NET Standard 2.0** (.NET Framework 4.6.1+ & .NET Core 2.0+) and **Xamarin/Mono.Android**
+# Troubleshooting guide
+
+Here is a list of common issues and how to fix them so that your program work correctly:
+1) Are you using the Nuget package instead of the library source code?
+
It is not recommended to copy/compile the source code of the library for a normal usage.
+
When built in DEBUG mode, the source code connects to Telegram test servers. So you can either:
+ - **Recommended:** Use the [official Nuget package](https://www.nuget.org/packages/WTelegramClient) or the [private nuget feed of development builds](https://dev.azure.com/wiz0u/WTelegramClient/_packaging?_a=package&feed=WTelegramClient&package=WTelegramClient&protocolType=NuGet)
+ - Build your code in RELEASE mode
+ - Modify your config callback to reply to "server_address" with the IP address of Telegram production servers (as found on your API development tools)
+
+2) After `ConnectAsync()`, are you calling `LoginUserIfNeeded()`?
+
If you don't authenticate as a user (or bot), you have access to a very limited subset of Telegram APIs
+
+3) Did you use `await` with every Client methods?
+
This library is completely Task-based and you should learn, understand and use the [asynchronous model of C# programming](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) before proceeding further.
+
+4) Are you keeping a live reference to the Client instance and dispose it only at the end of your program?
+
If you create the instance in a submethod and don't store it somewhere permanent, it might be destroyed by the garbage collector at some point. So as long as the client must be running, make sure the reference is stored in a (static) field or somewhere appropriate.
+
Also, as the Client class inherits `IDisposable`, remember to call `client.Dispose()` when your program ends (or exit a `using` scope).
+
+5) Is your program ending immediately instead of waiting for Updates?
+
Your program must be running/waiting continuously in order for the background Task to receive and process the Updates. So make sure your main program doesn't end immediately. For a console program, this is typical done by waiting for a key or some close event.
+
+6) Is every Telegram API call rejected? (typically with an exception message like `AUTH_RESTART`)
+
The user authentification might have failed at some point (or the user revoked the authorization). It is therefore necessary to go through the authentification again. This can be done by deleting the WTelegram.session file, or at runtime by calling `client.Reset()`
# Library uses and limitations
-This library can be used for any Telegram scenario including:
+This library can be used for any Telegram scenarios including:
- Sequential or parallel automated steps based on API requests/responses
-- Real-time [monitoring](https://wiz0u.github.io/WTelegramClient/EXAMPLES#updates) of incoming Updates/Messages
-- [Download](https://wiz0u.github.io/WTelegramClient/EXAMPLES#download)/[upload](https://wiz0u.github.io/WTelegramClient/EXAMPLES#upload) of files/media
-- Exchange end-to-end encrypted messages/files in [Secret Chats](https://wiz0u.github.io/WTelegramClient/EXAMPLES#e2e)
-- Building a full-featured interactive client
+- Real-time monitoring of incoming Updates/Messages
+- Download/upload of files/media
+- etc...
-It has been tested in a Console app, [in Windows Forms](https://wiz0u.github.io/WTelegramClient/Examples/WinForms_app.zip),
-[in ASP.NET webservice](https://wiz0u.github.io/WTelegramClient/Examples/ASPnet_webapp.zip), and in Xamarin/Android.
+Secret chats (end-to-end encryption, PFS) and connection to CDN DCs have not been tested yet.
-Don't use this library for Spam or Scam. Respect Telegram [Terms of Service](https://telegram.org/tos)
-as well as the [API Terms of Service](https://core.telegram.org/api/terms) or you might get banned from Telegram servers.
-
-If you read all this ReadMe, the [Frequently Asked Questions](https://wiz0u.github.io/WTelegramClient/FAQ),
-the [Examples codes](https://wiz0u.github.io/WTelegramClient/EXAMPLES) and still have questions, feedback is welcome in our Telegram group [@WTelegramClient](https://t.me/WTelegramClient)
-
-If you like this library, you can [buy me a coffee](https://buymeacoffee.com/wizou) ❤ This will help the project keep going.
-
-© 2021-2026 Olivier Marcoux
+Developers feedbacks are welcome in the Telegram channel [@WTelegramClient](https://t.me/WTelegramClient)
diff --git a/generator/MTProtoGenerator.cs b/generator/MTProtoGenerator.cs
deleted file mode 100644
index 44c7c35..0000000
--- a/generator/MTProtoGenerator.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-
-#pragma warning disable RS1024 // Symbols should be compared for equality
-
-namespace TL.Generator;
-
-[Generator]
-public class MTProtoGenerator : IIncrementalGenerator
-{
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName("TL.TLDefAttribute",
- (_, _) => true, (context, _) => (ClassDeclarationSyntax)context.TargetNode);
- var source = context.CompilationProvider.Combine(classDeclarations.Collect());
- context.RegisterSourceOutput(source, Execute);
- }
-
- static void Execute(SourceProductionContext context, (Compilation compilation, ImmutableArray classes) unit)
- {
- var object_ = unit.compilation.GetSpecialType(SpecialType.System_Object);
- if (unit.compilation.GetTypeByMetadataName("TL.TLDefAttribute") is not { } tlDefAttribute) return;
- if (unit.compilation.GetTypeByMetadataName("TL.IfFlagAttribute") is not { } ifFlagAttribute) return;
- if (unit.compilation.GetTypeByMetadataName("TL.Layer") is not { } layer) return;
- if (unit.compilation.GetTypeByMetadataName("TL.IObject") is not { } iobject) return;
- var nullables = LoadNullables(layer);
- var namespaces = new Dictionary>(); // namespace,class,methods
- var tableTL = new StringBuilder();
- var methodsTL = new StringBuilder();
- var source = new StringBuilder();
- source
- .AppendLine("using System;")
- .AppendLine("using System.Collections.Generic;")
- .AppendLine("using System.ComponentModel;")
- .AppendLine("using System.IO;")
- .AppendLine("using System.Linq;")
- .AppendLine("using TL;")
- .AppendLine()
- .AppendLine("#pragma warning disable CS0109")
- .AppendLine();
- tableTL
- .AppendLine("\t\tpublic static readonly Dictionary> Table = new()")
- .AppendLine("\t\t{");
- methodsTL
- .AppendLine("\t\tpublic static readonly Dictionary> Methods = new()")
- .AppendLine("\t\t{");
-
- foreach (var classDecl in unit.classes)
- {
- var semanticModel = unit.compilation.GetSemanticModel(classDecl.SyntaxTree);
- if (semanticModel.GetDeclaredSymbol(classDecl) is not { } symbol) continue;
- var tldef = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == tlDefAttribute);
- if (tldef == null) continue;
- var id = (uint)tldef.ConstructorArguments[0].Value;
- StringBuilder writeTl = new(), readTL = new();
- var ns = symbol.BaseType.ContainingNamespace.ToString();
- var name = symbol.BaseType.Name;
- if (ns != "System")
- {
- if (!namespaces.TryGetValue(ns, out var parentClasses)) namespaces[ns] = parentClasses = [];
- parentClasses.TryGetValue(name, out var parentMethods);
- if (symbol.BaseType.IsAbstract)
- {
- if (parentMethods == null)
- {
- if (name is "Peer")
- writeTl.AppendLine("\t\tpublic virtual void WriteTL(BinaryWriter writer) => throw new NotSupportedException();");
- else
- writeTl.AppendLine("\t\tpublic abstract void WriteTL(BinaryWriter writer);");
- parentClasses[name] = writeTl.ToString();
- writeTl.Clear();
- }
- }
- else if (parentMethods?.Contains(" virtual ") == false)
- parentClasses[name] = parentMethods.Replace("public void WriteTL(", "public virtual void WriteTL(");
- }
- ns = symbol.ContainingNamespace.ToString();
- name = symbol.Name;
- if (!namespaces.TryGetValue(ns, out var classes)) namespaces[ns] = classes = [];
- 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")
- {
- if (ns == "TL.Methods")
- methodsTL.AppendLine($"\t\t\t[0x{id:X8}] = {(ns == "TL" ? "" : ns + '.')}{name}{(symbol.IsGenericType ? "