diff --git a/.github/dev.yml b/.github/dev.yml
index 44fe595..5e87822 100644
--- a/.github/dev.yml
+++ b/.github/dev.yml
@@ -2,7 +2,7 @@ pr: none
trigger:
- master
-name: 1.6.5-dev.$(Rev:r)
+name: 1.7.1-dev.$(Rev:r)
pool:
vmImage: ubuntu-latest
diff --git a/.github/release.yml b/.github/release.yml
index eebf040..4ff32b6 100644
--- a/.github/release.yml
+++ b/.github/release.yml
@@ -1,7 +1,7 @@
pr: none
trigger: none
-name: 1.6.$(Rev:r)
+name: 1.7.$(Rev:r)
pool:
vmImage: ubuntu-latest
diff --git a/EXAMPLES.md b/EXAMPLES.md
index 9a82e7b..48a5cc1 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -83,21 +83,20 @@ await client.SendMediaAsync(peer, "Here is the photo", inputFile);
```csharp
using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
await client.LoginUserIfNeeded();
-var dialogsBase = await client.Messages_GetDialogs(default, 0, null, 0, 0);
-if (dialogsBase is Messages_Dialogs dialogs)
- while (dialogs.dialogs.Length != 0)
- {
- foreach (var dialog in dialogs.dialogs)
- switch (dialogs.UserOrChat(dialog))
- {
- case UserBase user when user.IsActive: Console.WriteLine("User " + user); break;
- case ChatBase chat when chat.IsActive: Console.WriteLine(chat); break;
- }
- var lastDialog = dialogs.dialogs[^1];
- var lastMsg = dialogs.messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage);
- var offsetPeer = dialogs.UserOrChat(lastDialog).ToInputPeer();
- dialogs = (Messages_Dialogs)await client.Messages_GetDialogs(lastMsg?.Date ?? default, lastDialog.TopMessage, offsetPeer, 500, 0);
- }
+var dialogs = await client.Messages_GetDialogs(default, 0, null, 0, 0);
+while (dialogs.Dialogs.Length != 0)
+{
+ foreach (var dialog in dialogs.Dialogs)
+ switch (dialogs.UserOrChat(dialog))
+ {
+ case UserBase user when user.IsActive: Console.WriteLine("User " + user); break;
+ case ChatBase chat when chat.IsActive: Console.WriteLine(chat); break;
+ }
+ var lastDialog = dialogs.Dialogs[^1];
+ var lastMsg = dialogs.Messages.LastOrDefault(m => m.Peer.ID == lastDialog.Peer.ID && m.ID == lastDialog.TopMessage);
+ var offsetPeer = dialogs.UserOrChat(lastDialog).ToInputPeer();
+ dialogs = await client.Messages_GetDialogs(lastMsg?.Date ?? default, lastDialog.TopMessage, offsetPeer, 500, 0);
+}
```
*Note: the lists returned by Messages_GetDialogs contains the `access_hash` for those chats and users.*
@@ -138,13 +137,12 @@ var chats = await client.Messages_GetAllChats(null);
InputPeer peer = chats.chats[1234567890]; // the chat we want
for (int offset = 0; ;)
{
- var messagesBase = await client.Messages_GetHistory(peer, 0, default, offset, 1000, 0, 0, 0);
- if (messagesBase is not Messages_ChannelMessages channelMessages) break;
- foreach (var msgBase in channelMessages.messages)
+ var messages = await client.Messages_GetHistory(peer, 0, default, offset, 1000, 0, 0, 0);
+ foreach (var msgBase in messages.Messages)
if (msgBase is Message msg)
Console.WriteLine(msg.message);
- offset += channelMessages.messages.Length;
- if (offset >= channelMessages.count) break;
+ offset += messages.Messages.Length;
+ if (offset >= messages.Count) break;
}
```
### Monitor all Telegram events happening for the user
diff --git a/README.md b/README.md
index bbe31b5..f3686ec 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,8 @@ This library can be used for any Telegram scenarios including:
- Download/upload of files/media
- etc...
+It has been tested in a Console app, WinForms app, ASP.NET webservice.
+
Secret chats (end-to-end encryption, PFS) and connection to CDN DCs have not been tested yet.
Please don't use this library for Spam or Scam. Respect Telegram [Terms of Service](https://telegram.org/tos) or you might get banned from Telegram servers.
diff --git a/src/Client.cs b/src/Client.cs
index 7e807d4..3b518a0 100644
--- a/src/Client.cs
+++ b/src/Client.cs
@@ -577,7 +577,7 @@ namespace WTelegram
writer.Write(msgId); // int64 message_id
writer.Write(0); // int32 message_data_length (to be patched)
writer.WriteTLObject(msg); // bytes message_data
- Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name}...");
+ Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_')}...");
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(20), (int)memStream.Length - 24); // patch message_data_length
}
else
@@ -597,9 +597,9 @@ namespace WTelegram
clearWriter.Write(0); // int32 message_data_length (to be patched)
clearWriter.WriteTLObject(msg); // bytes message_data
if ((seqno & 1) != 0)
- Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name,-40} #{(short)msgId.GetHashCode():X4}");
+ Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_'),-40} #{(short)msgId.GetHashCode():X4}");
else
- Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name,-40} {MsgIdToStamp(msgId):u} (svc)");
+ Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_'),-40} {MsgIdToStamp(msgId):u} (svc)");
int clearLength = (int)clearStream.Length - prepend; // length before padding (= 32 + message_data_length)
int padding = (0x7FFFFFF0 - clearLength) % 16;
#if !MTPROTO1
@@ -1092,7 +1092,8 @@ namespace WTelegram
/// Your message is a reply to an existing message with this ID, in the same chat
/// Text formatting entities for the caption. You can use MarkdownToEntities to create these
/// UTC timestamp when the message should be sent
- public Task SendMediaAsync(InputPeer peer, string caption, InputFileBase mediaFile, string mimeType = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default)
+ /// The transmitted message confirmed by Telegram
+ public Task SendMediaAsync(InputPeer peer, string caption, InputFileBase mediaFile, string mimeType = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default)
{
var filename = mediaFile is InputFile iFile ? iFile.name : (mediaFile as InputFileBig)?.name;
mimeType ??= Path.GetExtension(filename).ToLowerInvariant() switch
@@ -1123,16 +1124,53 @@ namespace WTelegram
/// Text formatting entities. You can use MarkdownToEntities to create these
/// UTC timestamp when the message should be sent
/// Should website/media preview be shown or not, for URLs in your message
- public Task SendMessageAsync(InputPeer peer, string text, InputMedia media = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default, bool disable_preview = false)
+ /// The transmitted message as confirmed by Telegram
+ public async Task SendMessageAsync(InputPeer peer, string text, InputMedia media = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default, bool disable_preview = false)
{
+ UpdatesBase updates;
+ long random_id = Helpers.RandomLong();
if (media == null)
- return this.Messages_SendMessage(peer, text, Helpers.RandomLong(),
- no_webpage: disable_preview, reply_to_msg_id: reply_to_msg_id, entities: entities, schedule_date: schedule_date);
- else
- return this.Messages_SendMedia(peer, media, text, Helpers.RandomLong(),
+ updates = await this.Messages_SendMessage(peer, text, random_id, no_webpage: disable_preview,
reply_to_msg_id: reply_to_msg_id, entities: entities, schedule_date: schedule_date);
+ else
+ updates = await this.Messages_SendMedia(peer, media, text, random_id,
+ reply_to_msg_id: reply_to_msg_id, entities: entities, schedule_date: schedule_date);
+ OnUpdate(updates);
+ int msgId = -1;
+ foreach (var update in updates.UpdateList)
+ {
+ switch (update)
+ {
+ case UpdateMessageID updMsgId when updMsgId.random_id == random_id: msgId = updMsgId.id; break;
+ case UpdateNewMessage { message: Message message } when message.id == msgId: return message;
+ case UpdateNewScheduledMessage { message: Message schedMsg } when schedMsg.id == msgId: return schedMsg;
+ }
+ }
+ if (updates is UpdateShortSentMessage sent)
+ {
+ return new Message
+ {
+ flags = (Message.Flags)sent.flags | (reply_to_msg_id == 0 ? 0 : Message.Flags.has_reply_to) | (peer is InputPeerSelf ? 0 : Message.Flags.has_from_id),
+ id = sent.id, date = sent.date, message = text, entities = sent.entities, media = sent.media, ttl_period = sent.ttl_period,
+ reply_to = reply_to_msg_id == 0 ? null : new MessageReplyHeader { reply_to_msg_id = reply_to_msg_id },
+ from_id = peer is InputPeerSelf ? null : new PeerUser { user_id = _session.UserId },
+ peer_id = InputToPeer(peer)
+ };
+ }
+ return null;
}
+ private Peer InputToPeer(InputPeer peer) => peer switch
+ {
+ InputPeerSelf => new PeerUser { user_id = _session.UserId },
+ InputPeerUser ipu => new PeerUser { user_id = ipu.user_id },
+ InputPeerChat ipc => new PeerChat { chat_id = ipc.chat_id },
+ InputPeerChannel ipch => new PeerChannel { channel_id = ipch.channel_id },
+ InputPeerUserFromMessage ipufm => new PeerUser { user_id = ipufm.user_id },
+ InputPeerChannelFromMessage ipcfm => new PeerChannel { channel_id = ipcfm.channel_id },
+ _ => null,
+ };
+
/// Download a photo from Telegram into the outputStream
/// The photo to download
/// Stream to write the file content to. This method does not close/dispose the stream
diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs
index 4ecf5ca..f36082c 100644
--- a/src/TL.Helpers.cs
+++ b/src/TL.Helpers.cs
@@ -120,6 +120,8 @@ namespace TL
public static implicit operator InputChannel(Channel channel) => new() { channel_id = channel.id, access_hash = channel.access_hash };
public override string ToString() =>
(flags.HasFlag(Flags.broadcast) ? "Channel " : "Group ") + (username != null ? '@' + username : $"\"{title}\"");
+ public bool IsChannel => (flags & Flags.broadcast) != 0;
+ public bool IsGroup => (flags & Flags.broadcast) == 0;
}
partial class ChannelForbidden
{
@@ -213,7 +215,7 @@ namespace TL
}
partial class Contacts_Blocked { public IPeerInfo UserOrChat(PeerBlocked peer) => peer.peer_id.UserOrChat(users, chats); }
- partial class Messages_Dialogs { public IPeerInfo UserOrChat(DialogBase dialog) => dialog.Peer.UserOrChat(users, chats); }
+ partial class Messages_DialogsBase { public IPeerInfo UserOrChat(DialogBase dialog) => UserOrChat(dialog.Peer); }
partial class Messages_MessagesBase { public abstract int Count { get; } }
partial class Messages_Messages { public override int Count => messages.Length; }
@@ -310,6 +312,35 @@ namespace TL
public override int Timeout => timeout;
}
+ partial class UpdatesBase { public abstract Update[] UpdateList { get; } }
+ partial class UpdatesTooLong { public override Update[] UpdateList => Array.Empty(); }
+ partial class UpdateShort { public override Update[] UpdateList => new[] { update }; }
+ partial class UpdatesCombined { public override Update[] UpdateList => updates; }
+ partial class Updates { public override Update[] UpdateList => updates; }
+ partial class UpdateShortSentMessage { public override Update[] UpdateList => Array.Empty(); }
+ partial class UpdateShortMessage { public override Update[] UpdateList => new[] { new UpdateNewMessage
+ {
+ message = new Message
+ {
+ flags = (Message.Flags)flags, id = id, date = date,
+ message = message, entities = entities, reply_to = reply_to,
+ from_id = new PeerUser { user_id = user_id },
+ peer_id = new PeerUser { user_id = user_id },
+ fwd_from = fwd_from, via_bot_id = via_bot_id, ttl_period = ttl_period
+ }, pts = pts, pts_count = pts_count
+ } }; }
+ partial class UpdateShortChatMessage { public override Update[] UpdateList => new[] { new UpdateNewMessage
+ {
+ message = new Message
+ {
+ flags = (Message.Flags)flags, id = id, date = date,
+ message = message, entities = entities, reply_to = reply_to,
+ from_id = new PeerUser { user_id = from_id },
+ peer_id = new PeerChat { chat_id = chat_id },
+ fwd_from = fwd_from, via_bot_id = via_bot_id, ttl_period = ttl_period
+ }, pts = pts, pts_count = pts_count
+ } }; }
+
partial class Messages_PeerDialogs { public IPeerInfo UserOrChat(DialogBase dialog) => dialog.Peer.UserOrChat(users, chats); }
partial class SecureFile
diff --git a/src/TL.Schema.cs b/src/TL.Schema.cs
index 0f47962..79670b2 100644
--- a/src/TL.Schema.cs
+++ b/src/TL.Schema.cs
@@ -4250,7 +4250,7 @@ namespace TL
/// returns a or for the given Peer
public IPeerInfo UserOrChat(Peer peer) => peer.UserOrChat(users, chats);
}
- /// See
+ /// Full constructor of updates See
[TLDef(0x74AE4240)]
public partial class Updates : UpdatesBase
{
@@ -4819,7 +4819,7 @@ namespace TL
public override byte[] Bytes => bytes;
}
- /// Derived classes: , See
+ /// Contains info on cofiguring parameters for key generation by Diffie-Hellman protocol. Derived classes: , See
public abstract partial class Messages_DhConfigBase : IObject { }
/// Configuring parameters did not change. See
[TLDef(0xC0E24635)]
@@ -7323,7 +7323,7 @@ namespace TL
public TopPeer[] peers;
}
- /// Derived classes: , See
+ /// Top peers Derived classes: , See
/// a null value means contacts.topPeersNotModified
public abstract partial class Contacts_TopPeersBase : IObject { }
/// Top peers See
@@ -7386,7 +7386,7 @@ namespace TL
}
}
- /// Derived classes: , See
+ /// Featured stickers Derived classes: , See
public abstract partial class Messages_FeaturedStickersBase : IObject { }
/// Featured stickers haven't changed See
[TLDef(0xC6DC0C66)]
@@ -7434,7 +7434,7 @@ namespace TL
public StickerSetCoveredBase[] sets;
}
- /// Derived classes: , See
+ /// Result of stickerset installation process Derived classes: , See
public abstract partial class Messages_StickerSetInstallResult : IObject { }
/// The stickerset was installed successfully See
[TLDef(0x38641628)]
@@ -8320,7 +8320,7 @@ namespace TL
}
}
- /// See
+ /// Validated user-provided info See
[TLDef(0xD1451883)]
public partial class Payments_ValidatedRequestedInfo : IObject
{
@@ -8340,7 +8340,7 @@ namespace TL
}
}
- /// Derived classes: , See
+ /// Payment result Derived classes: , See
public abstract partial class Payments_PaymentResultBase : IObject { }
/// Payment result See
[TLDef(0x4E5F810D)]
@@ -9481,7 +9481,7 @@ namespace TL
public StickerSetCoveredBase[] sets;
}
- /// See
+ /// SHA256 Hash of an uploaded file, to be checked for validity after download See
[TLDef(0x6242C773)]
public partial class FileHash : IObject
{
@@ -9503,7 +9503,7 @@ namespace TL
public int port;
}
- /// Derived classes: , See
+ /// Update of Telegram's terms of service Derived classes: , See
public abstract partial class Help_TermsOfServiceUpdateBase : IObject { }
/// No changes were made to telegram's terms of service See
[TLDef(0xE3309F7F)]
@@ -18461,7 +18461,7 @@ namespace TL
bytes = bytes,
});
- /// See
+ /// Returns content of an HTTP file or a part, by proxying the request through telegram. See
[TLDef(0x24E6818D)]
public partial class Upload_GetWebFile_ : IMethod
{
@@ -18472,7 +18472,7 @@ namespace TL
/// Number of bytes to be returned
public int limit;
}
- /// See Possible codes: 400 (details)
+ /// Returns content of an HTTP file or a part, by proxying the request through telegram. See Possible codes: 400 (details)
/// The file to download
/// Number of bytes to be skipped
/// Number of bytes to be returned
diff --git a/src/TL.cs b/src/TL.cs
index afc4bfc..ce45015 100644
--- a/src/TL.cs
+++ b/src/TL.cs
@@ -36,7 +36,7 @@ namespace TL
var tlDef = type.GetCustomAttribute();
var ctorNb = tlDef.CtorNb;
writer.Write(ctorNb);
- IEnumerable fields = type.GetFields();
+ IEnumerable fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (!tlDef.inheritAfter) fields = fields.GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
int flags = 0;
IfFlagAttribute ifFlag;
@@ -57,7 +57,7 @@ namespace TL
if (type == null) return null; // nullable ctor (class meaning is associated with null)
var tlDef = type.GetCustomAttribute();
var obj = Activator.CreateInstance(type);
- IEnumerable fields = type.GetFields();
+ IEnumerable fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (!tlDef.inheritAfter) fields = fields.GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
int flags = 0;
IfFlagAttribute ifFlag;
@@ -177,9 +177,9 @@ namespace TL
writer.Write(0); // patched below
writer.WriteTLObject(msg.body);
if ((msg.seqno & 1) != 0)
- WTelegram.Helpers.Log(1, $" Sending → {msg.body.GetType().Name,-40} #{(short)msg.msg_id.GetHashCode():X4}");
+ WTelegram.Helpers.Log(1, $" Sending → {msg.body.GetType().Name.TrimEnd('_'),-40} #{(short)msg.msg_id.GetHashCode():X4}");
else
- WTelegram.Helpers.Log(1, $" Sending → {msg.body.GetType().Name,-40}");
+ WTelegram.Helpers.Log(1, $" Sending → {msg.body.GetType().Name.TrimEnd('_'),-40}");
writer.BaseStream.Position = patchPos;
writer.Write((int)(writer.BaseStream.Length - patchPos - 4)); // patch bytes field
writer.Seek(0, SeekOrigin.End);