diff --git a/EXAMPLES.md b/EXAMPLES.md
index b9c70a4..65dd40d 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -404,6 +404,8 @@ WTelegram.Helpers.Log = (lvl, str) => _logger.Log((LogLevel)lvl, str);
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)
+
### Change 2FA password
```csharp
diff --git a/Examples/Program_SecretChats.cs b/Examples/Program_SecretChats.cs
index 19c565e..14412f3 100644
--- a/Examples/Program_SecretChats.cs
+++ b/Examples/Program_SecretChats.cs
@@ -91,10 +91,11 @@ Type a command, or a message to send to the active secret chat:");
{
if (msg.Media != null && unem.message is EncryptedMessage { file: EncryptedFile ef })
{
- Console.WriteLine($"{unem.message.ChatId}> {msg.Message} [file being downloaded to media.jpg]");
- using var output = File.Create("media.jpg"); // not necessarily a JPG, check the msg.Media mime_type
- using var decryptStream = new WTelegram.AES_IGE_Stream(output, msg.Media);
- await Client.DownloadFileAsync(ef, decryptStream, ef.dc_id, ef.size);
+ int slash = msg.Media.MimeType?.IndexOf('/') ?? 0; // quick & dirty conversion from MIME type to file extension
+ var filename = slash > 0 ? $"media.{msg.Media.MimeType[(slash + 1)..]}" : "media.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..]}");
diff --git a/src/Client.cs b/src/Client.cs
index 2a06512..de73394 100644
--- a/src/Client.cs
+++ b/src/Client.cs
@@ -1054,7 +1054,7 @@ namespace WTelegram
catch (RpcException e) when (e.Code == 400 && e.Message is "CODE_INVALID" or "EMAIL_TOKEN_INVALID")
{
Helpers.Log(4, "Wrong email verification code!");
- if (retry == MaxCodePwdAttempts) throw;
+ if (retry >= MaxCodePwdAttempts) throw;
}
if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be)
sentCode = verifiedLogin.sent_code;
@@ -1084,7 +1084,7 @@ namespace WTelegram
catch (RpcException e) when (e.Code == 400 && e.Message == "PHONE_CODE_INVALID")
{
Helpers.Log(4, "Wrong verification code!");
- if (retry == MaxCodePwdAttempts) throw;
+ if (retry >= MaxCodePwdAttempts) throw;
}
catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED")
{
@@ -1099,7 +1099,7 @@ namespace WTelegram
catch (RpcException pe) when (pe.Code == 400 && pe.Message == "PASSWORD_HASH_INVALID")
{
Helpers.Log(4, "Wrong password!");
- if (pwdRetry == MaxCodePwdAttempts) throw;
+ if (pwdRetry >= MaxCodePwdAttempts) throw;
}
}
}
diff --git a/src/Encryption.cs b/src/Encryption.cs
index c7b857e..72bf6fe 100644
--- a/src/Encryption.cs
+++ b/src/Encryption.cs
@@ -526,16 +526,13 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
#endif
}
- /// Stream for encryption/decryption of AES-256 with infinite garble extension (IGE)
- public class AES_IGE_Stream : Helpers.IndirectStream
+ internal class AES_IGE_Stream : Helpers.IndirectStream
{
private readonly ICryptoTransform aesCrypto;
private readonly byte[] prevBytes;
- /// Decryption of AES-256 IGE file with key/iv obtained from media structure
- public AES_IGE_Stream(Stream stream, DecryptedMessageMedia media) : this(stream, media.SizeKeyIV) { }
- public AES_IGE_Stream(Stream stream, (int size, byte[] key, byte[] iv) t) : this(stream, t.key, t.iv) { ContentLength = t.size; }
- public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt = false) : base(stream)
+ public AES_IGE_Stream(Stream stream, int size, byte[] key, byte[] iv) : this(stream, key, iv, false) { ContentLength = size; }
+ public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt) : base(stream)
{
aesCrypto = encrypt ? Encryption.AesECB.CreateEncryptor(key, null) : Encryption.AesECB.CreateDecryptor(key, null);
if (encrypt) prevBytes = (byte[])iv.Clone();
diff --git a/src/SecretChats.cs b/src/SecretChats.cs
index 89d5d3a..701619f 100644
--- a/src/SecretChats.cs
+++ b/src/SecretChats.cs
@@ -595,5 +595,28 @@ namespace WTelegram
_ => null
};
}
+
+ /// Download and decrypt an encrypted file from Telegram Secret Chat into the outputStream
+ /// The encrypted file to download & decrypt
+ /// The associated message media structure
+ /// Stream to write the decrypted file content to. This method does not close/dispose the stream
+ /// (optional) Callback for tracking the progression of the transfer
+ /// The mime type of the decrypted file, if unknown
+ public async Task DownloadFile(EncryptedFile encryptedFile, DecryptedMessageMedia media, Stream outputStream, Client.ProgressCallback progress = null)
+ {
+ var (size, key, iv) = media.SizeKeyIV;
+ if (key == null || iv == null) throw new ArgumentException("Media has no information about encrypted file", nameof(media));
+ using var md5 = MD5.Create();
+ md5.TransformBlock(key, 0, 32, null, 0);
+ var res = md5.TransformFinalBlock(iv, 0, 32);
+ long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(md5.Hash);
+ fingerprint ^= fingerprint >> 32;
+ if (encryptedFile.key_fingerprint != (int)fingerprint) throw new ApplicationException("Encrypted file fingerprint mismatch");
+
+ using var decryptStream = new AES_IGE_Stream(outputStream, size, key, iv);
+ var fileLocation = encryptedFile.ToFileLocation();
+ await client.DownloadFileAsync(fileLocation, decryptStream, encryptedFile.dc_id, encryptedFile.size, progress);
+ return media.MimeType;
+ }
}
}
diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs
index bca7c92..aad124b 100644
--- a/src/TL.Helpers.cs
+++ b/src/TL.Helpers.cs
@@ -78,7 +78,7 @@ namespace TL
partial class Peer
{
public abstract long ID { get; }
- abstract internal IPeerInfo UserOrChat(Dictionary users, Dictionary chats);
+ internal abstract IPeerInfo UserOrChat(Dictionary users, Dictionary chats);
}
partial class PeerUser
{
diff --git a/src/TL.SchemaFuncs.cs b/src/TL.SchemaFuncs.cs
index 5dc865a..af4a1a7 100644
--- a/src/TL.SchemaFuncs.cs
+++ b/src/TL.SchemaFuncs.cs
@@ -288,8 +288,8 @@ namespace TL
/// Register device to receive PUSH notifications See Possible codes: 400 (details)
/// Avoid receiving (silent and invisible background) notifications. Useful to save battery.
- /// Device token type.
Possible values:
1 - APNS (device token for apple push)
2 - FCM (firebase token for google firebase)
3 - MPNS (channel URI for microsoft push)
4 - Simple push (endpoint for firefox's simple push API)
5 - Ubuntu phone (token for ubuntu push)
6 - Blackberry (token for blackberry push)
7 - Unused
8 - WNS (windows push)
9 - APNS VoIP (token for apple push VoIP)
10 - Web push (web push, see below)
11 - MPNS VoIP (token for microsoft push VoIP)
12 - Tizen (token for tizen push)
For 10 web push, the token must be a JSON-encoded object containing the keys described in PUSH updates
- /// Device token
+ /// Device token type, see PUSH updates for the possible values.
+ /// Device token, see PUSH updates for the possible values.
/// If is transmitted, a sandbox-certificate will be used during transmission.
/// For FCM and APNS VoIP, optional encryption key used to encrypt push notifications
/// List of user identifiers of other users currently using the client
@@ -305,8 +305,8 @@ namespace TL
});
/// Deletes a device by its token, stops sending PUSH-notifications to it. See Possible codes: 400 (details)
- /// Device token type.
Possible values:
1 - APNS (device token for apple push)
2 - FCM (firebase token for google firebase)
3 - MPNS (channel URI for microsoft push)
4 - Simple push (endpoint for firefox's simple push API)
5 - Ubuntu phone (token for ubuntu push)
6 - Blackberry (token for blackberry push)
7 - Unused
8 - WNS (windows push)
9 - APNS VoIP (token for apple push VoIP)
10 - Web push (web push, see below)
11 - MPNS VoIP (token for microsoft push VoIP)
12 - Tizen (token for tizen push)
For 10 web push, the token must be a JSON-encoded object containing the keys described in PUSH updates
- /// Device token
+ /// Device token type, see PUSH updates for the possible values.
+ /// Device token, see PUSH updates for the possible values.
/// List of user identifiers of other users currently using the client
public static Task Account_UnregisterDevice(this Client client, int token_type, string token, params long[] other_uids)
=> client.Invoke(new Account_UnregisterDevice
@@ -4570,7 +4570,7 @@ namespace TL
/// If set, the user's video will be disabled by default upon joining.
/// The group call
/// Join the group call, presenting yourself as the specified user/channel
- /// The invitation hash from the invite link », if provided allows speaking in a livestream or muted group chat.
+ /// The invitation hash from the invite link », if provided allows speaking in a livestream or muted group chat.
/// WebRTC parameters
public static Task Phone_JoinGroupCall(this Client client, InputGroupCall call, InputPeer join_as, DataJSON params_, bool muted = false, bool video_stopped = false, string invite_hash = null)
=> client.Invoke(new Phone_JoinGroupCall
@@ -4714,7 +4714,7 @@ namespace TL
peer = peer,
});
- /// Get an invite link for a group call or livestream See Possible codes: 403 (details)
+ /// Get an invite link for a group call or livestream See Possible codes: 403 (details)
/// For livestreams or muted group chats, if set, users that join using this link will be able to speak without explicitly requesting permission by (for example by raising their hand).
/// The group call
public static Task Phone_ExportGroupCallInvite(this Client client, InputGroupCall call, bool can_self_unmute = false)
diff --git a/src/TL.Secret.cs b/src/TL.Secret.cs
index 0915f52..125f06b 100644
--- a/src/TL.Secret.cs
+++ b/src/TL.Secret.cs
@@ -31,7 +31,8 @@ namespace TL
/// a null value means decryptedMessageMediaEmpty
public abstract class DecryptedMessageMedia : IObject
{
- public virtual (int size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); }
+ public virtual string MimeType { get; }
+ internal virtual (int size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); }
}
/// Object describes the action to which a service message is linked. See
@@ -108,7 +109,8 @@ namespace TL
/// Initialization vector
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ public override string MimeType => "image/jpeg";
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Video attached to an encrypted message. See
[TLDef(0x4CEE6EF3)]
@@ -133,7 +135,7 @@ namespace TL
/// Initialization vector
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// GeoPoint attached to an encrypted message. See
[TLDef(0x35480A59)]
@@ -177,7 +179,10 @@ namespace TL
/// Initialization
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ /// File MIME-type
+ public override string MimeType => mime_type;
+
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Audio file attached to a secret chat message. See
[TLDef(0x6080758F)]
@@ -192,7 +197,7 @@ namespace TL
/// Initialization vector
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Setting of a message lifetime after reading. See
@@ -305,7 +310,10 @@ namespace TL
/// Initialization vector
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ /// MIME-type of the video file
Parameter added in Layer 17.
+ public override string MimeType => mime_type;
+
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Audio file attached to a secret chat message. See
[TLDef(0x57E0A9CB)]
@@ -322,7 +330,10 @@ namespace TL
/// Initialization vector
public byte[] iv;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ /// MIME-type of the audio file
Parameter added in Layer 13.
+ public override string MimeType => mime_type;
+
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Request for the other party in a Secret Chat to automatically resend a contiguous range of previously sent messages, as explained in Sequence number is Secret Chats. See
@@ -463,7 +474,8 @@ namespace TL
/// Caption
public string caption;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ public override string MimeType => "image/jpeg";
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Video attached to an encrypted message. See
[TLDef(0x970C8C0E)]
@@ -492,7 +504,10 @@ namespace TL
/// Caption
public string caption;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ /// MIME-type of the video file
Parameter added in Layer 17.
+ public override string MimeType => mime_type;
+
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Document attached to a message in a secret chat. See
[TLDef(0x7AFE8AE2)]
@@ -517,7 +532,10 @@ namespace TL
/// Caption
public string caption;
- public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
+ /// File MIME-type
+ public override string MimeType => mime_type;
+
+ internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// Venue See
[TLDef(0x8A0DF56F)]
@@ -727,6 +745,9 @@ namespace TL
public int dc_id;
/// Attributes for media types
public DocumentAttribute[] attributes;
+
+ /// Mime type
+ public override string MimeType => mime_type;
}
/// File is currently unavailable. See