mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2025-12-06 06:52:01 +01:00
updated docs (and reordered Examples.md)
This commit is contained in:
parent
d64c5c0c1e
commit
b902b33558
484
EXAMPLES.md
484
EXAMPLES.md
|
|
@ -21,6 +21,29 @@ and avoid calling the same methods (like `Messages_GetAllChats`) repetitively.
|
|||
ℹ️ 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](Examples) and in answers to [StackOverflow questions](https://stackoverflow.com/questions/tagged/wtelegramclient).
|
||||
|
||||
<a name="logging"></a>
|
||||
### 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 (THIS IS NOT RECOMMENDED 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)
|
||||
|
||||
<a name="msg-by-name"></a>
|
||||
### Send a message to someone by @username
|
||||
```csharp
|
||||
|
|
@ -30,15 +53,6 @@ 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.*
|
||||
|
||||
<a name="msg-by-phone"></a>
|
||||
### 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: To prevent spam, Telegram may restrict your ability to add new phone numbers.*
|
||||
|
||||
<a name="markdown"></a>
|
||||
<a name="html"></a>
|
||||
### Convert message to/from HTML or Markdown, and send it to ourself (Saved Messages)
|
||||
|
|
@ -63,6 +77,196 @@ 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, that user's access hash must have been collected first ([see below](#collect-access-hash))*
|
||||
|
||||
<a name="list-dialogs"></a>
|
||||
### 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);
|
||||
}
|
||||
```
|
||||
|
||||
*Note: 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](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
<a name="list-chats"></a>
|
||||
### 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](Examples/Program_GetAllChats.cs)
|
||||
|
||||
<a name="list-members"></a>
|
||||
### 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 correct 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*
|
||||
|
||||
<a name="history"></a>
|
||||
### Fetch all messages (history) from a chat
|
||||
```csharp
|
||||
var chats = await client.Messages_GetAllChats();
|
||||
InputPeer peer = chats.chats[1234567890]; // the chat 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:
|
||||
- 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);`
|
||||
|
||||
<a name="updates"></a>
|
||||
### Monitor all Telegram events happening for the user
|
||||
|
||||
This is done through the `client.OnUpdate` callback event.
|
||||
Your event handler implementation can either return `Task.CompletedTask` or be an `async Task` method.
|
||||
|
||||
See [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
<a name="monitor-msg"></a>
|
||||
### Monitor new messages being posted in chats in real-time
|
||||
|
||||
You have to handle `client.OnUpdate` events containing an `UpdateNewMessage`.
|
||||
|
||||
See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
You can filter specific chats the message are posted in, by looking at the `Message.peer_id` field.
|
||||
|
||||
<a name="download"></a>
|
||||
### 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](Examples/Program_DownloadSavedMedia.cs) that download all media files you forward to yourself (Saved Messages)
|
||||
|
||||
<a name="upload"></a>
|
||||
### 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);
|
||||
```
|
||||
|
||||
<a name="album"></a>
|
||||
### 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<Message>().Select(m => m.media).OfType<MessageMediaPhoto>().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<InputMedia>
|
||||
{
|
||||
photoFromTelegram, // PhotoBase has implicit conversion to InputMediaPhoto
|
||||
new InputMediaUploadedPhoto { file = uploadedFile },
|
||||
new InputMediaPhotoExternal() { url = photoUrl },
|
||||
};
|
||||
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*
|
||||
|
||||
<a name="forward"></a><a name="copy"></a>
|
||||
### 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.Messages_ForwardMessages(from_chat, new[] { msg.ID }, new[] { WTelegram.Helpers.RandomLong() }, to_chat);
|
||||
|
||||
// • Copy the message without the "Forwarded" header (only the source message id is necessary)
|
||||
await client.Messages_ForwardMessages(from_chat, new[] { msg.ID }, new[] { WTelegram.Helpers.RandomLong() }, 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);
|
||||
```
|
||||
|
||||
<a name="schedule-msg"></a>
|
||||
### 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);
|
||||
```
|
||||
|
||||
<a name="fun"></a>
|
||||
### Fun with stickers, GIFs, dice, and animated emojies
|
||||
```csharp
|
||||
|
|
@ -112,123 +316,41 @@ var typing = await client.Messages_SetTyping(InputPeer.Self, new SendMessageEmoj
|
|||
await Task.Delay(5000);
|
||||
```
|
||||
|
||||
<a name="list-chats"></a>
|
||||
### List all chats (groups/channels NOT users) that we joined and send a message to one
|
||||
<a name="reaction"></a><a name="pinned"></a><a name="custom_emoji"></a>
|
||||
### Fun with custom emojies and reactions on pinned messages
|
||||
```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](Examples/Program_GetAllChats.cs)
|
||||
// • Sending a message with custom emojies in Markdown to ourself:
|
||||
var text = "Vicksy says Hi! [👋](emoji?id=5190875290439525089)";
|
||||
var entities = client.MarkdownToEntities(ref text, premium: true);
|
||||
await client.SendMessageAsync(InputPeer.Self, text, entities: entities);
|
||||
// also available in HTML: <tg-emoji id="5190875290439525089">👋</tg-emoji>
|
||||
|
||||
<a name="list-dialogs"></a>
|
||||
### 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;
|
||||
}
|
||||
```
|
||||
|
||||
*Note: 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](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
<a name="schedule-msg"></a>
|
||||
### 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);
|
||||
```
|
||||
|
||||
<a name="upload"></a>
|
||||
### 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";
|
||||
// • Fetch all available standard emoji reactions
|
||||
var all_emoji = await client.Messages_GetAvailableReactions();
|
||||
|
||||
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);
|
||||
```
|
||||
var chat = chats.chats[1234567890]; // the chat we want
|
||||
|
||||
<a name="album"></a>
|
||||
### 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<Message>().Select(m => m.media).OfType<MessageMediaPhoto>().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<InputMedia>
|
||||
// • Check reactions available in this chat
|
||||
var full = await client.GetFullChat(chat);
|
||||
Reaction reaction = full.full_chat.AvailableReactions switch
|
||||
{
|
||||
photoFromTelegram, // PhotoBase has implicit conversion to InputMediaPhoto
|
||||
new InputMediaUploadedPhoto { file = uploadedFile },
|
||||
new InputMediaPhotoExternal() { url = photoUrl },
|
||||
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
|
||||
};
|
||||
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*
|
||||
if (reaction == null) return;
|
||||
|
||||
<a name="list-members"></a>
|
||||
### Get all 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);
|
||||
// • Send the selected reaction on the last 2 pinned messages
|
||||
var messages = await client.Messages_Search<InputMessagesFilterPinned>(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/method/messages.getFeaturedEmojiStickers) or inspecting incoming messages. Access hash is not required*
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
If you only need to list the channel owner/admins, you can use specific filter:
|
||||
```csharp
|
||||
var participants = await client.Channels_GetParticipants(channel, filter: new ChannelParticipantsAdmins());
|
||||
foreach (var participant in participants.participants) // This is the correct 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*
|
||||
|
||||
<a name="join-channel"></a>
|
||||
### Join a channel/group by their public name or invite link
|
||||
|
|
@ -278,46 +400,14 @@ await client.Messages_DeleteExportedChatInvite(chat, invite.link);
|
|||
await client.DeleteChatUser(chat, user);
|
||||
```
|
||||
|
||||
<a name="history"></a>
|
||||
### Get all messages (history) from a chat
|
||||
<a name="msg-by-phone"></a>
|
||||
### Send a message to someone by phone number
|
||||
```csharp
|
||||
var chats = await client.Messages_GetAllChats();
|
||||
InputPeer peer = chats.chats[1234567890]; // the chat 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;
|
||||
}
|
||||
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!");
|
||||
```
|
||||
Notes:
|
||||
- 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);`
|
||||
|
||||
<a name="updates"></a>
|
||||
### Monitor all Telegram events happening for the user
|
||||
|
||||
This is done through the `client.OnUpdate` callback event.
|
||||
Your event handler implementation can either return `Task.CompletedTask` or be an `async Task` method.
|
||||
|
||||
See [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
<a name="monitor-msg"></a>
|
||||
### Monitor new messages being posted in chats in real-time
|
||||
|
||||
You have to handle `client.OnUpdate` events containing an `UpdateNewMessage`.
|
||||
|
||||
See the `DisplayMessage` method in [Examples/Program_ListenUpdates.cs](Examples/Program_ListenUpdates.cs).
|
||||
|
||||
You can filter specific chats the message are posted in, by looking at the `Message.peer_id` field.
|
||||
*Note: Don't use this method too much. To prevent spam, Telegram may restrict your ability to add new phone numbers.*
|
||||
|
||||
<a name="contacts"></a>
|
||||
### Retrieve the current user's contacts list
|
||||
|
|
@ -348,14 +438,6 @@ finally
|
|||
}
|
||||
```
|
||||
|
||||
<a name="download"></a>
|
||||
### Downloading photos, medias, files
|
||||
|
||||
This is done using the helper method `client.DownloadFileAsync(file, outputStream)`
|
||||
that simplify 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](Examples/Program_DownloadSavedMedia.cs) that download all media files you forward to yourself (Saved Messages)
|
||||
|
||||
<a name="collect-access-hash"></a>
|
||||
### Collect Access Hash and save them for later use
|
||||
|
||||
|
|
@ -397,29 +479,6 @@ If your Telegram client is already connected to such MTPROTO proxy, you can also
|
|||
|
||||
*Note: WTelegramClient always uses transport obfuscation when connecting to Telegram servers, even without MTProxy*
|
||||
|
||||
<a name="logging"></a>
|
||||
### 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 (THIS IS NOT RECOMMENDED 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)
|
||||
|
||||
<a name="2FA"></a>
|
||||
### Change 2FA password
|
||||
```csharp
|
||||
|
|
@ -449,61 +508,6 @@ Use it to pass a custom Stream-derived class that will **read** (first initial c
|
|||
|
||||
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#L61)
|
||||
|
||||
<a name="reaction"></a><a name="pinned"></a><a name="custom_emoji"></a>
|
||||
### 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! [👋](emoji?id=5190875290439525089)";
|
||||
var entities = client.MarkdownToEntities(ref text, premium: true);
|
||||
await client.SendMessageAsync(InputPeer.Self, text, entities: entities);
|
||||
// also available in HTML: <tg-emoji id="5190875290439525089">👋</tg-emoji>
|
||||
|
||||
// • 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<InputMessagesFilterPinned>(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/method/messages.getFeaturedEmojiStickers) or inspecting incoming messages. Access hash is not required*
|
||||
|
||||
<a name="forward"></a><a name="copy"></a>
|
||||
### 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.Messages_ForwardMessages(from_chat, new[] { msg.ID }, new[] { WTelegram.Helpers.RandomLong() }, to_chat);
|
||||
|
||||
// • Copy the message without the "Forwarded" header (only the source message id is necessary)
|
||||
await client.Messages_ForwardMessages(from_chat, new[] { msg.ID }, new[] { WTelegram.Helpers.RandomLong() }, 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);
|
||||
```
|
||||
|
||||
<a name="e2e"></a><a name="secrets"></a>
|
||||
### Send/receive end-to-end encrypted messages & files in Secret Chats
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ or a [broadcast channel](https://corefork.telegram.org/api/channel#channels) (th
|
|||
- `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`
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ namespace WTelegram
|
|||
if (!IsMainDC && _pendingRpcs.Count <= 1 && ex is ApplicationException { Message: ConnectionShutDown } or IOException { InnerException: SocketException })
|
||||
if (_pendingRpcs.Values.FirstOrDefault() is not Rpc rpc || rpc.type == typeof(Pong))
|
||||
_reactorReconnects = 0;
|
||||
if (_reactorReconnects != 0)
|
||||
{
|
||||
if (_reactorReconnects == 0)
|
||||
throw;
|
||||
await Task.Delay(5000);
|
||||
if (_networkStream == null) return; // Dispose has been called in-between
|
||||
await ConnectAsync(); // start a new reactor after 5 secs
|
||||
|
|
@ -341,9 +341,6 @@ namespace WTelegram
|
|||
RaiseUpdate(updatesState);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
RaiseUpdate(reactorError);
|
||||
|
|
|
|||
|
|
@ -2268,24 +2268,24 @@ namespace TL
|
|||
/// <summary>Location of a certain size of a picture <para>See <a href="https://corefork.telegram.org/type/PhotoSize"/></para> <para>Derived classes: <see cref="PhotoSizeEmpty"/>, <see cref="PhotoSize"/>, <see cref="PhotoCachedSize"/>, <see cref="PhotoStrippedSize"/>, <see cref="PhotoSizeProgressive"/>, <see cref="PhotoPathSize"/></para></summary>
|
||||
public abstract partial class PhotoSizeBase : IObject
|
||||
{
|
||||
/// <summary>Thumbnail type (see. <see cref="PhotoSize"/>)</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public virtual string Type { get; }
|
||||
}
|
||||
/// <summary>Empty constructor. Image with this thumbnail is unavailable. <para>See <a href="https://corefork.telegram.org/constructor/photoSizeEmpty"/></para></summary>
|
||||
[TLDef(0x0E17E23C)]
|
||||
public partial class PhotoSizeEmpty : PhotoSizeBase
|
||||
{
|
||||
/// <summary>Thumbnail type (see. <see cref="PhotoSize"/>)</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public string type;
|
||||
|
||||
/// <summary>Thumbnail type (see. <see cref="PhotoSize"/>)</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public override string Type => type;
|
||||
}
|
||||
/// <summary>Image description. <para>See <a href="https://corefork.telegram.org/constructor/photoSize"/></para></summary>
|
||||
[TLDef(0x75C78E60)]
|
||||
public partial class PhotoSize : PhotoSizeBase
|
||||
{
|
||||
/// <summary>Thumbnail type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public string type;
|
||||
/// <summary>Image width</summary>
|
||||
public int w;
|
||||
|
|
@ -2294,7 +2294,7 @@ namespace TL
|
|||
/// <summary>File size</summary>
|
||||
public int size;
|
||||
|
||||
/// <summary>Thumbnail type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public override string Type => type;
|
||||
}
|
||||
/// <summary>Description of an image and its content. <para>See <a href="https://corefork.telegram.org/constructor/photoCachedSize"/></para></summary>
|
||||
|
|
@ -2329,7 +2329,7 @@ namespace TL
|
|||
[TLDef(0xFA3EFB95)]
|
||||
public partial class PhotoSizeProgressive : PhotoSizeBase
|
||||
{
|
||||
/// <summary>Photosize type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Photosize type »</a></summary>
|
||||
public string type;
|
||||
/// <summary>Photo width</summary>
|
||||
public int w;
|
||||
|
|
@ -2338,7 +2338,7 @@ namespace TL
|
|||
/// <summary>Sizes of progressive JPEG file prefixes, which can be used to preliminarily show the image.</summary>
|
||||
public int[] sizes;
|
||||
|
||||
/// <summary>Photosize type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Photosize type »</a></summary>
|
||||
public override string Type => type;
|
||||
}
|
||||
/// <summary>Messages with animated stickers can have a compressed svg (< 300 bytes) to show the outline of the sticker before fetching the actual lottie animation. <para>See <a href="https://corefork.telegram.org/constructor/photoPathSize"/></para></summary>
|
||||
|
|
@ -13151,9 +13151,9 @@ namespace TL
|
|||
public long bot_id;
|
||||
/// <summary>Attachment menu item name</summary>
|
||||
public string short_name;
|
||||
/// <summary>List of peer types where this attachment should be shown</summary>
|
||||
/// <summary>List of dialog types where this attachment menu entry should be shown</summary>
|
||||
public AttachMenuPeerType[] peer_types;
|
||||
/// <summary>Attachment menu icon</summary>
|
||||
/// <summary>List of platform-specific static icons and animations to use for the attachment menu button</summary>
|
||||
public AttachMenuBotIcon[] icons;
|
||||
|
||||
[Flags] public enum Flags : uint
|
||||
|
|
@ -13292,9 +13292,9 @@ namespace TL
|
|||
{
|
||||
///<summary>The bot attachment menu entry is available in the chat with the bot that offers it</summary>
|
||||
SameBotPM = 0x7D6BE90E,
|
||||
///<summary>The bot attachment menu entry is available in private chats with other bots</summary>
|
||||
///<summary>The bot attachment menu entry is available in private chats with other bots (excluding the bot that offers the current attachment menu)</summary>
|
||||
BotPM = 0xC32BFA1A,
|
||||
///<summary>The bot attachment menu entry is available in private chats with other users</summary>
|
||||
///<summary>The bot attachment menu entry is available in private chats with other users (not bots)</summary>
|
||||
PM = 0xF146D31F,
|
||||
///<summary>The bot attachment menu entry is available in <a href="https://corefork.telegram.org/api/channel">groups and supergroups</a></summary>
|
||||
Chat = 0x0509113F,
|
||||
|
|
|
|||
|
|
@ -3214,7 +3214,7 @@ namespace TL
|
|||
/// <param name="bot">Bot that owns the <a href="https://corefork.telegram.org/api/bots/webapps">web app</a></param>
|
||||
/// <param name="url"><a href="https://corefork.telegram.org/api/bots/webapps">Web app URL</a></param>
|
||||
/// <param name="start_param">If the web app was opened from the attachment menu using a <a href="https://corefork.telegram.org/api/links#bot-attachment-menu-links">attachment menu deep link</a>, <c>start_param</c> should contain the <c>data</c> from the <c>startattach</c> parameter.</param>
|
||||
/// <param name="theme_params">Theme parameters for the web app</param>
|
||||
/// <param name="theme_params"><a href="https://corefork.telegram.org/api/bots/webapps#theme-parameters">Theme parameters »</a></param>
|
||||
/// <param name="platform">Short name of the application; 0-64 English letters, digits, and underscores</param>
|
||||
/// <param name="reply_to_msg_id">Whether the inline message that will be sent by the bot on behalf of the user once the web app interaction is <a href="https://corefork.telegram.org/method/messages.sendWebViewResultMessage">terminated</a> should be sent in reply to this message ID.</param>
|
||||
/// <param name="send_as">Open the web app as the specified peer, sending the resulting the message as the specified peer.</param>
|
||||
|
|
@ -3253,7 +3253,7 @@ namespace TL
|
|||
/// <summary>Open a <a href="https://corefork.telegram.org/api/bots/webapps">bot web app</a>. <para>See <a href="https://corefork.telegram.org/method/messages.requestSimpleWebView"/></para></summary>
|
||||
/// <param name="bot">Bot that owns the webapp</param>
|
||||
/// <param name="url">Web app URL</param>
|
||||
/// <param name="theme_params">Theme parameters</param>
|
||||
/// <param name="theme_params"><a href="https://corefork.telegram.org/api/bots/webapps#theme-parameters">Theme parameters »</a></param>
|
||||
/// <param name="platform">Short name of the application; 0-64 English letters, digits, and underscores</param>
|
||||
public static Task<SimpleWebViewResult> Messages_RequestSimpleWebView(this Client client, InputUserBase bot, string url, string platform, DataJSON theme_params = null)
|
||||
=> client.Invoke(new Messages_RequestSimpleWebView
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ namespace TL
|
|||
[TLDef(0x77BFB61B)]
|
||||
public partial class PhotoSize : PhotoSizeBase
|
||||
{
|
||||
/// <summary>Thumbnail type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public string type;
|
||||
public FileLocationBase location;
|
||||
/// <summary>Image width</summary>
|
||||
|
|
@ -682,7 +682,7 @@ namespace TL
|
|||
/// <summary>File size</summary>
|
||||
public int size;
|
||||
|
||||
/// <summary>Thumbnail type</summary>
|
||||
/// <summary><a href="https://corefork.telegram.org/api/files#image-thumbnail-types">Thumbnail type »</a></summary>
|
||||
public override string Type => type;
|
||||
}
|
||||
/// <summary>Description of an image and its content. <para>See <a href="https://corefork.telegram.org/constructor/photoCachedSize"/></para></summary>
|
||||
|
|
|
|||
Loading…
Reference in a new issue