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:
teidesu 2021-04-30 22:44:17 +03:00
parent f6d229f250
commit 0eb0ac91eb
5 changed files with 146 additions and 43 deletions

View file

@ -34,20 +34,25 @@ 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")
if (typeof media === 'string' && media.match(/^file:/)) {
const uploaded = await this.uploadFile({
file: media.substr(5),
})
inputFile = uploaded.inputFile
} else {
const input = fileIdToInputPhoto(media)
photo = {
_: 'inputChatPhoto',
id: input
}
} else {
let inputFile: tl.TypeInputFile
if (typeof media === 'object' && tl.isAnyInputMedia(media)) {
}
} else if (typeof media === 'object' && tl.isAnyInputMedia(media)) {
throw new MtCuteArgumentError("Chat photo can't be InputMedia")
} else if (isUploadedFile(media)) {
inputFile = media.inputFile
@ -60,9 +65,10 @@ export async function setChatPhoto(
inputFile = uploaded.inputFile
}
if (!photo) {
photo = {
_: 'inputChatUploadedPhoto',
[type === 'photo' ? 'file' : 'video']: inputFile,
[type === 'photo' ? 'file' : 'video']: inputFile!,
videoStartTs: previewSec
}
}

View file

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

View file

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

View file

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

View file

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