Path: blob/master/node_modules/@adiwajshing/baileys/lib/Utils/messages.js
2593 views
"use strict";1Object.defineProperty(exports, "__esModule", { value: true });2exports.updateMessageWithReceipt = exports.getDevice = exports.extractMessageContent = exports.getContentType = exports.generateWAMessage = exports.generateWAMessageFromContent = exports.generateWAMessageContent = exports.generateForwardMessageContent = exports.prepareDisappearingMessageSettingContent = exports.prepareWAMessageMedia = void 0;3const boom_1 = require("@hapi/boom");4const fs_1 = require("fs");5const WAProto_1 = require("../../WAProto");6const Defaults_1 = require("../Defaults");7const Types_1 = require("../Types");8const generics_1 = require("./generics");9const messages_media_1 = require("./messages-media");10const MIMETYPE_MAP = {11image: 'image/jpeg',12video: 'video/mp4',13document: 'application/pdf',14audio: 'audio/ogg; codecs=opus',15sticker: 'image/webp',16history: 'application/x-protobuf',17'md-app-state': 'application/x-protobuf',18};19const MessageTypeProto = {20'image': Types_1.WAProto.ImageMessage,21'video': Types_1.WAProto.VideoMessage,22'audio': Types_1.WAProto.AudioMessage,23'sticker': Types_1.WAProto.StickerMessage,24'document': Types_1.WAProto.DocumentMessage,25};26const ButtonType = WAProto_1.proto.ButtonsMessage.ButtonsMessageHeaderType;27const prepareWAMessageMedia = async (message, options) => {28const logger = options.logger;29let mediaType;30for (const key of Defaults_1.MEDIA_KEYS) {31if (key in message) {32mediaType = key;33}34}35const uploadData = {36...message,37media: message[mediaType]38};39delete uploadData[mediaType];40// check if cacheable + generate cache key41const cacheableKey = typeof uploadData.media === 'object' &&42('url' in uploadData.media) &&43!!uploadData.media.url &&44!!options.mediaCache && (45// generate the key46mediaType + ':' + uploadData.media.url.toString());47if (mediaType === 'document' && !uploadData.fileName) {48uploadData.fileName = 'file';49}50if (!uploadData.mimetype) {51uploadData.mimetype = MIMETYPE_MAP[mediaType];52}53// check for cache hit54if (cacheableKey) {55const mediaBuff = options.mediaCache.get(cacheableKey);56if (mediaBuff) {57logger === null || logger === void 0 ? void 0 : logger.debug({ cacheableKey }, 'got media cache hit');58const obj = Types_1.WAProto.Message.decode(mediaBuff);59const key = `${mediaType}Message`;60delete uploadData.media;61Object.assign(obj[key], { ...uploadData });62return obj;63}64}65const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';66const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') &&67(typeof uploadData['jpegThumbnail'] === 'undefined');68const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;69const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } = await messages_media_1.encryptedStream(uploadData.media, mediaType, requiresOriginalForSomeProcessing);70// url safe Base64 encode the SHA256 hash of the body71const fileEncSha256B64 = encodeURIComponent(fileEncSha256.toString('base64')72.replace(/\+/g, '-')73.replace(/\//g, '_')74.replace(/\=+$/, ''));75const [{ mediaUrl, directPath }] = await Promise.all([76(async () => {77const result = await options.upload(encWriteStream, { fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs });78logger === null || logger === void 0 ? void 0 : logger.debug('uploaded media');79return result;80})(),81(async () => {82try {83if (requiresThumbnailComputation) {84uploadData.jpegThumbnail = await messages_media_1.generateThumbnail(bodyPath, mediaType, options);85logger === null || logger === void 0 ? void 0 : logger.debug('generated thumbnail');86}87if (requiresDurationComputation) {88uploadData.seconds = await messages_media_1.getAudioDuration(bodyPath);89logger === null || logger === void 0 ? void 0 : logger.debug('computed audio duration');90}91}92catch (error) {93logger === null || logger === void 0 ? void 0 : logger.warn({ trace: error.stack }, 'failed to obtain extra info');94}95})(),96])97.finally(async () => {98encWriteStream.destroy();99// remove tmp files100if (didSaveToTmpPath && bodyPath) {101await fs_1.promises.unlink(bodyPath);102logger === null || logger === void 0 ? void 0 : logger.debug('removed tmp files');103}104});105delete uploadData.media;106const obj = Types_1.WAProto.Message.fromObject({107[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({108url: mediaUrl,109directPath,110mediaKey,111fileEncSha256,112fileSha256,113fileLength,114mediaKeyTimestamp: generics_1.unixTimestampSeconds(),115...uploadData116})117});118if (cacheableKey) {119logger.debug({ cacheableKey }, 'set cache');120options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish());121}122return obj;123};124exports.prepareWAMessageMedia = prepareWAMessageMedia;125const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {126ephemeralExpiration = ephemeralExpiration || 0;127const content = {128ephemeralMessage: {129message: {130protocolMessage: {131type: Types_1.WAProto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING,132ephemeralExpiration133}134}135}136};137return Types_1.WAProto.Message.fromObject(content);138};139exports.prepareDisappearingMessageSettingContent = prepareDisappearingMessageSettingContent;140/**141* Generate forwarded message content like WA does142* @param message the message to forward143* @param options.forceForward will show the message as forwarded even if it is from you144*/145const generateForwardMessageContent = (message, forceForward) => {146var _a;147let content = message.message;148if (!content) {149throw new boom_1.Boom('no content in message', { statusCode: 400 });150}151// hacky copy152content = WAProto_1.proto.Message.decode(WAProto_1.proto.Message.encode(message.message).finish());153let key = Object.keys(content)[0];154let score = ((_a = content[key].contextInfo) === null || _a === void 0 ? void 0 : _a.forwardingScore) || 0;155score += message.key.fromMe && !forceForward ? 0 : 1;156if (key === 'conversation') {157content.extendedTextMessage = { text: content[key] };158delete content.conversation;159key = 'extendedTextMessage';160}161if (score > 0) {162content[key].contextInfo = { forwardingScore: score, isForwarded: true };163}164else {165content[key].contextInfo = {};166}167return content;168};169exports.generateForwardMessageContent = generateForwardMessageContent;170const generateWAMessageContent = async (message, options) => {171var _a, _b;172let m = {};173if ('text' in message) {174const extContent = { ...message };175if (!!options.getUrlInfo && message.text.match(Defaults_1.URL_REGEX)) {176try {177const data = await options.getUrlInfo(message.text);178extContent.canonicalUrl = data['canonical-url'];179extContent.matchedText = data['matched-text'];180extContent.jpegThumbnail = data.jpegThumbnail;181extContent.description = data.description;182extContent.title = data.title;183extContent.previewType = 0;184}185catch (error) { // ignore if fails186(_a = options.logger) === null || _a === void 0 ? void 0 : _a.warn({ trace: error.stack }, 'url generation failed');187}188}189m.extendedTextMessage = extContent;190}191else if ('contacts' in message) {192const contactLen = message.contacts.contacts.length;193if (!contactLen) {194throw new boom_1.Boom('require atleast 1 contact', { statusCode: 400 });195}196if (contactLen === 1) {197m.contactMessage = Types_1.WAProto.ContactMessage.fromObject(message.contacts.contacts[0]);198}199else {200m.contactsArrayMessage = Types_1.WAProto.ContactsArrayMessage.fromObject(message.contacts);201}202}203else if ('location' in message) {204m.locationMessage = Types_1.WAProto.LocationMessage.fromObject(message.location);205}206else if ('delete' in message) {207m.protocolMessage = {208key: message.delete,209type: Types_1.WAProto.ProtocolMessage.ProtocolMessageType.REVOKE210};211}212else if ('forward' in message) {213m = exports.generateForwardMessageContent(message.forward, message.force);214}215else if ('disappearingMessagesInChat' in message) {216const exp = typeof message.disappearingMessagesInChat === 'boolean' ?217(message.disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :218message.disappearingMessagesInChat;219m = exports.prepareDisappearingMessageSettingContent(exp);220}221else {222m = await exports.prepareWAMessageMedia(message, options);223}224if ('buttons' in message && !!message.buttons) {225const buttonsMessage = {226buttons: message.buttons.map(b => ({ ...b, type: WAProto_1.proto.Button.ButtonType.RESPONSE }))227};228if ('text' in message) {229buttonsMessage.contentText = message.text;230buttonsMessage.headerType = ButtonType.EMPTY;231}232else {233if ('caption' in message) {234buttonsMessage.contentText = message.caption;235}236const type = Object.keys(m)[0].replace('Message', '').toUpperCase();237buttonsMessage.headerType = ButtonType[type];238Object.assign(buttonsMessage, m);239}240if ('footer' in message && !!message.footer) {241buttonsMessage.footerText = message.footer;242}243m = { buttonsMessage };244}245else if ('templateButtons' in message && !!message.templateButtons) {246const templateMessage = {247hydratedTemplate: {248hydratedButtons: message.templateButtons249}250};251if ('text' in message) {252templateMessage.hydratedTemplate.hydratedContentText = message.text;253}254else {255if ('caption' in message) {256templateMessage.hydratedTemplate.hydratedContentText = message.caption;257}258Object.assign(templateMessage.hydratedTemplate, m);259}260if ('footer' in message && !!message.footer) {261templateMessage.hydratedTemplate.hydratedFooterText = message.footer;262}263m = { templateMessage };264}265if ('sections' in message && !!message.sections) {266const listMessage = {267sections: message.sections,268buttonText: message.buttonText,269title: message.title,270footerText: message.footer,271description: message.text,272listType: WAProto_1.proto.ListMessage.ListMessageListType['SINGLE_SELECT']273};274m = { listMessage };275}276if ('viewOnce' in message && !!message.viewOnce) {277m = { viewOnceMessage: { message: m } };278}279if ('mentions' in message && ((_b = message.mentions) === null || _b === void 0 ? void 0 : _b.length)) {280const [messageType] = Object.keys(m);281m[messageType].contextInfo = m[messageType] || {};282m[messageType].contextInfo.mentionedJid = message.mentions;283}284return Types_1.WAProto.Message.fromObject(m);285};286exports.generateWAMessageContent = generateWAMessageContent;287const generateWAMessageFromContent = (jid, message, options) => {288if (!options.timestamp) {289options.timestamp = new Date();290} // set timestamp to now291const key = Object.keys(message)[0];292const timestamp = generics_1.unixTimestampSeconds(options.timestamp);293const { quoted, userJid } = options;294if (quoted) {295const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid);296message[key].contextInfo = message[key].contextInfo || {};297message[key].contextInfo.participant = participant;298message[key].contextInfo.stanzaId = quoted.key.id;299message[key].contextInfo.quotedMessage = quoted.message;300// if a participant is quoted, then it must be a group301// hence, remoteJid of group must also be entered302if (quoted.key.participant || quoted.participant) {303message[key].contextInfo.remoteJid = quoted.key.remoteJid;304}305}306if (307// if we want to send a disappearing message308!!(options === null || options === void 0 ? void 0 : options.ephemeralExpiration) &&309// and it's not a protocol message -- delete, toggle disappear message310key !== 'protocolMessage' &&311// already not converted to disappearing message312key !== 'ephemeralMessage') {313message[key].contextInfo = {314...(message[key].contextInfo || {}),315expiration: options.ephemeralExpiration || Defaults_1.WA_DEFAULT_EPHEMERAL,316//ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()317};318message = {319ephemeralMessage: {320message321}322};323}324message = Types_1.WAProto.Message.fromObject(message);325const messageJSON = {326key: {327remoteJid: jid,328fromMe: true,329id: (options === null || options === void 0 ? void 0 : options.messageId) || generics_1.generateMessageID(),330},331message: message,332messageTimestamp: timestamp,333messageStubParameters: [],334participant: jid.includes('@g.us') ? userJid : undefined,335status: Types_1.WAMessageStatus.PENDING336};337return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON);338};339exports.generateWAMessageFromContent = generateWAMessageFromContent;340const generateWAMessage = async (jid, content, options) => {341var _a;342// ensure msg ID is with every log343options.logger = (_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.child({ msgId: options.messageId });344return exports.generateWAMessageFromContent(jid, await exports.generateWAMessageContent(content, options), options);345};346exports.generateWAMessage = generateWAMessage;347/** Get the key to access the true type of content */348const getContentType = (content) => {349if (content) {350const keys = Object.keys(content);351const key = keys.find(k => (k === 'conversation' || k.endsWith('Message')) && k !== 'senderKeyDistributionMessage');352return key;353}354};355exports.getContentType = getContentType;356/**357* Extract the true message content from a message358* Eg. extracts the inner message from a disappearing message/view once message359*/360const extractMessageContent = (content) => {361var _a, _b, _c, _d, _e, _f, _g, _h;362const extractFromTemplateMessage = (msg) => {363if (msg.imageMessage) {364return { imageMessage: msg.imageMessage };365}366else if (msg.documentMessage) {367return { documentMessage: msg.documentMessage };368}369else if (msg.videoMessage) {370return { videoMessage: msg.videoMessage };371}372else if (msg.locationMessage) {373return { locationMessage: msg.locationMessage };374}375else {376return { conversation: 'contentText' in msg ? msg.contentText : ('hydratedContentText' in msg ? msg.hydratedContentText : '') };377}378};379content = ((_a = content === null || content === void 0 ? void 0 : content.ephemeralMessage) === null || _a === void 0 ? void 0 : _a.message) ||380((_b = content === null || content === void 0 ? void 0 : content.viewOnceMessage) === null || _b === void 0 ? void 0 : _b.message) ||381content ||382undefined;383if (content === null || content === void 0 ? void 0 : content.buttonsMessage) {384return extractFromTemplateMessage(content.buttonsMessage);385}386if ((_c = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _c === void 0 ? void 0 : _c.hydratedFourRowTemplate) {387return extractFromTemplateMessage((_d = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _d === void 0 ? void 0 : _d.hydratedFourRowTemplate);388}389if ((_e = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _e === void 0 ? void 0 : _e.hydratedTemplate) {390return extractFromTemplateMessage((_f = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _f === void 0 ? void 0 : _f.hydratedTemplate);391}392if ((_g = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _g === void 0 ? void 0 : _g.fourRowTemplate) {393return extractFromTemplateMessage((_h = content === null || content === void 0 ? void 0 : content.templateMessage) === null || _h === void 0 ? void 0 : _h.fourRowTemplate);394}395return content;396};397exports.extractMessageContent = extractMessageContent;398/**399* Returns the device predicted by message ID400*/401const getDevice = (id) => {402const deviceType = id.length > 21 ? 'android' : id.substring(0, 2) === '3A' ? 'ios' : 'web';403return deviceType;404};405exports.getDevice = getDevice;406const updateMessageWithReceipt = (msg, receipt) => {407msg.userReceipt = msg.userReceipt || [];408const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);409if (recp) {410Object.assign(recp, receipt);411}412else {413msg.userReceipt.push(receipt);414}415};416exports.updateMessageWithReceipt = updateMessageWithReceipt;417418419