UNPKG

@aidulcandra/simple-wa-bot

Version:

A baileys wrapper module

1,438 lines (1,296 loc) 43.2 kB
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, S_WHATSAPP_NET, isJidGroup, downloadMediaMessage, Browsers } = require("baileys") const { extractGroupMetadata } = require("baileys/lib/Socket/groups"); const {createSticker} = require("./utils.js") require("colors"); const { inspect } = require("util"); const fs = require("fs"); const path = require("path"); const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }) const similarity = require("similarity"); const {CronJob} = require("cron") const vCard = require("vcard-parser") const { InputReaderProgram, StartupProgram, ScheduledProgram, createMessage, classOptions, } = require("./classes.js"); classOptions.helperFunctions = { sendMessage: (...values) => queue("socket", null, (_) => socket.sendMessage(...values)), blacklistCheck, downloadMediaMessage, saveMedia, getPushname, getMentionsFromText, isOwner: checkOwner, readMessages, getPPUrl, getBotName: () => socket.user.name, getEphemeralExpiration, getMessageFromStore, saveMessageToStore, removeMessageFromStore, getGroupData }; const pathToFfmpeg = require("ffmpeg-static"); process.env.FFMPEG_PATH = pathToFfmpeg; const CONFIG_PATH = "./bot-config.json"; const AUTH_STATE_PATH = "./simple-bot/state"; const PUSHNAMES_PATH = "./simple-bot/pushnames.json"; const VARS_PATH = "./simple-bot/vars.json"; const TESTVAR_PATH = "./simple-bot/testvars.json"; const GROUP_SUFFIX = "@g.us"; const STATUS_ROOM_ID = "status@broadcast"; const BLACKLIST_PATH = "./simple-bot/blacklist.json"; const TEST_BLACKLIST_PATH = "./simple-bot/test-blacklist.json"; const STORE_PATH = "./simple-bot/store.json" const inputReaderPrograms = []; const scheduledPrograms = []; const startupPrograms = []; const defaultPrograms = []; let testingMode = null; const getVarsPath = () => (testingMode ? TESTVAR_PATH : VARS_PATH); const getBlacklistPath = () => testingMode ? TEST_BLACKLIST_PATH : BLACKLIST_PATH; const botVar = {}; const blacklist = []; const pushNames = {}; const messageCaptureList = []; const settings = { ownerIds:[], parseMentions: true, recordPushnames: true, commandPrefixes: ["/"], autoRead: false, storeSize: 100, commandMenu: { menuTemplate: "> COMMAND LIST\n\n@categorylist\n\n_Created by: @aidulcandra_", categoryTemplate: "[@category]----\n@commandlist\n------------", categorySeparator: "\n\n", commandTemplate: "`/@command` - @description", commandSeparator: "\n", }, }; try { Object.assign(botVar, JSON.parse(fs.readFileSync(getVarsPath()).toString())); } catch {} try { blacklist.push(...JSON.parse(fs.readFileSync(getBlacklistPath())).toString()); } catch {} try { Object.assign( pushNames, JSON.parse(fs.readFileSync(PUSHNAMES_PATH).toString()) ); } catch {} try { Object.assign(settings, JSON.parse(fs.readFileSync(CONFIG_PATH).toString())); fs.writeFileSync(CONFIG_PATH, JSON.stringify(settings, null, 2)); } catch {console.log("Error reading bot-config.json".red)} finally { if (!fs.existsSync(CONFIG_PATH)) fs.writeFileSync(CONFIG_PATH, JSON.stringify(settings, null, 2)); } function saveSettings() { return fs.promises.writeFile(CONFIG_PATH, JSON.stringify(settings, null, 2)) } function addOwner(...list) { for (const item of list.flat()) { if (settings.ownerIds.includes(item)) continue settings.ownerIds.push(item) } return saveSettings() } function removeOwner(...list) { for (const item of list.flat()) { if (!settings.ownerIds.includes(item)) continue const pos = settings.ownerIds.indexOf(item) settings.ownerIds.splice(pos, 1) } return saveSettings() } function addPrefix(...list) { for (const item of list.flat()) { if (settings.commandPrefixes.includes(item)) continue settings.commandPrefixes.push(item) } return saveSettings() } function removePrefix(...list) { for (const item of list.flat()) { if (!settings.commandPrefixes.includes(item)) continue const pos = settings.commandPrefixes.indexOf(item) settings.commandPrefixes.splice(pos, 1) } return saveSettings() } function saveBotVar() { return fs.promises .writeFile(getVarsPath(), JSON.stringify(botVar)) .catch((e) => fs.promises.writeFile(getVarsPath(), "{}")); } function blacklistAdd(ids) { for (let id of [ids].flat()) { if (!/^\d+@s.whatsapp.net$/.test(id)) id = id.replace(/\D/g, "") + S_WHATSAPP_NET; if (blacklist.includes(id)) continue; blacklist.push(id); } return blacklistSave(); } function blacklistRemove(ids) { for (let id of [ids].flat()) { if (!/^\d+@s.whatsapp.net$/.test(id)) id = id.replace(/\D/g, "") + S_WHATSAPP_NET; const pos = blacklist.indexOf(id); blacklist.splice(pos, 1); } return blacklistSave(); } function blacklistGetList() { return [...blacklist]; } function blacklistCheck(id) { if (!id) return false if (!/^\d+@s.whatsapp.net$/.test(id)) id = id.replace(/\D/g, "") + S_WHATSAPP_NET; return blacklist.includes(id); } function blacklistSave() { return fs.promises.writeFile(getBlacklistPath(), JSON.stringify(blacklist)); } function getPushnames() { return pushNames; } function getPushname(id) { return pushNames[id]; } function savePushnames() { return fs.promises.writeFile(PUSHNAMES_PATH, JSON.stringify(pushNames)); } function saveMedia(message, path) { return downloadMediaMessage(message,'buffer',{},{reuploadRequest:socket.updateMediaMessage}) .then(buffer=>fs.promises.writeFile(path, buffer)) } function checkOwner(id) { return bot.settings.ownerIds.includes(id) } function readMessages(keys) { return queue("socket", null, ()=>socket.readMessages(keys)) } let socket; const getSocket = () => socket; let finishSocketStartup; queue( "socket", null, () => new Promise((resolve) => (finishSocketStartup = resolve)) ); global.seeQueue = () => console.log(queue.names); let connectionState = "closed"; let receivedPending = false; const getConnectionState = () => connectionState; const messageStore = [] function setStoreSize(limit=100) { settings.storeSize = limit if (messageStore.length > limit) { for (let i=0; i<messageStore.length - limit; i++) messageStore.shift() } return saveSettings() } function saveMessageToStore(msg) { messageStore.push(msg) while (messageStore.length > settings.storeSize) messageStore.shift() } function removeMessageFromStore(msg) { const index = messageStore.findIndex(m => m.key.id == msg.key.id && m.key.remoteJid == msg.key.remoteJid) if (index > -1) messageStore.splice(index, 1) } function getMessageFromStore (id, room) { return messageStore.find(m=>m.id == id && m.room == room) } async function start(platformName) { if (testingMode === true) { throw new Error("Bot is already running in testing mode!"); } testingMode = false; console.log("Starting Bot...".green); const { state, saveCreds } = await useMultiFileAuthState(AUTH_STATE_PATH); socket = makeWASocket({ auth: state, browser: Browsers.windows("Chrome"), cachedGroupMetadata: getGroupData, getMessage: (key) => getMessageFromStore(key.id, key.remoteJid) }); classOptions.socket = socket; socket.ev.on("creds.update", saveCreds); let requestingPhoneNumber = false let phoneNumber = null socket.ev.on("connection.update", async (update) => { console.log(inspect(update, null, 7).blue); if (update.connection == "close") { const reason = update.lastDisconnect?.error?.output?.statusCode; if (reason == "401" || reason == "405") { // Unauthorized console.log("Unauthorized. Removing state data. Please restart the program.".red); fs.rmSync(AUTH_STATE_PATH, { recursive: true }); //start() } else if (reason != DisconnectReason.loggedOut) { socket = null; start(); } } if (update.qr && !requestingPhoneNumber) { requestingPhoneNumber = true while (!phoneNumber) { phoneNumber = await new Promise(resolve => { rl.question("Enter phone number (ex: 12345678901, 628123456789):", (pn) => { resolve(pn.replace(/\D/g,'')) }) }) } rl.close() const code = await socket.requestPairingCode(phoneNumber) console.log("Pairing code:", code.slice(0,4), "-", code.slice(4)); } if (update.connection != undefined) connectionState = update.connection; if (update.receivedPendingNotifications != undefined) receivedPending = update.receivedPendingNotifications; if (receivedPending && connectionState == "open") { finishSocketStartup(); console.log("Socket startup complete"); classOptions.myId = getId(); console.log("myAppStateKeyId", socket.authState.creds.myAppStateKeyId); for (const p of startupPrograms) { console.log("Startup program:", p); await programAction(p); } for (const p of scheduledPrograms) { console.log("Scheduled Program:", p) p.job.start() } } }); socket.ev.on("messages.upsert", (update) => { console.log("Messages upsert update type:".green, update.type); processMsgs(update.messages); }); socket.ev.on("messages.update", (event) => { console.log("Messages update", inspect(event)) }) socket.ev.on("groups.update", (updates) => { for (const data of updates) { const {id} = data if (getGroupData.cache?.[id]) Object.assign(getGroupData.cache[id], data) } }) socket.ev.on("group-participants.update", (data) => { const {id:groupId, action} = data if (getGroupData.cache?.[groupId]) { const groupParticipants = getGroupData.cache[groupId].participants for (const userId of data.participants) { if (action == "add") groupParticipants.push({id:userId, admin:null}) else if (action == "remove") { const index = groupParticipants.indexOf(p => p.id == userId) groupParticipants.splice(index,1) } else if (action == "promote") { const p = groupParticipants.find(p=>p.id == userId) p.admin = "admin" } else if (action == "demote") { const p = groupParticipants.find(p=>p.id == userId) p.admin = null } } } }) } async function test() { if (testingMode === false) { throw new Error("Bot is already running in non testing mode"); } testingMode = true; // console.clear() console.log("-- TESTING MODE --".yellow); console.log(``); process.stdout.write(">>> "); connectionState = "open"; socket = { sendMessage: (id, content, options) => { const media = content.image?.url ? `[Image: ${content.image.url}]`.blue : content.video?.url ? `[Video: ${content.video.url}]`.blue : content.audio?.url ? `[Audio: ${content.audio.url}]`.blue : null; const text = content.text || content.caption; console.log(`\nBot sends message to ID [${id}]:`.green); media && console.log(media); text && console.log(text); return { key: { remoteJid: "tester-id@s.whatsapp.net", pushName: "", fromMe: true, }, message: { conversation: text, }, }; }, sendPresenceUpdate: (type, id) => { console.log(`\nBot is ${type} in ID [${id}]...`.grey); }, user: { id: "123@s.whatsapp.net", } }; classOptions.socket = socket; process.stdin.on("data", async (data) => { const text = data.toString().trim(); const message = { key: { remoteJid: "tester-id@s.whatsapp.net", fromMe: false, }, message: { conversation: text, }, pushName: "Tester", }; processMsgs([message]); console.log(); process.stdout.write(">>> "); }); finishSocketStartup(); console.log("Socket startup complete"); classOptions.myId = getId(); for (const p of startupPrograms) await programAction(p); } const receive = (...input) => { const p = new InputReaderProgram(input); inputReaderPrograms.push(p); return p; }; const receiveBeginning = (...input) => { const p = new InputReaderProgram([], { beginsWith: input }); inputReaderPrograms.push(p); return p; }; const receiveContaining = (...input) => { const p = new InputReaderProgram([], { contains: input }); inputReaderPrograms.push(p); return p; }; const receiveEnding = (...input) => { const p = new InputReaderProgram([], { endsWith: input }); inputReaderPrograms.push(p); return p; }; const receiveAny = () => { const p = new InputReaderProgram([], { anyInput: true }); inputReaderPrograms.push(p); return p; }; const sent = (...input) => { const p = new InputReaderProgram(input, { selfRespond: true }); inputReaderPrograms.push(p); return p; }; const command = (name, description, category, aliases) => { const a = [aliases].flat().filter(x=>x) const prefixRegex = bot.settings.commandPrefixes.map(escapeRegExp).join("|") const nameRegex = [name, ...a].map(escapeRegExp).join("|") const regex = new RegExp(`^(?:${prefixRegex})\\s*(?:${nameRegex})\\b`, "i") const p = new InputReaderProgram([regex], { command: true, commandName: name, commandDescription: description, commandCategory: category, commandAliases: a }); inputReaderPrograms.push(p); return p; }; const started = () => { const p = new StartupProgram(); startupPrograms.push(p); return p; }; const schedule = (cronTime, timezone) => { const p = new ScheduledProgram(cronTime); scheduledPrograms.push(p); p.job = new CronJob(p.time, ()=>programAction(p), null, true, timezone) return p; }; const removeProgram = (uid) => { for (let i=0; i<inputReaderPrograms.length; i++) { const p = inputReaderPrograms[i] if (p.uid == uid) { inputReaderPrograms.splice(i,1) return true } } for (let i=0; i<startupPrograms.length; i++) { const p = startupPrograms[i] if (p.uid == uid) { startupPrograms.splice(i,1) return true } } for (let i=0; i<scheduledPrograms.length; i++) { const p = scheduledPrograms[i] if (p.uid == uid) { scheduledPrograms.splice(i,1) return true } } } const setVar = async (name, value) => { botVar[name] = value; await saveBotVar(); }; const getVar = (name) => { return botVar[name]; }; const deleteVar = async (name) => { delete botVar[name]; await saveBotVar(); }; const defaultResponse = (output) => { addDefault(output); }; async function sendText(to, text) { const ephemeralExpiration = await getEphemeralExpiration(to) const mentions = getMentionsFromText(text); return createMessage( await queue("socket", null, () => socket.sendMessage(to, { text, mentions }, {ephemeralExpiration}) ) ); } async function sendImage(to, source, caption) { const ephemeralExpiration = await getEphemeralExpiration(to) const mentions = getMentionsFromText(caption); const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage( await queue("socket", null, () => socket.sendMessage(to, { image: media, caption, mentions }, {ephemeralExpiration}) ) ); } async function sendVideo(to, source, caption) { const ephemeralExpiration = await getEphemeralExpiration(to) const mentions = getMentionsFromText(caption); const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage( await queue("socket", null, () => socket.sendMessage(to, { video: media, caption, mentions }, {ephemeralExpiration}) ) ); } async function sendGif(to, source, caption) { const ephemeralExpiration = await getEphemeralExpiration(to) const mentions = getMentionsFromText(caption); const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage( await queue("socket", null, () => socket.sendMessage(to, { video: media, caption, gifPlayback:true, mentions }, {ephemeralExpiration}) ) ); } async function sendAudio(to, source, mimetype) { const ephemeralExpiration = await getEphemeralExpiration(to) const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage( await queue("socket", null, () => socket.sendMessage(to, { audio: media, mimetype: mimetype || "audio/mp4", }, {ephemeralExpiration}) ) ); } async function sendSticker(to, source, options) { const ephemeralExpiration = await getEphemeralExpiration(to) //const buff = await sharp(source).webp().toBuffer(); const sticker = await createSticker(source, { pack: options?.pack, author: options?.author, type: options?.type, quality: options?.quality, background: options?.background, categories: options?.categories }) return createMessage( await queue("socket", { to, sticker }, ({ to, sticker }) => socket.sendMessage(to, sticker, {ephemeralExpiration}) ) ); } async function sendDocument(to, source, mimetype, caption, fileName ) { const ephemeralExpiration = await getEphemeralExpiration(to) const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage( await queue("socket", null, () => socket.sendMessage(to, { document: media, caption, fileName, mimetype, }, {ephemeralExpiration}) ) ); } async function sendContacts(to, contacts) { const ephemeralExpiration = await getEphemeralExpiration(to) const vcards = contacts.map((c) => { return { vcard: `BEGIN:VCARD\nVERSION:3.0\nFN:${ c.name }\nTEL;type=CELL;type=VOICE;waid=${c.number.replace(/\D/g, "")}:${ c.number }\nEND:VCARD`, }; }); return createMessage( await queue("socket", { to, vcards }, ({ to, vcards }) => socket.sendMessage(to, { contacts: { contacts: vcards } }, {ephemeralExpiration}) ) ); } async function sendLocation(to, latitude=0, longitude=0) { const ephemeralExpiration = await getEphemeralExpiration(to) const location = {degreesLatitude: latitude, degreesLongitude: longitude} return createMessage( await queue("socket", null, () => socket.sendMessage(to, {location}, {ephemeralExpiration}) ) ) } async function sendPoll(to, title, choices, multiple = false) { const ephemeralExpiration = await getEphemeralExpiration(to) const poll = {name: title, values: choices, selectableCount: multiple ? 0 : 1} const m = await createMessage( await queue("socket", null, () => socket.sendMessage(to, {poll}, {ephemeralExpiration})) ) return m } async function updateStatus(newStatus) { await queue("socket", null, () => socket.updateProfileStatus(newStatus)); } async function updateName(newName) { await queue("socket", null, ()=>socket.updateProfileName(newName)) } async function processMsgs(messages) { while (messages.length) { const message = messages.shift() const m = await createMessage(message); if (m.type == 'protocol') { if (m.protocolData.chatDisappearingTime) { if (m.room.endsWith(GROUP_SUFFIX)) { if (getGroupData.cache[m.room]) { getGroupData.cache[m.room].ephemeralDuration = m.protocolData.chatDisappearingTime } } } } if (settings.autoRead) { await readMessages([m.getBaileysMessage().key]) } if (settings.recordPushnames) { if (m.sender?.name && pushNames[m.sender?.id] != m.sender?.name) { console.log(m.sender.id + "'s pushName is updated to ", m.sender.name); pushNames[m.sender.id] = m.sender.name; await savePushnames(); } } if (settings.parseMentions) { if (m.text && m.mentions.length) { m.text = m.text.replace(/@\d+/g, (ma) => { const number = ma.slice(1); return "@" + (pushNames[number + "@s.whatsapp.net"] || number); }); } if (m.quoted?.text && m.quoted?.mentions?.length) { m.quoted.text = m.quoted.text.replace(/@\d+/g, (ma) => { const number = ma.slice(1); return "@" + (pushNames[number + "@s.whatsapp.net"] || number); }); } } if (m.text && m.groupMentions.length) { m.text = m.text.replace(/@[\d\-]+@g\.us/g, (ma) => { const gid = ma.slice(1); return ( "@" + m.groupMentions.find((gm) => gm.groupJid == gid).groupSubject ); }); } console.log(inspect(m, null, 10).cyan); if (m.room == STATUS_ROOM_ID) continue; // console.log("Checking capture list", messageCaptureList.length) for (let i = 0; i < messageCaptureList.length; i++) { const messageCapture = messageCaptureList[i]; // console.log({ messageCapture }); if (messageCapture.jid == m.room) { if (messageCapture.filter) { if (!messageCapture.filter(m)) continue } messageCapture.cb(m); messageCaptureList.splice(i, 1); } } const progs = await findInputReaderPrograms(m); if (!progs.length) { for (let d of defaultPrograms) { if (typeof d.output == "string") return await m.reply(d.output); } } for (let p of progs) { await programAction(p, {message:m}) if (p.options?.stopHere) break } } } function fillTemplate(str, regxMatches) { if (typeof str != "string") return str; if (/\%\%.+?\%\%/.test(str)) { str = str.replace(/\%\%.+?\%\%/g, (ma) => getVar(ma.slice(2, -2))); } str = str.replace( /\<\<\d+?\>\>/g, (ma) => regxMatches[ma.replace(/\D/g, "") - 1] ); return str; } async function programAction(program, data={}) { const message = data.message const text = message?.text || "" const room = message?.room const senderId = message?.sender?.id const group = room?.endsWith(GROUP_SUFFIX) ? { id: message.room, getSubject: () => getGroupData(message.room).then((metadata) => metadata.subject), getParticipants: () => getGroupData(message.room).then((metadata) => metadata.participants), mute: () => queue("socket", null, ()=>socket.groupSettingUpdate(message.room, "announcement")), unmute: () => queue("socket", null, ()=>socket.groupSettingUpdate(message.room, "not_announcement")), } : null; const matches = []; const targetIds = [program.options?.to || message?.room].flat().filter((x) => x); try { if (program.options?.read && message) { await message.read() } if (room) { if (program.options?.showTyping) { await queue("socket", null, () => socket.sendPresenceUpdate("composing", room) ); } if (program.options?.showRecording) { await queue("socket", null, () => socket.sendPresenceUpdate("recording", room) ); } } if (message) { if (program.options?.stripMentions) { // console.log(`Stripping mentions from "${message.text}"`) message.text = stripMentions(message) // console.log(`Result: "${message.text}"`) } if (program.options?.command) { const [cmd, ...params] = text .replace(new RegExp(`^.+?(?=${program.options?.commandNameRegex})`,"i"), "") .replace(/ {2,}/g, " ") .split(" "); matches.push(...params); } for (let i of [program.input].filter(x=>x).flat()) { if (i instanceof RegExp) { matches.push(...(text.match(i)?.slice(1) || [])); } } if (program.options?.react) { await message.react(fillTemplate(program.options.react, matches)) } } if (program.options?.blacklist) { await blacklistAdd( fillTemplate( program.options?.blacklist || senderId || "", matches ) ); } if (program.options?.whitelist) { await blacklistRemove( fillTemplate( program.options?.whitelist || senderId || "", matches ) ); } if (program.options?.setVar) { for (let k in program.options.setVar) botVar[k] = program.options.setVar[k]; await saveBotVar(); } if (program.options?.run) { const parameters = [ message, matches, group, bot ] if (typeof program.options.run == "string") { const fullPath = path.join(process.cwd(), program.options.run); const functionName = program.options?.runFunctionName if (fs.existsSync(fullPath)) { const mdl = require(fullPath) if (functionName) { if (!mdl[functionName]) { console.log(`Function "${functionName}" not found in script:`, program.options.run) } else await mdl[functionName](...parameters) } else await mdl(...parameters); } else { console.log("Script not found:", program.options.run); } } else if (typeof program.options.run == "function") { await program.options.run(...parameters); } } let outputText = ""; if (program.options?.randomOutput) { program.output?.length && (outputText = fillTemplate(choose(program.output), matches)) } else if (program.output) { outputText = fillTemplate(program.output, matches); } if (program.options?.sendMenu) { outputText = createCommandMenu() } const quoted = program.options?.reply && message?.getBaileysMessage(); const mentions = getMentionsFromText(outputText); for (const id of targetIds) { const ephemeralExpiration = await getEphemeralExpiration(id) if (program.options?.image) { const url = fillTemplate(program.options.image, matches); const caption = fillTemplate(outputText, matches); await queue("socket", null, () => socket.sendMessage( id, { image: { url }, caption, mentions }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.video) { const url = fillTemplate(program.options.video, matches); const caption = fillTemplate(outputText, matches); await queue("socket", null, () => socket.sendMessage( id, { video: { url }, caption, mentions }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.gif) { const url = fillTemplate(program.options.gif, matches); const caption = fillTemplate(outputText, matches); await queue("socket", null, () => socket.sendMessage( id, { video: { url }, caption, gifPlayback:true, mentions }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.audio) { const url = fillTemplate(program.options.audio, matches); const mimetype = program.options?.audioType || "audio/mp4"; await queue("socket", null, () => socket.sendMessage( id, { audio: { url }, mimetype }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.sticker) { const s = program.options.sticker; //const buff = await sharp(fillTemplate(s.link, matches)) // .webp() // .toBuffer(); const sticker = await createSticker(fillTemplate(s.link, matches), { pack: fillTemplate(s.pack, matches), author: fillTemplate(s.author, matches), type: s.type, quality: s.quality, background: s.background, categories: s.categories }) await queue("socket", null, () => socket.sendMessage( id, sticker, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.document) { const url = fillTemplate(program.options.document, matches); const caption = fillTemplate(outputText, matches); const mimetype = program.options.documentType const fileName = fillTemplate(program.options.documentFileName, matches) await queue("socket", null, () => socket.sendMessage( id, { document: { url }, mimetype, fileName, caption, mentions }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.contacts) { const vcards = program.options.contacts.map((c) => { const name = fillTemplate(c.name, matches) const number = fillTemplate(c.number, matches) return { vcard: vCard.generate({ version: [{value:"3.0"}], fn: [{value:name}], tel: [{value:number, meta:{waid:[number]}}] }) }; }); await queue("socket", null, () => socket.sendMessage( id, { contacts: { contacts: vcards } }, { quoted, ephemeralExpiration } ) ); continue } if (program.options?.location) { const {latitude, longitude} = program.options.location const location = {degreesLatitude:latitude, degreesLongitude:longitude} await queue("socket", null, () => socket.sendMessage(id, {location}, {quoted, ephemeralExpiration}) ) continue } if (program.options?.poll) { const {title, choices, multiple} = program.options.poll const poll = {name: fillTemplate(title, matches), values: choices.map(c=>fillTemplate(c, matches)), selectableCount: multiple ? 0 : 1} await queue("socket", null, () => socket.sendMessage(id, {poll}, {quoted, ephemeralExpiration})) } if (outputText) { const text = outputText; await queue("socket", null, () => socket.sendMessage(id, { text, mentions }, { quoted, ephemeralExpiration }) ); continue } } if (program.options?.forward) { for (const id of targetIds) { await message.forward(id) } } if (program.options?.delete) { await message.delete() } } catch (e) { if (message) return message.reply(e.stack); if (targetIds.length) return bot.sendText(targetIds[0], e.stack) if (bot.settings.ownerIds.length) return bot.sendText(bot.settings.ownerIds[0], e.stack) } } function addDefault(output) { defaultPrograms.push({ output }); } async function findInputReaderPrograms(message) { const input = message.text || ""; if (message.type == 'protocol') return []; const testResult = await Promise.all( inputReaderPrograms.map((program) => { return (async () => { let passed = false; if (program.options?.selfRespond && !message.sender.isMe) return false; if (!program.options?.selfRespond && message.sender.isMe) return false if (!program.options?.includeBlacklist && message.sender.isBlacklisted) return false; if (program.options?.from?.length) { if (!program.options.from.includes(message.sender.id)) return false; } if (program.options?.notFrom?.length) { if (program.options.notFrom.includes(message.sender.id)) return false; } if (program.options?.in?.length) { if (!program.options.in.includes(message.room)) return false; } if (program.options?.notIn?.length) { if (program.options.notIn.includes(message.room)) return false; } if (program.options?.privateOnly && isJidGroup(message.room)) return false; if ( (program.options?.groupOnly || program.options?.adminOnly) && !isJidGroup(message.room) ) return false; if ( program.options?.adminOnly && !(await checkAdmin(message.sender.id, message.room)) ) return false; if (program.options?.ownerOnly && !message.sender.isOwner) return false if ( program.options?.mentionedOnly && !message.mentions.includes(getId()) ) return false; if (program.options?.quotedOnly && !message.quoted?.sender?.isMe) return false; if (!program.options?.anyInput) { const ignoreCase = program.options?.ignoreCase; const testInput = ignoreCase ? input.toLowerCase() : input; if (program.options?.beginsWith) { for (let i of [program.options.beginsWith].flat()) { if (typeof i != "string") continue; const testI = ignoreCase ? i.toLowerCase() : i; if (testInput.startsWith(testI)) { passed = true; break; } } if (!passed) return false; } if (program.options?.contains) { for (let i of [program.options.contains].flat()) { if (typeof i != "string") continue; const testI = ignoreCase ? i.toLowerCase() : i; if (testInput.includes(testI)) { passed = true; break; } } if (!passed) return false; } if (program.options?.endsWith) { for (let i of [program.options.endsWith].flat()) { if (!i) continue; if (typeof i != "string") continue; const testI = ignoreCase ? i.toLowerCase() : i; if (testInput.endsWith(testI)) { passed = true; break; } } if (!passed) return false; } if (program.input) { for (let i of program.input.flat()) { if (!i) continue; if (i instanceof RegExp) { if (!i.test(input)) continue; else { passed = true; break; } } else if (typeof i != "string") continue; const testI = ignoreCase ? i.toLowerCase() : i; if (program.options?.similarityThreshold) { if ( similarity(testInput, testI) < program.options.similarityThreshold ) continue; else { passed = true; break; } } if (testI == testInput) { passed = true; break; } } if (!passed) return false; } } if (program.options?.conditionFunctions) { for (const f of program.options.conditionFunctions) { console.log("Testing function:\n" + f.toString()); const result = await f(message); console.log("Result:", result); return result; } } if (program.options?.checkVarIs) { const cvi = program.options.checkVarIs; for (let key in cvi) { if (cvi[key] != getVar(key)) return false; } } if (program.options?.checkVarExists) { for (let key of program.options.checkVarExists) { if (botVar[key] == undefined) return false; } } if (program.options?.checkVarNotExists) { for (let key of program.options.checkVarNotExists) { if (botVar[key] != undefined) return false; } } if (program.options?.withImage && !message.image) return false; if (program.options?.withVideo && !message.video) return false; if (program.options?.withGif && !message.gif ) return false; if (program.options?.withAudio && !message.audio) return false; if (program.options?.withSticker && !message.sticker) return false; if (program.options?.withDocument && !message.document) return false; if (program.options?.withContacts && !message.contacts) return false; if (program.options?.withLocation && !message.location) return false; if (program.options?.withPoll && !message.poll) return false; return true; })(); }) ); const p = inputReaderPrograms.filter((_, index) => testResult[index]); return p; } async function getGroupData(jid, raw=false) { getGroupData.cache ??= {}; if (!getGroupData.cache?.[jid]) { try { const result = await queue("socket", null, _ => socket.query({ tag: "iq", attrs: {type: "get", xmlns: "w:g2", to: jid}, content: [{tag:"query", attrs:{request:"interactive"}}] })) // getGroupData.cache[jid] = await queue("socket", null, _ => socket.groupMetadata(jid)) getGroupData.cache[jid] = result } catch (e) {} } return raw ? getGroupData.cache[jid] : extractGroupMetadata(getGroupData.cache[jid]); } async function getEphemeralExpiration(jid) { if (jid.endsWith(GROUP_SUFFIX)) { const data = await getGroupData(jid) return data.ephemeralDuration } else { return } } async function checkAdmin(id, jid) { const groupData = await getGroupData(jid); const participants = groupData.participants; const p = participants.find((par) => par.id == id); if (p?.admin) return true; } async function waitForMessage(jid, options = {}) { if (!jid) throw new Error("Specify JID"); const timeout = options?.timeout || 10000; const filter = options?.filter return new Promise((res) => { const to = setTimeout((_) => { messageCaptureList.splice(mcid, 1); res(); console.log("waitForMessage Timeout!"); }, timeout); const cb = (msg) => { clearInterval(to); res(msg); }; const mcid = messageCaptureList.push({ jid, filter, cb }) - 1; console.log("mcl.length", messageCaptureList.length); console.log({ messageCaptureList }); }); } function getId() { return socket.user.id.replace(/\:\d+/,"") } async function getPPUrl(id) { return queue("socket", null, ()=>socket.profilePictureUrl(id)) } async function getStatus(id) { return queue("socket", null, ()=>socket.fetchStatus(id)) } function createCommandMenu(extraCommands=[]) { const options = bot.settings.commandMenu; const { menuTemplate, categoryTemplate, categorySeparator, commandPrefixes, commandTemplate, commandSeparator, } = options; const commands = inputReaderPrograms .filter((p) => p.options?.command && !p.options?.hideCommand) .map((p) => { return { name: p.options.commandName, description: p.options.commandDescription, category: p.options.commandCategory, }; }).concat(...extraCommands) const categories = {}; for (const c of commands) { categories[c.category] ??= []; categories[c.category].push(c); } return menuTemplate.replace( "@categorylist", Object.keys(categories) .map((c) => { return categoryTemplate.replace("@category", c).replace( "@commandlist", categories[c] .map((cmd) => { return commandTemplate .replace("@prefix", commandPrefixes[0]||"") .replace("@command", cmd.name) .replace("@description", cmd.description); }) .join(commandSeparator) ); }) .join(categorySeparator) ); } function getMentionsFromText(text) { if (!text) return [] return (text.match(/@\d+/g) || []).map((m) => m.slice(1) + "@s.whatsapp.net"); } function stripMentions(message) { let result = message.text for (const m of message.mentions) { const pn = getPushname(m) result = result.replace("@"+pn, "") } return result.trim() } function choose(choices) { return choices[Math.floor(Math.random() * choices.length)]; } function escapeRegExp(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } async function queue(name, input, task) { queue.names ??= {}; queue.run = async function (qu) { if (!qu.running) { qu.running = true; while (qu.list.length) { console.log("Next in queue", name); const next = qu.list.shift(); let result try { result = await next.task(next.input); next.resolve(result); } catch (error) { next.reject(error) } } qu.running = false; } }; const q = (queue.names[name] ??= { list: [], running: false, }); return new Promise((resolve,reject) => { q.list.push({ input, task, resolve, reject }); queue.run(q); }); } var bot = { getSocket, start, test, getConnectionState, receive, receiveBeginning, receiveContaining, receiveEnding, receiveAny, sent, command, started, schedule, removeProgram, setVar, getVar, deleteVar, defaultResponse, sendText, sendImage, sendVideo, sendGif, sendSticker, sendAudio, sendDocument, sendContacts, sendLocation, sendPoll, updateStatus, updateName, checkAdmin, checkOwner, addOwner, removeOwner, addPrefix, removePrefix, getPushnames, getPushname, getId, getPPUrl, getStatus, getGroupData, waitForMessage, blacklistAdd, blacklistRemove, blacklistGetList, blacklistCheck, createCommandMenu, setStoreSize, getMessageFromStore, saveMessageToStore, removeMessageFromStore, settings, saveSettings, debug: { inputReaderPrograms, startupPrograms, scheduledPrograms } }; module.exports = bot;