diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index d2f3bc04..5b580c3e 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -1603,12 +1603,15 @@ export interface TelegramClient extends BaseTelegramClient { * Send a single media. * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` - * @param media Media contained in the message + * @param media + * Media contained in the message. You can also pass TDLib + * and Bot API compatible File ID, which will be wrapped + * in {@link InputMedia.auto} * @param params Additional sending parameters */ sendMedia( chatId: InputPeerLike, - media: InputMediaLike, + media: InputMediaLike | string, params?: { /** * Message to reply to. Either a message object or message ID. diff --git a/packages/client/src/methods/chats/set-chat-photo.ts b/packages/client/src/methods/chats/set-chat-photo.ts index f2d317ba..290448bd 100644 --- a/packages/client/src/methods/chats/set-chat-photo.ts +++ b/packages/client/src/methods/chats/set-chat-photo.ts @@ -8,6 +8,7 @@ import { } from '../../types' import { normalizeToInputChannel, normalizeToInputPeer } from '../../utils/peer-utils' import { tl } from '@mtcute/tl' +import { fileIdToInputPhoto, tdFileId } from '@mtcute/file-id' /** * Set a new chat photo or video. @@ -33,27 +34,37 @@ export async function setChatPhoto( if (!(chat._ === 'inputPeerChat' || chat._ === 'inputPeerChannel')) throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel') - let input: tl.TypeInputFile + let photo: tl.TypeInputChatPhoto - if (typeof media === 'string' && media.match(/^https?:\/\//)) { - throw new MtCuteArgumentError("Chat photo can't be external") - } else if (typeof media === 'object' && tl.isAnyInputMedia(media)) { - throw new MtCuteArgumentError("Chat photo can't be InputMedia") - } else if (isUploadedFile(media)) { - input = media.inputFile - } else if (typeof media === 'object' && tl.isAnyInputFile(media)) { - input = media + if (tdFileId.isFileIdLike(media)) { + if (typeof media === 'string' && media.match(/^https?:\/\//)) + throw new MtCuteArgumentError("Chat photo can't be external") + + const input = fileIdToInputPhoto(media) + photo = { + _: 'inputChatPhoto', + id: input + } } else { - const uploaded = await this.uploadFile({ - file: media, - }) - input = uploaded.inputFile - } + let inputFile: tl.TypeInputFile + if (typeof media === 'object' && tl.isAnyInputMedia(media)) { + throw new MtCuteArgumentError("Chat photo can't be InputMedia") + } else if (isUploadedFile(media)) { + inputFile = media.inputFile + } else if (typeof media === 'object' && tl.isAnyInputFile(media)) { + inputFile = media + } else { + const uploaded = await this.uploadFile({ + file: media, + }) + inputFile = uploaded.inputFile + } - const photo: tl.RawInputChatUploadedPhoto = { - _: 'inputChatUploadedPhoto', - [type === 'photo' ? 'file' : 'video']: input, - videoStartTs: previewSec + photo = { + _: 'inputChatUploadedPhoto', + [type === 'photo' ? 'file' : 'video']: inputFile, + videoStartTs: previewSec + } } let res diff --git a/packages/client/src/methods/messages/send-media.ts b/packages/client/src/methods/messages/send-media.ts index 474d091f..2c2ddaf7 100644 --- a/packages/client/src/methods/messages/send-media.ts +++ b/packages/client/src/methods/messages/send-media.ts @@ -12,19 +12,28 @@ import { tl } from '@mtcute/tl' import { extractFileName } from '../../utils/file-utils' import { normalizeToInputPeer } from '../../utils/peer-utils' import { normalizeDate, randomUlong } from '../../utils/misc-utils' +import { + fileIdToInputDocument, + fileIdToInputPhoto, + parseFileId, + tdFileId, +} from '@mtcute/file-id' /** * Send a single media. * * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` - * @param media Media contained in the message + * @param media + * Media contained in the message. You can also pass TDLib + * and Bot API compatible File ID, which will be wrapped + * in {@link InputMedia.auto} * @param params Additional sending parameters * @internal */ export async function sendMedia( this: TelegramClient, chatId: InputPeerLike, - media: InputMediaLike, + media: InputMediaLike | string, params?: { /** * Message to reply to. Either a message object or message ID. @@ -76,6 +85,13 @@ export async function sendMedia( ): Promise { if (!params) params = {} + if (typeof media === 'string') { + media = { + type: 'auto', + file: media + } + } + if (media.type === 'photo') { return this.sendPhoto(chatId, media.file, { caption: media.caption, @@ -91,13 +107,38 @@ export async function sendMedia( let mime = 'application/octet-stream' const input = media.file - if (typeof input === 'object' && tl.isAnyInputMedia(input)) { - inputMedia = input - } else if (typeof input === 'string' && input.match(/^https?:\/\//)) { - inputMedia = { - _: 'inputMediaDocumentExternal', - url: input, + if (tdFileId.isFileIdLike(input)) { + if (typeof input === 'string' && input.match(/^https?:\/\//)) { + inputMedia = { + _: 'inputMediaDocumentExternal', + url: input, + } + } else { + const parsed = + typeof input === 'string' ? parseFileId(input) : input + + if (parsed.location._ === 'photo') { + inputMedia = { + _: 'inputMediaPhoto', + id: fileIdToInputPhoto(parsed), + } + } else if (parsed.location._ === 'web') { + inputMedia = { + _: + parsed.type === tdFileId.FileType.Photo + ? 'inputMediaPhotoExternal' + : 'inputMediaDocumentExternal', + url: parsed.location.url, + } + } else { + inputMedia = { + _: 'inputMediaDocument', + id: fileIdToInputDocument(parsed), + } + } } + } else if (typeof input === 'object' && tl.isAnyInputMedia(input)) { + inputMedia = input } else if (isUploadedFile(input)) { inputFile = input.inputFile mime = input.mime @@ -120,8 +161,8 @@ export async function sendMedia( const t = media.thumb if (typeof t === 'object' && tl.isAnyInputMedia(t)) { throw new MtCuteArgumentError("Thumbnail can't be InputMedia") - } else if (typeof t === 'string' && t.match(/^https?:\/\//)) { - throw new MtCuteArgumentError("Thumbnail can't be external") + } else if (tdFileId.isFileIdLike(t)) { + throw new MtCuteArgumentError("Thumbnail can't be a URL or a File ID") } else if (isUploadedFile(t)) { thumb = t.inputFile } else if (typeof t === 'object' && tl.isAnyInputFile(t)) { diff --git a/packages/client/src/methods/messages/send-photo.ts b/packages/client/src/methods/messages/send-photo.ts index 60b4efc0..585a80cd 100644 --- a/packages/client/src/methods/messages/send-photo.ts +++ b/packages/client/src/methods/messages/send-photo.ts @@ -10,6 +10,7 @@ import { tl } from '@mtcute/tl' import { TelegramClient } from '../../client' import { normalizeToInputPeer } from '../../utils/peer-utils' import { normalizeDate, randomUlong } from '../../utils/misc-utils' +import { fileIdToInputPhoto, tdFileId } from '@mtcute/file-id' /** * Send a single photo @@ -94,14 +95,23 @@ export async function sendPhoto( if (!params) params = {} let media: tl.TypeInputMedia - if (typeof photo === 'object' && tl.isAnyInputMedia(photo)) { - media = photo - } else if (typeof photo === 'string' && photo.match(/^https?:\/\//)) { - media = { - _: 'inputMediaPhotoExternal', - url: photo, - ttlSeconds: params.ttlSeconds, + + if (tdFileId.isFileIdLike(photo)) { + if (typeof photo === 'string' && photo.match(/^https?:\/\//)) { + media = { + _: 'inputMediaPhotoExternal', + url: photo, + ttlSeconds: params.ttlSeconds, + } + } else { + const input = fileIdToInputPhoto(photo) + media = { + _: 'inputMediaPhoto', + id: input + } } + } else if (typeof photo === 'object' && tl.isAnyInputMedia(photo)) { + media = photo } else if (isUploadedFile(photo)) { media = { _: 'inputMediaUploadedPhoto', diff --git a/packages/file-id/src/types.ts b/packages/file-id/src/types.ts index 9c246a16..3ef00ac4 100644 --- a/packages/file-id/src/types.ts +++ b/packages/file-id/src/types.ts @@ -35,7 +35,7 @@ export namespace tdFileId { * Provided File ID cannot be converted to that TL object. */ export class ConversionError extends FileIdError { - constructor (to: string) { + constructor(to: string) { super(`Cannot convert given File ID to ${to}`) } } @@ -190,4 +190,13 @@ export namespace tdFileId { */ readonly location: TypeRemoteFileLocation } + + export function isFileIdLike( + obj: any + ): obj is string | RawFullRemoteFileLocation { + return ( + typeof obj === 'string' || + (typeof obj === 'object' && obj._ === 'remoteFileLocation') + ) + } }