feat(client): support file:* for simpler file uploads by path
holy shit code for handling file is getting more and more ridiculous. i wonder if i could refactor it somehow...
This commit is contained in:
parent
f6d229f250
commit
0eb0ac91eb
5 changed files with 146 additions and 43 deletions
|
@ -34,35 +34,41 @@ export async function setChatPhoto(
|
|||
if (!(chat._ === 'inputPeerChat' || chat._ === 'inputPeerChannel'))
|
||||
throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel')
|
||||
|
||||
let photo: tl.TypeInputChatPhoto
|
||||
let photo: tl.TypeInputChatPhoto | undefined = undefined
|
||||
|
||||
let inputFile: tl.TypeInputFile
|
||||
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 {
|
||||
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 {
|
||||
if (typeof media === 'string' && media.match(/^file:/)) {
|
||||
const uploaded = await this.uploadFile({
|
||||
file: media,
|
||||
file: media.substr(5),
|
||||
})
|
||||
inputFile = uploaded.inputFile
|
||||
} else {
|
||||
const input = fileIdToInputPhoto(media)
|
||||
photo = {
|
||||
_: 'inputChatPhoto',
|
||||
id: input
|
||||
}
|
||||
}
|
||||
} else 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
|
||||
}
|
||||
|
||||
if (!photo) {
|
||||
photo = {
|
||||
_: 'inputChatUploadedPhoto',
|
||||
[type === 'photo' ? 'file' : 'video']: inputFile,
|
||||
[type === 'photo' ? 'file' : 'video']: inputFile!,
|
||||
videoStartTs: previewSec
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
isUploadedFile,
|
||||
Message,
|
||||
MtCuteArgumentError,
|
||||
ReplyMarkup,
|
||||
ReplyMarkup, UploadFileLike,
|
||||
} from '../../types'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { extractFileName } from '../../utils/file-utils'
|
||||
|
@ -88,7 +88,7 @@ export async function sendMedia(
|
|||
if (typeof media === 'string') {
|
||||
media = {
|
||||
type: 'auto',
|
||||
file: media
|
||||
file: media,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,10 +102,29 @@ export async function sendMedia(
|
|||
|
||||
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?:\/\//)) {
|
||||
|
@ -113,6 +132,8 @@ export async function sendMedia(
|
|||
_: '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
|
||||
|
@ -145,13 +166,7 @@ export async function sendMedia(
|
|||
} else if (typeof input === 'object' && tl.isAnyInputFile(input)) {
|
||||
inputFile = input
|
||||
} else {
|
||||
const uploaded = await this.uploadFile({
|
||||
file: input,
|
||||
fileName: media.fileName,
|
||||
progressCallback: params.progressCallback,
|
||||
})
|
||||
inputFile = uploaded.inputFile
|
||||
mime = uploaded.mime
|
||||
await upload(media, input)
|
||||
}
|
||||
|
||||
if (!inputMedia) {
|
||||
|
@ -162,7 +177,16 @@ export async function sendMedia(
|
|||
if (typeof t === 'object' && tl.isAnyInputMedia(t)) {
|
||||
throw new MtCuteArgumentError("Thumbnail can't be InputMedia")
|
||||
} else if (tdFileId.isFileIdLike(t)) {
|
||||
throw new MtCuteArgumentError("Thumbnail can't be a URL or a File ID")
|
||||
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)) {
|
||||
|
@ -212,6 +236,16 @@ export async function sendMedia(
|
|||
})
|
||||
}
|
||||
|
||||
if (media.type === 'sticker') {
|
||||
attributes.push({
|
||||
_: 'documentAttributeSticker',
|
||||
stickerset: {
|
||||
_: 'inputStickerSetEmpty',
|
||||
},
|
||||
alt: media.alt ?? '',
|
||||
})
|
||||
}
|
||||
|
||||
inputMedia = {
|
||||
_: 'inputMediaUploadedDocument',
|
||||
nosoundVideo: media.type === 'video' && media.isAnimated,
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
BotKeyboard,
|
||||
ReplyMarkup,
|
||||
isUploadedFile,
|
||||
UploadFileLike,
|
||||
} from '../../types'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TelegramClient } from '../../client'
|
||||
|
@ -90,12 +91,31 @@ export async function sendPhoto(
|
|||
* 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 = {
|
||||
|
@ -103,11 +123,13 @@ export async function sendPhoto(
|
|||
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
|
||||
id: input,
|
||||
}
|
||||
}
|
||||
} else if (typeof photo === 'object' && tl.isAnyInputMedia(photo)) {
|
||||
|
@ -125,15 +147,7 @@ export async function sendPhoto(
|
|||
ttlSeconds: params.ttlSeconds,
|
||||
}
|
||||
} else {
|
||||
const uploaded = await this.uploadFile({
|
||||
file: photo,
|
||||
progressCallback: params.progressCallback,
|
||||
})
|
||||
media = {
|
||||
_: 'inputMediaUploadedPhoto',
|
||||
file: uploaded.inputFile,
|
||||
ttlSeconds: params.ttlSeconds,
|
||||
}
|
||||
await upload(photo)
|
||||
}
|
||||
|
||||
const [message, entities] = await this._parseEntities(
|
||||
|
@ -147,7 +161,7 @@ export async function sendPhoto(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'messages.sendMedia',
|
||||
media,
|
||||
media: media!,
|
||||
peer,
|
||||
silent: params.silent,
|
||||
replyToMsgId: params.replyTo
|
||||
|
|
|
@ -35,12 +35,10 @@ export type UploadFileLike =
|
|||
* - `Readable` (for NodeJS, base readable stream)
|
||||
* - {@link UploadedFile} returned from {@link TelegramClient.uploadFile}
|
||||
* - `tl.TypeInputFile` and `tl.TypeInputMedia` TL objects
|
||||
* - `string` with a path to a local file prepended with `file:` (NodeJS only) (e.g. `file:image.jpg`)
|
||||
* - `string` with a URL to remote files (e.g. `https://example.com/image.jpg`)
|
||||
* - `string` with TDLib and Bot API compatible File ID.
|
||||
* - `td.RawFullRemoteFileLocation` (parsed File ID)
|
||||
*
|
||||
* > **Note**: Unlike {@link UploadFileLike}, you can't pass
|
||||
* > a file path directly. Use `fs.createReadStream('/path/to/file.png')`
|
||||
*/
|
||||
export type InputFileLike =
|
||||
| UploadFileLike
|
||||
|
|
|
@ -22,13 +22,24 @@ interface BaseInputMedia {
|
|||
* Override file name for the file.
|
||||
*/
|
||||
fileName?: string
|
||||
|
||||
/**
|
||||
* Override MIME type for the file
|
||||
*/
|
||||
mime?: string
|
||||
|
||||
/**
|
||||
* Override file size for the file
|
||||
*/
|
||||
fileSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically detect media type based on file contents.
|
||||
*
|
||||
* Only works for files that are internally documents, i.e.
|
||||
* *does not* infer photos, so use {@link InputMediaPhoto} instead.
|
||||
* *does not* infer photos, so use {@link InputMediaPhoto} instead
|
||||
* (except for File IDs, from which photos *are* inferred)
|
||||
*/
|
||||
export interface InputMediaAuto extends BaseInputMedia {
|
||||
type: 'auto'
|
||||
|
@ -105,6 +116,31 @@ export interface InputMediaPhoto extends BaseInputMedia {
|
|||
type: 'photo'
|
||||
}
|
||||
|
||||
/**
|
||||
* A sticker to be sent
|
||||
*/
|
||||
export interface InputMediaSticker extends BaseInputMedia {
|
||||
type: 'sticker'
|
||||
|
||||
caption?: never
|
||||
entities?: never
|
||||
|
||||
/**
|
||||
* Whether this sticker is animated?
|
||||
*
|
||||
* Note that animated stickers must be in TGS
|
||||
* format, which is Lottie JSON compressed using GZip
|
||||
*
|
||||
* Defaults to `false`
|
||||
*/
|
||||
isAnimated?: boolean
|
||||
|
||||
/**
|
||||
* An emoji representing this sticker
|
||||
*/
|
||||
alt?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A video to be sent
|
||||
*/
|
||||
|
@ -167,6 +203,7 @@ export type InputMediaLike =
|
|||
| InputMediaPhoto
|
||||
| InputMediaVideo
|
||||
| InputMediaAuto
|
||||
| InputMediaSticker
|
||||
|
||||
export namespace InputMedia {
|
||||
type OmitTypeAndFile<T extends InputMediaLike> = Omit<T, 'type' | 'file'>
|
||||
|
@ -256,6 +293,20 @@ export namespace InputMedia {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sticker to be sent
|
||||
*/
|
||||
export function sticker(
|
||||
file: InputFileLike,
|
||||
params?: OmitTypeAndFile<InputMediaSticker>
|
||||
): InputMediaSticker {
|
||||
return {
|
||||
type: 'sticker',
|
||||
file,
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document to be sent, which subtype
|
||||
* is inferred automatically by file contents.
|
||||
|
|
Loading…
Reference in a new issue