mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-27 10:44:19 +01:00
Support for Encrypted Files/Medias
This commit is contained in:
parent
c6adeb1f31
commit
f22990cb58
14
EXAMPLES.md
14
EXAMPLES.md
|
|
@ -440,19 +440,19 @@ var chat = chats.chats[1234567890]; // the chat we want
|
|||
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
|
||||
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 });
|
||||
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). Access hash is not required*
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TL;
|
||||
|
|
@ -31,9 +32,10 @@ namespace WTelegramClientTest
|
|||
var dialogs = await Client.Messages_GetAllDialogs(); // load the list of users/chats
|
||||
dialogs.CollectUsersChats(Users, Chats);
|
||||
Console.WriteLine(@"Available commands:
|
||||
/request <UserID> Initiate Secret Chat with user
|
||||
/discard [delete] Terminate active secret chat (and delete history)
|
||||
/request <UserID> Initiate Secret Chat with user (see /users)
|
||||
/discard [delete] Terminate active secret chat [and delete history]
|
||||
/select <ChatID> 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:");
|
||||
|
|
@ -52,6 +54,13 @@ Type a command, or a message to send to the active secret chat:");
|
|||
SelectActiveChat(await Secrets.Request(user));
|
||||
else
|
||||
Console.WriteLine("User not found");
|
||||
else if (line.StartsWith("/photo "))
|
||||
{
|
||||
var media = new TL.Layer45.DecryptedMessageMediaPhoto { caption = line[7..] };
|
||||
var file = await Secrets.UploadFile(File.OpenRead(line[7..]), media);
|
||||
var sent = await Secrets.SendMessage(ActiveChat, new TL.Layer73.DecryptedMessage { random_id = WTelegram.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");
|
||||
|
|
@ -73,7 +82,14 @@ Type a command, or a message to send to the active secret chat:");
|
|||
if (unem.message.ChatId != ActiveChat) SelectActiveChat(unem.message.ChatId);
|
||||
foreach (var msg in Secrets.DecryptMessage(unem.message))
|
||||
{
|
||||
if (msg.Action == null) Console.WriteLine($"{unem.message.ChatId}> {msg.Message}");
|
||||
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.OpenWrite("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);
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ After installing WTelegramClient through [Nuget](https://www.nuget.org/packages/
|
|||
static async Task Main(string[] _)
|
||||
{
|
||||
using var client = new WTelegram.Client();
|
||||
var my = await client.LoginUserIfNeeded();
|
||||
Console.WriteLine($"We are logged-in as {my.username ?? my.first_name + " " + my.last_name} (id {my.id})");
|
||||
var myself = await client.LoginUserIfNeeded();
|
||||
Console.WriteLine($"We are logged-in as {myself} (id {myself.id})");
|
||||
}
|
||||
```
|
||||
When run, this will prompt you interactively for your App **api_hash** and **api_id** (that you obtain through Telegram's
|
||||
|
|
@ -152,7 +152,7 @@ See [Examples/Program_ListenUpdates.cs](https://github.com/wiz0u/WTelegramClient
|
|||
|
||||
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.
|
||||
|
||||
The other configuration items that you can override include: **session_pathname, email, email_verification_code, session_key, server_address, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, user_id**
|
||||
The other configuration items that you can override include: **session_pathname, email, email_verification_code, session_key, server_address, device_model, system_version, app_version, system_lang_code, lang_pack, lang_code, 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 or experiment).
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ namespace WTelegram
|
|||
bool abort = false;
|
||||
for (long bytesLeft = length; !abort && bytesLeft != 0; file_part++)
|
||||
{
|
||||
var bytes = new byte[Math.Min(FilePartSize, bytesLeft)];
|
||||
var bytes = new byte[(Math.Min(FilePartSize, bytesLeft) + 15) & ~15];
|
||||
read = await stream.FullReadAsync(bytes, bytes.Length, default);
|
||||
await _parallelTransfers.WaitAsync();
|
||||
bytesLeft -= read;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -14,9 +15,9 @@ namespace WTelegram
|
|||
{
|
||||
internal static class Encryption
|
||||
{
|
||||
internal static readonly RNGCryptoServiceProvider RNG = new();
|
||||
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = new();
|
||||
private static readonly Aes AesECB = Aes.Create();
|
||||
internal static readonly RNGCryptoServiceProvider RNG = new();
|
||||
internal static readonly Aes AesECB = Aes.Create();
|
||||
|
||||
static Encryption()
|
||||
{
|
||||
|
|
@ -295,25 +296,23 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
|
||||
}
|
||||
|
||||
private static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
|
||||
internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
|
||||
{
|
||||
if (input.Length % 16 != 0) throw new ApplicationException("AES_IGE input size not divisible by 16");
|
||||
|
||||
// code adapted from PHP implementation found at https://mgp25.com/AESIGE/
|
||||
var output = new byte[input.Length];
|
||||
var xPrev = aes_iv.AsSpan(encrypt ? 16 : 0, 16);
|
||||
var yPrev = aes_iv.AsSpan(encrypt ? 0 : 16, 16);
|
||||
using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null);
|
||||
byte[] yXOR = new byte[16];
|
||||
for (int i = 0; i < input.Length; i += 16)
|
||||
var output = new byte[input.Length];
|
||||
var prevBytes = (byte[])aes_iv.Clone();
|
||||
var span = MemoryMarshal.Cast<byte, long>(input);
|
||||
var sout = MemoryMarshal.Cast<byte, long>(output);
|
||||
var prev = MemoryMarshal.Cast<byte, long>(prevBytes);
|
||||
if (!encrypt) { (prev[2], prev[0]) = (prev[0], prev[2]); (prev[3], prev[1]) = (prev[1], prev[3]); }
|
||||
for (int i = 0, count = input.Length / 8; i < count;)
|
||||
{
|
||||
for (int j = 0; j < 16; j++)
|
||||
yXOR[j] = (byte)(input[i + j] ^ yPrev[j]);
|
||||
aesCrypto.TransformBlock(yXOR, 0, 16, output, i);
|
||||
for (int j = 0; j < 16; j++)
|
||||
output[i + j] ^= xPrev[j];
|
||||
xPrev = input.Slice(i, 16);
|
||||
yPrev = output.AsSpan(i, 16);
|
||||
sout[i] = span[i] ^ prev[0]; sout[i + 1] = span[i + 1] ^ prev[1];
|
||||
aesCrypto.TransformBlock(output, i * 8, 16, output, i * 8);
|
||||
prev[0] = sout[i] ^= prev[2]; prev[1] = sout[i + 1] ^= prev[3];
|
||||
prev[2] = span[i++]; prev[3] = span[i++];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
|
@ -526,4 +525,52 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public class AES_IGE_Stream : Helpers.IndirectStream
|
||||
{
|
||||
private readonly ICryptoTransform aesCrypto;
|
||||
private readonly byte[] prevBytes;
|
||||
|
||||
public AES_IGE_Stream(Stream stream, DecryptedMessageMedia media) : this(stream, media.SizeKeyIV) { }
|
||||
public AES_IGE_Stream(Stream innerStream, (int size, byte[] key, byte[] iv) t) : this(innerStream, t.key, t.iv) { ContentLength = t.size; }
|
||||
public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt = false) : base(stream)
|
||||
{
|
||||
aesCrypto = encrypt ? Encryption.AesECB.CreateEncryptor(key, null) : Encryption.AesECB.CreateDecryptor(key, null);
|
||||
if (encrypt) prevBytes = (byte[])iv.Clone();
|
||||
else { prevBytes = new byte[32]; Array.Copy(iv, 0, prevBytes, 16, 16); Array.Copy(iv, 16, prevBytes, 0, 16); }
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = _innerStream.Read(buffer, offset, count);
|
||||
if (count == 0) return 0;
|
||||
Process(buffer, offset, count);
|
||||
if (ContentLength.HasValue && _innerStream.Position == _innerStream.Length)
|
||||
return count - (int)(_innerStream.Position - ContentLength.Value);
|
||||
return count;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Process(buffer, offset, count);
|
||||
if (ContentLength.HasValue && _innerStream.Position + count > ContentLength)
|
||||
count -= (int)(_innerStream.Position + count - ContentLength.Value);
|
||||
_innerStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public void Process(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = (count + 15) & ~15;
|
||||
var span = MemoryMarshal.Cast<byte, long>(buffer.AsSpan(offset, count));
|
||||
var prev = MemoryMarshal.Cast<byte, long>(prevBytes);
|
||||
for (offset = 0, count /= 8; offset < count;)
|
||||
{
|
||||
prev[0] ^= span[offset]; prev[1] ^= span[offset + 1];
|
||||
aesCrypto.TransformBlock(prevBytes, 0, 16, prevBytes, 0);
|
||||
prev[0] ^= prev[2]; prev[1] ^= prev[3];
|
||||
prev[2] = span[offset]; prev[3] = span[offset + 1];
|
||||
span[offset++] = prev[0]; span[offset++] = prev[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -568,7 +568,28 @@ namespace WTelegram
|
|||
_ = Discard(chat.ChatId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<InputEncryptedFileBase> UploadFile(Stream stream, DecryptedMessageMedia media)
|
||||
{
|
||||
byte[] aes_key = new byte[32], aes_iv = new byte[32];
|
||||
RNG.GetBytes(aes_key);
|
||||
RNG.GetBytes(aes_iv);
|
||||
media.SizeKeyIV = (checked((int)stream.Length), aes_key, aes_iv);
|
||||
|
||||
using var md5 = MD5.Create();
|
||||
md5.TransformBlock(aes_key, 0, 32, null, 0);
|
||||
var res = md5.TransformFinalBlock(aes_iv, 0, 32);
|
||||
var digest = md5.Hash;
|
||||
long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(digest);
|
||||
fingerprint ^= fingerprint >> 32;
|
||||
|
||||
using var ige = new AES_IGE_Stream(stream, aes_key, aes_iv, true);
|
||||
return await client.UploadFileAsync(ige, null) switch
|
||||
{
|
||||
InputFile ifl => new InputEncryptedFileUploaded { id = ifl.id, parts = ifl.parts, md5_checksum = ifl.md5_checksum, key_fingerprint = (int)fingerprint },
|
||||
InputFileBig ifb => new InputEncryptedFileBigUploaded { id = ifb.id, parts = ifb.parts, key_fingerprint = (int)fingerprint },
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO https://core.telegram.org/api/end-to-end#sending-encrypted-files
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@ namespace TL
|
|||
partial class EncryptedFile
|
||||
{
|
||||
public static implicit operator InputEncryptedFile(EncryptedFile file) => file == null ? null : new InputEncryptedFile { id = file.id, access_hash = file.access_hash };
|
||||
public static implicit operator InputEncryptedFileLocation(EncryptedFile file) => file == null ? null : new InputEncryptedFileLocation { id = file.id, access_hash = file.access_hash };
|
||||
public InputEncryptedFileLocation ToFileLocation() => new() { id = id, access_hash = access_hash };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,9 +170,9 @@ namespace TL
|
|||
|
||||
/// <summary>Binds a temporary authorization key <c>temp_auth_key_id</c> to the permanent authorization key <c>perm_auth_key_id</c>. Each permanent key may only be bound to one temporary key at a time, binding a new temporary key overwrites the previous one. <para>See <a href="https://corefork.telegram.org/method/auth.bindTempAuthKey"/> [bots: ✓]</para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/auth.bindTempAuthKey#possible-errors">details</a>)</para></summary>
|
||||
/// <param name="perm_auth_key_id">Permanent auth_key_id to bind to</param>
|
||||
/// <param name="nonce">Random long from <a href="#binding-message-contents">Binding message contents</a></param>
|
||||
/// <param name="expires_at">Unix timestamp to invalidate temporary key, see <a href="#binding-message-contents">Binding message contents</a></param>
|
||||
/// <param name="encrypted_message">See <a href="#generating-encrypted-message">Generating encrypted_message</a></param>
|
||||
/// <param name="nonce">Random long from <a href="https://corefork.telegram.org/method/auth.bindTempAuthKey#binding-message-contents">Binding message contents</a></param>
|
||||
/// <param name="expires_at">Unix timestamp to invalidate temporary key, see <a href="https://corefork.telegram.org/method/auth.bindTempAuthKey#binding-message-contents">Binding message contents</a></param>
|
||||
/// <param name="encrypted_message">See <a href="https://corefork.telegram.org/method/auth.bindTempAuthKey#generating-encrypted-message">Generating encrypted_message</a></param>
|
||||
public static Task<bool> Auth_BindTempAuthKey(this Client client, long perm_auth_key_id, long nonce, DateTime expires_at, byte[] encrypted_message)
|
||||
=> client.Invoke(new Auth_BindTempAuthKey
|
||||
{
|
||||
|
|
@ -3955,7 +3955,7 @@ namespace TL
|
|||
|
||||
/// <summary>Get <a href="https://corefork.telegram.org/api/channel">channels/supergroups/geogroups</a> we're admin in. Usually called when the user exceeds the <see cref="Config"/> for owned public <a href="https://corefork.telegram.org/api/channel">channels/supergroups/geogroups</a>, and the user is given the choice to remove one of his channels/supergroups/geogroups. <para>See <a href="https://corefork.telegram.org/method/channels.getAdminedPublicChannels"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/channels.getAdminedPublicChannels#possible-errors">details</a>)</para></summary>
|
||||
/// <param name="by_location">Get geogroups</param>
|
||||
/// <param name="check_limit">If set and the user has reached the limit of owned public <a href="https://corefork.telegram.org/api/channel">channels/supergroups/geogroups</a>, instead of returning the channel list one of the specified <a href="#possible-errors">errors</a> will be returned.<br/>Useful to check if a new public channel can indeed be created, even before asking the user to enter a channel username to use in <a href="https://corefork.telegram.org/method/channels.checkUsername">channels.checkUsername</a>/<a href="https://corefork.telegram.org/method/channels.updateUsername">channels.updateUsername</a>.</param>
|
||||
/// <param name="check_limit">If set and the user has reached the limit of owned public <a href="https://corefork.telegram.org/api/channel">channels/supergroups/geogroups</a>, instead of returning the channel list one of the specified <a href="https://corefork.telegram.org/method/channels.getAdminedPublicChannels#possible-errors">errors</a> will be returned.<br/>Useful to check if a new public channel can indeed be created, even before asking the user to enter a channel username to use in <a href="https://corefork.telegram.org/method/channels.checkUsername">channels.checkUsername</a>/<a href="https://corefork.telegram.org/method/channels.updateUsername">channels.updateUsername</a>.</param>
|
||||
public static Task<Messages_Chats> Channels_GetAdminedPublicChannels(this Client client, bool by_location = false, bool check_limit = false)
|
||||
=> client.Invoke(new Channels_GetAdminedPublicChannels
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ namespace TL
|
|||
|
||||
/// <summary>Object describes media contents of an encrypted message. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageMedia"/></para></summary>
|
||||
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaEmpty">decryptedMessageMediaEmpty</a></remarks>
|
||||
public abstract class DecryptedMessageMedia : IObject { }
|
||||
public abstract class DecryptedMessageMedia : IObject
|
||||
{
|
||||
public virtual (int, byte[], byte[]) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); }
|
||||
}
|
||||
|
||||
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para></summary>
|
||||
public abstract class DecryptedMessageAction : IObject { }
|
||||
|
|
@ -104,6 +107,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization vector</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Video attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVideo"/></para></summary>
|
||||
[TLDef(0x4CEE6EF3)]
|
||||
|
|
@ -127,6 +132,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization vector</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>GeoPoint attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaGeoPoint"/></para></summary>
|
||||
[TLDef(0x35480A59)]
|
||||
|
|
@ -169,6 +176,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Audio file attached to a secret chat message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaAudio"/></para></summary>
|
||||
[TLDef(0x6080758F)]
|
||||
|
|
@ -182,6 +191,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization vector</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
|
||||
/// <summary>Setting of a message lifetime after reading. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageActionSetMessageTTL"/></para></summary>
|
||||
|
|
@ -293,6 +304,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization vector</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Audio file attached to a secret chat message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaAudio"/></para></summary>
|
||||
[TLDef(0x57E0A9CB)]
|
||||
|
|
@ -308,6 +321,8 @@ namespace TL
|
|||
public byte[] key;
|
||||
/// <summary>Initialization vector</summary>
|
||||
public byte[] iv;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
|
||||
/// <summary>Request for the other party in a Secret Chat to automatically resend a contiguous range of previously sent messages, as explained in <a href="https://corefork.telegram.org/api/end-to-end/seq_no">Sequence number is Secret Chats</a>. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageActionResend"/></para></summary>
|
||||
|
|
@ -447,6 +462,8 @@ namespace TL
|
|||
public byte[] iv;
|
||||
/// <summary>Caption</summary>
|
||||
public string caption;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Video attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVideo"/></para></summary>
|
||||
[TLDef(0x970C8C0E)]
|
||||
|
|
@ -474,6 +491,8 @@ namespace TL
|
|||
public byte[] iv;
|
||||
/// <summary>Caption</summary>
|
||||
public string caption;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Document attached to a message in a secret chat. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaDocument"/></para></summary>
|
||||
[TLDef(0x7AFE8AE2)]
|
||||
|
|
@ -497,6 +516,8 @@ namespace TL
|
|||
public DocumentAttribute[] attributes;
|
||||
/// <summary>Caption</summary>
|
||||
public string caption;
|
||||
|
||||
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
|
||||
}
|
||||
/// <summary>Venue <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVenue"/></para></summary>
|
||||
[TLDef(0x8A0DF56F)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue