refactor(client): extracted input file and media normalization to own methods, merged sendPhoto and sendMedia methods
This commit is contained in:
parent
0eb0ac91eb
commit
a67c4ae85e
6 changed files with 283 additions and 444 deletions
|
@ -55,6 +55,8 @@ import { downloadAsBuffer } from './methods/files/download-buffer'
|
||||||
import { downloadToFile } from './methods/files/download-file'
|
import { downloadToFile } from './methods/files/download-file'
|
||||||
import { downloadAsIterable } from './methods/files/download-iterable'
|
import { downloadAsIterable } from './methods/files/download-iterable'
|
||||||
import { downloadAsStream } from './methods/files/download-stream'
|
import { downloadAsStream } from './methods/files/download-stream'
|
||||||
|
import { _normalizeInputFile } from './methods/files/normalize-input-file'
|
||||||
|
import { _normalizeInputMedia } from './methods/files/normalize-input-media'
|
||||||
import { uploadFile } from './methods/files/upload-file'
|
import { uploadFile } from './methods/files/upload-file'
|
||||||
import { deleteMessages } from './methods/messages/delete-messages'
|
import { deleteMessages } from './methods/messages/delete-messages'
|
||||||
import { editMessage } from './methods/messages/edit-message'
|
import { editMessage } from './methods/messages/edit-message'
|
||||||
|
@ -70,7 +72,6 @@ import { searchMessages } from './methods/messages/search-messages'
|
||||||
import { sendDice } from './methods/messages/send-dice'
|
import { sendDice } from './methods/messages/send-dice'
|
||||||
import { sendLocation } from './methods/messages/send-location'
|
import { sendLocation } from './methods/messages/send-location'
|
||||||
import { sendMedia } from './methods/messages/send-media'
|
import { sendMedia } from './methods/messages/send-media'
|
||||||
import { sendPhoto } from './methods/messages/send-photo'
|
|
||||||
import { sendText } from './methods/messages/send-text'
|
import { sendText } from './methods/messages/send-text'
|
||||||
import { unpinMessage } from './methods/messages/unpin-message'
|
import { unpinMessage } from './methods/messages/unpin-message'
|
||||||
import { initTakeoutSession } from './methods/misc/init-takeout-session'
|
import { initTakeoutSession } from './methods/misc/init-takeout-session'
|
||||||
|
@ -1600,7 +1601,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
}
|
}
|
||||||
): Promise<Message>
|
): Promise<Message>
|
||||||
/**
|
/**
|
||||||
* Send a single media.
|
* Send a single media (a photo or a document-based media)
|
||||||
*
|
*
|
||||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||||
* @param media
|
* @param media
|
||||||
|
@ -1608,6 +1609,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
* and Bot API compatible File ID, which will be wrapped
|
* and Bot API compatible File ID, which will be wrapped
|
||||||
* in {@link InputMedia.auto}
|
* in {@link InputMedia.auto}
|
||||||
* @param params Additional sending parameters
|
* @param params Additional sending parameters
|
||||||
|
* @see InputMedia
|
||||||
*/
|
*/
|
||||||
sendMedia(
|
sendMedia(
|
||||||
chatId: InputPeerLike,
|
chatId: InputPeerLike,
|
||||||
|
@ -1661,84 +1663,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
clearDraft?: boolean
|
clearDraft?: boolean
|
||||||
}
|
}
|
||||||
): Promise<Message>
|
): Promise<Message>
|
||||||
/**
|
|
||||||
* Send a single photo
|
|
||||||
*
|
|
||||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
|
||||||
* @param photo Photo contained in the message.
|
|
||||||
* @param params Additional sending parameters
|
|
||||||
*/
|
|
||||||
sendPhoto(
|
|
||||||
chatId: InputPeerLike,
|
|
||||||
photo: InputFileLike,
|
|
||||||
params?: {
|
|
||||||
/**
|
|
||||||
* Caption for the photo
|
|
||||||
*/
|
|
||||||
caption?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message to reply to. Either a message object or message ID.
|
|
||||||
*/
|
|
||||||
replyTo?: number | Message
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse mode to use to parse entities before sending
|
|
||||||
* the message. Defaults to current default parse mode (if any).
|
|
||||||
*
|
|
||||||
* Passing `null` will explicitly disable formatting.
|
|
||||||
*/
|
|
||||||
parseMode?: string | null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of formatting entities to use instead of parsing via a
|
|
||||||
* parse mode.
|
|
||||||
*
|
|
||||||
* **Note:** Passing this makes the method ignore {@link parseMode}
|
|
||||||
*/
|
|
||||||
entities?: tl.TypeMessageEntity[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to send this message silently.
|
|
||||||
*/
|
|
||||||
silent?: boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set, the message will be scheduled to this date.
|
|
||||||
* When passing a number, a UNIX time in ms is expected.
|
|
||||||
*/
|
|
||||||
schedule?: Date | number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For bots: inline or reply markup or an instruction
|
|
||||||
* to hide a reply keyboard or to force a reply.
|
|
||||||
*/
|
|
||||||
replyMarkup?: ReplyMarkup
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Self-Destruct timer.
|
|
||||||
* If set, the photo will self-destruct in a given number
|
|
||||||
* of seconds.
|
|
||||||
*/
|
|
||||||
ttlSeconds?: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function that will be called after some part has been uploaded.
|
|
||||||
* Only used when a file that requires uploading is passed.
|
|
||||||
*
|
|
||||||
* @param uploaded Number of bytes already uploaded
|
|
||||||
* @param total Total file size
|
|
||||||
*/
|
|
||||||
progressCallback?: (uploaded: number, total: number) => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to clear draft after sending this message.
|
|
||||||
*
|
|
||||||
* Defaults to `false`
|
|
||||||
*/
|
|
||||||
clearDraft?: boolean
|
|
||||||
}
|
|
||||||
): Promise<Message>
|
|
||||||
/**
|
/**
|
||||||
* Send a text message
|
* Send a text message
|
||||||
*
|
*
|
||||||
|
@ -2024,6 +1948,8 @@ export class TelegramClient extends BaseTelegramClient {
|
||||||
downloadToFile = downloadToFile
|
downloadToFile = downloadToFile
|
||||||
downloadAsIterable = downloadAsIterable
|
downloadAsIterable = downloadAsIterable
|
||||||
downloadAsStream = downloadAsStream
|
downloadAsStream = downloadAsStream
|
||||||
|
protected _normalizeInputFile = _normalizeInputFile
|
||||||
|
protected _normalizeInputMedia = _normalizeInputMedia
|
||||||
uploadFile = uploadFile
|
uploadFile = uploadFile
|
||||||
deleteMessages = deleteMessages
|
deleteMessages = deleteMessages
|
||||||
editMessage = editMessage
|
editMessage = editMessage
|
||||||
|
@ -2039,7 +1965,6 @@ export class TelegramClient extends BaseTelegramClient {
|
||||||
sendDice = sendDice
|
sendDice = sendDice
|
||||||
sendLocation = sendLocation
|
sendLocation = sendLocation
|
||||||
sendMedia = sendMedia
|
sendMedia = sendMedia
|
||||||
sendPhoto = sendPhoto
|
|
||||||
sendText = sendText
|
sendText = sendText
|
||||||
unpinMessage = unpinMessage
|
unpinMessage = unpinMessage
|
||||||
initTakeoutSession = initTakeoutSession
|
initTakeoutSession = initTakeoutSession
|
||||||
|
|
49
packages/client/src/methods/files/normalize-input-file.ts
Normal file
49
packages/client/src/methods/files/normalize-input-file.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { TelegramClient } from '../../client'
|
||||||
|
import { InputFileLike, isUploadedFile, MtCuteArgumentError } from '../../types'
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { tdFileId } from '@mtcute/file-id'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a {@link InputFileLike} to `InputFile`,
|
||||||
|
* uploading it if needed.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export async function _normalizeInputFile(
|
||||||
|
this: TelegramClient,
|
||||||
|
input: InputFileLike,
|
||||||
|
params: {
|
||||||
|
progressCallback?: (uploaded: number, total: number) => void
|
||||||
|
fileName?: string
|
||||||
|
fileSize?: number
|
||||||
|
fileMime?: string
|
||||||
|
}
|
||||||
|
): Promise<tl.TypeInputFile> {
|
||||||
|
if (typeof input === 'object' && tl.isAnyInputMedia(input)) {
|
||||||
|
throw new MtCuteArgumentError(
|
||||||
|
"InputFile can't be created from an InputMedia"
|
||||||
|
)
|
||||||
|
} else if (tdFileId.isFileIdLike(input)) {
|
||||||
|
if (typeof input === 'string' && input.match(/^file:/)) {
|
||||||
|
const uploaded = await this.uploadFile({
|
||||||
|
file: input.substr(5),
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
return uploaded.inputFile
|
||||||
|
} else {
|
||||||
|
throw new MtCuteArgumentError(
|
||||||
|
"InputFile can't be created from an URL or a File ID"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (isUploadedFile(input)) {
|
||||||
|
return input.inputFile
|
||||||
|
} else if (typeof input === 'object' && tl.isAnyInputFile(input)) {
|
||||||
|
return input
|
||||||
|
} else {
|
||||||
|
const uploaded = await this.uploadFile({
|
||||||
|
file: input,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
return uploaded.inputFile
|
||||||
|
}
|
||||||
|
}
|
171
packages/client/src/methods/files/normalize-input-media.ts
Normal file
171
packages/client/src/methods/files/normalize-input-media.ts
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import { TelegramClient } from '../../client'
|
||||||
|
import {
|
||||||
|
InputMediaLike,
|
||||||
|
isUploadedFile,
|
||||||
|
MtCuteArgumentError,
|
||||||
|
UploadFileLike,
|
||||||
|
} from '../../types'
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
import {
|
||||||
|
fileIdToInputDocument,
|
||||||
|
fileIdToInputPhoto,
|
||||||
|
parseFileId,
|
||||||
|
tdFileId,
|
||||||
|
} from '@mtcute/file-id'
|
||||||
|
import { extractFileName } from '../../utils/file-utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an {@link InputMediaLike} to `InputMedia`,
|
||||||
|
* uploading the file if needed.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export async function _normalizeInputMedia(
|
||||||
|
this: TelegramClient,
|
||||||
|
media: InputMediaLike,
|
||||||
|
params: {
|
||||||
|
progressCallback?: (uploaded: number, total: number) => void
|
||||||
|
}
|
||||||
|
): Promise<tl.TypeInputMedia> {
|
||||||
|
// my condolences to those poor souls who are going to maintain this (myself included)
|
||||||
|
|
||||||
|
let inputFile: tl.TypeInputFile | undefined = undefined
|
||||||
|
let thumb: tl.TypeInputFile | undefined = undefined
|
||||||
|
let mime = 'application/octet-stream'
|
||||||
|
|
||||||
|
const upload = async (file: UploadFileLike): Promise<void> => {
|
||||||
|
const uploaded = await this.uploadFile({
|
||||||
|
file,
|
||||||
|
progressCallback: params.progressCallback,
|
||||||
|
fileName: media.fileName,
|
||||||
|
fileMime:
|
||||||
|
media.type === 'sticker'
|
||||||
|
? media.isAnimated
|
||||||
|
? 'application/x-tgsticker'
|
||||||
|
: 'image/webp'
|
||||||
|
: media.fileMime,
|
||||||
|
fileSize: media.fileSize,
|
||||||
|
})
|
||||||
|
inputFile = uploaded.inputFile
|
||||||
|
mime = uploaded.mime
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = media.file
|
||||||
|
if (tdFileId.isFileIdLike(input)) {
|
||||||
|
if (typeof input === 'string' && input.match(/^https?:\/\//)) {
|
||||||
|
return {
|
||||||
|
_:
|
||||||
|
media.type === 'photo'
|
||||||
|
? 'inputMediaPhotoExternal'
|
||||||
|
: 'inputMediaDocumentExternal',
|
||||||
|
url: input,
|
||||||
|
}
|
||||||
|
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
||||||
|
await upload(input.substr(5))
|
||||||
|
} else {
|
||||||
|
const parsed =
|
||||||
|
typeof input === 'string' ? parseFileId(input) : input
|
||||||
|
|
||||||
|
if (parsed.location._ === 'photo') {
|
||||||
|
return {
|
||||||
|
_: 'inputMediaPhoto',
|
||||||
|
id: fileIdToInputPhoto(parsed),
|
||||||
|
}
|
||||||
|
} else if (parsed.location._ === 'web') {
|
||||||
|
return {
|
||||||
|
_:
|
||||||
|
parsed.type === tdFileId.FileType.Photo
|
||||||
|
? 'inputMediaPhotoExternal'
|
||||||
|
: 'inputMediaDocumentExternal',
|
||||||
|
url: parsed.location.url,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
_: 'inputMediaDocument',
|
||||||
|
id: fileIdToInputDocument(parsed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof input === 'object' && tl.isAnyInputMedia(input)) {
|
||||||
|
return input
|
||||||
|
} else if (isUploadedFile(input)) {
|
||||||
|
inputFile = input.inputFile
|
||||||
|
mime = input.mime
|
||||||
|
} else if (typeof input === 'object' && tl.isAnyInputFile(input)) {
|
||||||
|
inputFile = input
|
||||||
|
} else {
|
||||||
|
await upload(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputFile) throw new Error('should not happen')
|
||||||
|
|
||||||
|
if (media.type === 'photo') {
|
||||||
|
return {
|
||||||
|
_: 'inputMediaUploadedPhoto',
|
||||||
|
file: inputFile,
|
||||||
|
ttlSeconds: media.ttlSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('thumb' in media && media.thumb) {
|
||||||
|
thumb = await this._normalizeInputFile(media.thumb, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes: tl.TypeDocumentAttribute[] = []
|
||||||
|
|
||||||
|
if (media.type !== 'voice') {
|
||||||
|
attributes.push({
|
||||||
|
_: 'documentAttributeFilename',
|
||||||
|
fileName:
|
||||||
|
media.fileName ||
|
||||||
|
(typeof media.file === 'string'
|
||||||
|
? extractFileName(media.file)
|
||||||
|
: 'unnamed'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.type === 'video') {
|
||||||
|
attributes.push({
|
||||||
|
_: 'documentAttributeVideo',
|
||||||
|
duration: media.duration || 0,
|
||||||
|
w: media.width || 0,
|
||||||
|
h: media.height || 0,
|
||||||
|
supportsStreaming: media.supportsStreaming,
|
||||||
|
roundMessage: media.isRound,
|
||||||
|
})
|
||||||
|
if (media.isAnimated)
|
||||||
|
attributes.push({ _: 'documentAttributeAnimated' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.type === 'audio' || media.type === 'voice') {
|
||||||
|
attributes.push({
|
||||||
|
_: 'documentAttributeAudio',
|
||||||
|
voice: media.type === 'voice',
|
||||||
|
duration: media.duration || 0,
|
||||||
|
title: media.type === 'audio' ? media.title : undefined,
|
||||||
|
performer: media.type === 'audio' ? media.performer : undefined,
|
||||||
|
waveform: media.type === 'voice' ? media.waveform : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.type === 'sticker') {
|
||||||
|
attributes.push({
|
||||||
|
_: 'documentAttributeSticker',
|
||||||
|
stickerset: {
|
||||||
|
_: 'inputStickerSetEmpty',
|
||||||
|
},
|
||||||
|
alt: media.alt ?? '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_: 'inputMediaUploadedDocument',
|
||||||
|
nosoundVideo: media.type === 'video' && media.isAnimated,
|
||||||
|
forceFile: media.type === 'document',
|
||||||
|
file: inputFile,
|
||||||
|
thumb,
|
||||||
|
mimeType: mime,
|
||||||
|
attributes,
|
||||||
|
ttlSeconds: media.ttlSeconds
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,24 +3,14 @@ import {
|
||||||
BotKeyboard,
|
BotKeyboard,
|
||||||
InputMediaLike,
|
InputMediaLike,
|
||||||
InputPeerLike,
|
InputPeerLike,
|
||||||
isUploadedFile,
|
|
||||||
Message,
|
Message,
|
||||||
MtCuteArgumentError,
|
ReplyMarkup,
|
||||||
ReplyMarkup, UploadFileLike,
|
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { tl } from '@mtcute/tl'
|
|
||||||
import { extractFileName } from '../../utils/file-utils'
|
|
||||||
import { normalizeToInputPeer } from '../../utils/peer-utils'
|
import { normalizeToInputPeer } from '../../utils/peer-utils'
|
||||||
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
|
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
|
||||||
import {
|
|
||||||
fileIdToInputDocument,
|
|
||||||
fileIdToInputPhoto,
|
|
||||||
parseFileId,
|
|
||||||
tdFileId,
|
|
||||||
} from '@mtcute/file-id'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a single media.
|
* Send a single media (a photo or a document-based media)
|
||||||
*
|
*
|
||||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||||
* @param media
|
* @param media
|
||||||
|
@ -28,6 +18,7 @@ import {
|
||||||
* and Bot API compatible File ID, which will be wrapped
|
* and Bot API compatible File ID, which will be wrapped
|
||||||
* in {@link InputMedia.auto}
|
* in {@link InputMedia.auto}
|
||||||
* @param params Additional sending parameters
|
* @param params Additional sending parameters
|
||||||
|
* @see InputMedia
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export async function sendMedia(
|
export async function sendMedia(
|
||||||
|
@ -92,170 +83,7 @@ export async function sendMedia(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media.type === 'photo') {
|
const inputMedia = await this._normalizeInputMedia(media, params)
|
||||||
return this.sendPhoto(chatId, media.file, {
|
|
||||||
caption: media.caption,
|
|
||||||
entities: media.entities,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let inputMedia: tl.TypeInputMedia | null = null
|
|
||||||
|
|
||||||
// my condolences to those poor souls who are going to maintain this (myself included)
|
|
||||||
|
|
||||||
let inputFile: tl.TypeInputFile | undefined = undefined
|
|
||||||
let thumb: tl.TypeInputFile | undefined = undefined
|
|
||||||
let mime = 'application/octet-stream'
|
|
||||||
|
|
||||||
const upload = async (media: InputMediaLike, file: UploadFileLike): Promise<void> => {
|
|
||||||
const uploaded = await this.uploadFile({
|
|
||||||
file,
|
|
||||||
fileName: media.fileName,
|
|
||||||
progressCallback: params!.progressCallback,
|
|
||||||
fileMime:
|
|
||||||
media.type === 'sticker'
|
|
||||||
? media.isAnimated
|
|
||||||
? 'application/x-tgsticker'
|
|
||||||
: 'image/webp'
|
|
||||||
: media.mime,
|
|
||||||
fileSize: media.fileSize
|
|
||||||
})
|
|
||||||
inputFile = uploaded.inputFile
|
|
||||||
mime = uploaded.mime
|
|
||||||
}
|
|
||||||
|
|
||||||
const input = media.file
|
|
||||||
if (tdFileId.isFileIdLike(input)) {
|
|
||||||
if (typeof input === 'string' && input.match(/^https?:\/\//)) {
|
|
||||||
inputMedia = {
|
|
||||||
_: 'inputMediaDocumentExternal',
|
|
||||||
url: input,
|
|
||||||
}
|
|
||||||
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
|
||||||
await upload(media, input.substr(5))
|
|
||||||
} 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
|
|
||||||
} else if (typeof input === 'object' && tl.isAnyInputFile(input)) {
|
|
||||||
inputFile = input
|
|
||||||
} else {
|
|
||||||
await upload(media, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inputMedia) {
|
|
||||||
if (!inputFile) throw new Error('should not happen')
|
|
||||||
|
|
||||||
if ('thumb' in media && media.thumb) {
|
|
||||||
const t = media.thumb
|
|
||||||
if (typeof t === 'object' && tl.isAnyInputMedia(t)) {
|
|
||||||
throw new MtCuteArgumentError("Thumbnail can't be InputMedia")
|
|
||||||
} else if (tdFileId.isFileIdLike(t)) {
|
|
||||||
if (typeof t === 'string' && t.match(/^file:/)) {
|
|
||||||
const uploaded = await this.uploadFile({
|
|
||||||
file: t.substr(5),
|
|
||||||
})
|
|
||||||
thumb = uploaded.inputFile
|
|
||||||
} else {
|
|
||||||
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)) {
|
|
||||||
thumb = t
|
|
||||||
} else {
|
|
||||||
const uploaded = await this.uploadFile({
|
|
||||||
file: t,
|
|
||||||
})
|
|
||||||
thumb = uploaded.inputFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributes: tl.TypeDocumentAttribute[] = []
|
|
||||||
|
|
||||||
if (media.type !== 'voice') {
|
|
||||||
attributes.push({
|
|
||||||
_: 'documentAttributeFilename',
|
|
||||||
fileName:
|
|
||||||
media.fileName ||
|
|
||||||
(typeof media.file === 'string'
|
|
||||||
? extractFileName(media.file)
|
|
||||||
: 'unnamed'),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media.type === 'video') {
|
|
||||||
attributes.push({
|
|
||||||
_: 'documentAttributeVideo',
|
|
||||||
duration: media.duration || 0,
|
|
||||||
w: media.width || 0,
|
|
||||||
h: media.height || 0,
|
|
||||||
supportsStreaming: media.supportsStreaming,
|
|
||||||
roundMessage: media.isRound,
|
|
||||||
})
|
|
||||||
if (media.isAnimated)
|
|
||||||
attributes.push({ _: 'documentAttributeAnimated' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media.type === 'audio' || media.type === 'voice') {
|
|
||||||
attributes.push({
|
|
||||||
_: 'documentAttributeAudio',
|
|
||||||
voice: media.type === 'voice',
|
|
||||||
duration: media.duration || 0,
|
|
||||||
title: media.type === 'audio' ? media.title : undefined,
|
|
||||||
performer: media.type === 'audio' ? media.performer : undefined,
|
|
||||||
waveform: media.type === 'voice' ? media.waveform : undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media.type === 'sticker') {
|
|
||||||
attributes.push({
|
|
||||||
_: 'documentAttributeSticker',
|
|
||||||
stickerset: {
|
|
||||||
_: 'inputStickerSetEmpty',
|
|
||||||
},
|
|
||||||
alt: media.alt ?? '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
inputMedia = {
|
|
||||||
_: 'inputMediaUploadedDocument',
|
|
||||||
nosoundVideo: media.type === 'video' && media.isAnimated,
|
|
||||||
forceFile: media.type === 'document',
|
|
||||||
file: inputFile,
|
|
||||||
thumb,
|
|
||||||
mimeType: mime,
|
|
||||||
attributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [message, entities] = await this._parseEntities(
|
const [message, entities] = await this._parseEntities(
|
||||||
media.caption,
|
media.caption,
|
||||||
|
|
|
@ -1,181 +0,0 @@
|
||||||
import {
|
|
||||||
InputPeerLike,
|
|
||||||
InputFileLike,
|
|
||||||
Message,
|
|
||||||
BotKeyboard,
|
|
||||||
ReplyMarkup,
|
|
||||||
isUploadedFile,
|
|
||||||
UploadFileLike,
|
|
||||||
} from '../../types'
|
|
||||||
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
|
|
||||||
*
|
|
||||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
|
||||||
* @param photo Photo contained in the message.
|
|
||||||
* @param params Additional sending parameters
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export async function sendPhoto(
|
|
||||||
this: TelegramClient,
|
|
||||||
chatId: InputPeerLike,
|
|
||||||
photo: InputFileLike,
|
|
||||||
params?: {
|
|
||||||
/**
|
|
||||||
* Caption for the photo
|
|
||||||
*/
|
|
||||||
caption?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message to reply to. Either a message object or message ID.
|
|
||||||
*/
|
|
||||||
replyTo?: number | Message
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse mode to use to parse entities before sending
|
|
||||||
* the message. Defaults to current default parse mode (if any).
|
|
||||||
*
|
|
||||||
* Passing `null` will explicitly disable formatting.
|
|
||||||
*/
|
|
||||||
parseMode?: string | null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of formatting entities to use instead of parsing via a
|
|
||||||
* parse mode.
|
|
||||||
*
|
|
||||||
* **Note:** Passing this makes the method ignore {@link parseMode}
|
|
||||||
*/
|
|
||||||
entities?: tl.TypeMessageEntity[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to send this message silently.
|
|
||||||
*/
|
|
||||||
silent?: boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set, the message will be scheduled to this date.
|
|
||||||
* When passing a number, a UNIX time in ms is expected.
|
|
||||||
*/
|
|
||||||
schedule?: Date | number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For bots: inline or reply markup or an instruction
|
|
||||||
* to hide a reply keyboard or to force a reply.
|
|
||||||
*/
|
|
||||||
replyMarkup?: ReplyMarkup
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Self-Destruct timer.
|
|
||||||
* If set, the photo will self-destruct in a given number
|
|
||||||
* of seconds.
|
|
||||||
*/
|
|
||||||
ttlSeconds?: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function that will be called after some part has been uploaded.
|
|
||||||
* Only used when a file that requires uploading is passed.
|
|
||||||
*
|
|
||||||
* @param uploaded Number of bytes already uploaded
|
|
||||||
* @param total Total file size
|
|
||||||
*/
|
|
||||||
progressCallback?: (uploaded: number, total: number) => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to clear draft after sending this message.
|
|
||||||
*
|
|
||||||
* Defaults to `false`
|
|
||||||
*/
|
|
||||||
clearDraft?: boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File size. Only used when uploading from streams without
|
|
||||||
* known length.
|
|
||||||
*/
|
|
||||||
fileSize?: number
|
|
||||||
}
|
|
||||||
): Promise<Message> {
|
|
||||||
if (!params) params = {}
|
|
||||||
|
|
||||||
let media: tl.TypeInputMedia
|
|
||||||
|
|
||||||
const upload = async (photo: UploadFileLike) => {
|
|
||||||
const uploaded = await this.uploadFile({
|
|
||||||
file: photo,
|
|
||||||
progressCallback: params!.progressCallback,
|
|
||||||
fileSize: params!.fileSize,
|
|
||||||
})
|
|
||||||
media = {
|
|
||||||
_: 'inputMediaUploadedPhoto',
|
|
||||||
file: uploaded.inputFile,
|
|
||||||
ttlSeconds: params!.ttlSeconds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tdFileId.isFileIdLike(photo)) {
|
|
||||||
if (typeof photo === 'string' && photo.match(/^https?:\/\//)) {
|
|
||||||
media = {
|
|
||||||
_: 'inputMediaPhotoExternal',
|
|
||||||
url: photo,
|
|
||||||
ttlSeconds: params.ttlSeconds,
|
|
||||||
}
|
|
||||||
} else if (typeof photo === 'string' && photo.match(/^file:/)) {
|
|
||||||
await upload(photo.substr(5))
|
|
||||||
} 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',
|
|
||||||
file: photo.inputFile,
|
|
||||||
ttlSeconds: params.ttlSeconds,
|
|
||||||
}
|
|
||||||
} else if (typeof photo === 'object' && tl.isAnyInputFile(photo)) {
|
|
||||||
media = {
|
|
||||||
_: 'inputMediaUploadedPhoto',
|
|
||||||
file: photo,
|
|
||||||
ttlSeconds: params.ttlSeconds,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await upload(photo)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [message, entities] = await this._parseEntities(
|
|
||||||
params.caption,
|
|
||||||
params.parseMode,
|
|
||||||
params.entities
|
|
||||||
)
|
|
||||||
|
|
||||||
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
|
||||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
|
||||||
|
|
||||||
const res = await this.call({
|
|
||||||
_: 'messages.sendMedia',
|
|
||||||
media: media!,
|
|
||||||
peer,
|
|
||||||
silent: params.silent,
|
|
||||||
replyToMsgId: params.replyTo
|
|
||||||
? typeof params.replyTo === 'number'
|
|
||||||
? params.replyTo
|
|
||||||
: params.replyTo.id
|
|
||||||
: undefined,
|
|
||||||
randomId: randomUlong(),
|
|
||||||
scheduleDate: normalizeDate(params.schedule),
|
|
||||||
replyMarkup,
|
|
||||||
message,
|
|
||||||
entities,
|
|
||||||
clearDraft: params.clearDraft,
|
|
||||||
})
|
|
||||||
|
|
||||||
return this._findMessageInUpdate(res)
|
|
||||||
}
|
|
|
@ -20,26 +20,39 @@ interface BaseInputMedia {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override file name for the file.
|
* Override file name for the file.
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
fileName?: string
|
fileName?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override MIME type for the file
|
* Override MIME type for the file
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
mime?: string
|
fileMime?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override file size for the file
|
* Override file size for the file
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
fileSize?: number
|
fileSize?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTL for the media in seconds.
|
||||||
|
*
|
||||||
|
* Only applicable to some media types
|
||||||
|
*/
|
||||||
|
ttlSeconds?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically detect media type based on file contents.
|
* Automatically detect media type based on file contents.
|
||||||
*
|
*
|
||||||
* Only works for files that are internally documents, i.e.
|
* Photo type is only inferred for reused files,
|
||||||
* *does not* infer photos, so use {@link InputMediaPhoto} instead
|
* newly uploaded photos with `auto` will be
|
||||||
* (except for File IDs, from which photos *are* inferred)
|
* uploaded as a document
|
||||||
*/
|
*/
|
||||||
export interface InputMediaAuto extends BaseInputMedia {
|
export interface InputMediaAuto extends BaseInputMedia {
|
||||||
type: 'auto'
|
type: 'auto'
|
||||||
|
@ -57,21 +70,29 @@ export interface InputMediaAudio extends BaseInputMedia {
|
||||||
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
* A thumbnail's width and height should not exceed 320 pixels.
|
* A thumbnail's width and height should not exceed 320 pixels.
|
||||||
* Thumbnails can't be reused and can be only uploaded as a new file.
|
* Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
thumb?: InputFileLike
|
thumb?: InputFileLike
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of the audio in seconds
|
* Duration of the audio in seconds
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
duration?: number
|
duration?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performer of the audio
|
* Performer of the audio
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
performer?: string
|
performer?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Title of the audio
|
* Title of the audio
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
@ -84,11 +105,15 @@ export interface InputMediaVoice extends BaseInputMedia {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of the voice message in seconds
|
* Duration of the voice message in seconds
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
duration?: number
|
duration?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waveform of the voice message
|
* Waveform of the voice message
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
waveform?: Buffer
|
waveform?: Buffer
|
||||||
}
|
}
|
||||||
|
@ -105,6 +130,8 @@ export interface InputMediaDocument extends BaseInputMedia {
|
||||||
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
* A thumbnail's width and height should not exceed 320 pixels.
|
* A thumbnail's width and height should not exceed 320 pixels.
|
||||||
* Thumbnails can't be reused and can be only uploaded as a new file.
|
* Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
thumb?: InputFileLike
|
thumb?: InputFileLike
|
||||||
}
|
}
|
||||||
|
@ -132,11 +159,16 @@ export interface InputMediaSticker extends BaseInputMedia {
|
||||||
* format, which is Lottie JSON compressed using GZip
|
* format, which is Lottie JSON compressed using GZip
|
||||||
*
|
*
|
||||||
* Defaults to `false`
|
* Defaults to `false`
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
isAnimated?: boolean
|
isAnimated?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An emoji representing this sticker
|
* An emoji representing this sticker
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files,
|
||||||
|
* for some reason doesn't work with animated stickers.
|
||||||
*/
|
*/
|
||||||
alt?: string
|
alt?: string
|
||||||
}
|
}
|
||||||
|
@ -153,36 +185,50 @@ export interface InputMediaVideo extends BaseInputMedia {
|
||||||
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
* The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
* A thumbnail's width and height should not exceed 320 pixels.
|
* A thumbnail's width and height should not exceed 320 pixels.
|
||||||
* Thumbnails can't be reused and can be only uploaded as a new file.
|
* Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
thumb?: InputFileLike
|
thumb?: InputFileLike
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Width of the video in pixels
|
* Width of the video in pixels
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
width?: number
|
width?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Height of the video in pixels
|
* Height of the video in pixels
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
height?: number
|
height?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of the video in seconds
|
* Duration of the video in seconds
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
duration?: number
|
duration?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the video is suitable for streaming
|
* Whether the video is suitable for streaming
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
supportsStreaming?: boolean
|
supportsStreaming?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this video is an animated GIF
|
* Whether this video is an animated GIF
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
isAnimated?: boolean
|
isAnimated?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this video is a round message (aka video note)
|
* Whether this video is a round message (aka video note)
|
||||||
|
*
|
||||||
|
* Only applicable to newly uploaded files.
|
||||||
*/
|
*/
|
||||||
isRound?: boolean
|
isRound?: boolean
|
||||||
}
|
}
|
||||||
|
@ -311,8 +357,9 @@ export namespace InputMedia {
|
||||||
* Create a document to be sent, which subtype
|
* Create a document to be sent, which subtype
|
||||||
* is inferred automatically by file contents.
|
* is inferred automatically by file contents.
|
||||||
*
|
*
|
||||||
* Only infers photos from the File ID, otherwise
|
* Photo type is only inferred for reused files,
|
||||||
* photos will be sent as documents.
|
* newly uploaded photos with `auto` will be
|
||||||
|
* uploaded as a document
|
||||||
*/
|
*/
|
||||||
export function auto(
|
export function auto(
|
||||||
file: InputFileLike,
|
file: InputFileLike,
|
||||||
|
|
Loading…
Reference in a new issue