mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-02-23 16:05:22 +01:00
Added helper SendAlbumAsync, and implicit operators/constructors for media
This commit is contained in:
parent
411fcad556
commit
3730cdc7f0
2
.github/dev.yml
vendored
2
.github/dev.yml
vendored
|
|
@ -2,7 +2,7 @@ pr: none
|
|||
trigger:
|
||||
- master
|
||||
|
||||
name: 1.9.4-dev.$(Rev:r)
|
||||
name: 1.9.5-dev.$(Rev:r)
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -1297,8 +1297,7 @@ namespace WTelegram
|
|||
/// <returns>The transmitted message confirmed by Telegram</returns>
|
||||
public Task<Message> 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
|
||||
mimeType ??= Path.GetExtension(mediaFile.Name)?.ToLowerInvariant() switch
|
||||
{
|
||||
".jpg" or ".jpeg" or ".png" or ".bmp" => "photo",
|
||||
".gif" => "image/gif",
|
||||
|
|
@ -1309,13 +1308,8 @@ namespace WTelegram
|
|||
_ => "", // send as generic document with undefined MIME type
|
||||
};
|
||||
if (mimeType == "photo")
|
||||
return SendMessageAsync(peer, caption, new InputMediaUploadedPhoto { file = mediaFile },
|
||||
reply_to_msg_id, entities, schedule_date);
|
||||
var attributes = filename == null ? Array.Empty<DocumentAttribute>() : new[] { new DocumentAttributeFilename { file_name = filename } };
|
||||
return SendMessageAsync(peer, caption, new InputMediaUploadedDocument
|
||||
{
|
||||
file = mediaFile, mime_type = mimeType, attributes = attributes
|
||||
}, reply_to_msg_id, entities, schedule_date);
|
||||
return SendMessageAsync(peer, caption, new InputMediaUploadedPhoto { file = mediaFile }, reply_to_msg_id, entities, schedule_date);
|
||||
return SendMessageAsync(peer, caption, new InputMediaUploadedDocument(mediaFile, mimeType), reply_to_msg_id, entities, schedule_date);
|
||||
}
|
||||
|
||||
/// <summary>Helper function to send a text or media message easily</summary>
|
||||
|
|
@ -1362,6 +1356,87 @@ namespace WTelegram
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Helper function to send an album (media group) of photos or documents more easily</summary>
|
||||
/// <param name="peer">Destination of message (chat group, channel, user chat, etc..) </param>
|
||||
/// <param name="medias">An array of <see cref="InputMedia">InputMedia</see>-derived class</param>
|
||||
/// <param name="caption">Caption for the media <i>(in plain text)</i> or <see langword="null"/></param>
|
||||
/// <param name="reply_to_msg_id">Your message is a reply to an existing message with this ID, in the same chat</param>
|
||||
/// <param name="entities">Text formatting entities for the caption. You can use <see cref="Markdown.MarkdownToEntities">MarkdownToEntities</see> to create these</param>
|
||||
/// <param name="schedule_date">UTC timestamp when the message should be sent</param>
|
||||
/// <returns>The last of the media group messages, confirmed by Telegram</returns>
|
||||
/// <remarks>
|
||||
/// * The caption/entities are set on the last media<br/>
|
||||
/// * <see cref="InputMediaDocumentExternal"/> and <see cref="InputMediaPhotoExternal"/> are supported by downloading the file from the web via HttpClient and sending it to Telegram.
|
||||
/// WTelegramClient proxy settings don't apply to HttpClient<br/>
|
||||
/// * You may run into errors if you mix, in the same album, photos and file documents having no thumbnails/video attributes
|
||||
/// </remarks>
|
||||
public async Task<Message> SendAlbumAsync(InputPeer peer, InputMedia[] medias, string caption = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default)
|
||||
{
|
||||
System.Net.Http.HttpClient httpClient = null;
|
||||
var multiMedia = new InputSingleMedia[medias.Length];
|
||||
for (int i = 0; i < medias.Length; i++)
|
||||
{
|
||||
var ism = multiMedia[i] = new InputSingleMedia { random_id = Helpers.RandomLong(), media = medias[i] };
|
||||
retry:
|
||||
switch (ism.media)
|
||||
{
|
||||
case InputMediaUploadedPhoto imup:
|
||||
var mmp = (MessageMediaPhoto)await this.Messages_UploadMedia(peer, imup);
|
||||
ism.media = mmp.photo;
|
||||
break;
|
||||
case InputMediaUploadedDocument imud:
|
||||
var mmd = (MessageMediaDocument)await this.Messages_UploadMedia(peer, imud);
|
||||
ism.media = mmd.document;
|
||||
break;
|
||||
case InputMediaDocumentExternal imde:
|
||||
string mimeType = null;
|
||||
var inputFile = await UploadFromUrl(imde.url);
|
||||
ism.media = new InputMediaUploadedDocument(inputFile, mimeType);
|
||||
goto retry;
|
||||
case InputMediaPhotoExternal impe:
|
||||
inputFile = await UploadFromUrl(impe.url);
|
||||
ism.media = new InputMediaUploadedPhoto { file = inputFile };
|
||||
goto retry;
|
||||
|
||||
async Task<InputFileBase> UploadFromUrl(string url)
|
||||
{
|
||||
var filename = Path.GetFileName(new Uri(url).LocalPath);
|
||||
httpClient ??= new();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
using var stream = await response.Content.ReadAsStreamAsync();
|
||||
mimeType = response.Content.Headers.ContentType?.MediaType;
|
||||
if (response.Content.Headers.ContentLength is long length)
|
||||
return await UploadFileAsync(new Helpers.StreamWithLength { length = length, innerStream = stream }, filename);
|
||||
else
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
return await UploadFileAsync(ms, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var lastMedia = multiMedia[^1];
|
||||
lastMedia.message = caption;
|
||||
lastMedia.entities = entities;
|
||||
if (entities != null) lastMedia.flags = InputSingleMedia.Flags.has_entities;
|
||||
|
||||
var updates = await this.Messages_SendMultiMedia(peer, multiMedia, reply_to_msg_id: reply_to_msg_id, schedule_date: schedule_date);
|
||||
OnUpdate(updates);
|
||||
int msgId = -1;
|
||||
foreach (var update in updates.UpdateList)
|
||||
{
|
||||
switch (update)
|
||||
{
|
||||
case UpdateMessageID updMsgId when updMsgId.random_id == lastMedia.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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Peer InputToPeer(InputPeer peer) => peer switch
|
||||
{
|
||||
InputPeerSelf => new PeerUser { user_id = _session.UserId },
|
||||
|
|
|
|||
|
|
@ -252,5 +252,21 @@ namespace WTelegram
|
|||
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00,
|
||||
0x3f, 0x00
|
||||
};
|
||||
|
||||
internal class StreamWithLength : Stream
|
||||
{
|
||||
public Stream innerStream;
|
||||
public long length;
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => length;
|
||||
public override long Position { get => innerStream.Position; set => throw new NotSupportedException(); }
|
||||
public override void Flush() { }
|
||||
public override int Read(byte[] buffer, int offset, int count) => innerStream.Read(buffer, offset, count);
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ namespace TL
|
|||
public override InputSecureFileBase ToInputSecureFile(byte[] file_hash, byte[] secret) => new InputSecureFileUploaded { id = id, parts = parts, file_hash = file_hash, secret = secret };
|
||||
}
|
||||
|
||||
partial class InputPhoto
|
||||
{
|
||||
public static implicit operator InputMediaPhoto(InputPhoto photo) => new() { id = photo };
|
||||
}
|
||||
|
||||
partial class Peer
|
||||
{
|
||||
public abstract long ID { get; }
|
||||
|
|
@ -174,6 +179,7 @@ namespace TL
|
|||
public abstract long ID { get; }
|
||||
protected abstract InputPhoto ToInputPhoto();
|
||||
public static implicit operator InputPhoto(PhotoBase photo) => photo.ToInputPhoto();
|
||||
public static implicit operator InputMediaPhoto(PhotoBase photo) => photo.ToInputPhoto();
|
||||
}
|
||||
partial class PhotoEmpty
|
||||
{
|
||||
|
|
@ -247,7 +253,18 @@ namespace TL
|
|||
}
|
||||
}
|
||||
|
||||
partial class Contacts_Blocked { public IPeerInfo UserOrChat(PeerBlocked peer) => peer.peer_id.UserOrChat(users, chats); }
|
||||
public partial class InputMediaUploadedDocument
|
||||
{
|
||||
public InputMediaUploadedDocument() { }
|
||||
public InputMediaUploadedDocument(InputFileBase inputFile, string mimeType)
|
||||
{
|
||||
file = inputFile;
|
||||
mime_type = mimeType;
|
||||
if (inputFile.Name is string filename) attributes = new[] { new DocumentAttributeFilename { file_name = filename } };
|
||||
}
|
||||
}
|
||||
|
||||
partial class Contacts_Blocked { public IPeerInfo UserOrChat(PeerBlocked peer) => peer.peer_id.UserOrChat(users, chats); }
|
||||
partial class Messages_DialogsBase { public IPeerInfo UserOrChat(DialogBase dialog) => UserOrChat(dialog.Peer);
|
||||
public abstract int TotalCount { get; } }
|
||||
partial class Messages_Dialogs { public override int TotalCount => dialogs.Length; }
|
||||
|
|
@ -318,11 +335,17 @@ namespace TL
|
|||
public InputEncryptedFileLocation ToFileLocation() => new() { id = id, access_hash = access_hash };
|
||||
}
|
||||
|
||||
partial class InputDocument
|
||||
{
|
||||
public static implicit operator InputMediaDocument(InputDocument document) => new() { id = document };
|
||||
}
|
||||
|
||||
partial class DocumentBase
|
||||
{
|
||||
public abstract long ID { get; }
|
||||
protected abstract InputDocument ToInputDocument();
|
||||
public static implicit operator InputDocument(DocumentBase document) => document.ToInputDocument();
|
||||
public static implicit operator InputMediaDocument(DocumentBase document) => document.ToInputDocument();
|
||||
}
|
||||
partial class DocumentEmpty
|
||||
{
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ namespace TL
|
|||
}
|
||||
/// <summary>New document <para>See <a href="https://corefork.telegram.org/constructor/inputMediaUploadedDocument"/></para></summary>
|
||||
[TLDef(0x5B38C6C1)]
|
||||
public class InputMediaUploadedDocument : InputMedia
|
||||
public partial class InputMediaUploadedDocument : InputMedia
|
||||
{
|
||||
/// <summary>Flags, see <a href="https://corefork.telegram.org/mtproto/TL-combinators#conditional-fields">TL conditional fields</a></summary>
|
||||
public Flags flags;
|
||||
|
|
@ -502,7 +502,7 @@ namespace TL
|
|||
/// <summary>Defines a photo for further interaction. <para>See <a href="https://corefork.telegram.org/constructor/inputPhoto"/></para></summary>
|
||||
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/inputPhotoEmpty">inputPhotoEmpty</a></remarks>
|
||||
[TLDef(0x3BB3B94A)]
|
||||
public class InputPhoto : IObject
|
||||
public partial class InputPhoto : IObject
|
||||
{
|
||||
/// <summary>Photo identifier</summary>
|
||||
public long id;
|
||||
|
|
@ -4952,7 +4952,7 @@ namespace TL
|
|||
/// <summary>Defines a video for subsequent interaction. <para>See <a href="https://corefork.telegram.org/constructor/inputDocument"/></para></summary>
|
||||
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/inputDocumentEmpty">inputDocumentEmpty</a></remarks>
|
||||
[TLDef(0x1ABFB575)]
|
||||
public class InputDocument : IObject
|
||||
public partial class InputDocument : IObject
|
||||
{
|
||||
/// <summary>Document ID</summary>
|
||||
public long id;
|
||||
|
|
|
|||
Loading…
Reference in a new issue