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