import path from 'path' import { toAudio } from './converter.js' import chalk from 'chalk' import fetch from 'node-fetch' import PhoneNumber from 'awesome-phonenumber' import fs from 'fs' import util from 'util' import { fileTypeFromBuffer } from 'file-type' import { format } from 'util' import { fileURLToPath } from 'url' import logg from 'pino' import pino from'pino' import store from './store.js' let conv = await import('./sticker.js') const __dirname = path.dirname(fileURLToPath(import.meta.url)) //GB /** * @type {import('@adiwajshing/baileys')} */ const { default: _makeWaSocket, makeWALegacySocket, makeInMemoryStore, proto, downloadContentFromMessage, jidDecode, areJidsSameUser, generateForwardMessageContent, generateWAMessageFromContent, WAMessageStubType, generateWAMessage, getContentType, extractMessageContent, jidNormalizedUser } = (await import('baileys')).default import * as baileys from 'baileys' //const store = makeInMemoryStore({ logger: pino().child({ level: 'silent', stream: 'store' }) }) export function HelperConnection(conn, connectionOptions, options = {}) { /** * @type {import('@adiwajshing/baileys').WASocket | import('@adiwajshing/baileys').WALegacySocket} */ // let conn = (global.opts['legacy'] ? makeWALegacySocket : _makeWaSocket)(connectionOptions) let sock = Object.defineProperties(conn, { chats: { value: { ...(options.chats || {}) }, writable: true }, decodeJid: { value(jid) { if (!jid || typeof jid !== 'string') return (!nullish(jid) && jid) || null return jid.decodeJid() } }, logger: { get() { return { info(...args) { console.log( chalk.bold.bgRgb(51, 204, 51)('INFO '), `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, chalk.cyan(format(...args)) ) }, error(...args) { console.log( chalk.bold.bgRgb(247, 38, 33)('ERROR '), `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, chalk.rgb(255, 38, 0)(format(...args)) ) }, warn(...args) { console.log( chalk.bold.bgRgb(255, 153, 0)('WARNING '), `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, chalk.redBright(format(...args)) ) }, trace(...args) { console.log( chalk.grey('TRACE '), `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, chalk.white(format(...args)) ) }, debug(...args) { console.log( chalk.bold.bgRgb(66, 167, 245)('DEBUG '), `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, chalk.white(format(...args)) ) } } }, enumerable: true }, getFile: { /** * getBuffer hehe * @param {fs.PathLike} PATH * @param {Boolean} saveToFile */ async value(PATH, saveToFile = false) { let res, filename const data = Buffer.isBuffer(PATH) ? PATH : PATH instanceof ArrayBuffer ? PATH.toBuffer() : /^data:.*?\/.*?;base64,/i.test(PATH) ? Buffer.from(PATH.split`,`[1], 'base64') : /^https?:\/\//.test(PATH) ? await (res = await fetch(PATH)).buffer() : fs.existsSync(PATH) ? (filename = PATH, fs.readFileSync(PATH)) : typeof PATH === 'string' ? PATH : Buffer.alloc(0) if (!Buffer.isBuffer(data)) throw new TypeError('Result is not a buffer') const type = await fileTypeFromBuffer(data) || { mime: 'application/octet-stream', ext: '.bin' } if (data && saveToFile && !filename) (filename = path.join(__dirname, '../tmp/' + new Date * 1 + '.' + type.ext), await fs.promises.writeFile(filename, data)) return { res, filename, ...type, data, deleteFile() { return filename && fs.promises.unlink(filename) } } }, enumerable: true }, /* Yo ndak tau */ sendSticker: { async value(jid, media, m, opts = {}) { const medias = await conn.getFile(media) return this.sendMessage(jid, await conv.sticker(medias.data, null, opts.packname || 'Whatsapp Bot', opts.author || conn.user.name), 'stickerMessage', { quoted: m, ...opts }) } }, waitEvent: { /** * waitEvent * @param {String} eventName * @param {Boolean} is * @param {Number} maxTries */ value(eventName, is = () => true, maxTries = 25) { //Idk why this exist? return new Promise((resolve, reject) => { let tries = 0 let on = (...args) => { if (++tries > maxTries) reject('Max tries reached') else if (is()) { conn.ev.off(eventName, on) resolve(...args) } } conn.ev.on(eventName, on) }) } }, sendFile: { /** * Send Media/File with Automatic Type Specifier * @param {String} jid * @param {String|Buffer} path * @param {String} filename * @param {String} caption * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Boolean} ptt * @param {Object} options */ async value(jid, path, filename = '', caption = '', quoted, ptt = false, options = {}) { let type = await conn.getFile(path, true) let { res, data: file, filename: pathFile } = type if (res && res.status !== 200 || file.length <= 65536) { try { throw { json: JSON.parse(file.toString()) } } catch (e) { if (e.json) throw e.json } } const fileSize = fs.statSync(pathFile).size / 1024 / 1024 if (fileSize >= 100) throw new Error('File size is too big!') let opt = {} if (quoted) opt.quoted = quoted if (!type) options.asDocument = true let mtype = '', mimetype = options.mimetype || type.mime, convert if (/webp/.test(type.mime) || (/image/.test(type.mime) && options.asSticker)) mtype = 'sticker' else if (/image/.test(type.mime) || (/webp/.test(type.mime) && options.asImage)) mtype = 'image' else if (/video/.test(type.mime)) mtype = 'video' else if (/audio/.test(type.mime)) ( convert = await toAudio(file, type.ext), file = convert.data, pathFile = convert.filename, mtype = 'audio', mimetype = options.mimetype || 'audio/ogg; codecs=opus' ) else mtype = 'document' if (options.asDocument) mtype = 'document' delete options.asSticker delete options.asLocation delete options.asVideo delete options.asDocument delete options.asImage let message = { ...options, caption, ptt, [mtype]: { url: pathFile }, mimetype, fileName: filename || pathFile.split('/').pop() } /** * @type {import('@adiwajshing/baileys').proto.WebMessageInfo} */ let m try { m = await conn.sendMessage(jid, message, { ...opt, ...options }) } catch (e) { console.error(e) m = null } finally { if (!m) m = await conn.sendMessage(jid, { ...message, [mtype]: file }, { ...opt, ...options }) file = null // releasing the memory return m } }, enumerable: true }, sendContact: { /** * Send Contact * @param {String} jid * @param {String[][]|String[]} data * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ async value(jid, data, quoted, options) { if (!Array.isArray(data[0]) && typeof data[0] === 'string') data = [data] let contacts = [] for (let [number, name] of data) { number = number.replace(/[^0-9]/g, '') let njid = number + '@s.whatsapp.net' let biz = await conn.getBusinessProfile(njid).catch(_ => null) || {} let vcard = ` BEGIN:VCARD VERSION:3.0 N:;${name.replace(/\n/g, '\\n')};;; FN:${name.replace(/\n/g, '\\n')} TEL;type=CELL;type=VOICE;waid=${number}:${PhoneNumber('+' + number).getNumber('international')}${biz.description ? ` X-WA-BIZ-NAME:${(conn.chats[njid]?.vname || conn.getName(njid) || name).replace(/\n/, '\\n')} X-WA-BIZ-DESCRIPTION:${biz.description.replace(/\n/g, '\\n')} `.trim() : ''} END:VCARD `.trim() contacts.push({ vcard, displayName: name }) } return await conn.sendMessage(jid, { ...options, contacts: { ...options, displayName: (contacts.length >= 2 ? `${contacts.length} kontak` : contacts[0].displayName) || null, contacts, } }, { quoted, ...options }) }, enumerable: true }, /*sendMessageV2: { /** * Send Contact * @param {String} jid * @param {String[][]|String[]} data * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ /*async value(chatId, message, options){ let generate = await generateWAMessage(chatId, message, options) let type2 = getContentType(generate.message) if ('contextInfo' in options) generate.message[type2].contextInfo = options?.contextInfo if ('contextInfo' in message) generate.message[type2].contextInfo = message?.contextInfo return await conn.relayMessage(chatId, generate.message, { messageId: generate.key.id }) } },*/ reply: { /** * Reply to a message * @param {String} jid * @param {String|Buffer} text * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ value(jid, text = '', quoted, options) { let pp = conn.profilePictureUrl(conn.user.jid, 'image') const _uptime = process.uptime() * 1000 return Buffer.isBuffer(text) ? conn.sendFile(jid, text, 'file', '', quoted, false, options) : conn.sendMessage(jid, { ...options, text, mentions: conn.parseMention(text), contextInfo: global.adReply.contextInfo, mentions: conn.parseMention(text), ...options }, { quoted, ephemeralExpiration: 0, ...options }) } }, //V2 replyV2: { /** * Reply to a message * @param {String} jid * @param {String|Buffer} text * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ value(jid, text = '', quoted, options) { let pp = conn.profilePictureUrl(conn.user.jid, 'image') const _uptime = process.uptime() * 1000 return Buffer.isBuffer(text) ? conn.sendFile(jid, text, 'file', '', quoted, false, options) : conn.sendMessage(jid, { ...options, text, mentions: conn.parseMention(text), contextInfo: global.adReplyV2.contextInfo, mentions: conn.parseMention(text), ...options }, { quoted, ephemeralExpiration: 86400, ...options }) } }, sendButtonImg: { async value (jid, buffer, contentText, footerText, button1, id1, quoted, options) { let type = await conn.getFile(buffer) let { res, data: file } = type if (res && res.status !== 200 || file.length <= 65536) { try { throw { json: JSON.parse(file.toString()) } } catch (e) { if (e.json) throw e.json } } const buttons = [ { buttonId: id1, buttonText: { displayText: button1 }, type: 1 } ] const buttonMessage = { image: file, fileLength: 999999999999999, caption: contentText, footer: footerText, mentions: await conn.parseMention(contentText + footerText), ...options, buttons: buttons, headerType: 4 } return await conn.sendMessage(jid, buttonMessage, { quoted, ephemeralExpiration: global.ephemeral, contextInfo: { mentionedJid: conn.parseMention(contentText + footerText) }, ...options }) } }, sendButton: { /** * send Button * @param {String} jid * @param {String} text * @param {String} footer * @param {Buffer} buffer * @param {String[] | String[][]} buttons * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ async value(jid, text = '', footer = '', buffer, buttons, quoted, options) { let type if (Array.isArray(buffer)) (options = quoted, quoted = buttons, buttons = buffer, buffer = null) else if (buffer) try { (type = await conn.getFile(buffer), buffer = type.data) } catch { buffer = null } if (!Array.isArray(buttons[0]) && typeof buttons[0] === 'string') buttons = [buttons] if (!options) options = {} let message = { ...options, [buffer ? 'caption' : 'text']: text || '', footer, buttons: buttons.map(btn => ({ buttonId: !nullish(btn[1]) && btn[1] || !nullish(btn[0]) && btn[0] || '', buttonText: { displayText: !nullish(btn[0]) && btn[0] || !nullish(btn[1]) && btn[1] || '' } })), ...(buffer ? options.asLocation && /image/.test(type.mime) ? { location: { ...options, jpegThumbnail: buffer } } : { [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer } : {}) } return await conn.sendMessage(jid, message, { quoted, upload: conn.waUploadToServer, ...options }) }, enumerable: true }, sendHydrated: { /** * * @param {String} jid * @param {String} text * @param {String} footer * @param {fs.PathLike} buffer * @param {String|string[]} url * @param {String|string[]} urlText * @param {String|string[]} call * @param {String|string[]} callText * @param {String[][]} buttons * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ async value(jid, text = '', footer = '', buffer, url, urlText, call, callText, buttons, quoted, options) { let type if (buffer) try { (type = await conn.getFile(buffer), buffer = type.data) } catch { buffer = buffer } if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null) if (!options) options = {} let templateButtons = [] if (url || urlText) { if (!Array.isArray(url)) url = [url] if (!Array.isArray(urlText)) urlText = [urlText] templateButtons.push(...( url.map((v, i) => [v, urlText[i]]) .map(([url, urlText], i) => ({ index: templateButtons.length + i + 1, urlButton: { displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', url: !nullish(url) && url || !nullish(urlText) && urlText || '' } })) || [] )) } if (call || callText) { if (!Array.isArray(call)) call = [call] if (!Array.isArray(callText)) callText = [callText] templateButtons.push(...( call.map((v, i) => [v, callText[i]]) .map(([call, callText], i) => ({ index: templateButtons.length + i + 1, callButton: { displayText: !nullish(callText) && callText || !nullish(call) && call || '', phoneNumber: !nullish(call) && call || !nullish(callText) && callText || '' } })) || [] )) } if (buttons.length) { if (!Array.isArray(buttons[0])) buttons = [buttons] templateButtons.push(...( buttons.map(([text, id], index) => ({ index: templateButtons.length + index + 1, quickReplyButton: { displayText: !nullish(text) && text || !nullish(id) && id || '', id: !nullish(id) && id || !nullish(text) && text || '' } })) || [] )) } let message = { ...options, [buffer ? 'caption' : 'text']: text || '', footer, templateButtons, ...(buffer ? options.asLocation && /image/.test(type.mime) ? { location: { ...options, jpegThumbnail: buffer } } : { [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer } : {}) } return await conn.sendMessage(jid, message, { quoted, upload: conn.waUploadToServer, ...options }) }, enumerable: true }, sendHydrated2: { /** * * @param {String} jid * @param {String} text * @param {String} footer * @param {fs.PathLike} buffer * @param {String|string[]} url * @param {String|string[]} urlText * @param {String|string[]} call * @param {String|string[]} callText * @param {String[][]} buttons * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ async value(jid, text = '', footer = '', buffer, url, urlText, url2, urlText2, buttons, quoted, options) { let type if (buffer) try { (type = await conn.getFile(buffer), buffer = type.data) } catch { buffer = buffer } if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null) if (!options) options = {} let templateButtons = [] if (url || urlText) { if (!Array.isArray(url)) url = [url] if (!Array.isArray(urlText)) urlText = [urlText] templateButtons.push(...( url.map((v, i) => [v, urlText[i]]) .map(([url, urlText], i) => ({ index: templateButtons.length + i + 1, urlButton: { displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', url: !nullish(url) && url || !nullish(urlText) && urlText || '' } })) || [] )) } if (url2 || urlText2) { if (!Array.isArray(url2)) url2 = [url2] if (!Array.isArray(urlText2)) urlText2 = [urlText2] templateButtons.push(...( url2.map((v, i) => [v, urlText2[i]]) .map(([url2, urlText2], i) => ({ index: templateButtons.length + i + 1, urlButton: { displayText: !nullish(urlText2) && urlText2 || !nullish(url2) && url2 || '', url: !nullish(url2) && url2 || !nullish(urlText2) && urlText2 || '' } })) || [] )) } if (buttons.length) { if (!Array.isArray(buttons[0])) buttons = [buttons] templateButtons.push(...( buttons.map(([text, id], index) => ({ index: templateButtons.length + index + 1, quickReplyButton: { displayText: !nullish(text) && text || !nullish(id) && id || '', id: !nullish(id) && id || !nullish(text) && text || '' } })) || [] )) } let message = { ...options, [buffer ? 'caption' : 'text']: text || '', footer, templateButtons, ...(buffer ? options.asLocation && /image/.test(type.mime) ? { location: { ...options, jpegThumbnail: buffer } } : { [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer } : {}) } return await conn.sendMessage(jid, message, { quoted, upload: conn.waUploadToServer, ...options }) }, enumerable: true }, /** * Send Poll */ sendPoll: { async value(jid, name = '', optiPoll, options) { if (!Array.isArray(optiPoll[0]) && typeof optiPoll[0] === 'string') optiPoll = [optiPoll] if (!options) options = {} const pollMessage = { name: name, options: optiPoll.map(btn => ({ optionName: !nullish(btn[0]) && btn[0] || '' })), selectableOptionsCount: 1 } return conn.relayMessage(jid, { pollCreationMessage: pollMessage }, { ...options }); } }, cMod: { /** * cMod * @param {String} jid * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} message * @param {String} text * @param {String} sender * @param {*} options * @returns */ value(jid, message, text = '', sender = conn.user.jid, options = {}) { if (options.mentions && !Array.isArray(options.mentions)) options.mentions = [options.mentions] let copy = message.toJSON() delete copy.message.messageContextInfo delete copy.message.senderKeyDistributionMessage let mtype = Object.keys(copy.message)[0] let msg = copy.message let content = msg[mtype] if (typeof content === 'string') msg[mtype] = text || content else if (content.caption) content.caption = text || content.caption else if (content.text) content.text = text || content.text if (typeof content !== 'string') { msg[mtype] = { ...content, ...options } msg[mtype].contextInfo = { ...(content.contextInfo || {}), mentionedJid: options.mentions || content.contextInfo?.mentionedJid || [] } } if (copy.participant) sender = copy.participant = sender || copy.participant else if (copy.key.participant) sender = copy.key.participant = sender || copy.key.participant if (copy.key.remoteJid.includes('@s.whatsapp.net')) sender = sender || copy.key.remoteJid else if (copy.key.remoteJid.includes('@broadcast')) sender = sender || copy.key.remoteJid copy.key.remoteJid = jid copy.key.fromMe = areJidsSameUser(sender, conn.user.id) || false return proto.WebMessageInfo.fromObject(copy) }, enumerable: true }, copyNForward: { /** * Exact Copy Forward * @param {String} jid * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} message * @param {Boolean|Number} forwardingScore * @param {Object} options */ async value(jid, message, forwardingScore = true, options = {}) { let vtype if (options.readViewOnce && message.message.viewOnceMessage?.message) { vtype = Object.keys(message.message.viewOnceMessage.message)[0] delete message.message.viewOnceMessage.message[vtype].viewOnce message.message = proto.Message.fromObject( JSON.parse(JSON.stringify(message.message.viewOnceMessage.message)) ) message.message[vtype].contextInfo = message.message.viewOnceMessage.contextInfo } let mtype = Object.keys(message.message)[0] let m = generateForwardMessageContent(message, !!forwardingScore) let ctype = Object.keys(m)[0] if (forwardingScore && typeof forwardingScore === 'number' && forwardingScore > 200000) m[ctype].contextInfo.forwardingScore += forwardingScore m[ctype].contextInfo = { ...(message.message[mtype].contextInfo || {}), ...(m[ctype].contextInfo || {}) } m = generateWAMessageFromContent(jid, m, { ...options, userJid: conn.user.jid }) await conn.relayMessage(jid, m.message, { messageId: m.key.id, additionalAttributes: { ...options } }) return m }, enumerable: true }, fakeReply: { /** * Fake Replies * @param {String} jid * @param {String|Object} text * @param {String} fakeJid * @param {String} fakeText * @param {String} fakeGroupJid * @param {String} options */ value(jid, text = '', fakeJid = this.user.jid, fakeText = '', fakeGroupJid, options) { return conn.reply(jid, text, { key: { fromMe: areJidsSameUser(fakeJid, conn.user.id), participant: fakeJid, ...(fakeGroupJid ? { remoteJid: fakeGroupJid } : {}) }, message: { conversation: fakeText }, ...options }) } }, downloadM: { /** * Download media message * @param {Object} m * @param {String} type * @param {fs.PathLike | fs.promises.FileHandle} saveToFile * @returns {Promise} */ async value(m, type, saveToFile) { let filename if (!m || !(m.url || m.directPath)) return Buffer.alloc(0) const stream = await downloadContentFromMessage(m, type) let buffer = Buffer.from([]) for await (const chunk of stream) { buffer = Buffer.concat([buffer, chunk]) } if (saveToFile) ({ filename } = await conn.getFile(buffer, true)) return saveToFile && fs.existsSync(filename) ? filename : buffer }, enumerable: true }, parseMention: { /** * Parses string into mentionedJid(s) * @param {String} text * @returns {Array} */ value(text = '') { return [...text.matchAll(/@([0-9]{5,16}|0)/g)].map(v => v[1] + '@s.whatsapp.net') }, enumerable: true }, getName: { /** * Get name from jid * @param {String} jid * @param {Boolean} withoutContact */ value(jid = '', withoutContact = false) { jid = conn.decodeJid(jid) withoutContact = conn.withoutContact || withoutContact let v if (jid.endsWith('@g.us')) return new Promise(async (resolve) => { v = conn.chats[jid] || {} if (!(v.name || v.subject)) v = await conn.groupMetadata(jid) || {} resolve(v.name || v.subject || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international')) }) else v = jid === '0@s.whatsapp.net' ? { jid, vname: 'WhatsApp' } : areJidsSameUser(jid, conn.user.id) ? conn.user : (conn.chats[jid] || {}) return (withoutContact ? '' : v.name) || v.subject || v.vname || v.notify || v.verifiedName || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international') }, enumerable: true }, send3TemplateButtonImg: { async value (jid, buffer, content, footerText, button1, id1, button2, id2, button3, id3, quoted, options) { let type = await conn.getFile(buffer) let { res, data: file } = type if (res && res.status !== 200 || file.length <= 65536) { try { throw { json: JSON.parse(file.toString()) } } catch (e) { if (e.json) throw e.json } } const buttons = [ { index: 1, urlButton: { displayText: dtu, url: urlnya } }, { index: 2, quickReplyButton: { displayText: button1, id: id1 } }, { index: 3, quickReplyButton: { displayText: button2, id: id2 } }, { index: 4, quickReplyButton: { displayText: button3, id: id3 } } ] const buttonMessage = { image: file, caption: content, footer: footerText, mentions: await conn.parseMention(content + footerText), ephemeralExpiration: 86400, ...options, templateButtons: buttons, headerType: 1 } conn.sendMessage(jid, buttonMessage, { quoted, ephemeralExpiration: 86400, contextInfo: { mentionedJid: conn.parseMention(content + footerText), forwardingScore: 99999, isForwarded: true }, ...options, ephemeralExpiration: 86400 } ) } }, loadMessage: { /** * * @param {String} messageID * @returns {import('@adiwajshing/baileys').proto.WebMessageInfo} */ value(messageID) { return Object.entries(conn.chats) .filter(([_, { messages }]) => typeof messages === 'object') .find(([_, { messages }]) => Object.entries(messages) .find(([k, v]) => (k === messageID || v.key?.id === messageID))) ?.[1].messages?.[messageID] }, enumerable: true }, sendGroupV4Invite: { /** * sendGroupV4Invite * @param {String} jid * @param {*} participant * @param {String} inviteCode * @param {Number} inviteExpiration * @param {String} groupName * @param {String} caption * @param {Buffer} jpegThumbnail * @param {*} options */ async value(jid, participant, inviteCode, inviteExpiration, groupName = 'unknown subject', caption = 'Invitation to join my WhatsApp group', jpegThumbnail, options = {}) { const msg = proto.Message.fromObject({ groupInviteMessage: proto.GroupInviteMessage.fromObject({ inviteCode, inviteExpiration: parseInt(inviteExpiration) || + new Date(new Date + (3 * 86400000)), groupJid: jid, groupName: (groupName ? groupName : await conn.getName(jid)) || null, jpegThumbnail: Buffer.isBuffer(jpegThumbnail) ? jpegThumbnail : null, caption }) }) const message = generateWAMessageFromContent(participant, msg, options) await conn.relayMessage(participant, message.message, { messageId: message.key.id, additionalAttributes: { ...options } }) return message }, enumerable: true }, prepareMessageMedia: { /* * Tester! */ async value (message, options) { const logger = options.logger; let mediaType; for (const key of Defaults_1.MEDIA_KEYS) { if (key in message) { mediaType = key; } } const uploadData = { ...message, media: message[mediaType] }; delete uploadData[mediaType]; // check if cacheable + generate cache key const cacheableKey = typeof uploadData.media === 'object' && ('url' in uploadData.media) && !!uploadData.media.url && !!options.mediaCache && ( // generate the key mediaType + ':' + uploadData.media.url.toString()); if (mediaType === 'document' && !uploadData.fileName) { uploadData.fileName = 'file'; } if (!uploadData.mimetype) { uploadData.mimetype = MIMETYPE_MAP[mediaType]; } // check for cache hit if (cacheableKey) { const mediaBuff = options.mediaCache.get(cacheableKey); if (mediaBuff) { logger === null || logger === void 0 ? void 0 : logger.debug({ cacheableKey }, 'got media cache hit'); const obj = Types_1.WAProto.Message.decode(mediaBuff); const key = `${mediaType}Message`; delete uploadData.media; Object.assign(obj[key], { ...uploadData }); return obj; } } const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'; const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && (typeof uploadData['jpegThumbnail'] === 'undefined'); const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation; const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } = await messages_media_1.encryptedStream(uploadData.media, mediaType, requiresOriginalForSomeProcessing); // url safe Base64 encode the SHA256 hash of the body const fileEncSha256B64 = encodeURIComponent(fileEncSha256.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/\=+$/, '')); const [{ mediaUrl, directPath }] = await Promise.all([ (async () => { const result = await options.upload(encWriteStream, { fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs }); logger === null || logger === void 0 ? void 0 : logger.debug('uploaded media'); return result; })(), (async () => { try { if (requiresThumbnailComputation) { uploadData.jpegThumbnail = await messages_media_1.generateThumbnail(bodyPath, mediaType, options); logger === null || logger === void 0 ? void 0 : logger.debug('generated thumbnail'); } if (requiresDurationComputation) { uploadData.seconds = await messages_media_1.getAudioDuration(bodyPath); logger === null || logger === void 0 ? void 0 : logger.debug('computed audio duration'); } } catch (error) { logger === null || logger === void 0 ? void 0 : logger.warn({ trace: error.stack }, 'failed to obtain extra info'); } })(), ]) .finally(async () => { encWriteStream.destroy(); // remove tmp files if (didSaveToTmpPath && bodyPath) { await fs_1.promises.unlink(bodyPath); logger === null || logger === void 0 ? void 0 : logger.debug('removed tmp files'); } }); delete uploadData.media; const obj = Types_1.WAProto.Message.fromObject({ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({ url, directPath, mediaKey, fileEncSha256, fileSha256, fileLength: '999999999', mediaKeyTimestamp: generics_1.unixTimestampSeconds(), ...uploadData }) }); if (cacheableKey) { logger.debug({ cacheableKey }, 'set cache'); options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish()); } return obj; } }, processMessageStubType: { /** * to process MessageStubType * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} m */ async value(m) { if (!m.messageStubType) return const chat = conn.decodeJid(m.key.remoteJid || m.message?.senderKeyDistributionMessage?.groupId || '') if (!chat || chat === 'status@broadcast') return const emitGroupUpdate = (update) => { ev.emit('groups.update', [{ id: chat, ...update }]) } switch (m.messageStubType) { case WAMessageStubType.REVOKE: case WAMessageStubType.GROUP_CHANGE_INVITE_LINK: emitGroupUpdate({ revoke: m.messageStubParameters[0] }) break case WAMessageStubType.GROUP_CHANGE_ICON: emitGroupUpdate({ icon: m.messageStubParameters[0] }) break default: { console.log({ messageStubType: m.messageStubType, messageStubParameters: m.messageStubParameters, type: WAMessageStubType[m.messageStubType] }) break } } const isGroup = chat.endsWith('@g.us') if (!isGroup) return let chats = conn.chats[chat] if (!chats) chats = conn.chats[chat] = { id: chat } chats.isChats = true const metadata = await conn.groupMetadata(chat).catch(_ => null) if (!metadata) return chats.subject = metadata.subject chats.metadata = metadata } }, insertAllGroup: { async value() { const groups = await conn.groupFetchAllParticipating().catch(_ => null) || {} for (const group in groups) conn.chats[group] = { ...(conn.chats[group] || {}), id: group, subject: groups[group].subject, isChats: true, metadata: groups[group] } return conn.chats }, }, pushMessage: { /** * pushMessage * @param {import('@adiwajshing/baileys').proto.WebMessageInfo[]} m */ async value(m) { if (!m) return if (!Array.isArray(m)) m = [m] for (const message of m) { try { // if (!(message instanceof proto.WebMessageInfo)) continue // https://github.com/adiwajshing/Baileys/pull/696/commits/6a2cb5a4139d8eb0a75c4c4ea7ed52adc0aec20f if (!message) continue if (message.messageStubType && message.messageStubType != WAMessageStubType.CIPHERTEXT) conn.processMessageStubType(message).catch(console.error) const _mtype = Object.keys(message.message || {}) const mtype = (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(_mtype[0]) && _mtype[0]) || (_mtype.length >= 3 && _mtype[1] !== 'messageContextInfo' && _mtype[1]) || _mtype[_mtype.length - 1] const chat = conn.decodeJid(message.key.remoteJid || message.message?.senderKeyDistributionMessage?.groupId || '') if (message.message?.[mtype]?.contextInfo?.quotedMessage) { /** * @type {import('@adiwajshing/baileys').proto.IContextInfo} */ let context = message.message[mtype].contextInfo let participant = conn.decodeJid(context.participant) const remoteJid = conn.decodeJid(context.remoteJid || participant) /** * @type {import('@adiwajshing/baileys').proto.IMessage} * */ let quoted = message.message[mtype].contextInfo.quotedMessage if ((remoteJid && remoteJid !== 'status@broadcast') && quoted) { let qMtype = Object.keys(quoted)[0] if (qMtype == 'conversation') { quoted.extendedTextMessage = { text: quoted[qMtype] } delete quoted.conversation qMtype = 'extendedTextMessage' } if (!quoted[qMtype].contextInfo) quoted[qMtype].contextInfo = {} quoted[qMtype].contextInfo.mentionedJid = context.mentionedJid || quoted[qMtype].contextInfo.mentionedJid || [] const isGroup = remoteJid.endsWith('g.us') if (isGroup && !participant) participant = remoteJid const qM = { key: { remoteJid, fromMe: areJidsSameUser(conn.user.jid, remoteJid), id: context.stanzaId, participant, }, message: JSON.parse(JSON.stringify(quoted)), ...(isGroup ? { participant } : {}) } let qChats = conn.chats[participant] if (!qChats) qChats = conn.chats[participant] = { id: participant, isChats: !isGroup } if (!qChats.messages) qChats.messages = {} if (!qChats.messages[context.stanzaId] && !qM.key.fromMe) qChats.messages[context.stanzaId] = qM let qChatsMessages if ((qChatsMessages = Object.entries(qChats.messages)).length > 40) qChats.messages = Object.fromEntries(qChatsMessages.slice(30, qChatsMessages.length)) // maybe avoid memory leak } } if (!chat || chat === 'status@broadcast') continue const isGroup = chat.endsWith('@g.us') let chats = conn.chats[chat] if (!chats) { if (isGroup) await conn.insertAllGroup().catch(console.error) chats = conn.chats[chat] = { id: chat, isChats: true, ...(conn.chats[chat] || {}) } } let metadata, sender if (isGroup) { if (!chats.subject || !chats.metadata) { metadata = await conn.groupMetadata(chat).catch(_ => ({})) || {} if (!chats.subject) chats.subject = metadata.subject || '' if (!chats.metadata) chats.metadata = metadata } sender = conn.decodeJid(message.key?.fromMe && conn.user.id || message.participant || message.key?.participant || chat || '') if (sender !== chat) { let chats = conn.chats[sender] if (!chats) chats = conn.chats[sender] = { id: sender } if (!chats.name) chats.name = message.pushName || chats.name || '' } } else if (!chats.name) chats.name = message.pushName || chats.name || '' if (['senderKeyDistributionMessage', 'messageContextInfo'].includes(mtype)) continue chats.isChats = true if (!chats.messages) chats.messages = {} const fromMe = message.key.fromMe || areJidsSameUser(sender || chat, conn.user.id) if (!['protocolMessage'].includes(mtype) && !fromMe && message.messageStubType != WAMessageStubType.CIPHERTEXT && message.message) { delete message.message.messageContextInfo delete message.message.senderKeyDistributionMessage chats.messages[message.key.id] = JSON.parse(JSON.stringify(message, null, 2)) let chatsMessages if ((chatsMessages = Object.entries(chats.messages)).length > 40) chats.messages = Object.fromEntries(chatsMessages.slice(30, chatsMessages.length)) } } catch (e) { console.error(e) } } } }, serializeM: { /** * Serialize Message, so it easier to manipulate * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} m */ value(m) { return smsg(conn, m) } }, updateProfilePicture: { async value(jid, content) { const { img } = await generateProfilePicture(content) return conn.query({ tag: 'iq', attrs: { to: jidNormalizedUser(jid), type: 'set', xmlns: 'w:profile:picture' }, content: [ { tag: 'picture', attrs: { type: 'image' }, content: img }] }) }, enumerable: true }, ...(typeof conn.chatRead !== 'function' ? { chatRead: { /** * Read message * @param {String} jid * @param {String|undefined|null} participant * @param {String} messageID */ value(jid, participant = conn.user.jid, messageID) { return conn.sendReadReceipt(jid, participant, [messageID]) }, enumerable: true } } : {}), ...(typeof conn.setStatus !== 'function' ? { setStatus: { /** * setStatus bot * @param {String} status */ value(status) { return conn.query({ tag: 'iq', attrs: { to: '@s.whatsapp.net', type: 'set', xmlns: 'status', }, content: [ { tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') } ] }) }, enumerable: true } } : {}) }) if (sock.user?.id) sock.user.jid = sock.decodeJid(sock.user.id) store.bind(sock) return sock } /** * Serialize Message * @param {ReturnType} conn * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} m * @param {Boolean} hasParent */ export function smsg(conn, m, hasParent) { if (!m) return m /** * @type {import('@adiwajshing/baileys').proto.WebMessageInfo} */ let M = proto.WebMessageInfo m = M.fromObject(m) m.conn = conn let protocolMessageKey if (m.message) { if (m.mtype == 'protocolMessage' && m.msg.key) { protocolMessageKey = m.msg.key if (protocolMessageKey == 'status@broadcast') protocolMessageKey.remoteJid = m.chat if (!protocolMessageKey.participant || protocolMessageKey.participant == 'status_me') protocolMessageKey.participant = m.sender protocolMessageKey.fromMe = conn.decodeJid(protocolMessageKey.participant) === conn.decodeJid(conn.user.id) if (!protocolMessageKey.fromMe && protocolMessageKey.remoteJid === conn.decodeJid(conn.user.id)) protocolMessageKey.remoteJid = m.sender } if (m.quoted) if (!m.quoted.mediaMessage) delete m.quoted.download } if (!m.mediaMessage) delete m.download try { if (protocolMessageKey && m.mtype == 'protocolMessage') conn.ev.emit('message.delete', protocolMessageKey) } catch (e) { console.error(e) } return m } // https://github.com/Nurutomo/wabot-aq/issues/490 export function serialize() { const MediaType = ['imageMessage', 'videoMessage', 'audioMessage', 'stickerMessage', 'documentMessage'] return Object.defineProperties(proto.WebMessageInfo.prototype, { conn: { value: undefined, enumerable: false, writable: true }, id: { get() { return this.key?.id } }, isBaileys: { get() { return this.id?.length === 16 || this.id?.startsWith('3EB0') && this.id?.length === 12 || false } }, chat: { get() { const senderKeyDistributionMessage = this.message?.senderKeyDistributionMessage?.groupId return ( this.key?.remoteJid || (senderKeyDistributionMessage && senderKeyDistributionMessage !== 'status@broadcast' ) || '' ).decodeJid() } }, isGroup: { get() { return this.chat.endsWith('@g.us') }, enumerable: true }, sender: { get() { return this.conn?.decodeJid(this.key?.fromMe && this.conn?.user.id || this.participant || this.key.participant || this.chat || '') }, enumerable: true }, fromMe: { get() { return this.key?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender) || false } }, mtype: { get() { if (!this.message) return '' const type = Object.keys(this.message) return (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(type[0]) && type[0]) || // Sometimes message in the front (type.length >= 3 && type[1] !== 'messageContextInfo' && type[1]) || // Sometimes message in midle if mtype length is greater than or equal to 3 type[type.length - 1] // common case }, enumerable: true }, msg: { get() { if (!this.message) return null return this.message[this.mtype] } }, mediaMessage: { get() { if (!this.message) return null const Message = ((this.msg?.url || this.msg?.directPath) ? { ...this.message } : extractMessageContent(this.message)) || null if (!Message) return null const mtype = Object.keys(Message)[0] return MediaType.includes(mtype) ? Message : null }, enumerable: true }, mediaType: { get() { let message if (!(message = this.mediaMessage)) return null return Object.keys(message)[0] }, enumerable: true, }, quoted: { get() { /** * @type {ReturnType} */ const self = this const msg = self.msg const contextInfo = msg?.contextInfo const quoted = contextInfo?.quotedMessage if (!msg || !contextInfo || !quoted) return null const type = Object.keys(quoted)[0] let q = quoted[type] const text = typeof q === 'string' ? q : q.text return Object.defineProperties(JSON.parse(JSON.stringify(typeof q === 'string' ? { text: q } : q)), { mtype: { get() { return type }, enumerable: true }, mediaMessage: { get() { const Message = ((q.url || q.directPath) ? { ...quoted } : extractMessageContent(quoted)) || null if (!Message) return null const mtype = Object.keys(Message)[0] return MediaType.includes(mtype) ? Message : null }, enumerable: true }, mediaType: { get() { let message if (!(message = this.mediaMessage)) return null return Object.keys(message)[0] }, enumerable: true, }, id: { get() { return contextInfo.stanzaId }, enumerable: true }, chat: { get() { return contextInfo.remoteJid || self.chat }, enumerable: true }, isBaileys: { get() { return this.id?.length === 16 || this.id?.startsWith('3EB0') && this.id.length === 12 || false }, enumerable: true }, sender: { get() { return (contextInfo.participant || this.chat || '').decodeJid() }, enumerable: true }, fromMe: { get() { return areJidsSameUser(this.sender, self.conn?.user.jid) }, enumerable: true, }, text: { get() { return text || this.caption || this.contentText || this.selectedDisplayText || '' }, enumerable: true }, mentionedJid: { get() { return q.contextInfo?.mentionedJid || self.getQuotedObj()?.mentionedJid || [] }, enumerable: true }, name: { get() { const sender = this.sender return sender ? self.conn?.getName(sender) : null }, enumerable: true }, vM: { get() { return proto.WebMessageInfo.fromObject({ key: { fromMe: this.fromMe, remoteJid: this.chat, id: this.id }, message: quoted, ...(self.isGroup ? { participant: this.sender } : {}) }) } }, fakeObj: { get() { return this.vM } }, download: { value(saveToFile = false) { const mtype = this.mediaType return self.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile) }, enumerable: true, configurable: true, }, replyV2: { /** * Reply to quoted message * @param {String|Object} text * @param {String|false} chatId * @param {Object} options */ value(text, chatId, options) { return self.conn?.replyV2(chatId ? chatId : this.chat, text, this.vM, options) }, enumerable: true, }, reply: { /** * Reply to quoted message * @param {String|Object} text * @param {String|false} chatId * @param {Object} options */ value(text, chatId, options) { return self.conn?.reply(chatId ? chatId : this.chat, text, this.vM, options) }, enumerable: true, }, copy: { /** * Copy quoted message */ value() { const M = proto.WebMessageInfo return smsg(conn, M.fromObject(M.toObject(this.vM))) }, enumerable: true, }, forward: { /** * Forward quoted message * @param {String} jid * @param {Boolean} forceForward */ value(jid, force = false, options) { return self.conn?.sendMessage(jid, { forward: this.vM, force, ...options }, { ...options }) }, enumerable: true, }, copyNForward: { /** * Exact Forward quoted message * @param {String} jid * @param {Boolean|Number} forceForward * @param {Object} options */ value(jid, forceForward = false, options) { return self.conn?.copyNForward(jid, this.vM, forceForward, options) }, enumerable: true, }, cMod: { /** * Modify quoted Message * @param {String} jid * @param {String} text * @param {String} sender * @param {Object} options */ value(jid, text = '', sender = this.sender, options = {}) { return self.conn?.cMod(jid, this.vM, text, sender, options) }, enumerable: true, }, delete: { /** * Delete quoted message */ value() { return self.conn?.sendMessage(this.chat, { delete: this.vM.key }) }, enumerable: true, } }) }, enumerable: true }, _text: { value: null, writable: true, }, text: { get() { const msg = this.msg const text = (typeof msg === 'string' ? msg : msg?.text) || msg?.caption || msg?.contentText || '' return typeof this._text === 'string' ? this._text : '' || (typeof text === 'string' ? text : ( text?.selectedDisplayText || text?.hydratedTemplate?.hydratedContentText || text )) || '' }, set(str) { return this._text = str }, enumerable: true }, mentionedJid: { get() { return this.msg?.contextInfo?.mentionedJid?.length && this.msg.contextInfo.mentionedJid || [] }, enumerable: true }, name: { get() { return !nullish(this.pushName) && this.pushName || this.conn?.getName(this.sender) }, enumerable: true }, download: { value(saveToFile = false) { const mtype = this.mediaType return this.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile) }, enumerable: true, configurable: true }, replyV2: { value(text, chatId, options) { return this.conn?.replyV2(chatId ? chatId : this.chat, text, this, options) } }, reply: { value(text, chatId, options) { return this.conn?.reply(chatId ? chatId : this.chat, text, this, options) } }, copy: { value() { const M = proto.WebMessageInfo return smsg(this.conn, M.fromObject(M.toObject(this))) }, enumerable: true }, forward: { value(jid, force = false, options = {}) { return this.conn?.sendMessage(jid, { forward: this, force, ...options }, { ...options }) }, enumerable: true }, copyNForward: { value(jid, forceForward = false, options = {}) { return this.conn?.copyNForward(jid, this, forceForward, options) }, enumerable: true }, cMod: { value(jid, text = '', sender = this.sender, options = {}) { return this.conn?.cMod(jid, this, text, sender, options) }, enumerable: true }, getQuotedObj: { value() { if (!this.quoted.id) return null const q = proto.WebMessageInfo.fromObject(this.conn?.loadMessage(this.quoted.id) || this.quoted.vM) return smsg(this.conn, q) }, enumerable: true }, getQuotedMessage: { get() { return this.getQuotedObj } }, delete: { value() { return this.conn?.sendMessage(this.chat, { delete: this.key }) }, enumerable: true } }) } export function logic(check, inp, out) { if (inp.length !== out.length) throw new Error('Input and Output must have same length') for (let i in inp) if (util.isDeepStrictEqual(check, inp[i])) return out[i] return null } export function protoType() { Buffer.prototype.toArrayBuffer = function toArrayBufferV2() { const ab = new ArrayBuffer(this.length); const view = new Uint8Array(ab); for (let i = 0; i < this.length; ++i) { view[i] = this[i]; } return ab; } /** * @returns {ArrayBuffer} */ Buffer.prototype.toArrayBufferV2 = function toArrayBuffer() { return this.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength) } /** * @returns {Buffer} */ ArrayBuffer.prototype.toBuffer = function toBuffer() { return Buffer.from(new Uint8Array(this)) } // /** // * @returns {String} // */ // Buffer.prototype.toUtilFormat = ArrayBuffer.prototype.toUtilFormat = Object.prototype.toUtilFormat = Array.prototype.toUtilFormat = function toUtilFormat() { // return util.format(this) // } Uint8Array.prototype.getFileType = ArrayBuffer.prototype.getFileType = Buffer.prototype.getFileType = async function getFileType() { return await fileTypeFromBuffer(this) } /** * @returns {Boolean} */ String.prototype.isNumber = Number.prototype.isNumber = isNumber /** * * @returns {String} */ String.prototype.capitalize = function capitalize() { return this.charAt(0).toUpperCase() + this.slice(1, this.length) } /** * @returns {String} */ String.prototype.capitalizeV2 = function capitalizeV2() { const str = this.split(' ') return str.map(v => v.capitalize()).join(' ') } String.prototype.decodeJid = function decodeJid() { if (/:\d+@/gi.test(this)) { const decode = jidDecode(this) || {} return (decode.user && decode.server && decode.user + '@' + decode.server || this).trim() } else return this.trim() } /** * number must be milliseconds * @returns {string} */ Number.prototype.toTimeString = function toTimeString() { // const milliseconds = this % 1000 const seconds = Math.floor((this / 1000) % 60) const minutes = Math.floor((this / (60 * 1000)) % 60) const hours = Math.floor((this / (60 * 60 * 1000)) % 24) const days = Math.floor((this / (24 * 60 * 60 * 1000))) return ( (days ? `${days} day(s) ` : '') + (hours ? `${hours} hour(s) ` : '') + (minutes ? `${minutes} minute(s) ` : '') + (seconds ? `${seconds} second(s)` : '') ).trim() } Number.prototype.getRandom = String.prototype.getRandom = Array.prototype.getRandom = getRandom } function isNumber() { const int = parseInt(this) return typeof int === 'number' && !isNaN(int) } function getRandom() { if (Array.isArray(this) || this instanceof String) return this[Math.floor(Math.random() * this.length)] return Math.floor(Math.random() * this) } /** * ?? * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator * @returns {boolean} */ function nullish(args) { return !(args !== null && args !== undefined) } async function generateProfilePicture(mediaUpload) { let bufferOrFilePath if (Buffer.isBuffer(mediaUpload)) bufferOrFilePath = mediaUpload else if ('url' in mediaUpload) bufferOrFilePath = mediaUpload.url.toString() else bufferOrFilePath = await Baileys.toBuffer(mediaUpload.stream) const { read, MIME_JPEG, AUTO } = await Promise.resolve().then(async () => (await import('jimp')).default) const jimp = await read(bufferOrFilePath) const min = jimp.getWidth() const max = jimp.getHeight() const cropped = jimp.crop(0, 0, min, max) return { img: await cropped.quality(100).scaleToFit(720, 720, AUTO).getBufferAsync(MIME_JPEG) } } // Iyh