UNPKG

dograma

Version:

NodeJS/Browser MTProto API Telegram client library,

484 lines (483 loc) 17.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendFile = exports._sendAlbum = exports._fileToMedia = exports.uploadFile = exports.CustomFile = void 0; const tl_1 = require("../tl"); const Helpers_1 = require("../Helpers"); const Utils_1 = require("../Utils"); const path_1 = __importDefault(require("./path")); const fs_1 = require("./fs"); const index_1 = require("../index"); const messageParse_1 = require("./messageParse"); const messages_1 = require("./messages"); const big_integer_1 = __importDefault(require("big-integer")); /** * A custom file class that mimics the browser's File class.<br/> * You should use this whenever you want to upload a file. */ class CustomFile { constructor(name, size, path, buffer) { this.name = name; this.size = size; this.path = path; this.buffer = buffer; } } exports.CustomFile = CustomFile; class CustomBuffer { constructor(options) { this.options = options; if (!options.buffer && !options.filePath) { throw new Error("Either one of `buffer` or `filePath` should be specified"); } } async slice(begin, end) { const { buffer, filePath } = this.options; if (buffer) { return buffer.slice(begin, end); } else if (filePath) { const buffSize = end - begin; const buff = Buffer.alloc(buffSize); const fHandle = await fs_1.promises.open(filePath, "r"); await fHandle.read(buff, 0, buffSize, begin); await fHandle.close(); return Buffer.from(buff); } return Buffer.alloc(0); } } const KB_TO_BYTES = 1024; const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024; const UPLOAD_TIMEOUT = 15 * 1000; const DISCONNECT_SLEEP = 1000; async function getFileBuffer(file, fileSize) { const isBiggerThan2Gb = fileSize > 2 ** 31 - 1; const options = {}; if (isBiggerThan2Gb && file instanceof CustomFile) { options.filePath = file.path; } else { options.buffer = Buffer.from(await fileToBuffer(file)); } return new CustomBuffer(options); } /** @hidden */ async function uploadFile(client, fileParams) { const { file, onProgress } = fileParams; let { workers } = fileParams; const { name, size } = file; const fileId = (0, Helpers_1.readBigIntFromBuffer)((0, Helpers_1.generateRandomBytes)(8), true, true); const isLarge = size > LARGE_FILE_THRESHOLD; const partSize = (0, Utils_1.getAppropriatedPartSize)((0, big_integer_1.default)(size)) * KB_TO_BYTES; const partCount = Math.floor((size + partSize - 1) / partSize); const buffer = await getFileBuffer(file, size); // Make sure a new sender can be created before starting upload await client.getSender(client.session.dcId); if (!workers || !size) { workers = 1; } if (workers >= partCount) { workers = partCount; } let progress = 0; if (onProgress) { onProgress(progress); } for (let i = 0; i < partCount; i += workers) { const sendingParts = []; let end = i + workers; if (end > partCount) { end = partCount; } for (let j = i; j < end; j++) { const bytes = await buffer.slice(j * partSize, (j + 1) * partSize); // eslint-disable-next-line no-loop-func sendingParts.push((async (jMemo, bytesMemo) => { while (true) { let sender; try { // We always upload from the DC we are in sender = await client.getSender(client.session.dcId); await sender.send(isLarge ? new tl_1.Api.upload.SaveBigFilePart({ fileId, filePart: jMemo, fileTotalParts: partCount, bytes: bytesMemo, }) : new tl_1.Api.upload.SaveFilePart({ fileId, filePart: jMemo, bytes: bytesMemo, })); } catch (err) { if (sender && !sender.isConnected()) { await (0, Helpers_1.sleep)(DISCONNECT_SLEEP); continue; } else if (err instanceof index_1.errors.FloodWaitError) { await (0, Helpers_1.sleep)(err.seconds * 1000); continue; } throw err; } if (onProgress) { if (onProgress.isCanceled) { throw new Error("USER_CANCELED"); } progress += 1 / partCount; onProgress(progress); } break; } })(j, bytes)); } await Promise.all(sendingParts); } return isLarge ? new tl_1.Api.InputFileBig({ id: fileId, parts: partCount, name, }) : new tl_1.Api.InputFile({ id: fileId, parts: partCount, name, md5Checksum: "", // This is not a "flag", so not sure if we can make it optional. }); } exports.uploadFile = uploadFile; /** @hidden */ async function _fileToMedia(client, { file, forceDocument, fileSize, progressCallback, attributes, thumb, voiceNote = false, videoNote = false, supportsStreaming = false, mimeType, asImage, workers = 1, }) { if (!file) { return { fileHandle: undefined, media: undefined, image: undefined }; } const isImage = index_1.utils.isImage(file); if (asImage == undefined) { asImage = isImage && !forceDocument; } if (typeof file == "object" && !Buffer.isBuffer(file) && !(file instanceof tl_1.Api.InputFile) && !(file instanceof tl_1.Api.InputFileBig) && !(file instanceof CustomFile) && !("read" in file)) { try { return { fileHandle: undefined, media: index_1.utils.getInputMedia(file, { isPhoto: asImage, attributes: attributes, forceDocument: forceDocument, voiceNote: voiceNote, videoNote: videoNote, supportsStreaming: supportsStreaming, }), image: asImage, }; } catch (e) { return { fileHandle: undefined, media: undefined, image: isImage, }; } } let media; let fileHandle; let createdFile; if (file instanceof tl_1.Api.InputFile || file instanceof tl_1.Api.InputFileBig) { fileHandle = file; } else if (typeof file == "string" && (file.startsWith("https://") || file.startsWith("http://"))) { if (asImage) { media = new tl_1.Api.InputMediaPhotoExternal({ url: file }); } else { media = new tl_1.Api.InputMediaDocumentExternal({ url: file }); } } else if (!(typeof file == "string") || (await fs_1.promises.lstat(file)).isFile()) { if (typeof file == "string") { createdFile = new CustomFile(path_1.default.basename(file), (await fs_1.promises.stat(file)).size, file); } else if ((typeof File !== "undefined" && file instanceof File) || file instanceof CustomFile) { createdFile = file; } else { let name; if ("name" in file) { // @ts-ignore name = file.name; } else { name = "unnamed"; } if (Buffer.isBuffer(file)) { createdFile = new CustomFile(name, file.length, "", file); } } if (!createdFile) { throw new Error(`Could not create file from ${JSON.stringify(file)}`); } fileHandle = await uploadFile(client, { file: createdFile, onProgress: progressCallback, workers: workers, }); } else { throw new Error(`"Not a valid path nor a url ${file}`); } if (media != undefined) { } else if (fileHandle == undefined) { throw new Error(`Failed to convert ${file} to media. Not an existing file or an HTTP URL`); } else if (asImage) { media = new tl_1.Api.InputMediaUploadedPhoto({ file: fileHandle, }); } else { // @ts-ignore let res = index_1.utils.getAttributes(file, { mimeType: mimeType, attributes: attributes, forceDocument: forceDocument && !isImage, voiceNote: voiceNote, videoNote: videoNote, supportsStreaming: supportsStreaming, thumb: thumb, }); attributes = res.attrs; mimeType = res.mimeType; let uploadedThumb; if (!thumb) { uploadedThumb = undefined; } else { // todo refactor if (typeof thumb == "string") { uploadedThumb = new CustomFile(path_1.default.basename(thumb), (await fs_1.promises.stat(thumb)).size, thumb); } else if (typeof File !== "undefined" && thumb instanceof File) { uploadedThumb = thumb; } else { let name; if ("name" in thumb) { name = thumb.name; } else { name = "unnamed"; } if (Buffer.isBuffer(thumb)) { uploadedThumb = new CustomFile(name, thumb.length, "", thumb); } } if (!uploadedThumb) { throw new Error(`Could not create file from ${file}`); } uploadedThumb = await uploadFile(client, { file: uploadedThumb, workers: 1, }); } media = new tl_1.Api.InputMediaUploadedDocument({ file: fileHandle, mimeType: mimeType, attributes: attributes, thumb: uploadedThumb, forceFile: forceDocument && !isImage, }); } return { fileHandle: fileHandle, media: media, image: asImage, }; } exports._fileToMedia = _fileToMedia; /** @hidden */ async function _sendAlbum(client, entity, { file, caption, forceDocument = false, fileSize, clearDraft = false, progressCallback, replyTo, attributes, thumb, parseMode, voiceNote = false, videoNote = false, silent, supportsStreaming = false, scheduleDate, workers = 1, noforwards, commentTo, }) { entity = await client.getInputEntity(entity); let files = []; if (!Array.isArray(file)) { files = [file]; } else { files = file; } if (!Array.isArray(caption)) { if (!caption) { caption = ""; } caption = [caption]; } const captions = []; for (const c of caption) { captions.push(await (0, messageParse_1._parseMessageText)(client, c, parseMode)); } if (commentTo != undefined) { const discussionData = await (0, messages_1.getCommentData)(client, entity, commentTo); entity = discussionData.entity; replyTo = discussionData.replyTo; } else { replyTo = index_1.utils.getMessageId(replyTo); } const albumFiles = []; for (const file of files) { let { fileHandle, media, image } = await _fileToMedia(client, { file: file, forceDocument: forceDocument, fileSize: fileSize, progressCallback: progressCallback, attributes: attributes, thumb: thumb, voiceNote: voiceNote, videoNote: videoNote, supportsStreaming: supportsStreaming, workers: workers, }); if (media instanceof tl_1.Api.InputMediaUploadedPhoto || media instanceof tl_1.Api.InputMediaPhotoExternal) { const r = await client.invoke(new tl_1.Api.messages.UploadMedia({ peer: entity, media, })); if (r instanceof tl_1.Api.MessageMediaPhoto) { media = (0, Utils_1.getInputMedia)(r.photo); } } else if (media instanceof tl_1.Api.InputMediaUploadedDocument) { const r = await client.invoke(new tl_1.Api.messages.UploadMedia({ peer: entity, media, })); if (r instanceof tl_1.Api.MessageMediaDocument) { media = (0, Utils_1.getInputMedia)(r.document); } } let text = ""; let msgEntities = []; if (captions.length) { [text, msgEntities] = captions.shift(); } albumFiles.push(new tl_1.Api.InputSingleMedia({ media: media, message: text, entities: msgEntities, })); } const result = await client.invoke(new tl_1.Api.messages.SendMultiMedia({ peer: entity, replyToMsgId: replyTo, multiMedia: albumFiles, silent: silent, scheduleDate: scheduleDate, clearDraft: clearDraft, noforwards: noforwards, })); const randomIds = albumFiles.map((m) => m.randomId); return client._getResponseMessage(randomIds, result, entity); } exports._sendAlbum = _sendAlbum; /** @hidden */ async function sendFile(client, entity, { file, caption, forceDocument = false, fileSize, clearDraft = false, progressCallback, replyTo, attributes, thumb, parseMode, formattingEntities, voiceNote = false, videoNote = false, buttons, silent, supportsStreaming = false, scheduleDate, workers = 1, noforwards, commentTo, }) { if (!file) { throw new Error("You need to specify a file"); } if (!caption) { caption = ""; } entity = await client.getInputEntity(entity); if (commentTo != undefined) { const discussionData = await (0, messages_1.getCommentData)(client, entity, commentTo); entity = discussionData.entity; replyTo = discussionData.replyTo; } else { replyTo = index_1.utils.getMessageId(replyTo); } if (Array.isArray(file)) { return await _sendAlbum(client, entity, { file: file, caption: caption, replyTo: replyTo, parseMode: parseMode, silent: silent, scheduleDate: scheduleDate, supportsStreaming: supportsStreaming, clearDraft: clearDraft, forceDocument: forceDocument, noforwards: noforwards, }); } if (Array.isArray(caption)) { caption = caption[0] || ""; } let msgEntities; if (formattingEntities != undefined) { msgEntities = formattingEntities; } else { [caption, msgEntities] = await (0, messageParse_1._parseMessageText)(client, caption, parseMode); } const { fileHandle, media, image } = await _fileToMedia(client, { file: file, forceDocument: forceDocument, fileSize: fileSize, progressCallback: progressCallback, attributes: attributes, thumb: thumb, voiceNote: voiceNote, videoNote: videoNote, supportsStreaming: supportsStreaming, workers: workers, }); if (media == undefined) { throw new Error(`Cannot use ${file} as file.`); } const markup = client.buildReplyMarkup(buttons); const request = new tl_1.Api.messages.SendMedia({ peer: entity, media: media, replyToMsgId: replyTo, message: caption, entities: msgEntities, replyMarkup: markup, silent: silent, scheduleDate: scheduleDate, clearDraft: clearDraft, noforwards: noforwards, }); const result = await client.invoke(request); return client._getResponseMessage(request, result, entity); } exports.sendFile = sendFile; function fileToBuffer(file) { if (typeof File !== "undefined" && file instanceof File) { return new Response(file).arrayBuffer(); } else if (file instanceof CustomFile) { if (file.buffer != undefined) { return file.buffer; } else { return fs_1.promises.readFile(file.path); } } else { throw new Error("Could not create buffer from file " + file); } }