UNPKG

@gramio/files

Version:

Set of utils to work with files uploading to Telegram Bot API

372 lines (365 loc) 10.9 kB
'use strict'; var fs = require('node:fs/promises'); var node_path = require('node:path'); var node_stream = require('node:stream'); let isWarned = false; function isBlob(blob) { if (!blob || typeof blob !== "object") return false; if (blob instanceof Promise) { if (!isWarned) { const error = new Error( "Promise<File> is deprecated. Please put await before." ); console.warn(error); isWarned = true; } return true; } return blob instanceof Blob; } const MEDIA_METHODS = { setWebhook: [(params) => isBlob(params.certificate), null], sendPhoto: [(params) => isBlob(params.photo), null], sendAudio: [ (params) => isBlob(params.audio) || isBlob(params.thumbnail), null ], sendDocument: [ (params) => isBlob(params.document) || isBlob(params.thumbnail), null ], sendVideo: [ (params) => isBlob(params.video) || isBlob(params.thumbnail), null ], sendAnimation: [ (params) => isBlob(params.animation) || isBlob(params.thumbnail), null ], sendVoice: [(params) => isBlob(params.voice), null], sendVideoNote: [ (params) => isBlob(params.video_note) || isBlob(params.thumbnail), null ], sendPaidMedia: [ (params) => params.media.some((x) => "media" in x && isBlob(x.media)) || params.media.some((x) => "cover" in x && isBlob(x.cover)) || params.media.some((x) => "thumbnail" in x && isBlob(x.thumbnail)), [ { name: "media", property: "media", type: "array" }, { name: "cover", property: "media", type: "array" }, { name: "thumbnail", property: "media", type: "array" } ] ], sendMediaGroup: [ (params) => params.media.some((x) => "media" in x && isBlob(x.media)) || params.media.some((x) => "thumbnail" in x && isBlob(x.thumbnail)) || params.media.some((x) => "cover" in x && isBlob(x.cover)), [ { name: "media", property: "media", type: "array" }, { name: "thumbnail", property: "media", type: "array" }, { name: "cover", property: "media", type: "array" } ] ], setChatPhoto: [(params) => isBlob(params.photo), null], editMessageMedia: [ (params) => "media" in params.media && isBlob(params.media.media) || "thumbnail" in params.media && isBlob(params.media.thumbnail) || "cover" in params.media && isBlob(params.media.cover), [ { name: "media", property: "media", type: "union" }, { name: "thumbnail", property: "media", type: "union" }, { name: "cover", property: "media", type: "union" } ] ], sendSticker: [(params) => isBlob(params.sticker), null], uploadStickerFile: [(params) => isBlob(params.sticker), null], createNewStickerSet: [ (params) => params.stickers.some((x) => "sticker" in x && isBlob(x.sticker)), [ { name: "sticker", property: "stickers", type: "array" } ] ], addStickerToSet: [(params) => isBlob(params.sticker.sticker), null], replaceStickerInSet: [(params) => isBlob(params.sticker.sticker), null], setStickerSetThumbnail: [(params) => isBlob(params.thumbnail), null], setBusinessAccountProfilePhoto: [ (params) => params.photo.type === "static" && isBlob(params.photo.photo) || params.photo.type === "animated" && isBlob(params.photo.animation), [ { name: "photo", property: "photo", type: "union" }, { name: "animation", property: "photo", type: "union" } ] ], postStory: [ (params) => params.content.type === "photo" && isBlob(params.content.photo) || params.content.type === "video" && isBlob(params.content.video), [ { name: "photo", property: "content", type: "union" }, { name: "video", property: "content", type: "union" } ] ], editStory: [ (params) => params.content.type === "photo" && isBlob(params.content.photo) || params.content.type === "video" && isBlob(params.content.video), [ { name: "photo", property: "content", type: "union" }, { name: "video", property: "content", type: "union" } ] ] }; function isMediaUpload(method, params) { const mediaMethod = MEDIA_METHODS[method]; if (!mediaMethod) return false; return mediaMethod[0](params); } function isExtractor(value, type, params) { return value.type === type; } async function convertJsonToFormData(method, params) { const formData = new FormData(); const mediaMethod = MEDIA_METHODS[method]; const extractor = mediaMethod?.[1] ?? []; let attachId = 0; for (const extractorValue of extractor) { if (isExtractor(extractorValue, "union")) { let file = params[extractorValue.property][extractorValue.name]; if (file instanceof Promise) file = await file; if (!(file instanceof Blob)) continue; const currentAttachId = attachId++; formData.set(`file-${currentAttachId}`, file); params[extractorValue.property][extractorValue.name] = `attach://file-${currentAttachId}`; } if (isExtractor(extractorValue, "array")) { const array = params[extractorValue.property]; for (const [index, element] of array.entries()) { let file = element[extractorValue.name]; if (file instanceof Promise) file = await file; if (!(file instanceof Blob)) continue; const currentAttachId = attachId++; formData.set(`file-${currentAttachId}`, file); params[extractorValue.property][index][extractorValue.name] = `attach://file-${currentAttachId}`; } } } for (let [key, value] of Object.entries(params)) { if (value instanceof Promise) value = await value; if (value instanceof Blob) formData.append(key, value); else if (typeof value === "object") formData.append(key, JSON.stringify(value)); else formData.append(key, String(value)); } return formData; } async function extractFilesToFormData(method, params) { const formData = new FormData(); let isEmpty = true; const mediaMethod = MEDIA_METHODS[method]; const extractor = mediaMethod?.[1] ?? []; let attachId = 0; for (const extractorValue of extractor) { if (isExtractor(extractorValue, "union")) { let file = params[extractorValue.property][extractorValue.name]; if (file instanceof Promise) file = await file; if (!(file instanceof Blob)) continue; const currentAttachId = attachId++; formData.set(`file-${currentAttachId}`, file); isEmpty = false; params[extractorValue.property][extractorValue.name] = `attach://file-${currentAttachId}`; } if (isExtractor(extractorValue, "array")) { const array = params[extractorValue.property]; for (const [index, element] of array.entries()) { let file = element[extractorValue.name]; if (file instanceof Promise) file = await file; if (!(file instanceof Blob)) continue; const currentAttachId = attachId++; formData.set(`file-${currentAttachId}`, file); isEmpty = false; params[extractorValue.property][index][extractorValue.name] = `attach://file-${currentAttachId}`; } } } for (let [key, value] of Object.entries(params)) { if (value instanceof Promise) value = await value; if (value instanceof Blob) { formData.append(key, value); isEmpty = false; delete params[key]; } } return [isEmpty ? void 0 : formData, params]; } function convertStreamToBuffer(stream) { return new Promise((resolve) => { const chunks = []; stream.on("data", (chunk) => { const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); chunks.push(bufferChunk); }); stream.on("end", () => resolve(Buffer.concat(chunks))); }); } class MediaInput { /** * Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. * * [Documentation](https://core.telegram.org/bots/api/#inputmediaanimation) */ static animation(media, options = {}) { return { type: "animation", media, ...options }; } /** * Represents a general file to be sent. * * [Documentation](https://core.telegram.org/bots/api/#inputmediadocument) */ static document(media, options = {}) { return { type: "document", media, ...options }; } /** * Represents an audio file to be treated as music to be sent. * * [Documentation](https://core.telegram.org/bots/api/#inputmediaaudio) */ static audio(media, options = {}) { return { type: "audio", media, ...options }; } /** * Represents a photo to be sent. * * [Documentation](https://core.telegram.org/bots/api/#inputmediaphoto) */ static photo(media, options = {}) { return { type: "photo", media, ...options }; } /** * Represents a video to be sent. * * [Documentation](https://core.telegram.org/bots/api/#inputmediavideo) */ static video(media, options = {}) { return { type: "video", media, ...options }; } } class MediaUpload { /** * Method for uploading Media File by local path. */ static async path(path, filename) { const buffer = await fs.readFile(path); return new File([buffer], filename ?? node_path.basename(path)); } /** * Method for uploading Media File by Readable stream. */ static async stream(stream, filename = "file.stream") { const buffer = await convertStreamToBuffer(node_stream.Readable.from(stream)); return new File([buffer], filename); } /** * Method for uploading Media File by BinaryLike (Buffer or ArrayBuffer and etc). */ static buffer(buffer, filename = "file.buffer") { return new File([buffer], filename); } /** * Method for uploading Media File by URL (also with fetch options). */ static async url(url, filename, options) { const res = await fetch(url, options); return new File( [await res.blob()], filename ?? (typeof url === "string" ? node_path.basename(url) : node_path.basename(url.pathname)) ); } /** * Method for uploading Media File by text content. */ static text(text, filename = "text.txt") { return new File([text], filename); } } exports.MEDIA_METHODS = MEDIA_METHODS; exports.MediaInput = MediaInput; exports.MediaUpload = MediaUpload; exports.convertJsonToFormData = convertJsonToFormData; exports.convertStreamToBuffer = convertStreamToBuffer; exports.extractFilesToFormData = extractFilesToFormData; exports.isBlob = isBlob; exports.isMediaUpload = isMediaUpload;