From 3730cdc7f056c248665256feebeb3a27137a86c5 Mon Sep 17 00:00:00 2001
From: Wizou <11647984+wiz0u@users.noreply.github.com>
Date: Thu, 20 Jan 2022 02:44:43 +0100
Subject: [PATCH] Added helper SendAlbumAsync, and implicit
operators/constructors for media
---
.github/dev.yml | 2 +-
src/Client.cs | 93 ++++++++++++++++++++++++++++++++++++++++++-----
src/Helpers.cs | 16 ++++++++
src/TL.Helpers.cs | 25 ++++++++++++-
src/TL.Schema.cs | 6 +--
5 files changed, 128 insertions(+), 14 deletions(-)
diff --git a/.github/dev.yml b/.github/dev.yml
index 18d39e0..c69b98d 100644
--- a/.github/dev.yml
+++ b/.github/dev.yml
@@ -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
diff --git a/src/Client.cs b/src/Client.cs
index 0e8c3bf..59056d7 100644
--- a/src/Client.cs
+++ b/src/Client.cs
@@ -1297,8 +1297,7 @@ namespace WTelegram
/// The transmitted message confirmed by Telegram
public Task 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() : 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);
}
/// Helper function to send a text or media message easily
@@ -1362,6 +1356,87 @@ namespace WTelegram
return null;
}
+ /// Helper function to send an album (media group) of photos or documents more easily
+ /// Destination of message (chat group, channel, user chat, etc..)
+ /// An array of InputMedia-derived class
+ /// Caption for the media (in plain text) or
+ /// Your message is a reply to an existing message with this ID, in the same chat
+ /// Text formatting entities for the caption. You can use MarkdownToEntities to create these
+ /// UTC timestamp when the message should be sent
+ /// The last of the media group messages, confirmed by Telegram
+ ///
+ /// * The caption/entities are set on the last media
+ /// * and are supported by downloading the file from the web via HttpClient and sending it to Telegram.
+ /// WTelegramClient proxy settings don't apply to HttpClient
+ /// * You may run into errors if you mix, in the same album, photos and file documents having no thumbnails/video attributes
+ ///
+ public async Task 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 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 },
diff --git a/src/Helpers.cs b/src/Helpers.cs
index cc0056a..71a044c 100644
--- a/src/Helpers.cs
+++ b/src/Helpers.cs
@@ -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();
+ }
}
}
diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs
index a9d6930..c74cee4 100644
--- a/src/TL.Helpers.cs
+++ b/src/TL.Helpers.cs
@@ -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
{
diff --git a/src/TL.Schema.cs b/src/TL.Schema.cs
index d5bc671..09e026d 100644
--- a/src/TL.Schema.cs
+++ b/src/TL.Schema.cs
@@ -245,7 +245,7 @@ namespace TL
}
/// New document See
[TLDef(0x5B38C6C1)]
- public class InputMediaUploadedDocument : InputMedia
+ public partial class InputMediaUploadedDocument : InputMedia
{
/// Flags, see TL conditional fields
public Flags flags;
@@ -502,7 +502,7 @@ namespace TL
/// Defines a photo for further interaction. See
/// a null value means inputPhotoEmpty
[TLDef(0x3BB3B94A)]
- public class InputPhoto : IObject
+ public partial class InputPhoto : IObject
{
/// Photo identifier
public long id;
@@ -4952,7 +4952,7 @@ namespace TL
/// Defines a video for subsequent interaction. See
/// a null value means inputDocumentEmpty
[TLDef(0x1ABFB575)]
- public class InputDocument : IObject
+ public partial class InputDocument : IObject
{
/// Document ID
public long id;