@aidulcandra/simple-wa-bot
Version:
A baileys wrapper module
1,438 lines (1,296 loc) • 43.2 kB
JavaScript
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;