Added helper SendAlbumAsync, and implicit operators/constructors for media

This commit is contained in:
Wizou 2022-01-20 02:44:43 +01:00
parent 411fcad556
commit 3730cdc7f0
5 changed files with 128 additions and 14 deletions

2
.github/dev.yml vendored
View file

@ -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

View file

@ -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 },

View file

@ -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();
}
}
}

View file

@ -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
{

View file

@ -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;