UNPKG

@aidulcandra/simple-wa-bot

Version:

A baileys wrapper module

612 lines (547 loc) 22.9 kB
const vCard = require("vcard-parser") const {extractMessageContent, decryptPollVote} = require("baileys") const fs = require("fs") const {randomUUID, createHash} = require("crypto") const {createSticker} = require("./utils.js") class Program { constructor (options) { this.output = null this.options = options || {} this.uid = randomUUID() } ifVarIs (name, value) { this.options.checkVarIs ??= {} this.options.checkVarIs[name] = value return this } ifVarExists (name) { this.options.checkVarExists ??= [] this.options.checkVarExists.push(name) return this } ifVarNotExists (name) { this.options.checkVarNotExists ??= [] this.options.checkVarNotExists.push(name) return this } if (conditionFunction) { this.options.conditionFunctions ??= [] this.options.conditionFunctions.push(conditionFunction) return this } text (output) { this.output = output this.options.randomOutput = false return this } textRandom(...output) { this.output = [...output].flat() this.options.randomOutput = true return this } sendMenu () { this.options.sendMenu = true return this } image (link) { this.options.image = link return this } video (link) { this.options.video = link return this } gif (link) { this.options.gif = link return this } audio (link, mimetype) { this.options.audio = link this.options.audioType = mimetype return this } sticker (link, options) { this.options.sticker = { link, ...options } return this } document (link, mimetype, fileName) { this.options.document = link this.options.documentType = mimetype this.options.documentFileName = fileName return this } contacts (contacts) { this.options.contacts = contacts return this } location (latitude=0, longitude=0) { this.options.location = {latitude,longitude} return this } poll (title, choices, multiple = false) { this.options.poll = {title, choices, multiple} return this } showTyping () { this.options.showTyping = true return this } showRecording () { this.options.showRecording = true return this } run (f, fname) { this.options.run = f this.options.runFunctionName = fname return this } stopHere () { this.options.stopHere = true return this } setVar (name, value) { this.options.setVar ??= {} this.options.setVar[name] = value return this } blacklist (ids) { this.options.blacklist = ids return this } whitelist (ids) { this.options.whitelist = ids return this } to (ids) { this.options.to = ids return this } } class InputReaderProgram extends Program { constructor (input, options) { super(options) this.input = input } privateOnly () { this.options.privateOnly = true this.options.groupOnly = false this.options.adminOnly = false return this } groupOnly () { this.options.groupOnly = true this.options.privateOnly = false return this } adminOnly () { this.options.privateOnly = false this.options.groupOnly = true this.options.adminOnly = true return this } ownerOnly () { this.options.ownerOnly = true return this } mentionedOnly () { this.options.mentionedOnly = true return this } quotedOnly () { this.options.quotedOnly = true return this } in (...i) { const ids = i.flat() this.options.in = ids return this } from (...i) { const ids = i.flat() this.options.from = ids return this } notIn (...i) { const ids = i.flat() this.options.notIn = ids return this } notFrom (...i) { const ids = i.flat() this.options.notFrom = ids return this } includeBlacklist () { this.options.includeBlacklist = true return this } similarity (value) { this.options.similarityThreshold = value return this } ignoreCase () { this.options.ignoreCase = true return this } withImage () { this.options.withImage = true return this } withVideo () { this.options.withVideo = true return this } withGif () { this.options.withGif = true return this } withAudio () { this.options.withAudio = true return this } withSticker () { this.options.withSticker = true return this } withDocument () { this.options.withDocument = true return this } withContacts () { this.options.withContacts = true return this } withLocation () { this.options.withLocation = true return this } withPoll () { this.options.withPoll = true return this } reply (output) { this.output = output this.options.reply = true this.options.randomOutput = false return this } replyRandom (...output) { this.output = [output].flat() this.options.reply = true this.options.randomOutput = true return this } react (emoji) { this.options.react = emoji return this } forward () { this.options.forward = true return this } delete () { this.options.delete = true return this } read () { this.options.read = true return this } stripMentions () { this.options.stripMentions = true return this } hideCommand() { this.options.hideCommand = true return this } } class StartupProgram extends Program { constructor (options) { super(options) } } class ScheduledProgram extends Program { constructor (time, options) { super(options) this.time = time } } class Message { constructor (message) { const realMessage = extractMessageContent( message.message?.protocolMessage?.editedMessage || message.message?.groupMentionedMessage?.message || message.message?.viewOnceMessage?.message || message.message?.viewOnceMessageV2?.message || message.message ) this.id = message.key?.id this.text = realMessage?.conversation || realMessage?.extendedTextMessage?.text || realMessage?.documentMessage?.caption || realMessage?.imageMessage?.caption || realMessage?.liveLocationMessage?.caption || realMessage?.videoMessage?.caption || realMessage?.reactionMessage?.text || realMessage?.groupMentionedMessage?.text || realMessage?.pollCreationMessage?.name || realMessage?.pollCreationMessageV2?.name || realMessage?.pollCreationMessageV3?.name || realMessage?.pollCreationMessageV4?.name || realMessage?.pollCreationMessageV5?.name this.saveTextTo = path => fs.promises.writeFile(path, this.text||"") this.isEdit = Boolean(message.message?.protocolMessage?.editedMessage) const messageObjectNames = Object.keys(realMessage||{}) .filter(k=>k.includes("Message")) for (const name of messageObjectNames) { if (realMessage?.[name]?.contextInfo?.quotedMessage) { const remoteJid = message.key.remoteJid const participant = realMessage[name].contextInfo.participant || remoteJid this.quoted = Message.from({ key: { remoteJid, fromMe: participant == classOptions.myId, // id: realMessage[name].contextInfo.stanzaId, participant, }, messageTimestamp: 0, // message:realMessage[name].contextInfo.quotedMessage, broadcast: false, // pushName: classOptions.helperFunctions.getPushname(realMessage[name].contextInfo.participant), }) this.getQuoted = () => classOptions.helperFunctions.getMessageFromStore(realMessage[name].contextInfo.stanzaId, remoteJid) || this.quoted } if (realMessage?.[name]?.contextInfo?.expiration) this.disappearingTime = realMessage[name].contextInfo.expiration if (realMessage?.protocolMessage) this.protocolData = {} if (realMessage?.protocolMessage?.ephemeralExpiration) { this.protocolData.chatDisappearingTime = realMessage.protocolMessage.ephemeralExpiration } } this.room = message.key.remoteJid const senderId = message.key.fromMe ? classOptions.myId : (message.key.participant || message.key.remoteJid) this.sender = { id: senderId, isMe: message.key.fromMe, name: message.key.fromMe ? classOptions.helperFunctions.getBotName() : message.pushName, isBlacklisted: classOptions.helperFunctions.blacklistCheck(senderId), isOwner: classOptions.helperFunctions.isOwner(senderId), getPPUrl: ()=>classOptions.helperFunctions.getPPUrl(senderId) } this.type = realMessage?.conversation ? 'text' : realMessage?.extendedTextMessage ? 'text' : realMessage?.documentMessage ? 'document' : realMessage?.imageMessage ? 'image' : realMessage?.locationMessage ? 'location' : realMessage?.liveLocationMessage ? 'livelocation' : realMessage?.videoMessage?.gifPlayback ? 'gif' : realMessage?.videoMessage ? 'video' : realMessage?.audioMessage ? 'audio' : realMessage?.stickerMessage ? 'sticker' : realMessage?.contactMessage ? 'contacts' : realMessage?.contactsArrayMessage ? 'contacts' : realMessage?.reactionMessage ? 'reaction' : realMessage?.pollCreationMessage ? 'poll' : realMessage?.pollCreationMessageV2 ? 'poll' : realMessage?.pollCreationMessageV3 ? 'poll' : realMessage?.pollCreationMessageV4 ? 'poll' : realMessage?.pollCreationMessageV5 ? 'poll' : realMessage?.pollUpdateMessage ? 'vote' : realMessage?.protocolMessage ? 'protocol' : Object.keys(realMessage||{}).filter(k=>!['key'].includes(k))?.[0] this.isMedia = ['image','video','gif','audio','sticker','document'].includes(this.type) if (this.isMedia) { const mediaObject = { getBuffer:()=>classOptions.helperFunctions.downloadMediaMessage(message,'buffer',{},{reuploadRequest:classOptions.socket.updateMediaMessage}), getStream:()=>classOptions.helperFunctions.downloadMediaMessage(message,'stream',{},{reuploadRequest:classOptions.socket.updateMediaMessage}), saveTo: path=>classOptions.helperFunctions.saveMedia(message, path) } if (this.type == 'sticker') realMessage.stickerMessage.url = "https://mmg.whatsapp.net" + realMessage.stickerMessage.directPath + "&mms3=true" if (this.type == 'audio') mediaObject.duration = realMessage?.audioMessage?.seconds this[this.type] = mediaObject } if (this.type == 'contacts') { const parseVcard = (vc) => { const data = vCard.parse(vc) const name = data.fn[0].value const number = data.tel[0].value return {name, number} } this.contacts = [realMessage.contactMessage?.vcard || realMessage.contactsArrayMessage?.contacts.map(c=>c.vcard)].flat().filter(x=>x).map(parseVcard) } if (this.type == 'location') { this.location = { latitude:realMessage.locationMessage?.degreesLatitude, longitude:realMessage.locationMessage?.degreesLongitude } } if (this.type == 'reaction') { this.reactedId = realMessage?.reactionMessage?.key?.id } if (this.type == 'poll') { const pm = realMessage?.pollCreationMessage || realMessage?.pollCreationMessageV2 || realMessage?.pollCreationMessageV3 || realMessage?.pollCreationMessageV4 || realMessage?.pollCreationMessageV5 this.poll = { title: pm?.name, choices: pm?.options?.map(o=>o.optionName) || [], multiple: pm?.selectableOptionsCount != 1, votes: {} } for (const c of this.poll.choices) this.poll.votes[c] = [] } if (this.type == 'vote') { const pcmKey = realMessage?.pollUpdateMessage?.pollCreationMessageKey const pollCreationMessage = classOptions.helperFunctions.getMessageFromStore(pcmKey.id, pcmKey.remoteJid) if (!pollCreationMessage) { console.log(`Poll message not found in store for this vote`) this.vote = null } else { const pcmBM = pollCreationMessage.getBaileysMessage() const pollContext = { pollCreatorJid: realMessage?.pollUpdateMessage?.pollCreationMessageKey?.participant, pollMsgId: realMessage?.pollUpdateMessage?.pollCreationMessageKey?.id, pollEncKey: pcmBM.message?.messageContextInfo?.messageSecret, voterJid: message.key?.participant } const pcm = pcmBM.message?.pollCreationMessage || pcmBM.message?.pollCreationMessageV2 || pcmBM.message?.pollCreationMessageV3 || pcmBM.message?.pollCreationMessageV4 || pcmBM.message?.pollCreationMessageV5 const voteMsg = decryptPollVote(realMessage?.pollUpdateMessage?.vote, pollContext) const options = pcm?.options?.map(o=>o.optionName) this.vote = { pollId: pcmBM.key.id, list: voteMsg?.selectedOptions?.map(o=>options?.find(opt=>hashPollOption(opt) == o.toString('base64'))) } const votes = pollCreationMessage?.poll?.votes const choices = pollCreationMessage?.poll?.choices for (const c of choices) { if (this.vote.list.includes(c)) { if (!votes[c].includes(this.sender.id)) votes[c].push(this.sender.id) } else { if (votes[c].includes(this.sender.id)) votes[c].splice(votes[c].indexOf(this.sender.id), 1) } } } } this.mentions = realMessage?.extendedTextMessage?.contextInfo?.mentionedJid || [] this.groupMentions = realMessage?.extendedTextMessage?.contextInfo?.groupMentions || [] // this.baileysMessage = message this.getBaileysMessage = ()=>message } async reply (text) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const mentions = classOptions.helperFunctions.getMentionsFromText(text) return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {text, mentions}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyImage (source, caption) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const media = Buffer.isBuffer(source) ? source : {url:source} const mentions = classOptions.helperFunctions.getMentionsFromText(caption) return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {image:media, caption, mentions}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyVideo (source, caption) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const media = Buffer.isBuffer(source) ? source : {url:source} const mentions = classOptions.helperFunctions.getMentionsFromText(caption) return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {video:media, caption, mentions}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyGif (source, caption) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const media = Buffer.isBuffer(source) ? source : {url:source} const mentions = classOptions.helperFunctions.getMentionsFromText(caption) return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {video:media, caption, gifPlayback:true, mentions}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyAudio (source, mimetype) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {audio:media, mimetype:mimetype||'audio/mp4'}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replySticker (source, options) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) // 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 classOptions.helperFunctions.sendMessage(this.room, sticker, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyDocument(source, mimetype, fileName, caption) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const media = Buffer.isBuffer(source) ? source : {url:source} return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {document:media, mimetype, fileName, caption}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyContacts(contacts) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) 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 classOptions.helperFunctions.sendMessage(this.room, {contacts:{contacts:vcards}}, {quoted:this.getBaileysMessage(), ephemeralExpiration})) } async replyLocation(latitude=0, longitude=0) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const location = {degreesLatitude:latitude, degreesLongitude:longitude} return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {location}, {ephemeralExpiration})) } async replyPoll(title, choices, multiple = false) { const ephemeralExpiration = await classOptions.helperFunctions.getEphemeralExpiration(this.room) const poll = {name: title, values: choices, selectableCount: multiple ? 0 : 1} return createMessage( await classOptions.helperFunctions.sendMessage(this.room, {poll}, {quoted:this.getBaileysMessage(), ephemeralExpiration}) ) } async edit (text) { const mentions = await classOptions.helperFunctions.getMentionsFromText(text) return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {text, edit:this.getBaileysMessage().key, mentions})) } async forward (to) { return createMessage(await classOptions.helperFunctions.sendMessage(to, {forward:this.getBaileysMessage()})) } async react (emoji) { return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {react:{text:emoji, key:this.getBaileysMessage().key}})) } async delete () { return createMessage(await classOptions.helperFunctions.sendMessage(this.room, {delete:this.getBaileysMessage().key})) } async read () { return classOptions.helperFunctions.readMessages([this.getBaileysMessage().key]) } saveToStore () { return classOptions.helperFunctions.saveMessageToStore(this) } removeFromStore () { return classOptions.helperFunctions.removeMessageFromStore(this) } static from (message) { return new Message(message) } } const classOptions = { socket: null, helperFunctions: {} } async function createMessage(message) { const m = new Message(message) if (m.room.endsWith("@g.us") && m.sender.id.endsWith("@lid")) { const gd = await classOptions.helperFunctions.getGroupData(m.room, true) m.sender.lid = m.sender.id m.sender.id = gd?.content?.[0]?.content?.filter(c=>c.tag=="participant")?.find(c=>c.attrs.jid==m.sender.id)?.attrs?.phone_number } if (m.type=="poll") m.saveToStore() return m } function hashPollOption (name) { return createHash('sha256').update(Buffer.from(name)).digest().toString('base64') } module.exports = {InputReaderProgram, StartupProgram, ScheduledProgram, Message, createMessage, classOptions}