From feac79b44a8920fc1dad3048d5688f7f73d14c2d Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 25 Jan 2024 12:55:51 -0300 Subject: [PATCH 01/79] chore: bump versions --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 703c0c8..569ae46 100644 --- a/package.json +++ b/package.json @@ -15,15 +15,15 @@ }, "license": "unlicense", "dependencies": { - "change-case": "^5.3.0", + "change-case": "^5.4.2", "citty": "^0.1.5", "dayjs": "^1.11.10", - "dotenv": "^16.3.1", + "dotenv": "^16.4.1", "filenamify": "^6.0.0", "form-data": "^4.0.0", "mime-types": "^2.1.35", "node-cron": "^3.0.3", - "openai": "^4.24.1", + "openai": "^4.25.0", "pino": "^8.17.2", "pino-pretty": "^10.3.1", "qrcode-terminal": "^0.12.0", From a5c533a3860d5e24789540087849e45cfc4a80d9 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 25 Jan 2024 13:08:05 -0300 Subject: [PATCH 02/79] feat: change use baileys over wwebjs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 569ae46..332b596 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "license": "unlicense", "dependencies": { + "@whiskeysockets/baileys": "^6.6.0", "change-case": "^5.4.2", "citty": "^0.1.5", "dayjs": "^1.11.10", @@ -29,7 +30,6 @@ "qrcode-terminal": "^0.12.0", "qs": "^6.11.2", "sharp": "^0.32.6", - "whatsapp-web.js": "^1.23.0", "yargs": "^17.7.2" }, "devDependencies": { From a9ad2a10591ac7b6e9ca17804f45fb12db0e9d2b Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 25 Jan 2024 17:21:46 -0300 Subject: [PATCH 03/79] feat: move temp folder --- .gitignore | 7 +++---- src/services/functions/miscellaneous.js | 2 +- {temp => src/temp}/.gitkeep | 0 3 files changed, 4 insertions(+), 5 deletions(-) rename {temp => src/temp}/.gitkeep (100%) diff --git a/.gitignore b/.gitignore index 355f242..7b15b96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,10 @@ node_modules -.wwebjs_auth -.wwebjs_cache +auth_info_baileys .env package-lock.json errors.log -temp/* -!temp/.gitkeep \ No newline at end of file +src/temp/* +!src/temp/.gitkeep \ No newline at end of file diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index c97f375..06e90f4 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -302,7 +302,7 @@ export async function transcribe (msg) { // save file to temp folder const timestampish = Date.now().toString().slice(-10) - const filePath = `./temp/${timestampish}.mp3` + const filePath = `./src/temp/${timestampish}.mp3` const nomalizedFilePath = path.resolve(filePath) fs.writeFileSync(nomalizedFilePath, media.data, { encoding: 'base64' }) diff --git a/temp/.gitkeep b/src/temp/.gitkeep similarity index 100% rename from temp/.gitkeep rename to src/temp/.gitkeep From 434c6e512605c51257a5bc21d2e14b5dcafdd3d9 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 25 Jan 2024 17:22:15 -0300 Subject: [PATCH 04/79] feat: make baileys MVP --- package.json | 3 +- src/index.js | 207 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 125 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 332b596..d50e967 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deadbyte-v3", - "version": "3.0.0", + "version": "3.5.0", "description": "deadbyte 3rd generation", "author": "sergiooak", "main": "src/index.js", @@ -23,6 +23,7 @@ "filenamify": "^6.0.0", "form-data": "^4.0.0", "mime-types": "^2.1.35", + "node-cache": "^5.1.2", "node-cron": "^3.0.3", "openai": "^4.25.0", "pino": "^8.17.2", diff --git a/src/index.js b/src/index.js index 4015d3d..2d92009 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,13 @@ import 'dotenv/config' +import * as baileys from '@whiskeysockets/baileys' import { defineCommand, runMain } from 'citty' import { apiKey } from './config/api.js' -import { snakeCase } from 'change-case' -import wwebjs from 'whatsapp-web.js' +// import { snakeCase } from 'change-case' +import NodeCache from 'node-cache' import bot from './config/bot.js' import logger from './logger.js' -import fs from 'fs/promises' +// import fs from 'fs/promises' import './db.js' let globalArgs = {} @@ -22,34 +23,62 @@ const main = defineCommand({ type: 'positional', description: 'Bot name unique per session' }, - stickerOnly: { + sticker: { type: 'boolean', description: 'Deactivate all commands and only listen to stickers' }, - showBrowser: { + 'no-store': { type: 'boolean', - description: 'Deactivate headless mode and show the browser' + description: 'Do not store session data' }, - dummy: { + 'no-reply': { type: 'boolean', description: 'Do not reply to messages' + }, + 'use-pairing-code': { + type: 'boolean', + description: 'Use pairing code instead of QR code' + }, + mobile: { + type: 'boolean', + description: 'Use mobile user agent' } }, run ({ args }) { globalArgs = args bot.name = args.name logger.info(`Starting bot "${args.name}"`) - bot.headless = !args.showBrowser ? 'new' : false - logger.info(`Headless mode: ${bot.headless ? 'on' : 'off'}`) + bot.useStore = !args['no-store'] + logger.info(`Store mode: ${bot.useStore ? 'on' : 'off'}`) + bot.doReplies = !args['no-reply'] + logger.info(`Reply messages: ${bot.doReplies ? 'on' : 'off'}`) + + bot.usePairingCode = args['use-pairing-code'] + bot.useMobile = args.mobile + bot.mode = 'qr' + if (bot.usePairingCode) bot.mode = 'pairing' + if (bot.useMobile) bot.mode = 'mobile' + logger.info(`Mode: ${bot.mode}`) + bot.stickerOnly = args.stickerOnly logger.info(`Sticker only mode: ${bot.stickerOnly ? 'on' : 'off'}`) - bot.dummy = args.dummy - logger.info(`Dummy mode: ${bot.dummy ? 'on' : 'off'}`) - loadEvents() + // the store maintains the data of the WA connection in memory + // can be written out to a file & read from it + const store = bot.useStore ? baileys.makeInMemoryStore({ logger }) : undefined + + const storePath = `./src/temp/${bot.name}.json` + store.readFromFile(storePath) + // save every 10s + setInterval(() => { + store.writeToFile(storePath) + }, 10_000) + + connectToWhatsApp() } }) +const store = undefined runMain(main) /** @@ -60,93 +89,103 @@ export function getArgs () { return globalArgs } +// external map to store retry counts of messages when decryption/encryption fails +// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts +const msgRetryCounterCache = new NodeCache() + /** * Whatsapp Web Client * @type {wwebjs.Client} */ -let client = null +// let client = null /** * Grabs Whatsapp Web Client * @returns {wwebjs.Client} */ -export function getClient () { - return client -} +// export function getClient () { +// return client +// } + +async function connectToWhatsApp () { + // if no API KEY, kill the process + if (!apiKey) { + logger.fatal('API_KEY not found! Grab one at https://api.deadbyte.com.br') + process.exit(1) + } + + logger.info('Connecting to WhatsApp...') + const { state, saveCreds } = await baileys.useMultiFileAuthState('auth_info_baileys') + // fetch latest version of WA Web + // const { version, isLatest } = await baileys.fetchLatestBaileysVersion() + const { version, isLatest } = await baileys.fetchLatestBaileysVersion() + logger.info(`Using WA v${version.join('.')}, isLatest: ${isLatest}`) + + const sock = baileys.makeWASocket({ + version, + logger, + printQRInTerminal: !bot.usePairingCode, + mobile: bot.useMobile, + auth: { + creds: state.creds, + /** caching makes the store faster to send/recv messages */ + keys: baileys.makeCacheableSignalKeyStore(state.keys, logger) + }, + msgRetryCounterCache, + generateHighQualityLinkPreview: true, + shouldIgnoreJid: jid => baileys.isJidBroadcast(jid), + getMessage + }) + + store?.bind(sock.ev) -async function loadEvents () { logger.info('Loading events...', bot) - client = new wwebjs.Client({ - authStrategy: new wwebjs.LocalAuth({ - clientId: bot.name - }), - - puppeteer: { - headless: bot.headless, - executablePath: bot.chromePath, - args: [ - '--lang=pt-BR,pt', - '--autoplay-policy=user-gesture-required', - '--disable-background-timer-throttling', - '--disable-backgrounding-occluded-windows', - '--disable-breakpad', - '--disable-client-side-phishing-detection', - '--disable-component-update', - '--disable-default-apps', - '--disable-dev-shm-usage', - '--disable-domain-reliability', - '--disable-extensions', - '--disable-features=AudioServiceOutOfProcess', - '--disable-hang-monitor', - '--disable-ipc-flooding-protection', - '--disable-notifications', - '--disable-offer-store-unmasked-wallet-cards', - '--disable-popup-blocking', - '--disable-print-preview', - '--disable-prompt-on-repost', - '--disable-renderer-backgrounding', - '--disable-setuid-sandbox', - '--disable-speech-api', - '--disable-sync', - '--hide-scrollbars', - '--ignore-gpu-blacklist', - '--metrics-recording-only', - '--no-default-browser-check', - '--no-first-run', - '--no-pings', - '--no-sandbox', - '--no-zygote', - '--password-store=basic', - '--use-gl=swiftshader', - '--use-mock-keychain', - '--disable-web-security', - '--disable-accelerated-2d-canvas', - '--disable-accelerated-jpeg-decoding', - '--disable-features=Translate', - '--disable-features=site-per-process', - '--disable-features=IsolateOrigins', - '--disable-site-isolation-trials', - '--disable-software-rasterizer' - ] + + sock.ev.on('creds.update', saveCreds) + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect } = update + if (connection === 'close') { + const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== baileys.DisconnectReason.loggedOut + console.log('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) + // reconnect if not logged out + if (shouldReconnect) { + connectToWhatsApp() + } + } else if (connection === 'open') { + console.log('opened connection') } }) - const events = await fs.readdir('./src/services/events') - // if not in dummy mode, load all events - if (!bot.dummy) { - events.forEach(async event => { - const eventModule = await import(`./services/events/${event}`) - const eventName = snakeCase(event.split('.')[0]) - logger.info(`Loading event ${eventName} from file ${event}`) - client.on(eventName, eventModule.default) - }) - } - client.initialize() + sock.ev.on('messages.upsert', async m => { + console.log(JSON.stringify(m, undefined, 2)) + + const msg = m.messages[0] + + if (!msg.key.fromMe) { + const sender = msg.key.remoteJid + console.log('replying to', sender) + await sock.sendMessage(sender, { + text: msg.message?.conversation || 'Hello there!' + }) + } + }) + logger.info('Client initialized!') - // if no API KEY, kill the process - if (!apiKey) { - logger.fatal('API_KEY not found! Grab one at https://api.deadbyte.com.br') - process.exit(1) + return sock + + /** + * Retrieves a message from the store based on the provided key. + * @param {import('@whiskeysockets/baileys').WAMessageKey} key - The key of the message to retrieve. + * @returns {Promise} The retrieved message content, or undefined if not found. + */ + async function getMessage (key) { + if (store) { + const msg = await store.loadMessage(key.remoteJid, key.id) + return msg?.message || undefined + } + + // only if store is present + return baileys.proto.Message.fromObject({}) } } From 5fd90b262ad12ff605ab793bf3ee5f75fd968b48 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 25 Jan 2024 17:31:04 -0300 Subject: [PATCH 05/79] chore: move out old events --- src/services/{events => events.old}/call.js | 0 src/services/{events => events.old}/message.js | 0 src/services/{events => events.old}/qr.js | 0 src/services/{events => events.old}/ready.js | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/services/{events => events.old}/call.js (100%) rename src/services/{events => events.old}/message.js (100%) rename src/services/{events => events.old}/qr.js (100%) rename src/services/{events => events.old}/ready.js (100%) diff --git a/src/services/events/call.js b/src/services/events.old/call.js similarity index 100% rename from src/services/events/call.js rename to src/services/events.old/call.js diff --git a/src/services/events/message.js b/src/services/events.old/message.js similarity index 100% rename from src/services/events/message.js rename to src/services/events.old/message.js diff --git a/src/services/events/qr.js b/src/services/events.old/qr.js similarity index 100% rename from src/services/events/qr.js rename to src/services/events.old/qr.js diff --git a/src/services/events/ready.js b/src/services/events.old/ready.js similarity index 100% rename from src/services/events/ready.js rename to src/services/events.old/ready.js From 3c92286a71cb40e4c1687269d73fc899f9ab61f5 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 12:05:51 -0300 Subject: [PATCH 06/79] chore: move events to independent files --- src/index.js | 89 ++++++++++++++----------- src/services/events/call.js | 56 ++++++++++++++++ src/services/events/connectionUpdate.js | 22 ++++++ src/services/events/credsUpdate.js | 12 ++++ src/services/events/messagesUpsert.js | 19 ++++++ src/types.d.ts | 51 +------------- 6 files changed, 162 insertions(+), 87 deletions(-) create mode 100644 src/services/events/call.js create mode 100644 src/services/events/connectionUpdate.js create mode 100644 src/services/events/credsUpdate.js create mode 100644 src/services/events/messagesUpsert.js diff --git a/src/index.js b/src/index.js index 2d92009..943abec 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,11 @@ import 'dotenv/config' import * as baileys from '@whiskeysockets/baileys' import { defineCommand, runMain } from 'citty' import { apiKey } from './config/api.js' -// import { snakeCase } from 'change-case' +import { dotCase } from 'change-case' import NodeCache from 'node-cache' import bot from './config/bot.js' import logger from './logger.js' -// import fs from 'fs/promises' +import fs from 'fs/promises' import './db.js' let globalArgs = {} @@ -94,20 +94,20 @@ export function getArgs () { const msgRetryCounterCache = new NodeCache() /** - * Whatsapp Web Client - * @type {wwebjs.Client} + * Baileys socket or null if not connected + * @type {import('./types').WSocket} */ -// let client = null +let sock = null /** - * Grabs Whatsapp Web Client - * @returns {wwebjs.Client} + * Grabs the socket + * @returns {import('./types').WSocket} */ -// export function getClient () { -// return client -// } +export function getSocket () { + return sock +} -async function connectToWhatsApp () { +export async function connectToWhatsApp () { // if no API KEY, kill the process if (!apiKey) { logger.fatal('API_KEY not found! Grab one at https://api.deadbyte.com.br') @@ -115,13 +115,13 @@ async function connectToWhatsApp () { } logger.info('Connecting to WhatsApp...') - const { state, saveCreds } = await baileys.useMultiFileAuthState('auth_info_baileys') + const { state } = await baileys.useMultiFileAuthState('auth_info_baileys') // fetch latest version of WA Web // const { version, isLatest } = await baileys.fetchLatestBaileysVersion() const { version, isLatest } = await baileys.fetchLatestBaileysVersion() logger.info(`Using WA v${version.join('.')}, isLatest: ${isLatest}`) - const sock = baileys.makeWASocket({ + sock = baileys.makeWASocket({ version, logger, printQRInTerminal: !bot.usePairingCode, @@ -141,33 +141,44 @@ async function connectToWhatsApp () { logger.info('Loading events...', bot) - sock.ev.on('creds.update', saveCreds) - sock.ev.on('connection.update', (update) => { - const { connection, lastDisconnect } = update - if (connection === 'close') { - const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== baileys.DisconnectReason.loggedOut - console.log('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) - // reconnect if not logged out - if (shouldReconnect) { - connectToWhatsApp() - } - } else if (connection === 'open') { - console.log('opened connection') - } - }) - sock.ev.on('messages.upsert', async m => { - console.log(JSON.stringify(m, undefined, 2)) - - const msg = m.messages[0] + const events = await fs.readdir('./src/services/events') + console.log(events, bot.doReplies) + if (bot.doReplies) { + events.forEach(async event => { + const eventModule = await import(`./services/events/${event}`) + const eventName = dotCase(event.split('.')[0]) + logger.info(`Loading event ${eventName} from file ${event}`) + sock.ev.on(eventName, eventModule.default) + }) + } - if (!msg.key.fromMe) { - const sender = msg.key.remoteJid - console.log('replying to', sender) - await sock.sendMessage(sender, { - text: msg.message?.conversation || 'Hello there!' - }) - } - }) + // sock.ev.on('creds.update', saveCreds) + // sock.ev.on('connection.update', (update) => { + // const { connection, lastDisconnect } = update + // if (connection === 'close') { + // const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== baileys.DisconnectReason.loggedOut + // console.log('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) + // // reconnect if not logged out + // if (shouldReconnect) { + // connectToWhatsApp() + // } + // } else if (connection === 'open') { + // console.log('opened connection') + // } + // }) + // sock.ev.on('messages.upsert', async m => { + // console.log(JSON.stringify(m, undefined, 2)) + + // const msg = m.messages[0] + + // if (!msg.key.fromMe) { + // const sender = msg.key.remoteJid + // console.log('replying to', sender) + // await sock.sendMessage(sender, { + // text: msg.message?.conversation || 'Hello there!' + // }) + // } + // }) logger.info('Client initialized!') diff --git a/src/services/events/call.js b/src/services/events/call.js new file mode 100644 index 0000000..ebcaa29 --- /dev/null +++ b/src/services/events/call.js @@ -0,0 +1,56 @@ +import logger from '../../logger.js' +import { getSocket } from '../../index.js' +import spintax from '../../utils/spintax.js' + +// user is key, value is an object with the time of the warning and the number of warnings +const warnings = {} +const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) + +/** + * Receive an update on a call, including when the call was received, rejected, accepted + * @param {import('@whiskeysockets/baileys').BaileysEventMap['call']} event + */ +export default async (event) => { + const emoji = '📞' + const call = event[0] + if (call.status === 'offer') { + logger.info(`${emoji} - Incoming call`, call) + const sock = getSocket() + await sock.rejectCall(call.id, call.from) + + if (call.isGroup) return // Ignore group calls + + if (!warnings[call.from]) { + warnings[call.from] = { + time: Date.now(), + count: 1 + } + let message = '⚠️ - ' + message += '{Por favor, não ligue|Por favor, evite ligar|Peço que não ligue} para o bot!\n' + message += ' {Desculpe|Peço desculpas} se {você ligou por|se foi} engano, {irei|vou} {relevar|deixar passar|não irei fazer nada} {desta|dessa} vez, ' + message += '{mas|porém} {da|na} próxima vez, você {será bloqueado(a)|levará block}!' + return await sock.sendMessage(call.from, { text: spintax(message) }) + } + + if (warnings[call.from].count === 1) { + warnings[call.from].count++ + let message = '🚨 - ' + message += '{{Já|Eu já} {te|lhe} avisei uma vez|Você já foi avisado(a)|Mais uma vez}, não ligue para o bot!!!\n' + message += '\n{{Este|Esse} é o seu *último aviso*|Essa é a sua *última chance*}, {da próxima vez|na próxima|se ligar novamente} *você {será bloqueado|levará block}*!' + return await sock.sendMessage(call.from, { text: spintax(message) }) + } + + if (warnings[call.from].count === 2) { + warnings[call.from].count++ + let message = '🚫 - ' + message += '{Atenção!|Você} {foi *bloqueado*|levou um *block*}!\n' + message += '{Se|Caso} você {acha|acredita|acredite} que {foi {um|algum}|tenha ocorrido algum} {erro|engano|equívoco} ' + message += 'entre em contato com o desenvolvedor do bot' + await sock.sendMessage(call.from, { text: spintax(message) }) + await wait(5000) // wait 5 seconds to garantee the message is sent before blocking + + warnings[call.from] = undefined // remove from warnings + await sock.updateBlockStatus(call.from, 'block') + } + } +} diff --git a/src/services/events/connectionUpdate.js b/src/services/events/connectionUpdate.js new file mode 100644 index 0000000..7192ad3 --- /dev/null +++ b/src/services/events/connectionUpdate.js @@ -0,0 +1,22 @@ +import logger from '../../logger.js' +import { DisconnectReason } from '@whiskeysockets/baileys' +import { connectToWhatsApp } from '../../index.js' + +/** + * Connection state has been updated -- WS closed, opened, connecting etc. + * @param {import('@whiskeysockets/baileys').BaileysEventMap['connection.update']} update + */ +export default async (update) => { + logger.info('Connection updated', update) + const { connection, lastDisconnect } = update + if (connection === 'close') { + const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== DisconnectReason.loggedOut + logger.warn('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) + // reconnect if not logged out + if (shouldReconnect) { + connectToWhatsApp() + } + } else if (connection === 'open') { + logger.warn('opened connection') + } +} diff --git a/src/services/events/credsUpdate.js b/src/services/events/credsUpdate.js new file mode 100644 index 0000000..a636232 --- /dev/null +++ b/src/services/events/credsUpdate.js @@ -0,0 +1,12 @@ +import logger from '../../logger.js' +import { useMultiFileAuthState } from '@whiskeysockets/baileys' + +/** + * Credentials update event + * @param {import('@whiskeysockets/baileys').BaileysEventMap['creds.update']} event + */ +export default async (event) => { + logger.info('Credentials updated', event) + const { saveCreds } = await useMultiFileAuthState('auth_info_baileys') + saveCreds(event) +} diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js new file mode 100644 index 0000000..8fd3c2e --- /dev/null +++ b/src/services/events/messagesUpsert.js @@ -0,0 +1,19 @@ +import { getSocket } from '../../index.js' +// import logger from '../../logger.js' + +/** + * Add/update the given messages. If they were received while the connection was online, the update will have type: "notify" + * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.upsert']} upsert + */ +export default async (upsert) => { + const sock = getSocket() + const msg = upsert.messages[0] + console.log('upsert', upsert) + // if (!msg.key.fromMe) { + // const sender = msg.key.remoteJid + // console.log('replying to', sender) + // await sock.sendMessage(sender, { + // text: msg.message?.conversation || 'Hello there!' + // }) + // } +} diff --git a/src/types.d.ts b/src/types.d.ts index 283c33a..0233002 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,51 +1,6 @@ -import { Client, Chat, Contact, Message, GroupParticipant } from 'whatsapp-web.js'; +import { makeWASocket } from '@whiskeysockets/baileys'; /** - * Auxiliar message data + * Baileys socket */ -export interface AuxiliarMessageData { - /** The client */ - client: Client; - /** The chat */ - chat: Chat; - /** The sender */ - sender: Contact; - /** If the sender is the bot */ - senderIsMe: boolean; - /** If the message mentions the bot */ - mentionedMe: boolean; - /** The original message */ - originalMsg: Message; - /** The message history */ - history: Message[]; - /** The original message body */ - originalBody: string; - /** If the message is a function */ - isFunction: boolean; - /** The message prefix */ - prefix: string; - /** The message function */ - function: string; - /** If the original message is a function */ - hasOriginalFunction: boolean; - /** The original message function */ - originalFunction: string; - /** The bot id */ - me: string; - /** The mentions */ - mentions: string[]; - /** If the bot is mentioned */ - amIMentioned: boolean; - /** The chat participants */ - participants: GroupParticipant[]; - /** The chat admins */ - admins: string[]; - /** If the sender is admin */ - isSenderAdmin: boolean; - /** If the bot is admin */ - isBotAdmin: boolean; - /** If the chat is the sticker group */ - isStickerGroup: boolean; -} - -export type WWebJSMessage = Message & { aux: AuxiliarMessageData }; \ No newline at end of file +export interface WSocket extends ReturnType { } \ No newline at end of file From 9dcfc9775cd22a0452bbc32c6889738152b013ac Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 15:47:24 -0300 Subject: [PATCH 07/79] feat: load events on the fly --- src/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 943abec..7f98e95 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import 'dotenv/config' +import importFresh from './utils/importFresh.js' import * as baileys from '@whiskeysockets/baileys' import { defineCommand, runMain } from 'citty' import { apiKey } from './config/api.js' @@ -145,10 +146,13 @@ export async function connectToWhatsApp () { console.log(events, bot.doReplies) if (bot.doReplies) { events.forEach(async event => { - const eventModule = await import(`./services/events/${event}`) + const eventPath = `services/events/${event}` const eventName = dotCase(event.split('.')[0]) logger.info(`Loading event ${eventName} from file ${event}`) - sock.ev.on(eventName, eventModule.default) + sock.ev.on(eventName, async (event) => { + const module = await importFresh(eventPath) + module.default(event) + }) }) } From f7072408ea1d24a230c3417d9a23fe409e919e82 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 15:48:22 -0300 Subject: [PATCH 08/79] fix: trace events instead of logger.info --- src/services/events/connectionUpdate.js | 2 +- src/services/events/credsUpdate.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/events/connectionUpdate.js b/src/services/events/connectionUpdate.js index 7192ad3..7b43d63 100644 --- a/src/services/events/connectionUpdate.js +++ b/src/services/events/connectionUpdate.js @@ -7,7 +7,7 @@ import { connectToWhatsApp } from '../../index.js' * @param {import('@whiskeysockets/baileys').BaileysEventMap['connection.update']} update */ export default async (update) => { - logger.info('Connection updated', update) + logger.trace('Connection updated', update) const { connection, lastDisconnect } = update if (connection === 'close') { const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== DisconnectReason.loggedOut diff --git a/src/services/events/credsUpdate.js b/src/services/events/credsUpdate.js index a636232..df47b79 100644 --- a/src/services/events/credsUpdate.js +++ b/src/services/events/credsUpdate.js @@ -6,7 +6,7 @@ import { useMultiFileAuthState } from '@whiskeysockets/baileys' * @param {import('@whiskeysockets/baileys').BaileysEventMap['creds.update']} event */ export default async (event) => { - logger.info('Credentials updated', event) + logger.trace('Credentials updated', event) const { saveCreds } = await useMultiFileAuthState('auth_info_baileys') saveCreds(event) } From eeb046fa924bdd228dd4b616614d594d4bf85b33 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 15:51:34 -0300 Subject: [PATCH 09/79] feat: parse message types --- src/services/events/messagesUpsert.js | 79 ++++++++++++-- src/validators/messageType.js | 142 ++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 11 deletions(-) create mode 100644 src/validators/messageType.js diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 8fd3c2e..eedc897 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -1,19 +1,76 @@ +import importFresh from '../../utils/importFresh.js' +import spintax from '../../utils/spintax.js' import { getSocket } from '../../index.js' -// import logger from '../../logger.js' +import logger from '../../logger.js' +// +// ================================ Variables ================================= +// +const okTypes = [ + 'chat', + 'audio', + 'ptt', + 'image', + 'video', + 'document', + 'sticker', + 'revoked', + 'groups_v4_invite', + 'reaction', + 'edited' +] +// +// ================================ Main Function ============================= +// /** * Add/update the given messages. If they were received while the connection was online, the update will have type: "notify" * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.upsert']} upsert */ export default async (upsert) => { - const sock = getSocket() - const msg = upsert.messages[0] - console.log('upsert', upsert) - // if (!msg.key.fromMe) { - // const sender = msg.key.remoteJid - // console.log('replying to', sender) - // await sock.sendMessage(sender, { - // text: msg.message?.conversation || 'Hello there!' - // }) - // } + logger.trace('messages.upsert', upsert) + let msg = upsert.messages[0] + if (msg.key.fromMe) return // ignore self messages + + const messageType = await importFresh('validators/messageType.js') + const { type, editedMsg } = messageType.default(msg) + msg = editedMsg || msg + if (!msg.key.fromMe) { + if (!okTypes.includes(type)) return + + const sender = msg.key.remoteJid + const sock = getSocket() + if (type === 'revoked') { + // TODO: send random "Deus viu o que você apagou" sticker + await sock.sendMessage(sender, { + react: { + text: '👀', + key: msg.key + } + }) + return await sock.sendMessage(sender, { + text: '👀' + }) + } + if (type === 'edited') { + // TODO: send random "Mensagem editada" sticker + await sock.sendMessage(sender, { + react: { + text: '👀', + key: msg.key + } + }) + return await sock.sendMessage(sender, { + text: spintax( + '👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}' + ) + }, { + quoted: msg + }) + } + await sock.sendMessage(sender, { + text: type + }, { + quoted: msg + }) + } } diff --git a/src/validators/messageType.js b/src/validators/messageType.js new file mode 100644 index 0000000..f993931 --- /dev/null +++ b/src/validators/messageType.js @@ -0,0 +1,142 @@ +import logger from '../logger.js' +// +// ================================ Variables ================================= +// +const types = { + conversation: 'chat', + audioMessage: 'audio', + pttMessage: 'ptt', + imageMessage: 'image', + videoMessage: 'video', + documentMessage: 'document', + stickerMessage: 'sticker', + locationMessage: 'location', + liveLocationMessage: 'live_location', + contactMessage: 'vcard', + contactsArrayMessage: 'multi_vcard', + orderMessage: 'order', + REVOKE: 'revoked', + productMessage: 'product', + UNKNOWN: 'unknown', + groupInviteMessage: 'groups_v4_invite', + listMessage: 'list', + listResponseMessage: 'list_response', + buttonsMessage: 'buttons', + buttonsResponseMessage: 'buttons_response', + sendPaymentMessage: 'payment', + requestPaymentMessage: 'payment', + declinePaymentRequestMessage: 'payment', + cancelPaymentRequestMessage: 'payment', + interactiveMessage: 'interactive', + interactiveResponseMessage: 'interactive_response', + protocolMessage: 'protocol', + reactionMessage: 'reaction', + templateButtonReplyMessage: 'template_button_reply', + pollCreationMessage: 'poll_creation', + pollUpdateMessage: 'poll_update', + editedMessage: 'edited' +} +// +// ================================ Main Functions ================================= +// +/** + * Detect message type using the same pattern as WWebJS + * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg + */ +export default (msg) => { + const keysToIgnore = ['messageContextInfo'] + const keys = Object.keys(msg.message) + .filter(key => !keysToIgnore.includes(key)) + if (keys.length === 0) return types.UNKNOWN + if (keys.length > 1) { + logger.warn('Message has more than one key', msg) + } + const firstKey = keys[0] + let incomingType = firstKey + + /** + * Handle editMessage + */ + if (incomingType === 'editedMessage') { + console.log('editedMessage') + } + + /** + * Handle viewOnceMessage + */ + if (incomingType.startsWith('viewOnce')) { + const keys = Object.keys(msg.message[firstKey].message).filter(key => !keysToIgnore.includes(key)) + if (keys.length) { + incomingType = keys[0] + msg.message = msg.message[firstKey].message + } + } + + /** + * Check if audioMessage is ptt + */ + if (incomingType === 'audioMessage') { + incomingType = parseAudioTypes(msg) + } + + /** + * Parse protocolMessage + */ + if (incomingType === 'protocolMessage') { + incomingType = parseProtocolTypes(msg.message.protocolMessage.type) + } + + const typeName = types[incomingType] || types.UNKNOWN + logger.trace('messageType', { incomingType, typeName }) + return { typeName, msg } +} + +// +// ================================== Helper Functions ================================== +// +/** + * Parse audio types + * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg + */ +function parseAudioTypes (msg) { + const audioType = msg.message.audioMessage.ptt ? 'pttMessage' : 'audioMessage' + if (audioType === 'pttMessage') return audioType + + // if it is audio, and still can be a forwarded ppt + if (msg.message.audioMessage.contextInfo?.isForwarded) { + const hasWaveform = !!msg.message.audioMessage.waveform + if (hasWaveform) return 'pttMessage' + + const mimetype = msg.message.audioMessage.mimetype + const isOpus = mimetype === 'audio/ogg; codecs=opus' + if (isOpus) return 'pttMessage' + } + + return audioType +} + +/** + * Parse protocol types + * @param {number} type + */ +function parseProtocolTypes (type) { + const types = { + 0: 'REVOKE', + 3: 'EPHEMERAL_SETTING', + 4: 'EPHEMERAL_SYNC_RESPONSE', + 5: 'HISTORY_SYNC_NOTIFICATION', + 6: 'APP_STATE_SYNC_KEY_SHARE', + 7: 'APP_STATE_SYNC_KEY_REQUEST', + 8: 'MSG_FANOUT_BACKFILL_REQUEST', + 9: 'INITIAL_SECURITY_NOTIFICATION_SETTING_SYNC', + 10: 'APP_STATE_FATAL_EXCEPTION_NOTIFICATION', + 11: 'SHARE_PHONE_NUMBER', + 14: 'MESSAGE_EDIT', + 16: 'PEER_DATA_OPERATION_REQUEST_MESSAGE', + 17: 'PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE', + 18: 'REQUEST_WELCOME_MESSAGE', + 19: 'BOT_FEEDBACK_MESSAGE', + 20: 'MEDIA_NOTIFY_MESSAGE' + } + return types[type] || 'protocolMessage' +} From 49bd6a7503272f08e165a2b9c95a3d81838bc048 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 16:23:21 -0300 Subject: [PATCH 10/79] feat: create message meta middleware --- src/meta/message.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/meta/message.js diff --git a/src/meta/message.js b/src/meta/message.js new file mode 100644 index 0000000..520306d --- /dev/null +++ b/src/meta/message.js @@ -0,0 +1,39 @@ +import { getSocket } from '../index.js' +// +// ================================ Variables ================================= +// +let sock = null +// +// ================================ Main Functions ================================= +// +/** + * Inject functions into the message object to be drop in replacement for wwebjs + * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg + */ +export default (msg) => { + sock = getSocket() + msg.react = react + return msg +} + +// +// ================================== Helper Functions ================================== +// +/** + * React to this message with an emoji + * @param {string} reaction Emoji to react with. Send an empty string to remove the reaction. + * @returns {Promise} + */ +async function react (reaction) { + /** + * Message object itself + * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} + */ + const msg = this + await sock.sendMessage(msg.key.remoteJid, { + react: { + text: reaction, + key: msg.key + } + }) +} From f64a64d2f98c652687fc3ddf0a19b2828b85f5e3 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 16:23:53 -0300 Subject: [PATCH 11/79] fix: update message object --- src/services/events/messagesUpsert.js | 8 ++++---- src/validators/messageType.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index eedc897..177ec12 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -28,12 +28,12 @@ const okTypes = [ */ export default async (upsert) => { logger.trace('messages.upsert', upsert) - let msg = upsert.messages[0] + const meta = await importFresh('meta/message.js') + let msg = meta.default(upsert.messages[0]) if (msg.key.fromMe) return // ignore self messages - const messageType = await importFresh('validators/messageType.js') - const { type, editedMsg } = messageType.default(msg) - msg = editedMsg || msg + const { type, updatedMsg } = messageType.default(msg) + msg = updatedMsg if (!msg.key.fromMe) { if (!okTypes.includes(type)) return diff --git a/src/validators/messageType.js b/src/validators/messageType.js index f993931..e3a1302 100644 --- a/src/validators/messageType.js +++ b/src/validators/messageType.js @@ -88,7 +88,7 @@ export default (msg) => { const typeName = types[incomingType] || types.UNKNOWN logger.trace('messageType', { incomingType, typeName }) - return { typeName, msg } + return { type: typeName, updatedMsg: msg } } // From 5b42458bdb9c9ea702e91c951f1f5b873d4ee700 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 21:01:33 -0300 Subject: [PATCH 12/79] chore: remove comments and logs --- src/index.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/index.js b/src/index.js index 7f98e95..252e67e 100644 --- a/src/index.js +++ b/src/index.js @@ -143,7 +143,6 @@ export async function connectToWhatsApp () { logger.info('Loading events...', bot) const events = await fs.readdir('./src/services/events') - console.log(events, bot.doReplies) if (bot.doReplies) { events.forEach(async event => { const eventPath = `services/events/${event}` @@ -156,34 +155,6 @@ export async function connectToWhatsApp () { }) } - // sock.ev.on('creds.update', saveCreds) - // sock.ev.on('connection.update', (update) => { - // const { connection, lastDisconnect } = update - // if (connection === 'close') { - // const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== baileys.DisconnectReason.loggedOut - // console.log('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) - // // reconnect if not logged out - // if (shouldReconnect) { - // connectToWhatsApp() - // } - // } else if (connection === 'open') { - // console.log('opened connection') - // } - // }) - // sock.ev.on('messages.upsert', async m => { - // console.log(JSON.stringify(m, undefined, 2)) - - // const msg = m.messages[0] - - // if (!msg.key.fromMe) { - // const sender = msg.key.remoteJid - // console.log('replying to', sender) - // await sock.sendMessage(sender, { - // text: msg.message?.conversation || 'Hello there!' - // }) - // } - // }) - logger.info('Client initialized!') return sock From 19fdb29ecbd24cd2751300ca245c8a7edcdb6a5e Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 21:01:55 -0300 Subject: [PATCH 13/79] fix: better extraction of message type --- src/validators/messageType.js | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/validators/messageType.js b/src/validators/messageType.js index e3a1302..8aec19d 100644 --- a/src/validators/messageType.js +++ b/src/validators/messageType.js @@ -4,6 +4,7 @@ import logger from '../logger.js' // const types = { conversation: 'chat', + extendedTextMessage: 'chat', audioMessage: 'audio', pttMessage: 'ptt', imageMessage: 'image', @@ -48,23 +49,37 @@ export default (msg) => { const keys = Object.keys(msg.message) .filter(key => !keysToIgnore.includes(key)) if (keys.length === 0) return types.UNKNOWN - if (keys.length > 1) { - logger.warn('Message has more than one key', msg) - } + const firstKey = keys[0] let incomingType = firstKey - /** - * Handle editMessage - */ - if (incomingType === 'editedMessage') { - console.log('editedMessage') + if (keys.length > 1) { + // senderKeyDistributionMessage + if (keys.includes('senderKeyDistributionMessage')) { + // delete this key and continue from msg.message + delete msg.message.senderKeyDistributionMessage + // and make sure that the other key is the FIRST key of msg.message + const newMessage = {} + const leftOverKeys = Object.keys(msg.message) + const keyName = keys[1] + incomingType = keyName + newMessage[keyName] = msg.message[keyName] + leftOverKeys.forEach(key => { + if (key !== keyName) newMessage[key] = msg.message[key] + }) + msg.message = newMessage + } else { + logger.warn('Message has more than one key', msg) + } } /** - * Handle viewOnceMessage + * Handle viewOnceMessage && groupMentionedMessage */ - if (incomingType.startsWith('viewOnce')) { + if ( + incomingType.startsWith('viewOnce') || + incomingType === 'groupMentionedMessage' + ) { const keys = Object.keys(msg.message[firstKey].message).filter(key => !keysToIgnore.includes(key)) if (keys.length) { incomingType = keys[0] From 812bba7e98179ceaa358bab01ebfc768b6fb513c Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 21:02:23 -0300 Subject: [PATCH 14/79] feat: add middleware to parsw message like wwebjs --- src/meta/message.js | 186 +++++++++++++++++++++++++- src/services/events/messagesUpsert.js | 62 +++------ 2 files changed, 199 insertions(+), 49 deletions(-) diff --git a/src/meta/message.js b/src/meta/message.js index 520306d..bbac7ef 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -1,7 +1,21 @@ +import messageTypeValidator from '../validators/messageType.js' +import spintax from '../utils/spintax.js' import { getSocket } from '../index.js' +import fetch from 'node-fetch' +import logger from '../logger.js' +import relativeTime from 'dayjs/plugin/relativeTime.js' +import 'dayjs/locale/pt-br.js' +import dayjs from 'dayjs' + // // ================================ Variables ================================= // +dayjs.locale('pt-br') +dayjs.extend(relativeTime) +/** + * Socket instance + * @type {import('@whiskeysockets/baileys').Baileys} + */ let sock = null // // ================================ Main Functions ================================= @@ -10,14 +24,96 @@ let sock = null * Inject functions into the message object to be drop in replacement for wwebjs * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg */ -export default (msg) => { +const processMessage = (msg) => { + if (!msg?.message) return false + const { type, updatedMsg } = messageTypeValidator(msg) + msg = updatedMsg + sock = getSocket() - msg.react = react - return msg + + const newMsgObject = {} + + const firstKey = Object.keys(msg.message)[0] + const firstItem = msg.message[firstKey] + + const quotedDeep = structuredClone(msg) + quotedDeep.message = firstItem.contextInfo?.quotedMessage + + const body = typeof firstItem === 'string' + ? firstItem + : firstItem.caption || firstItem.text || '' + + const properties = { + id: msg.key.id, + type, + duration: firstItem.seconds, + from: msg.key.remoteJid, + fromMe: msg.key.fromMe, + body, + // ack: undefined, + author: undefined, + broadcast: msg.broadcast, + // deviceType: undefined, + fowardScore: firstItem.contextInfo?.forwardingScore, + isForwarded: firstItem.contextInfo?.isForwarded, + hasMedia: !!firstItem.mediaKey, + mediaKey: firstItem.mediaKey, + hasQuotedMsg: !!firstItem.contextInfo?.quotedMessage, + quotedMsg: firstItem.contextInfo?.quotedMessage, // TODO: Fix this + // hasReaction: undefined, + inviteV4: type === 'groups_v4_invite' ? firstItem : undefined, + isEphemeral: !!firstItem.contextInfo?.expiration, + isGif: !!firstItem.gifPlayback, + // isStarred: undefined, + // isStatus: undefined, + links: extractLinks(body), + location: ['location', 'live_location'].includes(type) + ? firstItem + : undefined, + mentionedIds: firstItem.contextInfo?.mentionedJid, + mentionedGroups: firstItem.contextInfo?.groupMentions, + // orderId: undefined, + // timestamp: msg.messageTimestamp, + timestamp: typeof msg.messageTimestamp === 'number' + ? msg.messageTimestamp + : msg.messageTimestamp.toInt(), + timestampIso: dayjs(msg.messageTimestamp * 1000).toISOString(), + // lag is the difference between local time and the time of the sender in ms + // using dayjs to convert + lag: dayjs().diff(dayjs(msg.messageTimestamp * 1000), 'ms'), + // to: msg.key.fromMe ? msg.key.remoteJid : botId, + vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined, + raw: structuredClone(msg), + client: sock + } + + for (const property in properties) { + newMsgObject[property] = properties[property] + } + + const methods = { + react, + reply, + sendSeen + } + + for (const method in methods) { + newMsgObject[method] = methods[method].bind(msg) + } + + // remove undefined properties + for (const property in newMsgObject) { + if (newMsgObject[property] === undefined) delete newMsgObject[property] + } + + logger.trace('newMsgObject', newMsgObject) + return newMsgObject } +export default processMessage + // -// ================================== Helper Functions ================================== +// ================================== Methods ================================== // /** * React to this message with an emoji @@ -32,8 +128,88 @@ async function react (reaction) { const msg = this await sock.sendMessage(msg.key.remoteJid, { react: { - text: reaction, + text: spintax(reaction), key: msg.key } }) } + +/** + * Sends a message as a reply to this message. If chatId is specified, it will be sent through the specified Chat. If not, it will send the message in the same Chat as the original message was sent. + * @param {string} content The message to send + * @param {string} [chatId] The chat to send the message in + * @param {import('@whiskeysockets/baileys').proto.IMessageOptions} [options] Additional options + * @returns {Promise} + */ +async function reply (content, chatId, options) { + // TODO: Add better support for media messages + /** + * Message object itself + * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} + */ + const msg = this + await sock.sendMessage(chatId || msg.key.remoteJid, { + text: spintax(content) + }, { + quoted: msg + }) +} + +/** + * Marks this message as seen + * @returns {Promise} + */ +async function sendSeen () { + /** + * Message object itself + * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} + */ + const msg = this + await sock.readMessages([msg.key]) +} +// +// ================================== Helper Functions ================================== +// +/** + * Extracts valid links from a string + * @param {string} string String to extract links from + * @returns {{link: string, isSuspicious: boolean, isValid: Promise}[]} + */ +function extractLinks (string) { + const regex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g + + const links = string.match(regex) + + const responseArray = [] + + if (links) { + for (const link of links) { + if (link.includes('@')) continue + // regex with suspicious characters for a url + const suspiciousCharacters = /[<>{}|\\^~\[\]`]/g + const isSuspicious = suspiciousCharacters.test(link) + + const isValid = new Promise((resolve, reject) => { + setTimeout(() => { + fetch('https://' + link, { + method: 'HEAD' + }) + .then(res => resolve(res.ok)) + .catch(error => { + logger.trace('extractLinks ERROR', { error }) + resolve(false) + }) + }, 2000) + }) + + responseArray.push({ + link, + isSuspicious, + isValid + }) + } + } + + if (!responseArray.length) return undefined + return responseArray +} diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 177ec12..c478a4b 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -1,7 +1,6 @@ import importFresh from '../../utils/importFresh.js' -import spintax from '../../utils/spintax.js' -import { getSocket } from '../../index.js' import logger from '../../logger.js' +import { getSocket } from '../../index.js' // // ================================ Variables ================================= // @@ -29,48 +28,23 @@ const okTypes = [ export default async (upsert) => { logger.trace('messages.upsert', upsert) const meta = await importFresh('meta/message.js') - let msg = meta.default(upsert.messages[0]) - if (msg.key.fromMe) return // ignore self messages - const messageType = await importFresh('validators/messageType.js') - const { type, updatedMsg } = messageType.default(msg) - msg = updatedMsg - if (!msg.key.fromMe) { - if (!okTypes.includes(type)) return + const msg = meta.default(upsert.messages[0]) + if (!msg) return + if (msg.fromMe) return // ignore self messages + + if (!okTypes.includes(msg.type)) return + await msg.sendSeen() - const sender = msg.key.remoteJid - const sock = getSocket() - if (type === 'revoked') { - // TODO: send random "Deus viu o que você apagou" sticker - await sock.sendMessage(sender, { - react: { - text: '👀', - key: msg.key - } - }) - return await sock.sendMessage(sender, { - text: '👀' - }) - } - if (type === 'edited') { - // TODO: send random "Mensagem editada" sticker - await sock.sendMessage(sender, { - react: { - text: '👀', - key: msg.key - } - }) - return await sock.sendMessage(sender, { - text: spintax( - '👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}' - ) - }, { - quoted: msg - }) - } - await sock.sendMessage(sender, { - text: type - }, { - quoted: msg - }) + if (msg.type === 'revoked') { + // TODO: send random "Deus viu o que você apagou" sticker + return await msg.reply('👀 - Eu vi o que você apagou') } + if (msg.type === 'edited') { + await msg.react('👀') + return await msg.reply('👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}') + } + await msg.reply(msg.type) + + const sock = getSocket() + await sock.sendPresenceUpdate('available') } From eede36fe7b71b9c70849c57e9ea1889dc584ace0 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:56:17 -0300 Subject: [PATCH 15/79] chore: new dependencies --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index d50e967..52ef427 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,13 @@ "dayjs": "^1.11.10", "dotenv": "^16.4.1", "filenamify": "^6.0.0", + "fluent-ffmpeg": "^2.1.2", "form-data": "^4.0.0", + "link-preview-js": "^3.0.5", "mime-types": "^2.1.35", "node-cache": "^5.1.2", "node-cron": "^3.0.3", + "node-webpmux": "^3.2.0", "openai": "^4.25.0", "pino": "^8.17.2", "pino-pretty": "^10.3.1", From 01aa51a4092c5513612c6e29b6b7e55c645feb42 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:56:40 -0300 Subject: [PATCH 16/79] feat: add startedAt property to processMessage function --- src/meta/message.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meta/message.js b/src/meta/message.js index bbac7ef..a415a3e 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -73,7 +73,7 @@ const processMessage = (msg) => { mentionedIds: firstItem.contextInfo?.mentionedJid, mentionedGroups: firstItem.contextInfo?.groupMentions, // orderId: undefined, - // timestamp: msg.messageTimestamp, + startedAt: Date.now(), timestamp: typeof msg.messageTimestamp === 'number' ? msg.messageTimestamp : msg.messageTimestamp.toInt(), @@ -84,7 +84,7 @@ const processMessage = (msg) => { // to: msg.key.fromMe ? msg.key.remoteJid : botId, vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined, raw: structuredClone(msg), - client: sock + sock } for (const property in properties) { From 114f02ac427a36b73587eb3833509aca054033a7 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:57:30 -0300 Subject: [PATCH 17/79] fix: disable command function temporary --- src/services/commands/groups.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/services/commands/groups.js b/src/services/commands/groups.js index ab4d939..d47b04d 100644 --- a/src/services/commands/groups.js +++ b/src/services/commands/groups.js @@ -5,14 +5,15 @@ */ export default (msg) => { return { - ban: msg.aux.chat.isGroup && /^(ban)$/.test(msg.aux.function), - promote: msg.aux.chat.isGroup && /^(promote|promove|promover)$/.test(msg.aux.function), - demote: msg.aux.chat.isGroup && /^(demote|rebaixa|rebaixar)$/.test(msg.aux.function), - giveaway: msg.aux.chat.isGroup && /^(sorteio|sortear)$/.test(msg.aux.function), - 'giveaway-admins-only': msg.aux.chat.isGroup && /^(sorteioadm|sortearadm)$/.test(msg.aux.function), - 'mark-all-members': msg.aux.chat.isGroup && /^(todos|all|hiddenmention)$/.test(msg.aux.function), - 'call-admins': msg.aux.chat.isGroup && /^(adm|adms|admins)$/.test(msg.aux.function), - 'close-group': msg.aux.chat.isGroup && /^(close|fechar)$/.test(msg.aux.function), - 'open-group': msg.aux.chat.isGroup && /^(open|abrir)$/.test(msg.aux.function) + debug: false + // ban: msg.aux.chat.isGroup && /^(ban)$/.test(msg.aux.function), + // promote: msg.aux.chat.isGroup && /^(promote|promove|promover)$/.test(msg.aux.function), + // demote: msg.aux.chat.isGroup && /^(demote|rebaixa|rebaixar)$/.test(msg.aux.function), + // giveaway: msg.aux.chat.isGroup && /^(sorteio|sortear)$/.test(msg.aux.function), + // 'giveaway-admins-only': msg.aux.chat.isGroup && /^(sorteioadm|sortearadm)$/.test(msg.aux.function), + // 'mark-all-members': msg.aux.chat.isGroup && /^(todos|all|hiddenmention)$/.test(msg.aux.function), + // 'call-admins': msg.aux.chat.isGroup && /^(adm|adms|admins)$/.test(msg.aux.function), + // 'close-group': msg.aux.chat.isGroup && /^(close|fechar)$/.test(msg.aux.function), + // 'open-group': msg.aux.chat.isGroup && /^(open|abrir)$/.test(msg.aux.function) } } From 786bd5c2df6de9f4f0c3c2281a36a786a7e45dd5 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:57:57 -0300 Subject: [PATCH 18/79] feat: implement mvp functions --- src/services/events/messagesUpsert.js | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index c478a4b..711da3d 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -1,6 +1,8 @@ import importFresh from '../../utils/importFresh.js' -import logger from '../../logger.js' +// import { saveActionToDB } from '../../db.js' import { getSocket } from '../../index.js' +import { camelCase } from 'change-case' +import logger from '../../logger.js' // // ================================ Variables ================================= // @@ -43,8 +45,34 @@ export default async (upsert) => { await msg.react('👀') return await msg.reply('👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}') } - await msg.reply(msg.type) + // await msg.reply(msg.type) const sock = getSocket() await sock.sendPresenceUpdate('available') + + const messageParser = await importFresh('validators/message.js') + const handlerModule = await messageParser.default(msg) + logger.trace('handlerModule: ', handlerModule) + console.log('handlerModule: ', handlerModule) + + if (!handlerModule) return logger.debug('handlerModule is undefined') + + // TODO: make legacy db works + // msg.aux.db = await saveActionToDB(handlerModule.type, handlerModule.command, msg) + + // only works with db + // const checkDisabled = await importFresh('validators/checkDisabled.js') + // const isEnabled = await checkDisabled.default(msg) + // if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) + + // const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') + // const isOwnerOnly = await checkOwnerOnly.default(msg) + // if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${handlerModule.command} - Restricted to admins`) + + // TODO: implement queue system + const moduleName = handlerModule.type + const functionName = handlerModule.command + const module = await importFresh(`services/functions/${moduleName}.js`) + const camelCaseFunctionName = camelCase(functionName) + module[camelCaseFunctionName](msg) } From f2c0f28bb42422f421731e5960e0b061f152ec66 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:58:31 -0300 Subject: [PATCH 19/79] fix: disable broken commands --- src/services/functions/miscellaneous.js | 342 ++++++++++++------------ src/services/functions/stickers.js | 310 +++++++++++---------- 2 files changed, 340 insertions(+), 312 deletions(-) diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 06e90f4..8c0f6ab 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -1,9 +1,9 @@ -import { getQueueLength } from '../queue.js' +// import { getQueueLength } from '../queue.js' import relativeTime from 'dayjs/plugin/relativeTime.js' import reactions from '../../config/reactions.js' import { createUrl } from '../../config/api.js' import spintax from '../../utils/spintax.js' -import wwebjs from 'whatsapp-web.js' +// import wwebjs from 'whatsapp-web.js' import logger from '../../logger.js' import FormData from 'form-data' import 'dayjs/locale/pt-br.js' @@ -33,10 +33,10 @@ export async function uptime (msg) { const clock = '{⏳|⌚|⏰|⏱️|⏲️|🕰️|🕛|🕧|🕐|🕜|🕑|🕝}' await msg.react(spintax(clock)) // react with random clock emoji - const saudation = `{${spintax(clock)}} - {Olá|Oi|Oie|E aí} ${msg.aux.sender.pushname || 'usuário'} tudo {jóia|bem}?` + const saudation = `{${spintax(clock)}} - {Olá|Oi|Oie|E aí} ${msg.aux?.sender?.pushname || 'usuário'} tudo {jóia|bem}?` const part1 = '{Eu estou|Estou|O bot {está|ta|tá}|O DeadByte {está|ta|tá}} {online|on|ligado}{ direto|} {a|á|tem}{:|} ' - const message = spintax(`${saudation}\n\n${part1}*${uptimeString}*`) + const message = `${saudation}\n\n${part1}*${uptimeString}*` await msg.reply(message) } @@ -49,7 +49,6 @@ export async function react (msg) { const json = await response.json() const emoji = String.fromCodePoint(...json.unicode.map(u => parseInt(u.replace('U+', '0x'), 16))) await msg.react(emoji) - await msg.aux.chat.sendSeen() } /** @@ -93,13 +92,7 @@ export async function dice (msg) { export async function debug (msg) { const debugEmoji = '🐛' await msg.react(debugEmoji) - - const announceGroup = '120363094244463491@g.us' - const chat = await msg.aux.client.getChatById(announceGroup) - const admins = chat.participants.filter(p => p.isAdmin || p.isSuperAdmin).map((p) => p.id._serialized) - const botIsAdmin = admins.includes(msg.aux.me) - - await msg.reply(JSON.stringify(botIsAdmin, null, 2)) + // Debug code goes here } /** @@ -107,50 +100,52 @@ export async function debug (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toFile (msg) { - if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - await msg.react(reactions.error) - - const header = '☠️🤖' - const part1 = 'Para usar o *{!toFile|!arquivo}* você {precisa|tem que}' - const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' - const end = '{!|!!|!!!}' - - const message = spintax(`${header} - ${part1} ${part2}${end}`) - return await msg.reply(message) - } - await msg.react('🗂️') - const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - if (!media) throw new Error('Error downloading media') - - // the last 10 chars of the timestamp - const timestampish = Date.now().toString().slice(-10) - const filename = `deadbyte-${timestampish}.${mime.extension(media.mimetype)}` - media.filename = media.filename || filename - - const buffer = Buffer.from(media.data, 'base64') - - let message = '' - message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' - message += 'o arquivo{ que você {me |}{pediu|enviou}|}!\n\n' - - const isImage = media.mimetype.includes('image') - const isVideo = media.mimetype.includes('video') - // use sharp to check if the image is animated - const isAnimated = isImage ? await sharp(buffer).metadata().then(m => parseInt(m.pages) > 1) : false - - const finalExtension = isImage ? isAnimated ? 'webp' : 'png' : mime.extension(media.mimetype) - - message += `É ${isImage ? 'uma imagem' : isVideo ? 'um vídeo' : 'um arquivo'} ${finalExtension.toUpperCase()}` - message += isImage && isAnimated ? ' animada' : '' - - if (isImage && !isAnimated) { - const converted = await sharp(buffer).toFormat('png').toBuffer() - media.data = converted.toString('base64') - media.mimetype = 'image/png' - media.filename = media.filename.split('.').slice(0, -1).join('.') + '.png' - return await msg.reply(media, undefined, { sendMediaAsDocument: false, caption: spintax(message) }) - } - await msg.reply(media, undefined, { sendMediaAsDocument: true, caption: spintax(message) }) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + // if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { + // await msg.react(reactions.error) + + // const header = '☠️🤖' + // const part1 = 'Para usar o *{!toFile|!arquivo}* você {precisa|tem que}' + // const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' + // const end = '{!|!!|!!!}' + + // const message = spintax(`${header} - ${part1} ${part2}${end}`) + // return await msg.reply(message) + // } + // await msg.react('🗂️') + // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() + // if (!media) throw new Error('Error downloading media') + + // // the last 10 chars of the timestamp + // const timestampish = Date.now().toString().slice(-10) + // const filename = `deadbyte-${timestampish}.${mime.extension(media.mimetype)}` + // media.filename = media.filename || filename + + // const buffer = Buffer.from(media.data, 'base64') + + // let message = '' + // message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' + // message += 'o arquivo{ que você {me |}{pediu|enviou}|}!\n\n' + + // const isImage = media.mimetype.includes('image') + // const isVideo = media.mimetype.includes('video') + // // use sharp to check if the image is animated + // const isAnimated = isImage ? await sharp(buffer).metadata().then(m => parseInt(m.pages) > 1) : false + + // const finalExtension = isImage ? isAnimated ? 'webp' : 'png' : mime.extension(media.mimetype) + + // message += `É ${isImage ? 'uma imagem' : isVideo ? 'um vídeo' : 'um arquivo'} ${finalExtension.toUpperCase()}` + // message += isImage && isAnimated ? ' animada' : '' + + // if (isImage && !isAnimated) { + // const converted = await sharp(buffer).toFormat('png').toBuffer() + // media.data = converted.toString('base64') + // media.mimetype = 'image/png' + // media.filename = media.filename.split('.').slice(0, -1).join('.') + '.png' + // return await msg.reply(media, undefined, { sendMediaAsDocument: false, caption: spintax(message) }) + // } + // await msg.reply(media, undefined, { sendMediaAsDocument: true, caption: spintax(message) }) // TODO convert to webp if animated to mp4 and send as "gif" } @@ -160,30 +155,32 @@ export async function toFile (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toUrl (msg) { - if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - await msg.react(reactions.error) - - const header = '☠️🤖' - const part1 = 'Para usar o *{!toUrl|!url}* você {precisa|tem que}' - const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' - const end = '{!|!!|!!!}' - - const message = spintax(`${header} - ${part1} ${part2}${end}`) - return await msg.reply(message) - } - - await msg.react('🔗') - const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - if (!media) throw new Error('Error downloading media') - const tempUrl = (await getTempUrl(media)).replace('http://', 'https://') - - let message = '🔗 - ' - message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' - message += '{a url temporária|o link temporário|o endereço temporário} ' - message += '{para {o|esse}|desse} arquivo: ' - message += `${tempUrl}\n\n` - message += '{Válido por {apenas|}|Com {validade|vigência} de|Por um período de} {3|03|três} dias' - await msg.reply(spintax(message)) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + // if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { + // await msg.react(reactions.error) + + // const header = '☠️🤖' + // const part1 = 'Para usar o *{!toUrl|!url}* você {precisa|tem que}' + // const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' + // const end = '{!|!!|!!!}' + + // const message = spintax(`${header} - ${part1} ${part2}${end}`) + // return await msg.reply(message) + // } + + // await msg.react('🔗') + // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() + // if (!media) throw new Error('Error downloading media') + // const tempUrl = (await getTempUrl(media)).replace('http://', 'https://') + + // let message = '🔗 - ' + // message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' + // message += '{a url temporária|o link temporário|o endereço temporário} ' + // message += '{para {o|esse}|desse} arquivo: ' + // message += `${tempUrl}\n\n` + // message += '{Válido por {apenas|}|Com {validade|vigência} de|Por um período de} {3|03|três} dias' + // await msg.reply(spintax(message)) } /** @@ -195,15 +192,15 @@ export async function ping (msg) { let message = '🏓 - Pong!\n\n' - const usersInQueue = getQueueLength('user') - const messagesInQueue = getQueueLength('messages') - if (usersInQueue || messagesInQueue) { - message += `{Atualmente|No momento|{Nesse|Neste}{ exato|} momento} tem *${usersInQueue} ${usersInQueue > 1 ? 'usuários' : 'usuário'}* na fila com *${messagesInQueue} ${messagesInQueue > 1 ? 'mensagens' : 'mensagem'}* ao todo!\n\n` - } + // const usersInQueue = getQueueLength('user') + // const messagesInQueue = getQueueLength('messages') + // if (usersInQueue || messagesInQueue) { + // message += `{Atualmente|No momento|{Nesse|Neste}{ exato|} momento} tem *${usersInQueue} ${usersInQueue > 1 ? 'usuários' : 'usuário'}* na fila com *${messagesInQueue} ${messagesInQueue > 1 ? 'mensagens' : 'mensagem'}* ao todo!\n\n` + // } - let lag = msg.lag + console.log(msg.lag) + let lag = msg.lag / 1000 lag = Math.max(lag, 0) // if lag is negative, set it to 0 - lag = lag < 5 ? 0 : lag // ignore lag if it is less than 5 seconds lag = isNaN(lag) ? 0 : lag const ping = Date.now() - msg.startedAt @@ -223,57 +220,59 @@ export async function ping (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function speak (msg) { - let msgToReply = msg - let input = msg.body - if (msg.hasQuotedMsg && !msg.body) { - const quotedMsg = await msg.getQuotedMessage() - input = quotedMsg.body - msgToReply = quotedMsg - } - - if (!input) { - await msg.reply( - spintax('Para usar o *{!speak|!fale|!falar|!voz|!diga|!dizer}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um texto!\n\nExemplo: `!diga Olá, eu sou o DeadByte!`') - ) - await msg.react(reactions.error) - return - } - - const inputLimit = 1000 - const originalInputSize = input.length - - if (originalInputSize > inputLimit) { - await msg.reply(`O texto não pode ter mais de ${inputLimit} caracteres!\n\nO seu texto tem ${originalInputSize} caracteres!\nVou cortar o texto para você!`) - input = input.slice(0, inputLimit) - } - - await msg.react('🗣️') - await msg.aux.chat.sendStateRecording() - - const voices = ['onyx', 'echo', 'fable', 'nova', 'shimmer'] - - // function can be called with !speak1, !speak2, !speak3, !speak4, !speak5 - let voiceId = parseInt(msg.aux.function.slice(-1)) - 1 || 0 - // say if the voice is invalid - if (voiceId > voices.length - 1) { - await msg.reply(`Essa voz não existe!\n\nAs vozes disponíveis são:\n${voices.map((v, i) => `${i + 1} - ${v}`).join('\n')}\n\nIrei usar a voz padrão!`) - voiceId = 0 - } - - const voice = voices[voiceId] - - const opus = await openai.audio.speech.create({ - input, - voice, - model: 'tts-1', - response_format: 'opus' - }) - - const buffer = Buffer.from(await opus.arrayBuffer()) - - // hand make the media object - const media = new wwebjs.MessageMedia('audio/ogg; codecs=opus', buffer.toString('base64'), 'DeadByte.opus') - await msgToReply.reply(media, undefined, { sendAudioAsVoice: true }) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + // let msgToReply = msg + // let input = msg.body + // if (msg.hasQuotedMsg && !msg.body) { + // const quotedMsg = await msg.getQuotedMessage() + // input = quotedMsg.body + // msgToReply = quotedMsg + // } + + // if (!input) { + // await msg.reply( + // spintax('Para usar o *{!speak|!fale|!falar|!voz|!diga|!dizer}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um texto!\n\nExemplo: `!diga Olá, eu sou o DeadByte!`') + // ) + // await msg.react(reactions.error) + // return + // } + + // const inputLimit = 1000 + // const originalInputSize = input.length + + // if (originalInputSize > inputLimit) { + // await msg.reply(`O texto não pode ter mais de ${inputLimit} caracteres!\n\nO seu texto tem ${originalInputSize} caracteres!\nVou cortar o texto para você!`) + // input = input.slice(0, inputLimit) + // } + + // await msg.react('🗣️') + // await msg.aux.chat.sendStateRecording() + + // const voices = ['onyx', 'echo', 'fable', 'nova', 'shimmer'] + + // // function can be called with !speak1, !speak2, !speak3, !speak4, !speak5 + // let voiceId = parseInt(msg.aux.function.slice(-1)) - 1 || 0 + // // say if the voice is invalid + // if (voiceId > voices.length - 1) { + // await msg.reply(`Essa voz não existe!\n\nAs vozes disponíveis são:\n${voices.map((v, i) => `${i + 1} - ${v}`).join('\n')}\n\nIrei usar a voz padrão!`) + // voiceId = 0 + // } + + // const voice = voices[voiceId] + + // const opus = await openai.audio.speech.create({ + // input, + // voice, + // model: 'tts-1', + // response_format: 'opus' + // }) + + // const buffer = Buffer.from(await opus.arrayBuffer()) + + // // hand make the media object + // const media = new wwebjs.MessageMedia('audio/ogg; codecs=opus', buffer.toString('base64'), 'DeadByte.opus') + // await msgToReply.reply(media, undefined, { sendAudioAsVoice: true }) } /** @@ -281,38 +280,41 @@ export async function speak (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function transcribe (msg) { - let msgToReply = msg - let media = msg.hasMedia ? await msg.downloadMedia() : null - if (msg.hasQuotedMsg && !msg.hasMedia) { - const quotedMsg = await msg.getQuotedMessage() - media = await quotedMsg.downloadMedia() - msgToReply = quotedMsg - } - - if (!media) { - await msg.reply( - spintax('Para usar o *{!transcribe|!transcricao|!transcrever}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um áudio!\n\nExemplo: `!transcrever` respondendo um áudio') - ) - await msg.react(reactions.error) - return - } - - await msg.react('🎙️') - await msg.aux.chat.sendStateTyping() - - // save file to temp folder - const timestampish = Date.now().toString().slice(-10) - const filePath = `./src/temp/${timestampish}.mp3` - const nomalizedFilePath = path.resolve(filePath) - fs.writeFileSync(nomalizedFilePath, media.data, { encoding: 'base64' }) - - const transcription = await openai.audio.transcriptions.create({ - file: fs.createReadStream(nomalizedFilePath), - model: 'whisper-1', - response_format: 'text' - }) - fs.unlinkSync(nomalizedFilePath) - await msgToReply.reply(`🎙️ - ${transcription.trim()}`) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + + // let msgToReply = msg + // let media = msg.hasMedia ? await msg.downloadMedia() : null + // if (msg.hasQuotedMsg && !msg.hasMedia) { + // const quotedMsg = await msg.getQuotedMessage() + // media = await quotedMsg.downloadMedia() + // msgToReply = quotedMsg + // } + + // if (!media) { + // await msg.reply( + // spintax('Para usar o *{!transcribe|!transcricao|!transcrever}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um áudio!\n\nExemplo: `!transcrever` respondendo um áudio') + // ) + // await msg.react(reactions.error) + // return + // } + + // await msg.react('🎙️') + // await msg.aux.chat.sendStateTyping() + + // // save file to temp folder + // const timestampish = Date.now().toString().slice(-10) + // const filePath = `./src/temp/${timestampish}.mp3` + // const nomalizedFilePath = path.resolve(filePath) + // fs.writeFileSync(nomalizedFilePath, media.data, { encoding: 'base64' }) + + // const transcription = await openai.audio.transcriptions.create({ + // file: fs.createReadStream(nomalizedFilePath), + // model: 'whisper-1', + // response_format: 'text' + // }) + // fs.unlinkSync(nomalizedFilePath) + // await msgToReply.reply(`🎙️ - ${transcription.trim()}`) } // diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 99ffe30..e87cef2 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -1,4 +1,4 @@ -import wwebjs from 'whatsapp-web.js' +// import wwebjs from 'whatsapp-web.js' import Util from '../../utils/sticker.js' import sharp from 'sharp' import { createUrl } from '../../config/api.js' @@ -7,6 +7,9 @@ import logger from '../../logger.js' import spintax from '../../utils/spintax.js' import fetch from 'node-fetch' import FormData from 'form-data' +import { getSocket } from '../../index.js' + +const sock = getSocket() /** * Make sticker from media (image, video, gif) @@ -16,21 +19,23 @@ import FormData from 'form-data' * @param {string} StickerPack - sticker pack name */ export async function stickerCreator (msg, crop = false, stickerAuthor, stickerPack) { - await msg.react(reactions.wait) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + // await msg.react(reactions.wait) - const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - if (!media) throw new Error('Error downloading media') + // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() + // if (!media) throw new Error('Error downloading media') - let stickerMedia = await Util.formatToWebpSticker(media, {}, crop) - if (msg.type === 'document') msg.body = '' // remove file name from caption - if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia + // let stickerMedia = await Util.formatToWebpSticker(media, {}, crop) + // if (msg.type === 'document') msg.body = '' // remove file name from caption + // if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia - await sendMediaAsSticker(msg.aux.chat, stickerMedia, stickerAuthor, stickerPack) + // await sendMediaAsSticker(msg.aux.chat, stickerMedia, stickerAuthor, stickerPack) - if (!crop) { - await stickerCreator(msg, true) // make cropped version - await msg.react(reactions.success) - } + // if (!crop) { + // await stickerCreator(msg, true) // make cropped version + // await msg.react(reactions.success) + // } } /** @@ -41,11 +46,18 @@ export async function textSticker (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp', { message: msg.body }) + // await msg.reply(url) - const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - if (!media) throw new Error('Error downloading media') + const buffer = await fetch(url).then((res) => res.buffer()) + const webp = await sharp(buffer).webp().toBuffer() + + await sock.sendMessage(msg.from, { + sticker: webp + }) + // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) + // if (!media) throw new Error('Error downloading media') - await sendMediaAsSticker(msg.aux.chat, media) + // await sendMediaAsSticker(msg.aux.chat, media) await msg.react(reactions.success) } @@ -57,11 +69,17 @@ export async function textSticker2 (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp2', { message: msg.body }) + const buffer = await fetch(url).then((res) => res.buffer()) + const webp = await sharp(buffer).webp().toBuffer() - const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - if (!media) throw new Error('Error downloading media') + await sock.sendMessage(msg.from, { + sticker: webp + }) + + // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) + // if (!media) throw new Error('Error downloading media') - await sendMediaAsSticker(msg.aux.chat, media) + // await sendMediaAsSticker(msg.aux.chat, media) await msg.react(reactions.success) } @@ -73,11 +91,17 @@ export async function textSticker3 (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp3', { message: msg.body }) + const buffer = await fetch(url).then((res) => res.buffer()) + const webp = await sharp(buffer).webp().toBuffer() - const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - if (!media) throw new Error('Error downloading media') + await sock.sendMessage(msg.from, { + sticker: webp + }) + + // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) + // if (!media) throw new Error('Error downloading media') - await sendMediaAsSticker(msg.aux.chat, media) + // await sendMediaAsSticker(msg.aux.chat, media) await msg.react(reactions.success) } @@ -87,43 +111,45 @@ export async function textSticker3 (msg) { * */ export async function removeBg (msg) { - await msg.react(reactions.wait) - - if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - await msg.react(reactions.error) - - const header = '☠️🤖' - const part1 = 'Para usar o {remove.bg|removedor de fundo|*!bg*} você {precisa|tem que}' - const part2 = '{enviar|mandar} {esse|o} comando {junto com|na legenda de} uma {imagem|foto}' - const end = '{!|!!|!!!}' - - const message = spintax(`${header} - ${part1} ${part2}${end}`) - return await msg.reply(message) - } - - const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - if (!media) throw new Error('Error downloading media') - if (!media.mimetype.includes('image')) { - await msg.react(reactions.error) - return await msg.reply('❌ Só consigo remover o fundo de imagens') - } - - // use shapr to convert to a max 512 (bigger side) jpg image, crank up the contrast - const buffer = Buffer.from(media.data, 'base64') - const resizedBuffer = await sharp(buffer) - .resize(1024, 1024, { fit: 'inside' }) - .jpeg() - .toBuffer() - - const tempUrl = await getTempUrl(resizedBuffer) - const url = await createUrl('image-processing', 'removebg', { img: tempUrl, trim: true }) - const bgMedia = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - - let stickerMedia = await Util.formatToWebpSticker(bgMedia, {}) - if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia - - await sendMediaAsSticker(msg.aux.chat, stickerMedia) - await msg.react(reactions.success) + await msg.react('❌') + await msg.reply('❌ - Esse comando está desativado no momento!') + // await msg.react(reactions.wait) + + // if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { + // await msg.react(reactions.error) + + // const header = '☠️🤖' + // const part1 = 'Para usar o {remove.bg|removedor de fundo|*!bg*} você {precisa|tem que}' + // const part2 = '{enviar|mandar} {esse|o} comando {junto com|na legenda de} uma {imagem|foto}' + // const end = '{!|!!|!!!}' + + // const message = spintax(`${header} - ${part1} ${part2}${end}`) + // return await msg.reply(message) + // } + + // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() + // if (!media) throw new Error('Error downloading media') + // if (!media.mimetype.includes('image')) { + // await msg.react(reactions.error) + // return await msg.reply('❌ Só consigo remover o fundo de imagens') + // } + + // // use shapr to convert to a max 512 (bigger side) jpg image, crank up the contrast + // const buffer = Buffer.from(media.data, 'base64') + // const resizedBuffer = await sharp(buffer) + // .resize(1024, 1024, { fit: 'inside' }) + // .jpeg() + // .toBuffer() + + // const tempUrl = await getTempUrl(resizedBuffer) + // const url = await createUrl('image-processing', 'removebg', { img: tempUrl, trim: true }) + // const bgMedia = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) + + // let stickerMedia = await Util.formatToWebpSticker(bgMedia, {}) + // if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia + + // await sendMediaAsSticker(msg.aux.chat, stickerMedia) + // await msg.react(reactions.success) } /** @@ -216,7 +242,7 @@ export async function stickerLySearch (msg) { await msg.reply(spintax(message)) } - await sendStickers(stickersPaginated, msg.aux.chat) + // await sendStickers(stickersPaginated, msg.aux.chat) await msg.react(reactions.success) } @@ -273,7 +299,7 @@ export async function stickerLyPack (msg) { await msg.reply(spintax(message)) } - await sendStickers(stickersPaginated, msg.aux.chat) + // await sendStickers(stickersPaginated, msg.aux.chat) await msg.react(reactions.success) } @@ -292,25 +318,25 @@ export async function stickerLyPack (msg) { * @returns {Promise} A Promise that resolves with the Message object of the sent sticker. */ async function sendMediaAsSticker (chat, media, stickerName, stickerAuthor) { - const buffer = Buffer.from(media.data, 'base64') - - // if heavier than 1MB, compress it - if (buffer.byteLength > 1_000_000) { - media = await compressMediaBuffer(buffer) - } - - media = new wwebjs.MessageMedia(media.mimetype || 'image/webp', media.data, media.filename || 'sticker.webp') - - try { - return await chat.sendMessage(media, { - sendMediaAsSticker: true, - stickerName: stickerName || 'DeadByte.com.br', - stickerAuthor: stickerAuthor || 'bot de figurinhas', - stickerCategories: ['💀', '🤖'] - }) - } catch (error) { - logger.error(error) - } + // const buffer = Buffer.from(media.data, 'base64') + + // // if heavier than 1MB, compress it + // if (buffer.byteLength > 1_000_000) { + // media = await compressMediaBuffer(buffer) + // } + + // media = new wwebjs.MessageMedia(media.mimetype || 'image/webp', media.data, media.filename || 'sticker.webp') + + // try { + // return await chat.sendMessage(media, { + // sendMediaAsSticker: true, + // stickerName: stickerName || 'DeadByte.com.br', + // stickerAuthor: stickerAuthor || 'bot de figurinhas', + // stickerCategories: ['💀', '🤖'] + // }) + // } catch (error) { + // logger.error(error) + // } } /** @@ -322,32 +348,32 @@ async function sendMediaAsSticker (chat, media, stickerName, stickerAuthor) { * @returns {Promise} A Promise that resolves with a new MessageMedia object containing the media with the subtitle overlayed. * @throws {Error} If there was an error downloading the subtitle media. */ -async function overlaySubtitle (text, stickerMedia) { - const mediaBuffer = Buffer.from(stickerMedia.data, 'base64') - - const url = await createUrl('image-creator', 'ttp', { - message: text, - subtitle: true - }) - const subtitleMedia = await wwebjs.MessageMedia.fromUrl(url, { - unsafeMime: true - }) - if (!subtitleMedia) throw new Error('Error downloading subtitle media') - - const subtitleBuffer = Buffer.from(subtitleMedia.data, 'base64') - const finalBuffer = await sharp(mediaBuffer, { animated: true }) - .composite([{ - input: subtitleBuffer, - gravity: 'south', - animated: true, - tile: true - }]) - .webp() - .toBuffer() - - // replace media data with the new data from sharp - return new wwebjs.MessageMedia('image/webp', finalBuffer.toString('base64'), 'deadbyte.webp', true) -} +// async function overlaySubtitle (text, stickerMedia) { +// const mediaBuffer = Buffer.from(stickerMedia.data, 'base64') + +// const url = await createUrl('image-creator', 'ttp', { +// message: text, +// subtitle: true +// }) +// const subtitleMedia = await wwebjs.MessageMedia.fromUrl(url, { +// unsafeMime: true +// }) +// if (!subtitleMedia) throw new Error('Error downloading subtitle media') + +// const subtitleBuffer = Buffer.from(subtitleMedia.data, 'base64') +// const finalBuffer = await sharp(mediaBuffer, { animated: true }) +// .composite([{ +// input: subtitleBuffer, +// gravity: 'south', +// animated: true, +// tile: true +// }]) +// .webp() +// .toBuffer() + +// // replace media data with the new data from sharp +// return new wwebjs.MessageMedia('image/webp', finalBuffer.toString('base64'), 'deadbyte.webp', true) +// } /** * Compresses a media buffer for a sticker image. @@ -357,43 +383,43 @@ async function overlaySubtitle (text, stickerMedia) { * @returns {Promise} A Promise that resolves with a compressed MessageMedia object. * @throws {Error} If the compressed buffer is still too heavy. */ -async function compressMediaBuffer (mediaBuffer) { - logger.debug('compressing sticker...', mediaBuffer.byteLength) - const compressedBuffer = await sharp(mediaBuffer, { animated: true }) - .webp({ quality: 33 }) - .toBuffer() - logger.debug('compressed sticker!', mediaBuffer.byteLength, '->', compressedBuffer.byteLength) +// async function compressMediaBuffer (mediaBuffer) { +// logger.debug('compressing sticker...', mediaBuffer.byteLength) +// const compressedBuffer = await sharp(mediaBuffer, { animated: true }) +// .webp({ quality: 33 }) +// .toBuffer() +// logger.debug('compressed sticker!', mediaBuffer.byteLength, '->', compressedBuffer.byteLength) - if (compressedBuffer.byteLength > 1_000_000) throw new Error('Sticker is still too heavy!', mediaBuffer.byteLength) +// if (compressedBuffer.byteLength > 1_000_000) throw new Error('Sticker is still too heavy!', mediaBuffer.byteLength) - return new wwebjs.MessageMedia('image/webp', compressedBuffer.toString('base64'), 'deadbyte.webp', true) -} +// return new wwebjs.MessageMedia('image/webp', compressedBuffer.toString('base64'), 'deadbyte.webp', true) +// } /** * Uploads an image to get a temporary URL * @param {import ('whatsapp-web.js').MessageMedia} media - The media to upload * @returns {promise} A Promise that resolves with the temporary URL of the uploaded image. */ -async function getTempUrl (buffer) { - const formData = new FormData() - formData.append('file', buffer, 'sticker.png') +// async function getTempUrl (buffer) { +// const formData = new FormData() +// formData.append('file', buffer, 'sticker.png') - const url = await createUrl('uploader', 'tempurl', {}) - const response = await fetch(url, { - method: 'POST', - body: formData - }) +// const url = await createUrl('uploader', 'tempurl', {}) +// const response = await fetch(url, { +// method: 'POST', +// body: formData +// }) - if (!response.ok) { - logger.error('Error uploading image to remove.bg') - throw new Error('Error uploading image to remove.bg') - } +// if (!response.ok) { +// logger.error('Error uploading image to remove.bg') +// throw new Error('Error uploading image to remove.bg') +// } - const json = await response.json() - const tempUrl = json.result +// const json = await response.json() +// const tempUrl = json.result - return tempUrl -} +// return tempUrl +// } /** * Get a sticker pack from sticker.ly @@ -557,13 +583,13 @@ function paginateStickers (stickers, cursor, limit) { * @param {Object} chat * @returns {Promise} */ -async function sendStickers (stickersPaginated, chat) { - for (const s of stickersPaginated) { - const media = await wwebjs.MessageMedia.fromUrl(s.url) - await sendMediaAsSticker(chat, media) - await waitRandomTime() - } -} +// async function sendStickers (stickersPaginated, chat) { +// for (const s of stickersPaginated) { +// const media = await wwebjs.MessageMedia.fromUrl(s.url) +// await sendMediaAsSticker(chat, media) +// await waitRandomTime() +// } +// } /** * Wait random time @@ -571,8 +597,8 @@ async function sendStickers (stickersPaginated, chat) { * @param {number} max @default 500 * @returns {Promise} */ -function waitRandomTime (min = 50, max = 500) { - return new Promise((resolve) => { - setTimeout(resolve, Math.random() * (max - min) + min) - }) -} +// function waitRandomTime (min = 50, max = 500) { +// return new Promise((resolve) => { +// setTimeout(resolve, Math.random() * (max - min) + min) +// }) +// } From aa9284a7babb7a4ae8369ea82e501cf284f2c146 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 27 Jan 2024 23:59:02 -0300 Subject: [PATCH 20/79] fix: disable not supported yet validations --- src/validators/message.js | 83 +++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/validators/message.js b/src/validators/message.js index d027fd3..ebafd56 100644 --- a/src/validators/message.js +++ b/src/validators/message.js @@ -30,28 +30,32 @@ const commandless = (msg, aux) => { */ export default async (msg) => { const aux = {} // auxiliar variables - aux.client = (await import('../index.js')).getClient() - aux.chat = await msg.getChat() - aux.sender = await msg.getContact() + // aux.client = (await import('../index.js')).getClient() + // aux.chat = await msg.getChat() + // aux.sender = await msg.getContact() aux.senderIsMe = msg.fromMe - aux.mentionedMe = msg.mentionedIds.includes(aux.client.info.wid._serialized) + aux.me = msg.sock.user.id.split(':')[0] + '@s.whatsapp.net' + aux.mentionedMe = msg.mentionedIds ? msg.mentionedIds.includes(aux.me) : false if (aux.mentionedMe) { - msg.body = msg.body.replace(new RegExp(`@${aux.client.info.wid.user}`, 'g'), '').trim() + msg.body = msg.body.replace(new RegExp(`@${aux.me.split('@')[0]}`, 'g'), '').trim() } - if (msg.hasQuotedMsg) { - aux.quotedMsg = await msg.getQuotedMessage() - } - - let msgCurrent = msg - const msgPrevious = [] - while (msgCurrent.hasQuotedMsg) { - msgPrevious.push(msgCurrent) - msgCurrent = await msgCurrent.getQuotedMessage() - } - aux.originalMsg = msgCurrent - msgPrevious.push(aux.originalMsg) - aux.history = msgPrevious.reverse() + // TODO: create getQuotedMessage() method + // if (msg.hasQuotedMsg) { + // aux.quotedMsg = await msg.getQuotedMessage() + // } + + // let msgCurrent = msg + // const msgPrevious = [] + // while (msgCurrent.hasQuotedMsg) { + // msgPrevious.push(msgCurrent) + // msgCurrent = await msgCurrent.getQuotedMessage() + // } + // aux.originalMsg = msgCurrent + aux.originalMsg = msg + // msgPrevious.push(aux.originalMsg) + // aux.history = msgPrevious.reverse() + aux.history = [aux.originalMsg] // Check if the message is a command const prefixes = await importFresh('config/bot.js').then(config => config.prefixes) @@ -74,21 +78,16 @@ export default async (msg) => { } } - aux.me = aux.client.info.wid._serialized ? aux.client.info.wid._serialized : aux.client.info.wid aux.mentions = msg.mentionedIds - if (typeof aux.mentions[0] !== 'string') { - // sometimes is an array of objects, sometimes is an array of strings - aux.mentions = aux.mentions.map((mention) => mention._serialized) // convert to array of strings - } - aux.amIMentioned = aux.mentions.includes(aux.me) - aux.participants = aux.chat.isGroup ? aux.chat.participants : [] - aux.admins = aux.chat.isGroup ? aux.participants.filter((p) => p.isAdmin || p.isSuperAdmin).map((p) => p.id._serialized) : [] - aux.isSenderAdmin = aux.admins.includes(msg.author) - aux.isBotAdmin = aux.admins.includes(aux.me) + aux.amIMentioned = aux.mentions ? aux.mentions.includes(aux.me) : false + // aux.participants = aux.chat.isGroup ? aux.chat.participants : [] + // aux.admins = aux.chat.isGroup ? aux.participants.filter((p) => p.isAdmin || p.isSuperAdmin).map((p) => p.id._serialized) : [] + // aux.isSenderAdmin = aux.admins.includes(msg.author) + // aux.isBotAdmin = aux.admins.includes(aux.me) - const stickerGroup = '120363187692992289@g.us' - aux.isStickerGroup = aux.chat.isGroup ? aux.chat.id._serialized === stickerGroup : false + // const stickerGroup = '120363187692992289@g.us' + // aux.isStickerGroup = aux.chat.isGroup ? aux.chat.id._serialized === stickerGroup : false try { msg.aux = aux @@ -145,18 +144,18 @@ export default async (msg) => { // Send incorrect function reaction if (aux.isFunction) return false // if any function reach this point, it is an incorrect function - if (aux.chat.isGroup && !aux.mentionedMe) { - if (aux.isStickerGroup && msg.type === 'chat') { - return false // ignore texts in sticker group - } - - if (!aux.isStickerGroup) { - const isStickerPack = msg.body.startsWith('https://sticker.ly/s/') - if (!isStickerPack && !['audio', 'ptt'].includes(msg.type)) { - return false - } - } - } + // if (aux.chat.isGroup && !aux.mentionedMe) { + // if (aux.isStickerGroup && msg.type === 'chat') { + // return false // ignore texts in sticker group + // } + + // if (!aux.isStickerGroup) { + // const isStickerPack = msg.body.startsWith('https://sticker.ly/s/') + // if (!isStickerPack && !['audio', 'ptt'].includes(msg.type)) { + // return false + // } + // } + // } if (isOneOf(commandless(msg, aux))) { const command = getFirstMatch(commandless(msg, aux)) From ec2b6d38533398047f96cf3024359424ad9b35b4 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 28 Jan 2024 16:24:44 -0300 Subject: [PATCH 21/79] feat: add messageMedia generator --- package.json | 3 + src/index.js | 4 + src/meta/message.js | 111 ++++++- src/meta/messageMedia.js | 70 +++++ src/services/commands/miscellaneous.js | 2 +- src/services/events/messagesUpsert.js | 4 +- src/services/functions/admin-commands.js | 9 +- src/services/functions/miscellaneous.js | 98 +++--- src/services/functions/stickers.js | 385 +++++++++++------------ src/services/functions/tools.js | 21 +- src/utils/converters.js | 39 +++ src/utils/sticker.js | 34 +- src/validators/message.js | 2 +- src/validators/messageType.js | 49 +-- 14 files changed, 518 insertions(+), 313 deletions(-) create mode 100644 src/meta/messageMedia.js create mode 100644 src/utils/converters.js diff --git a/package.json b/package.json index 52ef427..fb841d4 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "dependencies": { "@whiskeysockets/baileys": "^6.6.0", "change-case": "^5.4.2", + "cheerio": "^1.0.0-rc.12", "citty": "^0.1.5", "dayjs": "^1.11.10", "dotenv": "^16.4.1", + "file-type": "^19.0.0", "filenamify": "^6.0.0", "fluent-ffmpeg": "^2.1.2", "form-data": "^4.0.0", @@ -33,6 +35,7 @@ "pino-pretty": "^10.3.1", "qrcode-terminal": "^0.12.0", "qs": "^6.11.2", + "read-chunk": "^4.0.3", "sharp": "^0.32.6", "yargs": "^17.7.2" }, diff --git a/src/index.js b/src/index.js index 252e67e..b417579 100644 --- a/src/index.js +++ b/src/index.js @@ -157,6 +157,10 @@ export async function connectToWhatsApp () { logger.info('Client initialized!') + // await sock.sendMessage('asd', { + // document: + // }) + return sock /** diff --git a/src/meta/message.js b/src/meta/message.js index a415a3e..f451538 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -1,10 +1,13 @@ import messageTypeValidator from '../validators/messageType.js' +import { downloadMediaMessage, downloadContentFromMessage } from '@whiskeysockets/baileys' +import relativeTime from 'dayjs/plugin/relativeTime.js' import spintax from '../utils/spintax.js' +import { MessageMedia } from './messageMedia.js' import { getSocket } from '../index.js' -import fetch from 'node-fetch' import logger from '../logger.js' -import relativeTime from 'dayjs/plugin/relativeTime.js' +import fetch from 'node-fetch' import 'dayjs/locale/pt-br.js' +import fs from 'fs/promises' import dayjs from 'dayjs' // @@ -43,8 +46,17 @@ const processMessage = (msg) => { ? firstItem : firstItem.caption || firstItem.text || '' + const quotedMsg = firstItem.contextInfo?.quotedMessage + if (quotedMsg) { + const { type } = messageTypeValidator(quotedMsg) + quotedMsg.type = type + const firstKey = Object.keys(quotedMsg)[0] + quotedMsg.hasMedia = !!quotedMsg[firstKey].mediaKey + } + const properties = { id: msg.key.id, + pushname: msg.pushName, type, duration: firstItem.seconds, from: msg.key.remoteJid, @@ -94,7 +106,8 @@ const processMessage = (msg) => { const methods = { react, reply, - sendSeen + sendSeen, + downloadMedia } for (const method in methods) { @@ -142,17 +155,54 @@ async function react (reaction) { * @returns {Promise} */ async function reply (content, chatId, options) { - // TODO: Add better support for media messages + const mode = typeof content === 'string' ? 'text' : 'media' + let messageObject = {} + let tempPath = '' + if (mode === 'text') messageObject.text = spintax(content) + if (mode === 'media') { + messageObject = content + + if (messageObject.caption) { + messageObject.caption = spintax(messageObject.caption) + } + + if (messageObject.media) { + const media = messageObject.media + delete messageObject.media + + tempPath = `./src/temp/${media.filename}` + await fs.writeFile(tempPath, media.data, 'base64') + + let type = 'document' + if (media.mimetype.split('/')[0] === 'image') type = 'image' + if (media.mimetype.split('/')[0] === 'video') type = 'video' + if (media.mimetype.split('/')[0] === 'audio') type = 'audio' + + if (type === 'image' && media.mimetype === 'image/webp') { + // if is a webp iamge send as documment + type = 'document' + } + + messageObject[type] = { url: tempPath } + messageObject.mimetype = media.mimetype + messageObject.fileName = media.filename + } + } /** * Message object itself * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ const msg = this - await sock.sendMessage(chatId || msg.key.remoteJid, { - text: spintax(content) - }, { - quoted: msg + + const firstKey = Object.keys(msg.message)[0] + const firstItem = msg.message[firstKey] + const isEphemeral = !!firstItem.contextInfo?.expiration + + await sock.sendMessage(chatId || msg.key.remoteJid, messageObject, { + quoted: msg, + ephemeralExpiration: isEphemeral ? firstItem.contextInfo?.expiration : undefined }) + if (tempPath) await fs.unlink(tempPath) } /** @@ -167,6 +217,51 @@ async function sendSeen () { const msg = this await sock.readMessages([msg.key]) } + +/** + * Downloads the media of this message + * @returns {Promise} + */ +async function downloadMedia (quoted = false) { + /** + * Message object itself + * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} + */ + const msg = this + const firstKey = Object.keys(msg.message)[0] + const firstItem = msg.message[firstKey] + const firstKeyFromQuoted = quoted ? Object.keys(firstItem.contextInfo.quotedMessage)[0] : undefined + const firstItemFromQuoted = quoted ? firstItem.contextInfo.quotedMessage[firstKeyFromQuoted] : undefined + const downloadType = quoted + ? firstKeyFromQuoted.replace('Message', '') + : firstKey.replace('Message', '') + + try { + const stream = await downloadContentFromMessage(!quoted ? firstItem : firstItemFromQuoted, downloadType) + let buffer = Buffer.from([]) + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]) + } + + // const buffer = await downloadMediaMessage(msg, 'buffer', {}, { + // logger, + // reuploadRequest: sock.updateMediaMessage + // }) + const media = await MessageMedia.fromBuffer(buffer) + const metadataSource = quoted ? firstItemFromQuoted : firstItem + media.metadata = { + width: metadataSource.width, + height: metadataSource.height, + ratio: metadataSource.width / metadataSource.height, + duration: metadataSource.seconds + } + return media + } catch (error) { + logger.error('downloadMedia ERROR', { error }) + throw error + } +} + // // ================================== Helper Functions ================================== // diff --git a/src/meta/messageMedia.js b/src/meta/messageMedia.js new file mode 100644 index 0000000..6b1a797 --- /dev/null +++ b/src/meta/messageMedia.js @@ -0,0 +1,70 @@ +import { fileTypeFromBuffer } from 'file-type' +import fetch from 'node-fetch' + +/** + * @class MessageMedia + * @property {string} mimetype - MIME type of the attachment + * @property {string} data - Base64-encoded data of the file + * @property {string|null} [filename] - Document file name. Value can be null + * @property {number|null} [filesize] - Document file size in bytes. Value can be null. + */ +export class MessageMedia { + constructor (mimetype, data, filename, filesize) { + this.mimetype = mimetype + this.data = data + this.filename = filename + this.filesize = filesize + } + + /** + * Creates a MessageMedia instance from a local file path + * @param {string} filePath - The path of the file + * @returns {MessageMedia} - The created MessageMedia instance + */ + static fromFilePath (filePath) { + // implementation here + } + + /** + * Creates a MessageMedia instance from a URL + * @param {string} url - The URL of the media + * @param {MediaFromURLOptions} [options] - The options for the media from URL + * @param {number} [maxRetries=3] - The max number of retries + * @returns {Promise} - The Promise which resolves to a MessageMedia instance + */ + static async fromUrl (url, options, maxRetries = 3) { + let retries = 0 + while (retries < maxRetries) { + try { + const buffer = await fetch(url).then((res) => { + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`) + return res.buffer() + }) + return MessageMedia.fromBuffer(buffer) + } catch (error) { + if (retries === maxRetries - 1) throw new Error(`Failed to fetch from URL after ${maxRetries} attempts.`) + retries++ + } + } + } + + /** + * Creates a MessageMedia instance from a raw buffer + * @param {Buffer} buffer - The raw buffer + * @returns {Promise} - The Promise which resolves to a MessageMedia instance + */ + static async fromBuffer (buffer) { + const type = await fileTypeFromBuffer(buffer) + + if (!type) { + throw new Error('Unsupported buffer') + } + + return new MessageMedia( + type.mime, + buffer.toString('base64'), + `DeadByte-${Math.random().toString(36).substring(7)}.${type.ext}`, + buffer.byteLength + ) + } +} diff --git a/src/services/commands/miscellaneous.js b/src/services/commands/miscellaneous.js index 50815a9..ca3c826 100644 --- a/src/services/commands/miscellaneous.js +++ b/src/services/commands/miscellaneous.js @@ -8,7 +8,7 @@ export default (msg) => { uptime: /^(uptime|online|up|tempo)$/.test(msg.aux.function), react: /^(react|reacao)$/.test(msg.aux.function) || msg.aux.function === '', dice: /^\d*d\d+([\+\-\*\/]\d+)?$/.test(msg.aux.function), - toFile: /^(tofile|file|arquivo|imagem|img|togif|image)$/.test(msg.aux.function), + toFile: /^(tofile|revert|file|arquivo|imagem|img|togif|image)$/.test(msg.aux.function), toUrl: /^(tourl|url)$/.test(msg.aux.function), ping: /^(ping|pong)$/.test(msg.aux.function), speak: /^(speak|fale|falar|voz|diga|dizer|fala)\d?$/.test(msg.aux.function), diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 711da3d..b9f7f09 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -29,10 +29,10 @@ const okTypes = [ */ export default async (upsert) => { logger.trace('messages.upsert', upsert) + if (upsert.messages[0].key.fromMe) return // ignore self messages const meta = await importFresh('meta/message.js') const msg = meta.default(upsert.messages[0]) if (!msg) return - if (msg.fromMe) return // ignore self messages if (!okTypes.includes(msg.type)) return await msg.sendSeen() @@ -45,11 +45,9 @@ export default async (upsert) => { await msg.react('👀') return await msg.reply('👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}') } - // await msg.reply(msg.type) const sock = getSocket() await sock.sendPresenceUpdate('available') - const messageParser = await importFresh('validators/message.js') const handlerModule = await messageParser.default(msg) logger.trace('handlerModule: ', handlerModule) diff --git a/src/services/functions/admin-commands.js b/src/services/functions/admin-commands.js index 9535b1e..5ed1880 100644 --- a/src/services/functions/admin-commands.js +++ b/src/services/functions/admin-commands.js @@ -16,11 +16,10 @@ dayjs.extend(relativeTime) export async function debug (msg) { const debugEmoji = '🐛' await msg.react(debugEmoji) + // Debug code goes here - const announceGroup = '120363094244463491@g.us' - const chat = await msg.aux.client.getChatById(announceGroup) - const admins = chat.participants.filter(p => p.isAdmin || p.isSuperAdmin).map((p) => p.id._serialized) - const botIsAdmin = admins.includes(msg.aux.me) + const readMore = '​'.repeat(783) + const message = `Dead${readMore}Byte` - await msg.reply(JSON.stringify(botIsAdmin, null, 2)) + await msg.reply(message) } diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 8c0f6ab..6c67c83 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -1,5 +1,7 @@ // import { getQueueLength } from '../queue.js' +import { MessageMedia } from '../../meta/messageMedia.js' import relativeTime from 'dayjs/plugin/relativeTime.js' +import { webpToMp4 } from '../../utils/converters.js' import reactions from '../../config/reactions.js' import { createUrl } from '../../config/api.js' import spintax from '../../utils/spintax.js' @@ -12,8 +14,6 @@ import mime from 'mime-types' import OpenAI from 'openai' import sharp from 'sharp' import dayjs from 'dayjs' -import path from 'path' -import fs from 'fs' dayjs.locale('pt-br') dayjs.extend(relativeTime) @@ -33,7 +33,7 @@ export async function uptime (msg) { const clock = '{⏳|⌚|⏰|⏱️|⏲️|🕰️|🕛|🕧|🕐|🕜|🕑|🕝}' await msg.react(spintax(clock)) // react with random clock emoji - const saudation = `{${spintax(clock)}} - {Olá|Oi|Oie|E aí} ${msg.aux?.sender?.pushname || 'usuário'} tudo {jóia|bem}?` + const saudation = `{${spintax(clock)}} - {Olá|Oi|Oie|E aí} ${msg.pushname || 'usuário'} tudo {jóia|bem}?` const part1 = '{Eu estou|Estou|O bot {está|ta|tá}|O DeadByte {está|ta|tá}} {online|on|ligado}{ direto|} {a|á|tem}{:|} ' const message = `${saudation}\n\n${part1}*${uptimeString}*` @@ -85,69 +85,60 @@ export async function dice (msg) { await msg.reply(message) } -/** - * Tests functions - * @param {import('../../types.d.ts').WWebJSMessage} msg - */ -export async function debug (msg) { - const debugEmoji = '🐛' - await msg.react(debugEmoji) - // Debug code goes here -} - /** * Send the files as a document * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toFile (msg) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - // if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - // await msg.react(reactions.error) - - // const header = '☠️🤖' - // const part1 = 'Para usar o *{!toFile|!arquivo}* você {precisa|tem que}' - // const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' - // const end = '{!|!!|!!!}' + if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + await msg.react(reactions.error) - // const message = spintax(`${header} - ${part1} ${part2}${end}`) - // return await msg.reply(message) - // } - // await msg.react('🗂️') - // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - // if (!media) throw new Error('Error downloading media') + const header = '☠️🤖' + const part1 = 'Para usar o *{!toFile|!arquivo}* você {precisa|tem que}' + const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' + const end = '{!|!!|!!!}' - // // the last 10 chars of the timestamp - // const timestampish = Date.now().toString().slice(-10) - // const filename = `deadbyte-${timestampish}.${mime.extension(media.mimetype)}` - // media.filename = media.filename || filename + const message = spintax(`${header} - ${part1} ${part2}${end}`) + return await msg.reply(message) + } + let media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() + if (!media) throw new Error('Error downloading media') - // const buffer = Buffer.from(media.data, 'base64') + // the last 10 chars of the timestamp + const timestampish = Date.now().toString().slice(-10) + const filename = `deadbyte-${timestampish}.${mime.extension(media.mimetype)}` + media.filename = media.filename || filename - // let message = '' - // message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' - // message += 'o arquivo{ que você {me |}{pediu|enviou}|}!\n\n' + const buffer = Buffer.from(media.data, 'base64') - // const isImage = media.mimetype.includes('image') - // const isVideo = media.mimetype.includes('video') - // // use sharp to check if the image is animated - // const isAnimated = isImage ? await sharp(buffer).metadata().then(m => parseInt(m.pages) > 1) : false + const isImage = media.mimetype.includes('image') + const isVideo = media.mimetype.includes('video') + // use sharp to check if the image is animated + const isAnimated = isImage ? await sharp(buffer).metadata().then(m => parseInt(m.pages) > 1) : false + + if (isImage && !isAnimated) { + const converted = await sharp(buffer).toFormat('png').toBuffer() + media.data = converted.toString('base64') + media.mimetype = 'image/png' + media.filename = media.filename.split('.').slice(0, -1).join('.') + '.png' + await msg.reply({ media, caption: 'DeadByte.com.br - bot de figurinhas' }) + } - // const finalExtension = isImage ? isAnimated ? 'webp' : 'png' : mime.extension(media.mimetype) + if (isImage && isAnimated) { + await msg.reply({ media, caption: 'Espere um pouco que vou converter esse *webp* formato melhor para você...' }) + await msg.react(reactions.loading) - // message += `É ${isImage ? 'uma imagem' : isVideo ? 'um vídeo' : 'um arquivo'} ${finalExtension.toUpperCase()}` - // message += isImage && isAnimated ? ' animada' : '' + const tempUrl = await getTempUrl(media) + const url = await webpToMp4(tempUrl) + media = await MessageMedia.fromUrl(url) + await msg.reply({ media, gifPlayback: true, caption: 'DeadByte.com.br - bot de figurinhas' }) + } - // if (isImage && !isAnimated) { - // const converted = await sharp(buffer).toFormat('png').toBuffer() - // media.data = converted.toString('base64') - // media.mimetype = 'image/png' - // media.filename = media.filename.split('.').slice(0, -1).join('.') + '.png' - // return await msg.reply(media, undefined, { sendMediaAsDocument: false, caption: spintax(message) }) - // } - // await msg.reply(media, undefined, { sendMediaAsDocument: true, caption: spintax(message) }) + if (isVideo) { + await msg.reply({ media, caption: 'DeadByte.com.br - bot de figurinhas' }) + } - // TODO convert to webp if animated to mp4 and send as "gif" + await msg.react('🗂️') } /** @@ -198,7 +189,6 @@ export async function ping (msg) { // message += `{Atualmente|No momento|{Nesse|Neste}{ exato|} momento} tem *${usersInQueue} ${usersInQueue > 1 ? 'usuários' : 'usuário'}* na fila com *${messagesInQueue} ${messagesInQueue > 1 ? 'mensagens' : 'mensagem'}* ao todo!\n\n` // } - console.log(msg.lag) let lag = msg.lag / 1000 lag = Math.max(lag, 0) // if lag is negative, set it to 0 lag = isNaN(lag) ? 0 : lag @@ -377,7 +367,7 @@ async function getTempUrl (media) { const json = await response.json() const tempUrl = json.result - return tempUrl + return tempUrl.replace('http://', 'https://') } /** diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index e87cef2..20f4063 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -1,41 +1,47 @@ -// import wwebjs from 'whatsapp-web.js' -import Util from '../../utils/sticker.js' -import sharp from 'sharp' -import { createUrl } from '../../config/api.js' +import { MessageMedia } from '../../meta/messageMedia.js' import reactions from '../../config/reactions.js' -import logger from '../../logger.js' +import { createUrl } from '../../config/api.js' import spintax from '../../utils/spintax.js' -import fetch from 'node-fetch' -import FormData from 'form-data' import { getSocket } from '../../index.js' +import Util from '../../utils/sticker.js' +import logger from '../../logger.js' +import FormData from 'form-data' +import fetch from 'node-fetch' +import sharp from 'sharp' const sock = getSocket() /** * Make sticker from media (image, video, gif) * @param {import('../../types.d.ts').WWebJSMessage} msg - * @param {boolean} [crop=false] - crop the image to a square - * @param {string} StickerAuthor - sticker author name - * @param {string} StickerPack - sticker pack name + * @param {string} stickerName + * @param {string} stickerAuthor */ -export async function stickerCreator (msg, crop = false, stickerAuthor, stickerPack) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - // await msg.react(reactions.wait) +export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite = true) { + await msg.react(reactions.wait) - // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - // if (!media) throw new Error('Error downloading media') + const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() + const metadata = media.metadata - // let stickerMedia = await Util.formatToWebpSticker(media, {}, crop) - // if (msg.type === 'document') msg.body = '' // remove file name from caption - // if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia + const promises = [] + promises.push(Util.formatToWebpSticker(media, {}, false)) - // await sendMediaAsSticker(msg.aux.chat, stickerMedia, stickerAuthor, stickerPack) + const ratioDistanceFromSquare = Math.abs(metadata.ratio - 1) + const needToCrop = ratioDistanceFromSquare > 0.1 - // if (!crop) { - // await stickerCreator(msg, true) // make cropped version - // await msg.react(reactions.success) - // } + if (needToCrop) { + promises.push(Util.formatToWebpSticker(media, {}, true)) + } + + for (const promise of promises) { + let stickerMedia = await promise + + if (msg.type === 'document') msg.body = '' // remove file name from caption + if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia + + await sendMediaAsSticker(msg, stickerMedia, stickerName, stickerAuthor, overwrite) + } + await msg.react(reactions.success) } /** @@ -46,18 +52,11 @@ export async function textSticker (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp', { message: msg.body }) - // await msg.reply(url) - - const buffer = await fetch(url).then((res) => res.buffer()) - const webp = await sharp(buffer).webp().toBuffer() - await sock.sendMessage(msg.from, { - sticker: webp - }) - // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - // if (!media) throw new Error('Error downloading media') + let media = await MessageMedia.fromUrl(url) + media = await Util.formatToWebpSticker(media, {}, false) - // await sendMediaAsSticker(msg.aux.chat, media) + await sendMediaAsSticker(msg, media) await msg.react(reactions.success) } @@ -69,17 +68,10 @@ export async function textSticker2 (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp2', { message: msg.body }) - const buffer = await fetch(url).then((res) => res.buffer()) - const webp = await sharp(buffer).webp().toBuffer() - - await sock.sendMessage(msg.from, { - sticker: webp - }) + let media = await MessageMedia.fromUrl(url) + media = await Util.formatToWebpSticker(media, {}, false) - // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - // if (!media) throw new Error('Error downloading media') - - // await sendMediaAsSticker(msg.aux.chat, media) + await sendMediaAsSticker(msg, media) await msg.react(reactions.success) } @@ -91,17 +83,10 @@ export async function textSticker3 (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'ttp3', { message: msg.body }) - const buffer = await fetch(url).then((res) => res.buffer()) - const webp = await sharp(buffer).webp().toBuffer() - - await sock.sendMessage(msg.from, { - sticker: webp - }) - - // const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - // if (!media) throw new Error('Error downloading media') + let media = await MessageMedia.fromUrl(url) + media = await Util.formatToWebpSticker(media, {}, false) - // await sendMediaAsSticker(msg.aux.chat, media) + await sendMediaAsSticker(msg, media) await msg.react(reactions.success) } @@ -111,45 +96,42 @@ export async function textSticker3 (msg) { * */ export async function removeBg (msg) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - // await msg.react(reactions.wait) - - // if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - // await msg.react(reactions.error) - - // const header = '☠️🤖' - // const part1 = 'Para usar o {remove.bg|removedor de fundo|*!bg*} você {precisa|tem que}' - // const part2 = '{enviar|mandar} {esse|o} comando {junto com|na legenda de} uma {imagem|foto}' - // const end = '{!|!!|!!!}' - - // const message = spintax(`${header} - ${part1} ${part2}${end}`) - // return await msg.reply(message) - // } - - // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - // if (!media) throw new Error('Error downloading media') - // if (!media.mimetype.includes('image')) { - // await msg.react(reactions.error) - // return await msg.reply('❌ Só consigo remover o fundo de imagens') - // } - - // // use shapr to convert to a max 512 (bigger side) jpg image, crank up the contrast - // const buffer = Buffer.from(media.data, 'base64') - // const resizedBuffer = await sharp(buffer) - // .resize(1024, 1024, { fit: 'inside' }) - // .jpeg() - // .toBuffer() - - // const tempUrl = await getTempUrl(resizedBuffer) - // const url = await createUrl('image-processing', 'removebg', { img: tempUrl, trim: true }) - // const bgMedia = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - - // let stickerMedia = await Util.formatToWebpSticker(bgMedia, {}) - // if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia - - // await sendMediaAsSticker(msg.aux.chat, stickerMedia) - // await msg.react(reactions.success) + await msg.react(reactions.wait) + if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + await msg.react(reactions.error) + + const header = '☠️🤖' + const part1 = 'Para usar o {remove.bg|removedor de fundo|*!bg*} você {precisa|tem que}' + const part2 = '{enviar|mandar} {esse|o} comando {junto com|na legenda de} uma {imagem|foto}' + const end = '{!|!!|!!!}' + + const message = spintax(`${header} - ${part1} ${part2}${end}`) + return await msg.reply(message) + } + + const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() + if (!media) throw new Error('Error downloading media') + if (!media.mimetype.includes('image')) { + await msg.react(reactions.error) + return await msg.reply('❌ Só consigo remover o fundo de imagens') + } + + // use sharp to convert to a max 512 (bigger side) jpg image, crank up the contrast + const buffer = Buffer.from(media.data, 'base64') + const resizedBuffer = await sharp(buffer) + .resize(1024, 1024, { fit: 'inside' }) + .jpeg() + .toBuffer() + + const tempUrl = await getTempUrl(resizedBuffer) + const url = await createUrl('image-processing', 'removebg', { img: tempUrl, trim: true }) + const bgMedia = await MessageMedia.fromUrl(url, { unsafeMime: true }) + + let stickerMedia = await Util.formatToWebpSticker(bgMedia, {}) + if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia + + await sendMediaAsSticker(msg, stickerMedia) + await msg.react(reactions.success) } /** @@ -158,23 +140,25 @@ export async function removeBg (msg) { * */ export async function stealSticker (msg) { + if (!msg.hasQuotedMsg && (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia) && !msg.hasMedia) { + await msg.react(reactions.error) + return await msg.reply('❌ - Você precisa responder a uma figurinha para usar esse comando') + } await msg.react(reactions.wait) - const quotedMsg = await msg.getQuotedMessage() - const delimiters = ['|', '/', '\\'] let messageParts = [msg.body] // default to the whole message for (const delimiter of delimiters) { if (messageParts.length > 1) break messageParts = msg.body.split(delimiter) } - const stickerName = messageParts[0]?.trim() || msg.aux.sender.pushname - const stickerAuthor = messageParts[1]?.trim() || 'DeadByte.com.br' + const stickerName = messageParts[0]?.trim() || undefined + const stickerAuthor = messageParts[1]?.trim() || undefined - if (!msg.hasQuotedMsg || !quotedMsg.hasMedia) { + if (!msg.hasQuotedMsg || !msg.quotedMsg.hasMedia) { if (msg.hasMedia && (msg.type === 'image' || msg.type === 'video' || msg.type === 'sticker')) { msg.body = '' - return await stickerCreator(msg, undefined, stickerName, stickerAuthor) + return await stickerCreator(msg, stickerName, stickerAuthor, false) } await msg.react(reactions.error) @@ -188,10 +172,10 @@ export async function stealSticker (msg) { return await msg.reply(message) } - const media = await quotedMsg.downloadMedia() + const media = await msg.downloadMedia(true) if (!media) throw new Error('Error downloading media') - await sendMediaAsSticker(msg.aux.chat, media, stickerName, stickerAuthor) + await sendMediaAsSticker(msg, media, stickerName, stickerAuthor) await msg.react(reactions.success) } @@ -200,7 +184,8 @@ export async function stealSticker (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function stickerLySearch (msg) { - const isStickerGroup = checkStickerGroup(msg.aux.chat.id) + // const isStickerGroup = checkStickerGroup(msg.aux.chat.id) + const isStickerGroup = false const limit = getStickerLimit(isStickerGroup) if (!msg.body) { @@ -242,7 +227,7 @@ export async function stickerLySearch (msg) { await msg.reply(spintax(message)) } - // await sendStickers(stickersPaginated, msg.aux.chat) + await sendStickers(stickersPaginated, msg) await msg.react(reactions.success) } @@ -254,7 +239,8 @@ export async function stickerLyPack (msg) { // remove https://sticker.ly/s/ from the beginning of the message if it exists msg.body = msg.body.replace('https://sticker.ly/s/', '') - const isStickerGroup = checkStickerGroup(msg.aux.chat.id) + // const isStickerGroup = checkStickerGroup(msg.aux.chat.id) + const isStickerGroup = false const limit = getStickerLimit(isStickerGroup) if (!msg.body) { @@ -299,7 +285,7 @@ export async function stickerLyPack (msg) { await msg.reply(spintax(message)) } - // await sendStickers(stickersPaginated, msg.aux.chat) + await sendStickers(stickersPaginated, msg) await msg.react(reactions.success) } @@ -315,28 +301,37 @@ export async function stickerLyPack (msg) { * @param {import ('whatsapp-web.js').MessageMedia} media - The media to send as a sticker. * @param {string} [stickerName='DeadByte.com.br'] - The name of the sticker. * @param {string} [stickerAuthor='bot de figurinhas'] - The author of the sticker. + * @param {boolean} [overwrite=true] - Whether to overwrite the sticker pack or not. * @returns {Promise} A Promise that resolves with the Message object of the sent sticker. */ -async function sendMediaAsSticker (chat, media, stickerName, stickerAuthor) { - // const buffer = Buffer.from(media.data, 'base64') - - // // if heavier than 1MB, compress it - // if (buffer.byteLength > 1_000_000) { - // media = await compressMediaBuffer(buffer) - // } - - // media = new wwebjs.MessageMedia(media.mimetype || 'image/webp', media.data, media.filename || 'sticker.webp') - - // try { - // return await chat.sendMessage(media, { - // sendMediaAsSticker: true, - // stickerName: stickerName || 'DeadByte.com.br', - // stickerAuthor: stickerAuthor || 'bot de figurinhas', - // stickerCategories: ['💀', '🤖'] - // }) - // } catch (error) { - // logger.error(error) - // } +async function sendMediaAsSticker (msg, media, author, pack, overwrite = true) { + const stickerMedia = await Util.formatToWebpSticker(media, { + author: author || (overwrite ? 'DeadByte.com.br' : undefined), + pack: pack || (overwrite ? 'bot de figurinhas' : undefined) + }, false) + const buffer = Buffer.from(stickerMedia.data, 'base64') + + // if heavier than 1MB, compress it + if (buffer.byteLength > 1_000_000) { + media = await compressMediaBuffer(buffer, media) + media = await Util.formatToWebpSticker(media, { + author: author || (overwrite ? 'DeadByte.com.br' : undefined), + pack: pack || (overwrite ? 'bot de figurinhas' : undefined) + }, false) + } + const firstKey = Object.keys(msg.raw.message)[0] + const firstItem = msg.raw.message[firstKey] + const isEphemeral = !!firstItem.contextInfo?.expiration + + try { + return await sock.sendMessage(msg.from, { + sticker: buffer + }, { + ephemeralExpiration: isEphemeral ? firstItem.contextInfo?.expiration : undefined + }) + } catch (error) { + logger.error(error) + } } /** @@ -348,32 +343,29 @@ async function sendMediaAsSticker (chat, media, stickerName, stickerAuthor) { * @returns {Promise} A Promise that resolves with a new MessageMedia object containing the media with the subtitle overlayed. * @throws {Error} If there was an error downloading the subtitle media. */ -// async function overlaySubtitle (text, stickerMedia) { -// const mediaBuffer = Buffer.from(stickerMedia.data, 'base64') - -// const url = await createUrl('image-creator', 'ttp', { -// message: text, -// subtitle: true -// }) -// const subtitleMedia = await wwebjs.MessageMedia.fromUrl(url, { -// unsafeMime: true -// }) -// if (!subtitleMedia) throw new Error('Error downloading subtitle media') - -// const subtitleBuffer = Buffer.from(subtitleMedia.data, 'base64') -// const finalBuffer = await sharp(mediaBuffer, { animated: true }) -// .composite([{ -// input: subtitleBuffer, -// gravity: 'south', -// animated: true, -// tile: true -// }]) -// .webp() -// .toBuffer() - -// // replace media data with the new data from sharp -// return new wwebjs.MessageMedia('image/webp', finalBuffer.toString('base64'), 'deadbyte.webp', true) -// } +async function overlaySubtitle (text, stickerMedia) { + const mediaBuffer = Buffer.from(stickerMedia.data, 'base64') + + const url = await createUrl('image-creator', 'ttp', { + message: text, + subtitle: true + }) + + const subtitleBuffer = await fetch(url).then((res) => res.buffer()) + const finalBuffer = await sharp(mediaBuffer, { animated: true }) + .composite([{ + input: subtitleBuffer, + gravity: 'south', + animated: true, + tile: true + }]) + .webp() + .toBuffer() + + stickerMedia.data = finalBuffer.toString('base64') + stickerMedia.filesize = finalBuffer.byteLength + return stickerMedia +} /** * Compresses a media buffer for a sticker image. @@ -383,43 +375,46 @@ async function sendMediaAsSticker (chat, media, stickerName, stickerAuthor) { * @returns {Promise} A Promise that resolves with a compressed MessageMedia object. * @throws {Error} If the compressed buffer is still too heavy. */ -// async function compressMediaBuffer (mediaBuffer) { -// logger.debug('compressing sticker...', mediaBuffer.byteLength) -// const compressedBuffer = await sharp(mediaBuffer, { animated: true }) -// .webp({ quality: 33 }) -// .toBuffer() -// logger.debug('compressed sticker!', mediaBuffer.byteLength, '->', compressedBuffer.byteLength) +async function compressMediaBuffer (mediaBuffer, media) { + logger.debug('compressing sticker...', mediaBuffer.byteLength) + const compressedBuffer = await sharp(mediaBuffer, { animated: true }) + .webp({ quality: 33 }) + .toBuffer() + logger.debug('compressed sticker!', mediaBuffer.byteLength, '->', compressedBuffer.byteLength) -// if (compressedBuffer.byteLength > 1_000_000) throw new Error('Sticker is still too heavy!', mediaBuffer.byteLength) + if (compressedBuffer.byteLength > 1_000_000) throw new Error('Sticker is still too heavy!', mediaBuffer.byteLength) -// return new wwebjs.MessageMedia('image/webp', compressedBuffer.toString('base64'), 'deadbyte.webp', true) -// } + media.data = compressedBuffer.toString('base64') + media.filesize = compressedBuffer.byteLength + + return media +} /** * Uploads an image to get a temporary URL * @param {import ('whatsapp-web.js').MessageMedia} media - The media to upload * @returns {promise} A Promise that resolves with the temporary URL of the uploaded image. */ -// async function getTempUrl (buffer) { -// const formData = new FormData() -// formData.append('file', buffer, 'sticker.png') +async function getTempUrl (buffer) { + const formData = new FormData() + formData.append('file', buffer, 'sticker.png') -// const url = await createUrl('uploader', 'tempurl', {}) -// const response = await fetch(url, { -// method: 'POST', -// body: formData -// }) + const url = await createUrl('uploader', 'tempurl', {}) + const response = await fetch(url, { + method: 'POST', + body: formData + }) -// if (!response.ok) { -// logger.error('Error uploading image to remove.bg') -// throw new Error('Error uploading image to remove.bg') -// } + if (!response.ok) { + logger.error('Error uploading image to remove.bg') + throw new Error('Error uploading image to remove.bg') + } -// const json = await response.json() -// const tempUrl = json.result + const json = await response.json() + const tempUrl = json.result -// return tempUrl -// } + return tempUrl +} /** * Get a sticker pack from sticker.ly @@ -540,10 +535,10 @@ function addPaginationToTheMessage (message, prefix, command, term, limit, total * @param {string} chatId * @returns {boolean} */ -function checkStickerGroup (chatId) { - const stickerGroup = '120363187692992289@g.us' - return chatId._serialized === stickerGroup -} +// function checkStickerGroup (chatId) { +// const stickerGroup = '120363187692992289@g.us' +// return chatId._serialized === stickerGroup +// } /** * Get the limit of stickers based on the chat type @@ -583,22 +578,24 @@ function paginateStickers (stickers, cursor, limit) { * @param {Object} chat * @returns {Promise} */ -// async function sendStickers (stickersPaginated, chat) { -// for (const s of stickersPaginated) { -// const media = await wwebjs.MessageMedia.fromUrl(s.url) -// await sendMediaAsSticker(chat, media) -// await waitRandomTime() -// } -// } +async function sendStickers (stickersPaginated, msg) { + for (const s of stickersPaginated) { + // const media = await wwebjs.MessageMedia.fromUrl(s.url) + let media = await MessageMedia.fromUrl(s.url) + media = await Util.formatToWebpSticker(media, {}, false) + await sendMediaAsSticker(msg, media) + await waitRandomTime() + } +} /** * Wait random time - * @param {number} min @default 50 - * @param {number} max @default 500 + * @param {number} min @default 25 + * @param {number} max @default 250 * @returns {Promise} */ -// function waitRandomTime (min = 50, max = 500) { -// return new Promise((resolve) => { -// setTimeout(resolve, Math.random() * (max - min) + min) -// }) -// } +function waitRandomTime (min = 25, max = 250) { + return new Promise((resolve) => { + setTimeout(resolve, Math.random() * (max - min) + min) + }) +} diff --git a/src/services/functions/tools.js b/src/services/functions/tools.js index dbaab83..f1a13da 100644 --- a/src/services/functions/tools.js +++ b/src/services/functions/tools.js @@ -1,13 +1,12 @@ -import wwebjs from 'whatsapp-web.js' -import fetch from 'node-fetch' -import reactions from '../../config/reactions.js' -import dayjs from 'dayjs' -import 'dayjs/locale/pt-br.js' import relativeTime from 'dayjs/plugin/relativeTime.js' +import reactions from '../../config/reactions.js' import { createUrl } from '../../config/api.js' +import logger from '../../logger.js' import FormData from 'form-data' +import 'dayjs/locale/pt-br.js' +import fetch from 'node-fetch' +import dayjs from 'dayjs' import sharp from 'sharp' -import logger from '../../logger.js' dayjs.locale('pt-br') dayjs.extend(relativeTime) @@ -31,10 +30,16 @@ export async function qrImageCreator (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'qr', { text: msg.body }) + console.log(url) try { - const media = await wwebjs.MessageMedia.fromUrl(url, { unsafeMime: true }) - await msg.reply(media) + // const media = await MessageMedia.fromUrl(url, { unsafeMime: true }) + // console.log(media) + await msg.reply({ + image: { + url + } + }) await msg.react(reactions.success) } catch (error) { logger.error(error) diff --git a/src/utils/converters.js b/src/utils/converters.js new file mode 100644 index 0000000..cfd3469 --- /dev/null +++ b/src/utils/converters.js @@ -0,0 +1,39 @@ +import FormData from 'form-data' +import fetch from 'node-fetch' +import { load } from 'cheerio' +import fs from 'fs/promises' + +/** + * Convert Webp to Mp4 + * @param {String} path + * @returns {Promise<{status: Boolean, message: String, result: String}>} + */ +async function webpToMp4 (url) { + const ezGifUrl = `https://ezgif.com/webp-to-mp4?url=${url}` + + const response = await fetch(ezGifUrl) + + const data = await response.text() + const $ = load(data) + + const action = $('form.ajax-form').attr('action') + const file = $('form.ajax-form > input[type=hidden]').attr('value') + + const bodyFormThen = new FormData() + bodyFormThen.append('file', file) + + const responseThen = await fetch(action + '?ajax=true', { + method: 'POST', + body: bodyFormThen, + headers: bodyFormThen.getHeaders() + }) + + const dataThen = await responseThen.text() + await fs.writeFile('./src/temp/ezgif-then.html', dataThen) + const $Then = load(dataThen) + + const result = $Then('video > source').attr('src') + return 'https:' + result +} + +export { webpToMp4 } diff --git a/src/utils/sticker.js b/src/utils/sticker.js index 2503ca5..996ebbf 100644 --- a/src/utils/sticker.js +++ b/src/utils/sticker.js @@ -164,8 +164,8 @@ class Util { /** * Sticker metadata. * @typedef {Object} StickerMetadata - * @property {string} [name] * @property {string} [author] + * @property {string} [pack] * @property {string[]} [categories] */ @@ -182,22 +182,22 @@ class Util { if (media.mimetype.includes('image')) { webpMedia = await this.formatImageToWebpSticker(media, crop) } else if (media.mimetype.includes('video')) { webpMedia = await this.formatVideoToWebpSticker(media, crop) } else { throw new Error('Invalid media format') } - if (metadata.name || metadata.author) { - const img = new webp.Image() - const hash = this.generateHash(32) - const stickerPackId = hash - const packname = metadata.name - const author = metadata.author - const categories = metadata.categories || [''] - const json = { 'sticker-pack-id': stickerPackId, 'sticker-pack-name': packname, 'sticker-pack-publisher': author, emojis: categories } - const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]) - const jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8') - const exif = Buffer.concat([exifAttr, jsonBuffer]) - exif.writeUIntLE(jsonBuffer.length, 14, 4) - await img.load(Buffer.from(webpMedia.data, 'base64')) - img.exif = exif - webpMedia.data = (await img.save(null)).toString('base64') - } + // if (metadata.author || metadata.pack) { + const img = new webp.Image() + const hash = this.generateHash(32) + const stickerPackId = hash + const packname = metadata.author // Yes, I know it is twisted + const author = metadata.pack // ¯\_(ツ)_/¯ + const categories = metadata.categories || [''] + const json = { 'sticker-pack-id': stickerPackId, 'sticker-pack-name': packname, 'sticker-pack-publisher': author, emojis: categories } + const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]) + const jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8') + const exif = Buffer.concat([exifAttr, jsonBuffer]) + exif.writeUIntLE(jsonBuffer.length, 14, 4) + await img.load(Buffer.from(webpMedia.data, 'base64')) + img.exif = exif + webpMedia.data = (await img.save(null)).toString('base64') + // } return webpMedia } diff --git a/src/validators/message.js b/src/validators/message.js index ebafd56..05f7769 100644 --- a/src/validators/message.js +++ b/src/validators/message.js @@ -10,7 +10,7 @@ const commandless = (msg, aux) => { stickersFNstickerLyPack: msg.body && msg.body.startsWith('https://sticker.ly/s/'), stickersFNstickerCreator: ( (msg.hasMedia && ['image', 'video', 'document'].includes(msg.type)) || - (msg.hasQuotedMsg && (aux.quotedMsg.hasMedia && ['image', 'video', 'document'].includes(aux.quotedMsg.type)))), + (msg.hasQuotedMsg && (msg.quotedMsg.hasMedia && ['image', 'video', 'document'].includes(msg.quotedMsg.type)))), stickersFNtextSticker: msg.body && msg.type === 'chat', miscellaneousFNtranscribe: msg.hasMedia && ['audio', 'ptt'].includes(msg.type) } diff --git a/src/validators/messageType.js b/src/validators/messageType.js index 8aec19d..60a9e35 100644 --- a/src/validators/messageType.js +++ b/src/validators/messageType.js @@ -46,30 +46,35 @@ const types = { */ export default (msg) => { const keysToIgnore = ['messageContextInfo'] - const keys = Object.keys(msg.message) - .filter(key => !keysToIgnore.includes(key)) - if (keys.length === 0) return types.UNKNOWN - - const firstKey = keys[0] + const hasKeys = Object.keys(msg).length > 1 + let firstKey = Object.keys(msg)[0] let incomingType = firstKey + if (hasKeys) { + const keys = Object.keys(msg.message) + .filter(key => !keysToIgnore.includes(key)) + if (keys.length === 0) return types.UNKNOWN + + firstKey = keys[0] + incomingType = firstKey - if (keys.length > 1) { - // senderKeyDistributionMessage - if (keys.includes('senderKeyDistributionMessage')) { - // delete this key and continue from msg.message - delete msg.message.senderKeyDistributionMessage - // and make sure that the other key is the FIRST key of msg.message - const newMessage = {} - const leftOverKeys = Object.keys(msg.message) - const keyName = keys[1] - incomingType = keyName - newMessage[keyName] = msg.message[keyName] - leftOverKeys.forEach(key => { - if (key !== keyName) newMessage[key] = msg.message[key] - }) - msg.message = newMessage - } else { - logger.warn('Message has more than one key', msg) + if (keys.length > 1) { + // senderKeyDistributionMessage + if (keys.includes('senderKeyDistributionMessage')) { + // delete this key and continue from msg.message + delete msg.message.senderKeyDistributionMessage + // and make sure that the other key is the FIRST key of msg.message + const newMessage = {} + const leftOverKeys = Object.keys(msg.message) + const keyName = keys[1] + incomingType = keyName + newMessage[keyName] = msg.message[keyName] + leftOverKeys.forEach(key => { + if (key !== keyName) newMessage[key] = msg.message[key] + }) + msg.message = newMessage + } else { + logger.warn('Message has more than one key', msg) + } } } From c20c1a554be36bd1ca88b74872785d13a34ae7b6 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 29 Jan 2024 21:52:39 -0300 Subject: [PATCH 22/79] fix: wait message on chats --- src/index.js | 95 ++++++++++++++++++---------------------------------- 1 file changed, 33 insertions(+), 62 deletions(-) diff --git a/src/index.js b/src/index.js index b417579..294700e 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ import { dotCase } from 'change-case' import NodeCache from 'node-cache' import bot from './config/bot.js' import logger from './logger.js' +import pino from 'pino' import fs from 'fs/promises' import './db.js' @@ -64,9 +65,9 @@ const main = defineCommand({ bot.stickerOnly = args.stickerOnly logger.info(`Sticker only mode: ${bot.stickerOnly ? 'on' : 'off'}`) - // the store maintains the data of the WA connection in memory - // can be written out to a file & read from it - const store = bot.useStore ? baileys.makeInMemoryStore({ logger }) : undefined + const store = bot.useStore + ? baileys.makeInMemoryStore({ logger: pino().child({ level: 'fatal', stream: 'store' }) }) + : undefined const storePath = `./src/temp/${bot.name}.json` store.readFromFile(storePath) @@ -90,22 +91,18 @@ export function getArgs () { return globalArgs } -// external map to store retry counts of messages when decryption/encryption fails -// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts -const msgRetryCounterCache = new NodeCache() - /** * Baileys socket or null if not connected - * @type {import('./types').WSocket} - */ -let sock = null +*/ +// @type {import('@whiskeysockets/baileys').WSocket | null} +let socket = null /** * Grabs the socket * @returns {import('./types').WSocket} */ export function getSocket () { - return sock + return socket } export async function connectToWhatsApp () { @@ -116,67 +113,41 @@ export async function connectToWhatsApp () { } logger.info('Connecting to WhatsApp...') - const { state } = await baileys.useMultiFileAuthState('auth_info_baileys') - // fetch latest version of WA Web - // const { version, isLatest } = await baileys.fetchLatestBaileysVersion() - const { version, isLatest } = await baileys.fetchLatestBaileysVersion() - logger.info(`Using WA v${version.join('.')}, isLatest: ${isLatest}`) - - sock = baileys.makeWASocket({ - version, - logger, - printQRInTerminal: !bot.usePairingCode, - mobile: bot.useMobile, - auth: { - creds: state.creds, - /** caching makes the store faster to send/recv messages */ - keys: baileys.makeCacheableSignalKeyStore(state.keys, logger) - }, - msgRetryCounterCache, + + const { state, saveCreds } = await baileys.useMultiFileAuthState('auth_info_baileys') + + socket = baileys.makeWASocket({ + printQRInTerminal: true, + logger: pino({ level: 'fatal' }), + auth: state, + browser: ['DeadByte', 'Safari', '3.0'], generateHighQualityLinkPreview: true, - shouldIgnoreJid: jid => baileys.isJidBroadcast(jid), - getMessage + shouldIgnoreJid: jid => baileys.isJidBroadcast(jid), // TODO: make a stories downloader + getMessage: async key => { return { } } }) - store?.bind(sock.ev) + store?.bind(socket.ev) logger.info('Loading events...', bot) const events = await fs.readdir('./src/services/events') - if (bot.doReplies) { - events.forEach(async event => { - const eventPath = `services/events/${event}` - const eventName = dotCase(event.split('.')[0]) - logger.info(`Loading event ${eventName} from file ${event}`) - sock.ev.on(eventName, async (event) => { - const module = await importFresh(eventPath) - module.default(event) - }) + events.forEach(async event => { + if (!bot.doReplies) { + const ignoreEvents = ['call.js', 'messagesUpsert.js'] + if (ignoreEvents.includes(event)) return + } + const eventPath = `services/events/${event}` + const eventName = dotCase(event.split('.')[0]) + logger.trace(`Loading event ${eventName} from file ${event}`) + socket.ev.on(eventName, async (event) => { + const module = await importFresh(eventPath) + module.default(event) }) - } + }) + socket.ev.on('creds.update', saveCreds) logger.info('Client initialized!') - - // await sock.sendMessage('asd', { - // document: - // }) - - return sock - - /** - * Retrieves a message from the store based on the provided key. - * @param {import('@whiskeysockets/baileys').WAMessageKey} key - The key of the message to retrieve. - * @returns {Promise} The retrieved message content, or undefined if not found. - */ - async function getMessage (key) { - if (store) { - const msg = await store.loadMessage(key.remoteJid, key.id) - return msg?.message || undefined - } - - // only if store is present - return baileys.proto.Message.fromObject({}) - } + return socket } // clear terminal From 1449aec1e7867555ec56def9b032fd5efa95769e Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 29 Jan 2024 21:52:56 -0300 Subject: [PATCH 23/79] fix: delete separate event --- src/services/events/credsUpdate.js | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/services/events/credsUpdate.js diff --git a/src/services/events/credsUpdate.js b/src/services/events/credsUpdate.js deleted file mode 100644 index df47b79..0000000 --- a/src/services/events/credsUpdate.js +++ /dev/null @@ -1,12 +0,0 @@ -import logger from '../../logger.js' -import { useMultiFileAuthState } from '@whiskeysockets/baileys' - -/** - * Credentials update event - * @param {import('@whiskeysockets/baileys').BaileysEventMap['creds.update']} event - */ -export default async (event) => { - logger.trace('Credentials updated', event) - const { saveCreds } = await useMultiFileAuthState('auth_info_baileys') - saveCreds(event) -} From 80739a375b84dcb62d5ad28fb59acfd2f20584cd Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 30 Jan 2024 00:09:16 -0300 Subject: [PATCH 24/79] Fix linting errors and update database connection --- .eslintrc.json | 3 + src/db.js | 83 +++++----- src/index.js | 6 +- src/meta/message.js | 105 ++++++++----- src/services/events/connectionUpdate.js | 14 +- src/services/events/messagesUpsert.js | 52 +++---- src/services/functions/miscellaneous.js | 198 ++++++++++++------------ src/services/functions/stickers.js | 1 + src/validators/message.js | 64 ++++---- src/validators/messageType.js | 31 +++- 10 files changed, 291 insertions(+), 266 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index cf8961c..1b9d851 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,9 @@ "rules": { "no-useless-escape": [ "off" + ], + "no-unmodified-loop-condition": [ + "off" ] } } \ No newline at end of file diff --git a/src/db.js b/src/db.js index 9cd66ab..2e49b37 100644 --- a/src/db.js +++ b/src/db.js @@ -1,6 +1,6 @@ -import fetch from 'node-fetch' -import logger from './logger.js' import { kebabCase } from 'change-case' +import logger from './logger.js' +import fetch from 'node-fetch' import qs from 'qs' // // ===================================== Variables ====================================== @@ -98,7 +98,6 @@ export async function loadCommands () { /** * Get the commands - * * @returns {object} commands */ export function getCommands () { @@ -129,14 +128,16 @@ export function getBot () { * @param {import('whatsapp-web.js').Contact} contact */ export async function findOrCreateContact (contact) { - // 1 - Check if contact.id._serialized is on the cache - if (contactsCache[contact.id._serialized]) { - contactsCache[contact.id._serialized].lastSeen = new Date() - return contactsCache[contact.id._serialized] + const id = contact.id.replace('@s.whatsapp.net', '@c.us') + + // 1 - Check if contact is on the cache + if (contactsCache[id]) { + contactsCache[id].lastSeen = new Date() + return contactsCache[id] } // 2 - If not, fetch from the database - const response = await fetch(`${dbUrl}/contacts/${contact.id._serialized}`, { + const response = await fetch(`${dbUrl}/contacts/${id}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -144,20 +145,18 @@ export async function findOrCreateContact (contact) { }, body: JSON.stringify({ data: { - name: contact.name, - number: contact.id.user, + number: id.split('@')[0], pushname: contact.pushname, - isMyContact: contact.isMyContact, - wid: contact.id._serialized + wid: id } }) }) const data = await response.json() // 3 - Save on the cache - contactsCache[contact.id._serialized] = data - contactsCache[contact.id._serialized].lastSeen = new Date() - return contactsCache[contact.id._serialized] + contactsCache[id] = data + contactsCache[id].lastSeen = new Date() + return contactsCache[id] } // mini cache system for contacts, every minute filter out the contacts that haven't been seen in the last 5 minutes @@ -173,18 +172,20 @@ setInterval(() => { /** * Find or create a chat on the database - * - * @param {import('whatsapp-web.js').Chat} chat */ -export async function findOrCreateChat (chat) { +export async function findOrCreateChat (msg) { // 1 - Check if chat.id._serialized is on the cache - if (chatsCache[chat.id._serialized]) { - chatsCache[chat.id._serialized].lastSeen = new Date() - return chatsCache[chat.id._serialized] + const id = msg.isGroup + ? msg.aux.group.id + : msg.contact.replace('@s.whatsapp.net', '@c.us') + + if (chatsCache[id]) { + chatsCache[id].lastSeen = new Date() + return chatsCache[id] } // 2 - If not, fetch from the database - const response = await fetch(`${dbUrl}/chats/${chat.id._serialized}`, { + const response = await fetch(`${dbUrl}/chats/${id}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -192,17 +193,17 @@ export async function findOrCreateChat (chat) { }, body: JSON.stringify({ data: { - name: chat.name, - isGroup: chat.isGroup, - wid: chat.id._serialized + name: msg.isGroup ? msg.aux.group.subject : msg.pushname, + isGroup: msg.isGroup, + wid: id } }) }) const data = await response.json() // 3 - Save on the cache - chatsCache[chat.id._serialized] = data - chatsCache[chat.id._serialized].lastSeen = new Date() + chatsCache[id] = data + chatsCache[id].lastSeen = new Date() return data } @@ -274,30 +275,27 @@ export async function saveActionToDB (moduleName, functionName, msg) { const commandGroupID = commandGroup?.id const command = commandGroup.commands.find((command) => command.slug === kebabCase(functionName)) const commandID = command?.id - const contact = await findOrCreateContact(msg.aux.sender) + const contact = await findOrCreateContact(msg.contact) const contactID = contact.id - const chat = await findOrCreateChat(msg.aux.chat) + const chat = await findOrCreateChat(msg) const chatID = chat.id - - const action = await createAction(commandGroupID, commandID, chatID, contactID) - + const action = createAction(commandGroupID, commandID, chatID, contactID) try { - const actionID = action.id - return { action, actionID, commandGroup, commandGroupID, command, commandID, contact, contactID, chat, chatID } + return { action, commandGroup, commandGroupID, command, commandID, contact, contactID, chat, chatID } } catch (error) { logger.error('Error saving action to database', error) logger.error('Action:', action) } } -export async function findCurrentBot (client) { +export async function findCurrentBot (socket) { + const id = socket.user.id.split(':')[0] + '@c.us' // 1 - Check if bot already exists on db - const findQuery = qs.stringify( { filters: { wid: { - $eq: client.info.wid._serialized + $eq: id } } }, @@ -305,6 +303,9 @@ export async function findCurrentBot (client) { encodeValuesOnly: true // prettify URL } ) + while (!token) { // wait token to be populated + await new Promise(resolve => setTimeout(resolve, 100)) + } const find = await fetch(`${dbUrl}/bots?${findQuery}`, { method: 'GET', headers: { @@ -312,7 +313,6 @@ export async function findCurrentBot (client) { Authorization: `Bearer ${token}` } }) - const { data: findData } = await find.json() if (findData.length) { @@ -321,7 +321,6 @@ export async function findCurrentBot (client) { } // 2 - If not, create it - const create = await fetch(`${dbUrl}/bots`, { method: 'POST', headers: { @@ -330,12 +329,12 @@ export async function findCurrentBot (client) { }, body: JSON.stringify({ data: { - wid: client.info.wid._serialized, - pushname: client.info.pushname, - platform: client.info.platform + wid: id, + pushname: socket.user.name } }) }) + const { data: createData } = await create.json() bot = createData.id } diff --git a/src/index.js b/src/index.js index 294700e..d08e3b8 100644 --- a/src/index.js +++ b/src/index.js @@ -5,12 +5,11 @@ import * as baileys from '@whiskeysockets/baileys' import { defineCommand, runMain } from 'citty' import { apiKey } from './config/api.js' import { dotCase } from 'change-case' -import NodeCache from 'node-cache' import bot from './config/bot.js' import logger from './logger.js' -import pino from 'pino' +import * as db from './db.js' import fs from 'fs/promises' -import './db.js' +import pino from 'pino' let globalArgs = {} @@ -147,6 +146,7 @@ export async function connectToWhatsApp () { socket.ev.on('creds.update', saveCreds) logger.info('Client initialized!') + await db.findCurrentBot(socket) // find the current bot on the database return socket } diff --git a/src/meta/message.js b/src/meta/message.js index f451538..bb4e53a 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -1,8 +1,8 @@ +import { downloadContentFromMessage } from '@whiskeysockets/baileys' import messageTypeValidator from '../validators/messageType.js' -import { downloadMediaMessage, downloadContentFromMessage } from '@whiskeysockets/baileys' import relativeTime from 'dayjs/plugin/relativeTime.js' -import spintax from '../utils/spintax.js' import { MessageMedia } from './messageMedia.js' +import spintax from '../utils/spintax.js' import { getSocket } from '../index.js' import logger from '../logger.js' import fetch from 'node-fetch' @@ -15,11 +15,8 @@ import dayjs from 'dayjs' // dayjs.locale('pt-br') dayjs.extend(relativeTime) -/** - * Socket instance - * @type {import('@whiskeysockets/baileys').Baileys} - */ -let sock = null + +const socket = getSocket() // // ================================ Main Functions ================================= // @@ -27,58 +24,88 @@ let sock = null * Inject functions into the message object to be drop in replacement for wwebjs * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg */ -const processMessage = (msg) => { - if (!msg?.message) return false - const { type, updatedMsg } = messageTypeValidator(msg) - msg = updatedMsg - - sock = getSocket() - +const serializeMessage = (msg) => { + msg.raw = structuredClone(msg) const newMsgObject = {} + const { type } = messageTypeValidator(msg) - const firstKey = Object.keys(msg.message)[0] - const firstItem = msg.message[firstKey] - - const quotedDeep = structuredClone(msg) - quotedDeep.message = firstItem.contextInfo?.quotedMessage + newMsgObject.type = type + try { + const berak = Object.keys(msg.message)[0] + newMsgObject.originalType = berak + } catch { + newMsgObject.originalType = null + } - const body = typeof firstItem === 'string' + const firstItem = msg.message[newMsgObject.originalType] + newMsgObject.body = typeof firstItem === 'string' ? firstItem : firstItem.caption || firstItem.text || '' - const quotedMsg = firstItem.contextInfo?.quotedMessage - if (quotedMsg) { - const { type } = messageTypeValidator(quotedMsg) - quotedMsg.type = type - const firstKey = Object.keys(quotedMsg)[0] - quotedMsg.hasMedia = !!quotedMsg[firstKey].mediaKey + newMsgObject.hasQuotedMsg = false + newMsgObject.quotedMsg = firstItem.contextInfo?.quotedMessage?.ephemeralMessage + ? firstItem.contextInfo.quotedMessage.ephemeralMessage.message + : firstItem.contextInfo?.quotedMessage + + if (newMsgObject.quotedMsg) { + newMsgObject.hasQuotedMsg = true + newMsgObject.quotedMsg.type = Object.keys(newMsgObject.quotedMsg)[0] + const firstItem = newMsgObject.quotedMsg[newMsgObject.quotedMsg.type] + newMsgObject.quotedMsg.body = typeof firstItem === 'string' + ? firstItem + : firstItem.caption || firstItem.text || '' + newMsgObject.quotedMsg.sender = msg.message[msg.originalType].contextInfo.participant + newMsgObject.quotedMsg.fromMe = msg.quotedMsg.sender === socket.user.id.split(':')[0] + '@s.whatsapp.net' + const ane = msg.quotedMsg + newMsgObject.quotedMsg.chats = (ane.type === 'conversation' && ane.conversation) ? ane.conversation : (ane.type === 'imageMessage') && ane.imageMessage.caption ? ane.imageMessage.caption : (ane.type === 'documentMessage') && ane.documentMessage.caption ? ane.documentMessage.caption : (ane.type === 'videoMessage') && ane.videoMessage.caption ? ane.videoMessage.caption : (ane.type === 'extendedTextMessage') && ane.extendedTextMessage.text ? ane.extendedTextMessage.text : (ane.type === 'buttonsMessage') && ane.buttonsMessage.contentText ? ane.buttonsMessage.contentText : '' + msg.quotedMsg.id = msg.message[msg.originalType].contextInfo.stanzaId + } + + try { + const mention = msg.message[msg.originalType].contextInfo.mentionedJid + newMsgObject.mentioned = mention + } catch { + newMsgObject.mentioned = [] + } + + newMsgObject.isGroup = msg.key.remoteJid.endsWith('@g.us') + if (newMsgObject.isGroup) { + newMsgObject.sender = msg.participant + } else { + newMsgObject.sender = msg.key.remoteJid } + if (msg.key.fromMe) { + newMsgObject.sender = socket.user.id.split(':')[0] + '@s.whatsapp.net' + } + + newMsgObject.isBaileys = msg.key.id.startsWith('BAE5') || msg.key.id.startsWith('3EB0') const properties = { id: msg.key.id, pushname: msg.pushName, - type, + contact: { + id: newMsgObject.sender || msg.key.participant, + pushname: msg.pushName + }, + author: newMsgObject.isGroup ? msg.key.participant : undefined, duration: firstItem.seconds, from: msg.key.remoteJid, fromMe: msg.key.fromMe, - body, // ack: undefined, - author: undefined, broadcast: msg.broadcast, + bot: socket.user, // deviceType: undefined, fowardScore: firstItem.contextInfo?.forwardingScore, isForwarded: firstItem.contextInfo?.isForwarded, hasMedia: !!firstItem.mediaKey, mediaKey: firstItem.mediaKey, - hasQuotedMsg: !!firstItem.contextInfo?.quotedMessage, - quotedMsg: firstItem.contextInfo?.quotedMessage, // TODO: Fix this // hasReaction: undefined, inviteV4: type === 'groups_v4_invite' ? firstItem : undefined, isEphemeral: !!firstItem.contextInfo?.expiration, isGif: !!firstItem.gifPlayback, // isStarred: undefined, // isStatus: undefined, - links: extractLinks(body), + links: extractLinks(newMsgObject.body), location: ['location', 'live_location'].includes(type) ? firstItem : undefined, @@ -92,11 +119,9 @@ const processMessage = (msg) => { timestampIso: dayjs(msg.messageTimestamp * 1000).toISOString(), // lag is the difference between local time and the time of the sender in ms // using dayjs to convert - lag: dayjs().diff(dayjs(msg.messageTimestamp * 1000), 'ms'), + lag: dayjs().diff(dayjs(msg.messageTimestamp * 1000), 'second'), // to: msg.key.fromMe ? msg.key.remoteJid : botId, - vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined, - raw: structuredClone(msg), - sock + vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined } for (const property in properties) { @@ -123,7 +148,7 @@ const processMessage = (msg) => { return newMsgObject } -export default processMessage +export default serializeMessage // // ================================== Methods ================================== @@ -139,7 +164,7 @@ async function react (reaction) { * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ const msg = this - await sock.sendMessage(msg.key.remoteJid, { + await socket.sendMessage(msg.key.remoteJid, { react: { text: spintax(reaction), key: msg.key @@ -198,7 +223,7 @@ async function reply (content, chatId, options) { const firstItem = msg.message[firstKey] const isEphemeral = !!firstItem.contextInfo?.expiration - await sock.sendMessage(chatId || msg.key.remoteJid, messageObject, { + await socket.sendMessage(chatId || msg.key.remoteJid, messageObject, { quoted: msg, ephemeralExpiration: isEphemeral ? firstItem.contextInfo?.expiration : undefined }) @@ -215,7 +240,7 @@ async function sendSeen () { * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ const msg = this - await sock.readMessages([msg.key]) + await socket.readMessages([msg.key]) } /** diff --git a/src/services/events/connectionUpdate.js b/src/services/events/connectionUpdate.js index 7b43d63..e46d215 100644 --- a/src/services/events/connectionUpdate.js +++ b/src/services/events/connectionUpdate.js @@ -8,15 +8,13 @@ import { connectToWhatsApp } from '../../index.js' */ export default async (update) => { logger.trace('Connection updated', update) + if (global.qr !== update.qr) { + global.qr = update.qr + } const { connection, lastDisconnect } = update if (connection === 'close') { - const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== DisconnectReason.loggedOut - logger.warn('connection closed due to ', lastDisconnect.error, ', reconnecting ', shouldReconnect) - // reconnect if not logged out - if (shouldReconnect) { - connectToWhatsApp() - } - } else if (connection === 'open') { - logger.warn('opened connection') + lastDisconnect.error?.output?.statusCode !== DisconnectReason.loggedOut + ? connectToWhatsApp() + : logger.fatal('connection logged out...') } } diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index b9f7f09..724c578 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -1,24 +1,11 @@ import importFresh from '../../utils/importFresh.js' -// import { saveActionToDB } from '../../db.js' +import { saveActionToDB } from '../../db.js' import { getSocket } from '../../index.js' import { camelCase } from 'change-case' import logger from '../../logger.js' // // ================================ Variables ================================= // -const okTypes = [ - 'chat', - 'audio', - 'ptt', - 'image', - 'video', - 'document', - 'sticker', - 'revoked', - 'groups_v4_invite', - 'reaction', - 'edited' -] // // ================================ Main Function ============================= @@ -29,25 +16,25 @@ const okTypes = [ */ export default async (upsert) => { logger.trace('messages.upsert', upsert) - if (upsert.messages[0].key.fromMe) return // ignore self messages + if (!upsert.messages) return // ignore if there are no messages + let msg = upsert.messages[0] + if (msg.key.fromMe) return // ignore self messages const meta = await importFresh('meta/message.js') - const msg = meta.default(upsert.messages[0]) - if (!msg) return - - if (!okTypes.includes(msg.type)) return - await msg.sendSeen() + msg = meta.default(msg) if (msg.type === 'revoked') { // TODO: send random "Deus viu o que você apagou" sticker + await msg.sendSeen() return await msg.reply('👀 - Eu vi o que você apagou') } if (msg.type === 'edited') { + await msg.sendSeen() await msg.react('👀') return await msg.reply('👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}') } - const sock = getSocket() - await sock.sendPresenceUpdate('available') + const socket = getSocket() + await socket.sendPresenceUpdate('available') const messageParser = await importFresh('validators/message.js') const handlerModule = await messageParser.default(msg) logger.trace('handlerModule: ', handlerModule) @@ -55,17 +42,20 @@ export default async (upsert) => { if (!handlerModule) return logger.debug('handlerModule is undefined') - // TODO: make legacy db works - // msg.aux.db = await saveActionToDB(handlerModule.type, handlerModule.command, msg) + await msg.sendSeen() + msg.aux.db = await saveActionToDB( + handlerModule.type, + handlerModule.command, + msg + ) - // only works with db - // const checkDisabled = await importFresh('validators/checkDisabled.js') - // const isEnabled = await checkDisabled.default(msg) - // if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) + const checkDisabled = await importFresh('validators/checkDisabled.js') + const isEnabled = await checkDisabled.default(msg) + if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) - // const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') - // const isOwnerOnly = await checkOwnerOnly.default(msg) - // if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${handlerModule.command} - Restricted to admins`) + const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') + const isOwnerOnly = await checkOwnerOnly.default(msg) + if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${handlerModule.command} - Restricted to admins`) // TODO: implement queue system const moduleName = handlerModule.type diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 6c67c83..6dad2cd 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -14,6 +14,8 @@ import mime from 'mime-types' import OpenAI from 'openai' import sharp from 'sharp' import dayjs from 'dayjs' +import path from 'path' +import fs from 'fs' dayjs.locale('pt-br') dayjs.extend(relativeTime) @@ -146,32 +148,31 @@ export async function toFile (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toUrl (msg) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - // if ((!msg.hasQuotedMsg && !msg.hasMedia) || (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { - // await msg.react(reactions.error) - - // const header = '☠️🤖' - // const part1 = 'Para usar o *{!toUrl|!url}* você {precisa|tem que}' - // const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' - // const end = '{!|!!|!!!}' - - // const message = spintax(`${header} - ${part1} ${part2}${end}`) - // return await msg.reply(message) - // } + if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + await msg.react(reactions.error) + + const header = '☠️🤖' + const part1 = 'Para usar o *{!toUrl|!url}* você {precisa|tem que}' + const part2 = '{enviar|mandar} {esse|o} comando {respondendo ou na legenda} um {arquivo}' + const end = '{!|!!|!!!}' - // await msg.react('🔗') - // const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() - // if (!media) throw new Error('Error downloading media') - // const tempUrl = (await getTempUrl(media)).replace('http://', 'https://') - - // let message = '🔗 - ' - // message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' - // message += '{a url temporária|o link temporário|o endereço temporário} ' - // message += '{para {o|esse}|desse} arquivo: ' - // message += `${tempUrl}\n\n` - // message += '{Válido por {apenas|}|Com {validade|vigência} de|Por um período de} {3|03|três} dias' - // await msg.reply(spintax(message)) + const message = spintax(`${header} - ${part1} ${part2}${end}`) + return await msg.reply(message) + } + + await msg.react('🔗') + const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() + if (!media) throw new Error('Error downloading media') + const tempUrl = (await getTempUrl(media)) + console.log('tempUrl', tempUrl) + + let message = '🔗 - ' + message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' + message += '{a url temporária|o link temporário|o endereço temporário} ' + message += '{para {o|esse}|desse} arquivo: ' + message += `${tempUrl}\n\n` + message += '{Válido por {apenas|}|Com {validade|vigência} de|Por um período de} {3|03|três} dias' + await msg.reply(message) } /** @@ -210,59 +211,66 @@ export async function ping (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function speak (msg) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - // let msgToReply = msg - // let input = msg.body - // if (msg.hasQuotedMsg && !msg.body) { - // const quotedMsg = await msg.getQuotedMessage() - // input = quotedMsg.body - // msgToReply = quotedMsg - // } + let input = msg.body + console.log('msg.hasQuotedMsg', msg.hasQuotedMsg) + console.log('!msg.body', !msg.body) + if (msg.hasQuotedMsg && !msg.body) { + const quotedMsg = msg.quotedMsg + console.log('quotedMsg', quotedMsg) + console.log('quotedMsg.text', quotedMsg.text) + const keys = Object.keys(quotedMsg) + const firstItem = quotedMsg[keys[0]] + console.log('keys', keys) + input = typeof firstItem === 'string' + ? firstItem + : firstItem.caption || firstItem.text || '' + console.log('input', input) + } - // if (!input) { - // await msg.reply( - // spintax('Para usar o *{!speak|!fale|!falar|!voz|!diga|!dizer}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um texto!\n\nExemplo: `!diga Olá, eu sou o DeadByte!`') - // ) - // await msg.react(reactions.error) - // return - // } + if (!input) { + await msg.reply( + spintax('Para usar o *{!speak|!fale|!falar|!voz|!diga|!dizer}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um texto!\n\nExemplo: `!diga Olá, eu sou o DeadByte!`') + ) + await msg.react(reactions.error) + return + } - // const inputLimit = 1000 - // const originalInputSize = input.length + const inputLimit = 1000 + const originalInputSize = input.length - // if (originalInputSize > inputLimit) { - // await msg.reply(`O texto não pode ter mais de ${inputLimit} caracteres!\n\nO seu texto tem ${originalInputSize} caracteres!\nVou cortar o texto para você!`) - // input = input.slice(0, inputLimit) - // } + if (originalInputSize > inputLimit) { + await msg.reply(`O texto não pode ter mais de ${inputLimit} caracteres!\n\nO seu texto tem ${originalInputSize} caracteres!\nVou cortar o texto para você!`) + input = input.slice(0, inputLimit) + } - // await msg.react('🗣️') + await msg.react('🗣️') // await msg.aux.chat.sendStateRecording() - // const voices = ['onyx', 'echo', 'fable', 'nova', 'shimmer'] + const voices = ['onyx', 'echo', 'fable', 'nova', 'shimmer'] - // // function can be called with !speak1, !speak2, !speak3, !speak4, !speak5 - // let voiceId = parseInt(msg.aux.function.slice(-1)) - 1 || 0 - // // say if the voice is invalid - // if (voiceId > voices.length - 1) { - // await msg.reply(`Essa voz não existe!\n\nAs vozes disponíveis são:\n${voices.map((v, i) => `${i + 1} - ${v}`).join('\n')}\n\nIrei usar a voz padrão!`) - // voiceId = 0 - // } + // function can be called with !speak1, !speak2, !speak3, !speak4, !speak5 + let voiceId = parseInt(msg.aux.function.slice(-1)) - 1 || 0 + // say if the voice is invalid + if (voiceId > voices.length - 1) { + await msg.reply(`Essa voz não existe!\n\nAs vozes disponíveis são:\n${voices.map((v, i) => `${i + 1} - ${v}`).join('\n')}\n\nIrei usar a voz padrão!`) + voiceId = 0 + } - // const voice = voices[voiceId] + const voice = voices[voiceId] - // const opus = await openai.audio.speech.create({ - // input, - // voice, - // model: 'tts-1', - // response_format: 'opus' - // }) + const opus = await openai.audio.speech.create({ + input, + voice, + model: 'tts-1', + response_format: 'opus' + }) - // const buffer = Buffer.from(await opus.arrayBuffer()) + const buffer = Buffer.from(await opus.arrayBuffer()) - // // hand make the media object - // const media = new wwebjs.MessageMedia('audio/ogg; codecs=opus', buffer.toString('base64'), 'DeadByte.opus') - // await msgToReply.reply(media, undefined, { sendAudioAsVoice: true }) + // hand make the media object + const media = new MessageMedia('audio/ogg; codecs=opus', buffer.toString('base64'), 'DeadByte' + Date.now() + '.opus' + , buffer.length) + await msg.reply({ media }, undefined, { ptt: true }) } /** @@ -270,41 +278,35 @@ export async function speak (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function transcribe (msg) { - await msg.react('❌') - await msg.reply('❌ - Esse comando está desativado no momento!') - - // let msgToReply = msg - // let media = msg.hasMedia ? await msg.downloadMedia() : null - // if (msg.hasQuotedMsg && !msg.hasMedia) { - // const quotedMsg = await msg.getQuotedMessage() - // media = await quotedMsg.downloadMedia() - // msgToReply = quotedMsg - // } + let media = msg.hasMedia ? await msg.downloadMedia() : null + if (msg.hasQuotedMsg && !msg.hasMedia) { + media = await msg.downloadMedia(true) + } - // if (!media) { - // await msg.reply( - // spintax('Para usar o *{!transcribe|!transcricao|!transcrever}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um áudio!\n\nExemplo: `!transcrever` respondendo um áudio') - // ) - // await msg.react(reactions.error) - // return - // } + if (!media) { + await msg.reply( + spintax('Para usar o *{!transcribe|!transcricao|!transcrever}* você {precisa|tem que} {enviar|mandar} {esse|o} comando junto com um áudio!\n\nExemplo: `!transcrever` respondendo um áudio') + ) + await msg.react(reactions.error) + return + } - // await msg.react('🎙️') + await msg.react('🎙️') // await msg.aux.chat.sendStateTyping() - // // save file to temp folder - // const timestampish = Date.now().toString().slice(-10) - // const filePath = `./src/temp/${timestampish}.mp3` - // const nomalizedFilePath = path.resolve(filePath) - // fs.writeFileSync(nomalizedFilePath, media.data, { encoding: 'base64' }) - - // const transcription = await openai.audio.transcriptions.create({ - // file: fs.createReadStream(nomalizedFilePath), - // model: 'whisper-1', - // response_format: 'text' - // }) - // fs.unlinkSync(nomalizedFilePath) - // await msgToReply.reply(`🎙️ - ${transcription.trim()}`) + // save file to temp folder + const timestampish = Date.now().toString().slice(-10) + const filePath = `./src/temp/${timestampish}.mp3` + const nomalizedFilePath = path.resolve(filePath) + fs.writeFileSync(nomalizedFilePath, media.data, { encoding: 'base64' }) + + const transcription = await openai.audio.transcriptions.create({ + file: fs.createReadStream(nomalizedFilePath), + model: 'whisper-1', + response_format: 'text' + }) + fs.unlinkSync(nomalizedFilePath) + await msg.reply(`🎙️ - ${transcription.trim()}`) } // diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 20f4063..c1bb046 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -18,6 +18,7 @@ const sock = getSocket() * @param {string} stickerAuthor */ export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite = true) { + console.log('toUrl', msg) await msg.react(reactions.wait) const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() diff --git a/src/validators/message.js b/src/validators/message.js index 05f7769..a05ea0f 100644 --- a/src/validators/message.js +++ b/src/validators/message.js @@ -2,6 +2,7 @@ import importFresh from '../utils/importFresh.js' import fs from 'fs/promises' import logger from '../logger.js' import reactions from '../config/reactions.js' +import { getSocket } from '../index.js' // // ================================ Variables ================================= // @@ -15,6 +16,8 @@ const commandless = (msg, aux) => { miscellaneousFNtranscribe: msg.hasMedia && ['audio', 'ptt'].includes(msg.type) } } + +const socket = getSocket() // // ================================ Main Functions ================================= // @@ -30,31 +33,14 @@ const commandless = (msg, aux) => { */ export default async (msg) => { const aux = {} // auxiliar variables - // aux.client = (await import('../index.js')).getClient() - // aux.chat = await msg.getChat() - // aux.sender = await msg.getContact() aux.senderIsMe = msg.fromMe - aux.me = msg.sock.user.id.split(':')[0] + '@s.whatsapp.net' + aux.me = msg.bot.id.split(':')[0] + '@s.whatsapp.net' aux.mentionedMe = msg.mentionedIds ? msg.mentionedIds.includes(aux.me) : false if (aux.mentionedMe) { msg.body = msg.body.replace(new RegExp(`@${aux.me.split('@')[0]}`, 'g'), '').trim() } - // TODO: create getQuotedMessage() method - // if (msg.hasQuotedMsg) { - // aux.quotedMsg = await msg.getQuotedMessage() - // } - - // let msgCurrent = msg - // const msgPrevious = [] - // while (msgCurrent.hasQuotedMsg) { - // msgPrevious.push(msgCurrent) - // msgCurrent = await msgCurrent.getQuotedMessage() - // } - // aux.originalMsg = msgCurrent aux.originalMsg = msg - // msgPrevious.push(aux.originalMsg) - // aux.history = msgPrevious.reverse() aux.history = [aux.originalMsg] // Check if the message is a command @@ -79,15 +65,15 @@ export default async (msg) => { } aux.mentions = msg.mentionedIds - aux.amIMentioned = aux.mentions ? aux.mentions.includes(aux.me) : false - // aux.participants = aux.chat.isGroup ? aux.chat.participants : [] - // aux.admins = aux.chat.isGroup ? aux.participants.filter((p) => p.isAdmin || p.isSuperAdmin).map((p) => p.id._serialized) : [] - // aux.isSenderAdmin = aux.admins.includes(msg.author) - // aux.isBotAdmin = aux.admins.includes(aux.me) + aux.group = msg.isGroup ? await socket.groupMetadata(msg.from) : '' + aux.participants = msg.isGroup ? aux.group.participants : [] + aux.admins = msg.isGroup ? aux.participants.filter((p) => !!p.admin).map((p) => p.id) : [] + aux.isSenderAdmin = aux.admins.includes(msg.author) + aux.isBotAdmin = aux.admins.includes(aux.me) - // const stickerGroup = '120363187692992289@g.us' - // aux.isStickerGroup = aux.chat.isGroup ? aux.chat.id._serialized === stickerGroup : false + const stickerGroup = '120363187692992289@g.us' + aux.isStickerGroup = msg.isGroup ? msg.from === stickerGroup : false try { msg.aux = aux @@ -143,19 +129,21 @@ export default async (msg) => { msg.body = aux.originalBody // Send incorrect function reaction - if (aux.isFunction) return false // if any function reach this point, it is an incorrect function - // if (aux.chat.isGroup && !aux.mentionedMe) { - // if (aux.isStickerGroup && msg.type === 'chat') { - // return false // ignore texts in sticker group - // } - - // if (!aux.isStickerGroup) { - // const isStickerPack = msg.body.startsWith('https://sticker.ly/s/') - // if (!isStickerPack && !['audio', 'ptt'].includes(msg.type)) { - // return false - // } - // } - // } + if (aux.isFunction) { + return false // if any function reach this point, it is an incorrect function + } + if (msg.isGroup && !aux.mentionedMe) { + if (aux.isStickerGroup && msg.type === 'chat') { + return false // ignore texts in sticker group + } + + if (!aux.isStickerGroup) { + const isStickerPack = msg.body.startsWith('https://sticker.ly/s/') + if (!isStickerPack && !['audio', 'ptt'].includes(msg.type)) { + return false + } + } + } if (isOneOf(commandless(msg, aux))) { const command = getFirstMatch(commandless(msg, aux)) diff --git a/src/validators/messageType.js b/src/validators/messageType.js index 60a9e35..8fea4e0 100644 --- a/src/validators/messageType.js +++ b/src/validators/messageType.js @@ -78,6 +78,24 @@ export default (msg) => { } } + while (incomingType === 'ephemeralMessage') { + msg.message = msg.message[firstKey].message + firstKey = Object.keys(msg.message)[0] + if (!firstKey) throw new Error('firstKey is undefined') + incomingType = firstKey + } + // while (incomingType === 'ephemeralMessage') { + // const firstInside = msg[] + // } + // nometime msg.message comes with layers of "ephemeralMessage" + // loop digging until it finds something that is not ephemeralMessage + // if (incomingType === 'ephemeralMessage') { + // msg = msg[firstKey].message + // console.log(incomingType, msg) + // // firstKey = Object.keys(msg)[0] + // // incomingType = firstKey + // } + /** * Handle viewOnceMessage && groupMentionedMessage */ @@ -85,10 +103,11 @@ export default (msg) => { incomingType.startsWith('viewOnce') || incomingType === 'groupMentionedMessage' ) { - const keys = Object.keys(msg.message[firstKey].message).filter(key => !keysToIgnore.includes(key)) + const keys = Object.keys((msg.message ? msg.message : msg)[firstKey].message).filter(key => !keysToIgnore.includes(key)) if (keys.length) { incomingType = keys[0] - msg.message = msg.message[firstKey].message + if (msg.message) msg.message = msg.message[firstKey].message + else msg = msg[firstKey].message } } @@ -119,15 +138,15 @@ export default (msg) => { * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg */ function parseAudioTypes (msg) { - const audioType = msg.message.audioMessage.ptt ? 'pttMessage' : 'audioMessage' + const audioType = (msg.message ? msg.message : msg).audioMessage.ptt ? 'pttMessage' : 'audioMessage' if (audioType === 'pttMessage') return audioType // if it is audio, and still can be a forwarded ppt - if (msg.message.audioMessage.contextInfo?.isForwarded) { - const hasWaveform = !!msg.message.audioMessage.waveform + if ((msg.message ? msg.message : msg).audioMessage.contextInfo?.isForwarded) { + const hasWaveform = !!(msg.message ? msg.message : msg).audioMessage.waveform if (hasWaveform) return 'pttMessage' - const mimetype = msg.message.audioMessage.mimetype + const mimetype = (msg.message ? msg.message : msg).audioMessage.mimetype const isOpus = mimetype === 'audio/ogg; codecs=opus' if (isOpus) return 'pttMessage' } From 3d579338f7651ae70bfc406c0f106c3a2dbd0a60 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 30 Jan 2024 02:33:50 -0300 Subject: [PATCH 25/79] mvp --- src/db.js | 2 +- src/meta/message.js | 39 +++++----- src/services/commands/groups.js | 20 +++--- src/services/commands/tools.js | 2 +- src/services/events/messagesUpsert.js | 1 - src/services/functions/groups.js | 95 ++++++++++++++----------- src/services/functions/menu.js | 10 +-- src/services/functions/miscellaneous.js | 7 -- src/services/functions/statistics.js | 32 +++++---- src/services/functions/stickers.js | 1 - src/services/functions/tools.js | 40 +++++------ 11 files changed, 125 insertions(+), 124 deletions(-) diff --git a/src/db.js b/src/db.js index 2e49b37..bdf763a 100644 --- a/src/db.js +++ b/src/db.js @@ -177,7 +177,7 @@ export async function findOrCreateChat (msg) { // 1 - Check if chat.id._serialized is on the cache const id = msg.isGroup ? msg.aux.group.id - : msg.contact.replace('@s.whatsapp.net', '@c.us') + : msg.contact.id.replace('@s.whatsapp.net', '@c.us') if (chatsCache[id]) { chatsCache[id].lastSeen = new Date() diff --git a/src/meta/message.js b/src/meta/message.js index bb4e53a..c4fe735 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -25,17 +25,14 @@ const socket = getSocket() * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg */ const serializeMessage = (msg) => { - msg.raw = structuredClone(msg) + const raw = structuredClone(msg) const newMsgObject = {} const { type } = messageTypeValidator(msg) newMsgObject.type = type - try { - const berak = Object.keys(msg.message)[0] - newMsgObject.originalType = berak - } catch { - newMsgObject.originalType = null - } + + const berak = Object.keys(msg.message)[0] + newMsgObject.originalType = berak const firstItem = msg.message[newMsgObject.originalType] newMsgObject.body = typeof firstItem === 'string' @@ -49,16 +46,16 @@ const serializeMessage = (msg) => { if (newMsgObject.quotedMsg) { newMsgObject.hasQuotedMsg = true + newMsgObject.quotedMsg.id = msg.message[newMsgObject.originalType].contextInfo?.stanzaId + newMsgObject.quotedMsg.type = Object.keys(newMsgObject.quotedMsg)[0] const firstItem = newMsgObject.quotedMsg[newMsgObject.quotedMsg.type] + newMsgObject.quotedMsg.hasMedia = Object.keys(firstItem).includes('mediaKey') newMsgObject.quotedMsg.body = typeof firstItem === 'string' ? firstItem : firstItem.caption || firstItem.text || '' - newMsgObject.quotedMsg.sender = msg.message[msg.originalType].contextInfo.participant - newMsgObject.quotedMsg.fromMe = msg.quotedMsg.sender === socket.user.id.split(':')[0] + '@s.whatsapp.net' - const ane = msg.quotedMsg - newMsgObject.quotedMsg.chats = (ane.type === 'conversation' && ane.conversation) ? ane.conversation : (ane.type === 'imageMessage') && ane.imageMessage.caption ? ane.imageMessage.caption : (ane.type === 'documentMessage') && ane.documentMessage.caption ? ane.documentMessage.caption : (ane.type === 'videoMessage') && ane.videoMessage.caption ? ane.videoMessage.caption : (ane.type === 'extendedTextMessage') && ane.extendedTextMessage.text ? ane.extendedTextMessage.text : (ane.type === 'buttonsMessage') && ane.buttonsMessage.contentText ? ane.buttonsMessage.contentText : '' - msg.quotedMsg.id = msg.message[msg.originalType].contextInfo.stanzaId + newMsgObject.quotedMsg.sender = msg.message[newMsgObject.originalType].contextInfo?.participant + newMsgObject.quotedMsg.fromMe = newMsgObject.quotedMsg.sender === socket.user.id.split(':')[0] + '@s.whatsapp.net' } try { @@ -97,8 +94,8 @@ const serializeMessage = (msg) => { // deviceType: undefined, fowardScore: firstItem.contextInfo?.forwardingScore, isForwarded: firstItem.contextInfo?.isForwarded, - hasMedia: !!firstItem.mediaKey, - mediaKey: firstItem.mediaKey, + hasMedia: Object.keys(firstItem).includes('mediaKey'), + mediaKey: Object.keys(firstItem).includes('mediaKey') ? firstItem.mediaKey : undefined, // hasReaction: undefined, inviteV4: type === 'groups_v4_invite' ? firstItem : undefined, isEphemeral: !!firstItem.contextInfo?.expiration, @@ -121,7 +118,8 @@ const serializeMessage = (msg) => { // using dayjs to convert lag: dayjs().diff(dayjs(msg.messageTimestamp * 1000), 'second'), // to: msg.key.fromMe ? msg.key.remoteJid : botId, - vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined + vCards: type === 'multi_vcard' ? firstItem.contacts : type === 'vcard' ? [firstItem] : undefined, + raw } for (const property in properties) { @@ -177,7 +175,7 @@ async function react (reaction) { * @param {string} content The message to send * @param {string} [chatId] The chat to send the message in * @param {import('@whiskeysockets/baileys').proto.IMessageOptions} [options] Additional options - * @returns {Promise} + * @returns {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ async function reply (content, chatId, options) { const mode = typeof content === 'string' ? 'text' : 'media' @@ -219,15 +217,12 @@ async function reply (content, chatId, options) { */ const msg = this - const firstKey = Object.keys(msg.message)[0] - const firstItem = msg.message[firstKey] - const isEphemeral = !!firstItem.contextInfo?.expiration - - await socket.sendMessage(chatId || msg.key.remoteJid, messageObject, { + const message = await socket.sendMessage(chatId || msg.key.remoteJid, messageObject, { quoted: msg, - ephemeralExpiration: isEphemeral ? firstItem.contextInfo?.expiration : undefined + ephemeralExpiration: msg.message[Object.keys(msg.message)[0]].contextInfo?.expiration || undefined }) if (tempPath) await fs.unlink(tempPath) + return message } /** diff --git a/src/services/commands/groups.js b/src/services/commands/groups.js index d47b04d..af928aa 100644 --- a/src/services/commands/groups.js +++ b/src/services/commands/groups.js @@ -5,15 +5,15 @@ */ export default (msg) => { return { - debug: false - // ban: msg.aux.chat.isGroup && /^(ban)$/.test(msg.aux.function), - // promote: msg.aux.chat.isGroup && /^(promote|promove|promover)$/.test(msg.aux.function), - // demote: msg.aux.chat.isGroup && /^(demote|rebaixa|rebaixar)$/.test(msg.aux.function), - // giveaway: msg.aux.chat.isGroup && /^(sorteio|sortear)$/.test(msg.aux.function), - // 'giveaway-admins-only': msg.aux.chat.isGroup && /^(sorteioadm|sortearadm)$/.test(msg.aux.function), - // 'mark-all-members': msg.aux.chat.isGroup && /^(todos|all|hiddenmention)$/.test(msg.aux.function), - // 'call-admins': msg.aux.chat.isGroup && /^(adm|adms|admins)$/.test(msg.aux.function), - // 'close-group': msg.aux.chat.isGroup && /^(close|fechar)$/.test(msg.aux.function), - // 'open-group': msg.aux.chat.isGroup && /^(open|abrir)$/.test(msg.aux.function) + ban: msg.isGroup && /^(ban)$/.test(msg.aux.function), + unban: msg.isGroup && /^(unban)$/.test(msg.aux.function), + promote: msg.isGroup && /^(promote|promove|promover)$/.test(msg.aux.function), + demote: msg.isGroup && /^(demote|rebaixa|rebaixar)$/.test(msg.aux.function), + giveaway: msg.isGroup && /^(sorteio|sortear)$/.test(msg.aux.function), + 'giveaway-admins-only': msg.isGroup && /^(sorteioadm|sortearadm)$/.test(msg.aux.function), + 'mark-all-members': msg.isGroup && /^(todos|all|hiddenmention)$/.test(msg.aux.function), + 'call-admins': msg.isGroup && /^(adm|adms|admins)$/.test(msg.aux.function), + 'close-group': msg.isGroup && /^(close|fechar)$/.test(msg.aux.function), + 'open-group': msg.isGroup && /^(open|abrir)$/.test(msg.aux.function) } } diff --git a/src/services/commands/tools.js b/src/services/commands/tools.js index 716a67e..fe52152 100644 --- a/src/services/commands/tools.js +++ b/src/services/commands/tools.js @@ -5,7 +5,7 @@ */ export default (msg) => { return { - 'qr-reader': /^(qrl|lerqr|readqr)$/.test(msg.aux.function) || (/^(qr)$/.test(msg.aux.function) && (msg.hasMedia || msg.aux.quotedMsg?.hasMedia)), + 'qr-reader': /^(qrl|lerqr|readqr)$/.test(msg.aux.function) || (/^(qr)$/.test(msg.aux.function) && (msg.hasMedia || msg.quotedMsg?.hasMedia)), 'qr-image-creator': /^(qr|qrimg|createqr)$/.test(msg.aux.function), 'qr-text-creator': /^(qrt|qrtexto|textqr)$/.test(msg.aux.function) } diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 724c578..b477ee3 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -38,7 +38,6 @@ export default async (upsert) => { const messageParser = await importFresh('validators/message.js') const handlerModule = await messageParser.default(msg) logger.trace('handlerModule: ', handlerModule) - console.log('handlerModule: ', handlerModule) if (!handlerModule) return logger.debug('handlerModule is undefined') diff --git a/src/services/functions/groups.js b/src/services/functions/groups.js index 3f80683..1bdc799 100644 --- a/src/services/functions/groups.js +++ b/src/services/functions/groups.js @@ -1,4 +1,6 @@ import spintax from '../../utils/spintax.js' +import { getSocket } from '../../index.js' +const socket = getSocket() /** * Ban user from group * @param {import('../../types.d.ts').WWebJSMessage} msg @@ -16,13 +18,36 @@ export async function ban (msg) { return await msg.reply('para usar o !ban *você* precisa ser admin') } - const quotedMsg = await msg.getQuotedMessage() - const author = await quotedMsg.getContact() + const author = await msg.quotedMsg.sender + const admins = msg.aux.admins + if (admins.includes(author)) { + await msg.react('🤡') + return await msg.reply('Desculpe, mas eu não posso banir administradores') + } - await msg.aux.chat.removeParticipants([author.id._serialized]) + await socket.groupParticipantsUpdate(msg.from, [author], 'remove') await msg.react('🔨') } +export async function unban (msg) { + if (!msg.hasQuotedMsg) { + return await msg.reply('para usar o !unban você precisa responder a mensagem da pessoa que deseja banir') + } + + if (!msg.aux.isBotAdmin) { + return await msg.reply('para usar o !unban *o bot* precisa ser admin') + } + + if (!msg.aux.isSenderAdmin) { + return await msg.reply('para usar o !unban *você* precisa ser admin') + } + + const author = await msg.quotedMsg.sender + + await socket.groupParticipantsUpdate(msg.from, [author], 'add') + await msg.react('🔄') +} + /** * Promote user to admin * @param {import('../../types.d.ts').WWebJSMessage} msg @@ -39,8 +64,7 @@ export async function promote (msg) { if (!msg.aux.isSenderAdmin) { return await msg.reply('para usar o !promove *você* precisa ser admin') } - - await msg.aux.chat.promoteParticipants(msg.aux.mentions) + await socket.groupParticipantsUpdate(msg.from, msg.aux.mentions, 'promote') await msg.react('↗️') } @@ -49,8 +73,8 @@ export async function promote (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function demote (msg) { - if (!msg.aux.mentions.length === 0) { - return await msg.reply('para usar o !demote você precisa *mensionar* o @ da pessoa que deseja rebaixar') + if (msg.aux.mentions.length === 0) { + return await msg.reply('Para usar o !demote você precisa *mensionar* o @ da pessoa que deseja rebaixar') } if (!msg.aux.isBotAdmin) { @@ -61,7 +85,7 @@ export async function demote (msg) { return await msg.reply('para usar o !demote *você* precisa ser admin') } - await msg.aux.chat.demoteParticipants(msg.aux.mentions) + await socket.groupParticipantsUpdate(msg.from, msg.aux.mentions, 'demote') await msg.react('↘️') } @@ -73,19 +97,15 @@ export async function giveaway (msg) { const hasText = !!msg.body.trim() let participants = await msg.aux.participants - const botId = msg.aux.client.info.wid._serialized - participants = participants.filter((p) => p.id._serialized !== botId) + const botId = msg.aux.me + participants = participants.filter((p) => p.id !== botId) const random = Math.floor(Math.random() * (participants.length)) const winner = participants[random] - const winnerContact = await msg.aux.client.getContactById(winner.id._serialized) - - let message = `{🎉|🎊|🥳|✨|🌟} - {@${winnerContact.id.user} parabéns| {Meus p|P}arabéns @${winnerContact.id.user}}! {Você|Tu|Vc} {ganhou|venceu|acaba de ganhar} o {incrível |super |magnífico |maravilhoso |fantástico |excepcional |}{sorteio|concurso|prêmio}` + let message = `{🎉|🎊|🥳|✨|🌟} - {@${winner.id.split('@')[0]} parabéns|Meus parabéns @${winner.id.split('@')[0]}}! {Você|Tu|Vc} {ganhou|venceu|acaba de ganhar} o {incrível |super |magnífico |maravilhoso |fantástico |excepcional |}{sorteio|concurso|prêmio}` message = hasText ? `${message} de *${msg.body.trim()}*!` : message + '!' - await msg.aux.chat.sendMessage(spintax(message), { - mentions: [winnerContact] - }) + await socket.sendMessage(msg.from, { text: spintax(message), mentions: [winner.id] }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) await msg.react(spintax('{🎉|🎊|🥳}')) } @@ -98,20 +118,16 @@ export async function giveawayAdminsOnly (msg) { const hasText = msg.body.split(' ').length > 1 const text = hasText ? msg.body : '' - let participants = await msg.aux.participants.filter((p) => p.isAdmin || p.isSuperAdmin) - const botId = msg.aux.client.info.wid._serialized - participants = participants.filter((p) => p.id._serialized !== botId) + let participants = await msg.aux.participants.filter((p) => p.admin) + const botId = msg.aux.me + participants = participants.filter((p) => p.id !== botId) const random = Math.floor(Math.random() * (participants.length)) const winner = participants[random] - const winnerContact = await msg.aux.client.getContactById(winner.id._serialized) - - let message = `🎉 - @${winnerContact.id.user} parabéns! Você ganhou o sorteio` + let message = `🎉 - @${winner.id.split('@')[0]} parabéns! Você ganhou o sorteio` message = hasText ? `${message} *${text.trim()}*!` : message + '!' - await msg.aux.chat.sendMessage(message, { - mentions: [winnerContact] - }) + await socket.sendMessage(msg.from, { text: spintax(message), mentions: [winner.id] }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) await msg.react('🎉') } @@ -127,13 +143,12 @@ export async function markAllMembers (msg) { msg.body = msg.body.charAt(0).toUpperCase() + msg.body.slice(1) - const participants = msg.aux.participants.map((p) => p.id._serialized) - const contactArray = [] - for (let i = 0; i < participants.length; i++) { - contactArray.push(await msg.aux.client.getContactById(participants[i])) - } + const participants = msg.aux.participants.map((p) => p.id) - await msg.aux.chat.sendMessage(msg.body ? `📣 - ${msg.body}` : '📣', { mentions: contactArray }) + await socket.sendMessage(msg.from, { + text: msg.body ? `📣 - ${msg.body}` : '📣', + mentions: participants + }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) await msg.react('📣') } @@ -143,11 +158,10 @@ export async function markAllMembers (msg) { */ export async function callAdmins (msg) { const admins = msg.aux.admins - const contactArray = [] - for (let i = 0; i < admins.length; i++) { - contactArray.push(await msg.aux.client.getContactById(admins[i])) - } - await msg.aux.chat.sendMessage('👑 - Atenção administradores!', { mentions: contactArray }) + await socket.sendMessage(msg.from, { + text: '👑 - Atenção administradores!', + mentions: admins + }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) await msg.react('👑') } @@ -156,6 +170,7 @@ export async function callAdmins (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function closeGroup (msg) { + await msg.react('🔒') if (!msg.aux.isBotAdmin) { return await msg.reply('para usar o !fechar *o bot* precisa ser admin') } @@ -163,9 +178,7 @@ export async function closeGroup (msg) { if (!msg.aux.isSenderAdmin) { return await msg.reply('para usar o !fechar *você* precisa ser admin') } - - await msg.aux.chat.setMessagesAdminsOnly(true) - await msg.react('🔒') + await socket.groupSettingUpdate(msg.from, 'announcement') } /** @@ -173,6 +186,7 @@ export async function closeGroup (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function openGroup (msg) { + await msg.react('🔓') if (!msg.aux.isBotAdmin) { return await msg.reply('para usar o !abrir *o bot* precisa ser admin') } @@ -181,6 +195,5 @@ export async function openGroup (msg) { return await msg.reply('para usar o !abrir *você* precisa ser admin') } - await msg.aux.chat.setMessagesAdminsOnly(false) - await msg.react('🔓') + await socket.groupSettingUpdate(msg.from, 'not_announcement') } diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index 9b7dc39..702269c 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -1,7 +1,7 @@ +import { MessageMedia } from '../../meta/messageMedia.js' import relativeTime from 'dayjs/plugin/relativeTime.js' import spintax from '../../utils/spintax.js' import { getCommands } from '../../db.js' -import wwebjs from 'whatsapp-web.js' import 'dayjs/locale/pt-br.js' import dayjs from 'dayjs' @@ -36,7 +36,7 @@ export async function menu (msg) { ] const randomImage = menuImages[Math.floor(Math.random() * menuImages.length)] - const media = await wwebjs.MessageMedia.fromUrl(randomImage, { unsafeMime: true }) + const media = await MessageMedia.fromUrl(randomImage) if (!media) throw new Error('Error downloading media') // remove CommandGroups where hideFromMenu is true @@ -69,6 +69,8 @@ export async function menu (msg) { // Tell About prefix message += 'Os seguintes prefixos são aceitos para os comandos: *! . # /*\n\n' + const readMore = '​'.repeat(783) + message += readMore // const menuEmojis = '{📋|🗒️|📜}' // message += '```━━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━━```\n\n' @@ -95,7 +97,7 @@ export async function menu (msg) { // remove the last \n message = message.trim().replace(/\n$/, '').trim() // await msg.reply(JSON.stringify(msg.aux, null, 2)) - await msg.reply(spintax(message), undefined, { media }) + await msg.reply({ media, caption: spintax(message) }, undefined) } export async function menuGroup (msg) { @@ -109,7 +111,7 @@ export async function menuGroup (msg) { // if there is image, send it let media if (commandGroups[0].menuImageUrl) { - media = await wwebjs.MessageMedia.fromUrl(commandGroups[0].menuImageUrl, { unsafeMime: true }) + media = await MessageMedia.fromUrl(commandGroups[0].menuImageUrl) if (!media) throw new Error('Error downloading media') } diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 6dad2cd..ca4c9d1 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -164,7 +164,6 @@ export async function toUrl (msg) { const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() if (!media) throw new Error('Error downloading media') const tempUrl = (await getTempUrl(media)) - console.log('tempUrl', tempUrl) let message = '🔗 - ' message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' @@ -212,19 +211,13 @@ export async function ping (msg) { */ export async function speak (msg) { let input = msg.body - console.log('msg.hasQuotedMsg', msg.hasQuotedMsg) - console.log('!msg.body', !msg.body) if (msg.hasQuotedMsg && !msg.body) { const quotedMsg = msg.quotedMsg - console.log('quotedMsg', quotedMsg) - console.log('quotedMsg.text', quotedMsg.text) const keys = Object.keys(quotedMsg) const firstItem = quotedMsg[keys[0]] - console.log('keys', keys) input = typeof firstItem === 'string' ? firstItem : firstItem.caption || firstItem.text || '' - console.log('input', input) } if (!input) { diff --git a/src/services/functions/statistics.js b/src/services/functions/statistics.js index 9ca452b..ea6181f 100644 --- a/src/services/functions/statistics.js +++ b/src/services/functions/statistics.js @@ -30,7 +30,7 @@ export async function stats (msg) { const stats = await fetchStats(contactID) const emojis = '{📊|📈|📉|🔍|🔬|📚}' - let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!\n\n` + let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!\n\n` // 📊 - Olá, Sergio Carvalho! message += '```━━━━━━━━━━ {📊|📈|📉|🔍|🔬|📚} ━━━━━━━━━━```\n\n' @@ -73,7 +73,7 @@ export async function botStats (msg) { const stats = await fetchStats() const emojis = '{🤖|👾|💀}' - let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!` + let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!` // 🤖 - Olá, Sergio Carvalho! message += '\n\n```━━━━━━━━━━ {📊|📈|📉|🔍|🔬|📚} ━━━━━━━━━━```\n\n' @@ -122,7 +122,7 @@ export async function weekStats (msg) { const stats = await fetchStats(contactID, 'week') const emojis = '{📊|📈|📉|🔍|🔬|📚}' - let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!\n\n` + let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!\n\n` // 📊 - Olá, Sergio Carvalho! message += '```━━━━━━━━━━ {📊|📈|📉|🔍|🔬|📚} ━━━━━━━━━━```\n\n' @@ -166,7 +166,7 @@ export async function dayStats (msg) { const stats = await fetchStats(contactID, 'day') const emojis = '{📊|📈|📉|🔍|🔬|📚}' - let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!\n\n` + let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!\n\n` // 📊 - Olá, Sergio Carvalho! message += '```━━━━━━━━━━ {📊|📈|📉|🔍|🔬|📚} ━━━━━━━━━━```\n\n' @@ -210,7 +210,7 @@ export async function hourStats (msg) { const stats = await fetchStats(contactID, 'hour') const emojis = '{📊|📈|📉|🔍|🔬|📚}' - let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!\n\n` + let message = `${emojis} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!\n\n` // 📊 - Olá, Sergio Carvalho! message += '```━━━━━━━━━━ {📊|📈|📉|🔍|🔬|📚} ━━━━━━━━━━```\n\n' @@ -288,7 +288,7 @@ async function waitForMinimumTime (startedAt) { */ async function reactAndReply (msg, emojis, reply, message) { await msg.react(spintax(emojis)) - if (reply) { return await reply.edit(spintax(message)) } + // if (reply) { return await reply.edit(spintax(message)) } await msg.reply(spintax(message)) } @@ -325,17 +325,19 @@ export async function fetchStats (contact = undefined, mode = undefined, bot = u * @returns {Promise} */ async function sendInitialReply (msg, sufix) { + await msg.react(reactions.wait) const saudation = getSaudation() - let initialMessage = `{⏳|⌛|🕰️|🕛|🕒|🕞} - {Olá|Oi|Oie|${saudation}} ${msg.aux.sender.pushname}!\n\n` - // ⏳ - Olá, Sergio Carvalho! - initialMessage += `{Espere|Espera|Péra} um {pouco|pouquinho|momento|segundo} enquanto eu {pego|busco|procuro} as {suas |}estatísticas${sufix ? ' ' + sufix : ''}...` - // Espere um pouco enquanto eu pego as suas estatísticas... - let reply = null - if (!msg.aux.chat.isGroup) { - reply = await msg.reply(spintax(initialMessage)) - } const startedAt = Date.now() - return { saudation, startedAt, reply } + return { saudation, startedAt, reply: null } + // let initialMessage = `{⏳|⌛|🕰️|🕛|🕒|🕞} - {Olá|Oi|Oie|${saudation}} ${msg.pushname}!\n\n` + // // ⏳ - Olá, Sergio Carvalho! + // initialMessage += `{Espere|Espera|Péra} um {pouco|pouquinho|momento|segundo} enquanto eu {pego|busco|procuro} as {suas |}estatísticas${sufix ? ' ' + sufix : ''}...` + // // Espere um pouco enquanto eu pego as suas estatísticas... + // let reply = null + // if (!msg.aux.chat.isGroup) { + // reply = await msg.reply(spintax(initialMessage)) + // } + // return { saudation, startedAt, reply } } /** diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index c1bb046..20f4063 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -18,7 +18,6 @@ const sock = getSocket() * @param {string} stickerAuthor */ export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite = true) { - console.log('toUrl', msg) await msg.react(reactions.wait) const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() diff --git a/src/services/functions/tools.js b/src/services/functions/tools.js index f1a13da..86328ae 100644 --- a/src/services/functions/tools.js +++ b/src/services/functions/tools.js @@ -11,7 +11,7 @@ import sharp from 'sharp' dayjs.locale('pt-br') dayjs.extend(relativeTime) -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) +// const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) // // ================================ Main Functions ================================= @@ -30,11 +30,8 @@ export async function qrImageCreator (msg) { await msg.react(reactions.wait) const url = await createUrl('image-creator', 'qr', { text: msg.body }) - console.log(url) try { - // const media = await MessageMedia.fromUrl(url, { unsafeMime: true }) - // console.log(media) await msg.reply({ image: { url @@ -81,7 +78,7 @@ export async function qrTextCreator (msg) { */ export async function qrReader (msg) { // if is not replying to a image - if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.aux.quotedMsg.hasMedia)) { + if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { await msg.reply('Para ler um QR Code, responda uma imagem com !qr') await msg.react(reactions.error) return @@ -89,30 +86,31 @@ export async function qrReader (msg) { await msg.react(reactions.wait) - const media = msg.hasQuotedMsg ? await msg.aux.quotedMsg.downloadMedia() : await msg.downloadMedia() + const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() if (!media) throw new Error('Error downloading media') if (!media.mimetype.includes('image')) { await msg.react(reactions.error) return await msg.reply('❌ Só consigo ler QR Codes em imagens') } - + await msg.react(reactions.wait) // a clock doing a full circle - const spinner = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'] - let spinnerIndex = 0 + // const spinner = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'] + // let spinnerIndex = 0 - const reply = await msg.reply(`${spinner[spinnerIndex]} - Lendo QR code da imagem`) // send the first message - const interval = setInterval(async () => { - spinnerIndex++ - if (spinnerIndex === spinner.length) spinnerIndex = 0 + // const reply = await msg.reply(`${spinner[spinnerIndex]} - Lendo QR code da imagem`) // send the first message + // console.log('reply', reply) + // const interval = setInterval(async () => { + // spinnerIndex++ + // if (spinnerIndex === spinner.length) spinnerIndex = 0 - await reply.edit(`${spinner[spinnerIndex]} - Lendo QR code da imagem`) // edit the message - }, 1000) + // await reply.edit(`${spinner[spinnerIndex]} - Lendo QR code da imagem`) // edit the message + // }, 1000) // auto cancel if the process takes more than 30 seconds const timeout = setTimeout(async () => { - clearInterval(interval) + // clearInterval(interval) await msg.react(reactions.error) - await reply.edit('❌ - Tempo limite excedido') + await msg.reply('❌ - Tempo limite excedido') throw new Error('Timeout') }, 30_000) @@ -134,11 +132,11 @@ export async function qrReader (msg) { const qrResponse = await fetch(qrUrl) const qrData = await qrResponse.json() - clearInterval(interval) // stop the interval + // clearInterval(interval) // stop the interval clearTimeout(timeout) // stop the timeout - await reply.edit(`✅ - ${qrData.result}`) - await wait(1000) - await reply.edit(`✅ - ${qrData.result}`) + await msg.reply(`✅ - ${qrData.result}`) + // await wait(1000) + // await reply.edit(`✅ - ${qrData.result}`) await msg.react(reactions.success) } From dcb5326e2a5a5ce0e6e5ad1bd5d15dd8c9c29ed5 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 30 Jan 2024 17:24:32 -0300 Subject: [PATCH 26/79] fix: forgotten commit --- src/index.js | 2 +- src/services/commands/groups.js | 2 +- src/services/functions/groups.js | 31 ++++++++++++++++++------- src/services/functions/miscellaneous.js | 3 ++- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/index.js b/src/index.js index d08e3b8..363eceb 100644 --- a/src/index.js +++ b/src/index.js @@ -113,7 +113,7 @@ export async function connectToWhatsApp () { logger.info('Connecting to WhatsApp...') - const { state, saveCreds } = await baileys.useMultiFileAuthState('auth_info_baileys') + const { state, saveCreds } = await baileys.useMultiFileAuthState(`./src/temp/${bot.name}`) socket = baileys.makeWASocket({ printQRInTerminal: true, diff --git a/src/services/commands/groups.js b/src/services/commands/groups.js index af928aa..042a745 100644 --- a/src/services/commands/groups.js +++ b/src/services/commands/groups.js @@ -6,7 +6,7 @@ export default (msg) => { return { ban: msg.isGroup && /^(ban)$/.test(msg.aux.function), - unban: msg.isGroup && /^(unban)$/.test(msg.aux.function), + unban: msg.isGroup && /^(unban|desban|add)$/.test(msg.aux.function), promote: msg.isGroup && /^(promote|promove|promover)$/.test(msg.aux.function), demote: msg.isGroup && /^(demote|rebaixa|rebaixar)$/.test(msg.aux.function), giveaway: msg.isGroup && /^(sorteio|sortear)$/.test(msg.aux.function), diff --git a/src/services/functions/groups.js b/src/services/functions/groups.js index 1bdc799..c2f0b87 100644 --- a/src/services/functions/groups.js +++ b/src/services/functions/groups.js @@ -6,8 +6,11 @@ const socket = getSocket() * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function ban (msg) { - if (!msg.hasQuotedMsg) { - return await msg.reply('para usar o !ban você precisa responder a mensagem da pessoa que deseja banir') + const hasMentions = msg.aux.mentions.length > 0 + const hasQuotedMsg = msg.hasQuotedMsg + + if (!hasMentions && !hasQuotedMsg) { + return await msg.reply('para usar o !ban você precisa responder a mensagem da pessoa que deseja banir ou *mensionar* o @ da pessoa que deseja banir') } if (!msg.aux.isBotAdmin) { @@ -18,20 +21,28 @@ export async function ban (msg) { return await msg.reply('para usar o !ban *você* precisa ser admin') } - const author = await msg.quotedMsg.sender + let target = [] + if (hasMentions) { target.push(...msg.aux.mentions) } + if (hasQuotedMsg) { target.push(await msg.quotedMsg.sender) } const admins = msg.aux.admins - if (admins.includes(author)) { + + target = target.filter((t) => !admins.includes(t)) + if (target.length === 0) { await msg.react('🤡') return await msg.reply('Desculpe, mas eu não posso banir administradores') } - await socket.groupParticipantsUpdate(msg.from, [author], 'remove') + await socket.groupParticipantsUpdate(msg.from, target, 'remove') await msg.react('🔨') } export async function unban (msg) { - if (!msg.hasQuotedMsg) { - return await msg.reply('para usar o !unban você precisa responder a mensagem da pessoa que deseja banir') + // TODO: Verify if can add to avoid ban + const hasMentions = msg.aux.mentions.length > 0 + const hasQuotedMsg = msg.hasQuotedMsg + + if (!hasMentions && !hasQuotedMsg) { + return await msg.reply('para usar o !unban você precisa responder a mensagem da pessoa que deseja banir ou *mensionar* o @ da pessoa que deseja banir') } if (!msg.aux.isBotAdmin) { @@ -42,9 +53,11 @@ export async function unban (msg) { return await msg.reply('para usar o !unban *você* precisa ser admin') } - const author = await msg.quotedMsg.sender + const target = [] + if (hasMentions) { target.push(...msg.aux.mentions) } + if (hasQuotedMsg) { target.push(await msg.quotedMsg.sender) } - await socket.groupParticipantsUpdate(msg.from, [author], 'add') + await socket.groupParticipantsUpdate(msg.from, target, 'add') await msg.react('🔄') } diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index ca4c9d1..d80147f 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -296,7 +296,8 @@ export async function transcribe (msg) { const transcription = await openai.audio.transcriptions.create({ file: fs.createReadStream(nomalizedFilePath), model: 'whisper-1', - response_format: 'text' + response_format: 'text', + prompt: '"DeadByte" sticker.ly' }) fs.unlinkSync(nomalizedFilePath) await msg.reply(`🎙️ - ${transcription.trim()}`) From fb65b5643a79d902f24e17973840dbe874bf44fc Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 16:04:26 -0300 Subject: [PATCH 27/79] fix: auto restart --- src/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 363eceb..b9ccf66 100644 --- a/src/index.js +++ b/src/index.js @@ -155,8 +155,16 @@ process.stdout.write('\x1B[2J\x1B[0f') // catch unhandled rejections and errors to avoid crashing process.on('unhandledRejection', (err) => { + console.log('unhandledRejection') logger.fatal(err) }) process.on('uncaughtException', (err) => { - logger.fatal(err) + console.log('uncaughtException') + // Connection Closed try connectToWhatsApp + if (err.message.includes('Connection Closed')) { + logger.fatal('Connection Closed AAAAAAAAAAA') + connectToWhatsApp() + } else { + logger.fatal(err) + } }) From 0974d6effe75ed22dc815b871bc01496dfc1c2c7 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 17:33:31 -0300 Subject: [PATCH 28/79] fix: better close --- src/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/index.js b/src/index.js index b9ccf66..f961628 100644 --- a/src/index.js +++ b/src/index.js @@ -156,10 +156,6 @@ process.stdout.write('\x1B[2J\x1B[0f') // catch unhandled rejections and errors to avoid crashing process.on('unhandledRejection', (err) => { console.log('unhandledRejection') - logger.fatal(err) -}) -process.on('uncaughtException', (err) => { - console.log('uncaughtException') // Connection Closed try connectToWhatsApp if (err.message.includes('Connection Closed')) { logger.fatal('Connection Closed AAAAAAAAAAA') From 7a27bbfc7b77b96e80c8dd4d1ad2ed32e40b8fe8 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 17:47:04 -0300 Subject: [PATCH 29/79] fix: less chars --- src/services/functions/menu.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index 702269c..3e03561 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -72,7 +72,7 @@ export async function menu (msg) { const readMore = '​'.repeat(783) message += readMore // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━━```\n\n' + // message += '```━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) @@ -132,12 +132,12 @@ export async function menuGroup (msg) { message += 'Os seguintes prefixos são aceitos para os comandos: *! . # /*\n\n' // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━━```\n\n' + // message += '```━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) commandGroups.forEach((commandGroup, i) => { - message += '```━━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━━━```\n\n' + message += '```━━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━━```\n\n' message += `*${commandGroup.description}*\n\n` commandGroup.commands.forEach(command => { command = structuredClone(command) From 41758b8bb3848c4aa33b6605ba0ec37b27f4ec85 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 17:48:07 -0300 Subject: [PATCH 30/79] fix: even less --- src/services/functions/menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index 3e03561..5c36daf 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -72,12 +72,12 @@ export async function menu (msg) { const readMore = '​'.repeat(783) message += readMore // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━```\n\n' + // message += '```━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) commandGroups.forEach((commandGroup, i) => { - message += '```━━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━━```\n\n' + message += '```━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━```\n\n' message += `*${commandGroup.description}*\n\n` commandGroup.commands.forEach(command => { command = structuredClone(command) @@ -132,12 +132,12 @@ export async function menuGroup (msg) { message += 'Os seguintes prefixos são aceitos para os comandos: *! . # /*\n\n' // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━━```\n\n' + // message += '```━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) commandGroups.forEach((commandGroup, i) => { - message += '```━━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━━```\n\n' + message += '```━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━```\n\n' message += `*${commandGroup.description}*\n\n` commandGroup.commands.forEach(command => { command = structuredClone(command) From d017eb66ea4467d283f36f39ec2052a3efc304b3 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 17:52:38 -0300 Subject: [PATCH 31/79] fix: new divider --- src/services/functions/menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index 5c36daf..bd304b6 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -72,12 +72,12 @@ export async function menu (msg) { const readMore = '​'.repeat(783) message += readMore // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━```\n\n' + // message += '```•·········• ' + menuEmojis + ' •·········•```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) commandGroups.forEach((commandGroup, i) => { - message += '```━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━```\n\n' + message += '```•·········• ' + commandGroup.emoji + ' •·········•```\n\n' message += `*${commandGroup.description}*\n\n` commandGroup.commands.forEach(command => { command = structuredClone(command) @@ -132,12 +132,12 @@ export async function menuGroup (msg) { message += 'Os seguintes prefixos são aceitos para os comandos: *! . # /*\n\n' // const menuEmojis = '{📋|🗒️|📜}' - // message += '```━━━━━━━━ ' + menuEmojis + ' ━━━━━━━━```\n\n' + // message += '```•·········• ' + menuEmojis + ' •·········•```\n\n' // await msg.reply(JSON.stringify(commands, null, 2)) commandGroups.forEach((commandGroup, i) => { - message += '```━━━━━━━━ ' + commandGroup.emoji + ' ━━━━━━━━```\n\n' + message += '```•·········• ' + commandGroup.emoji + ' •·········•```\n\n' message += `*${commandGroup.description}*\n\n` commandGroup.commands.forEach(command => { command = structuredClone(command) From 446a3214f9da464d1222a1a930460412fe79cdfe Mon Sep 17 00:00:00 2001 From: sergiooak Date: Wed, 31 Jan 2024 18:05:51 -0300 Subject: [PATCH 32/79] fix --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index f961628..3b32dee 100644 --- a/src/index.js +++ b/src/index.js @@ -159,7 +159,7 @@ process.on('unhandledRejection', (err) => { // Connection Closed try connectToWhatsApp if (err.message.includes('Connection Closed')) { logger.fatal('Connection Closed AAAAAAAAAAA') - connectToWhatsApp() + process.exit(0) // kill the process and pm2 will restart it } else { logger.fatal(err) } From b9f040e4281ae36702ece80dd114286be9278a76 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 21:54:16 -0300 Subject: [PATCH 33/79] feat: warn user to restart after qr-code first read --- src/db.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/db.js b/src/db.js index bdf763a..d42b374 100644 --- a/src/db.js +++ b/src/db.js @@ -289,6 +289,11 @@ export async function saveActionToDB (moduleName, functionName, msg) { } export async function findCurrentBot (socket) { + if (!socket.user) { // TODO: auto-restart + logger.warn('Bot never connected before') + logger.warn('Remember to restart after reading the QR code') + return + } const id = socket.user.id.split(':')[0] + '@c.us' // 1 - Check if bot already exists on db const findQuery = qs.stringify( From 5c1227bb3e94d7b4d19805dfaaf4a099533049ec Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 21:54:44 -0300 Subject: [PATCH 34/79] feat: show error to user with a debug group invite link --- src/services/events/messagesUpsert.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index b477ee3..96062cd 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -61,5 +61,17 @@ export default async (upsert) => { const functionName = handlerModule.command const module = await importFresh(`services/functions/${moduleName}.js`) const camelCaseFunctionName = camelCase(functionName) - module[camelCaseFunctionName](msg) + try { + await module[camelCaseFunctionName](msg) + } catch (error) { + logger.error(`Error with command ${camelCaseFunctionName}`, error) + const readMore = '​'.repeat(783) + const prefix = msg.aux.prefix ?? '!' + msg.react('❌') + let message = `❌ - Ocorreu um erro inesperado com o comando *${prefix}${msg.aux.function}*\n\n` + message += 'Se for possível, tira um print e manda para meu administrador nesse grupo aqui: ' + message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR\n\n' + message += `${readMore}\n${error}` + msg.reply(message) + } } From 6a334ad0f77919bfd9cfc3575e3cf0aa76cee239 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 21:55:18 -0300 Subject: [PATCH 35/79] feat: refactor and add 3 retries to each fetch --- src/utils/converters.js | 76 ++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/utils/converters.js b/src/utils/converters.js index cfd3469..b4452f4 100644 --- a/src/utils/converters.js +++ b/src/utils/converters.js @@ -1,39 +1,75 @@ import FormData from 'form-data' import fetch from 'node-fetch' import { load } from 'cheerio' -import fs from 'fs/promises' +// +// ===================================== Variables ====================================== +// + +const EZ_GIF_URL = 'https://ezgif.com/webp-to-mp4?url=' + +// +// ==================================== Main Function ==================================== +// /** * Convert Webp to Mp4 * @param {String} path * @returns {Promise<{status: Boolean, message: String, result: String}>} */ async function webpToMp4 (url) { - const ezGifUrl = `https://ezgif.com/webp-to-mp4?url=${url}` - - const response = await fetch(ezGifUrl) + const response = await fetchWithRetry(EZ_GIF_URL + url) + const { action, file } = await parseHtml(response) - const data = await response.text() - const $ = load(data) + const formData = new FormData() + formData.append('file', file) - const action = $('form.ajax-form').attr('action') - const file = $('form.ajax-form > input[type=hidden]').attr('value') - - const bodyFormThen = new FormData() - bodyFormThen.append('file', file) - - const responseThen = await fetch(action + '?ajax=true', { + const responseThen = await fetchWithRetry(action + '?ajax=true', { method: 'POST', - body: bodyFormThen, - headers: bodyFormThen.getHeaders() + body: formData, + headers: formData.getHeaders() }) - const dataThen = await responseThen.text() - await fs.writeFile('./src/temp/ezgif-then.html', dataThen) - const $Then = load(dataThen) + const $then = load(responseThen) + const resultFile = $then('video > source').attr('src') - const result = $Then('video > source').attr('src') - return 'https:' + result + return resultFile.startsWith('//') ? 'https:' + resultFile : resultFile } export { webpToMp4 } + +// +// ================================== Helper Functions ================================== +// + +/** + * Fetch with retry + * @param {String} url + * @param {Object} options + * @param {Number} retries + * @returns {Promise} + */ +async function fetchWithRetry (url, options = {}, retries = 3) { + for (let i = 0; i < retries; i++) { + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) + return response + } catch (error) { + if (i < retries - 1) continue + throw error + } + } +} + +/** + * Parse HTML + * @param {Response} response + * @returns {Promise<{action: String, file: String}>} + */ +async function parseHtml (response) { + const data = await response.text() + const $ = load(data) + const action = $('form.ajax-form').attr('action') + const file = $('form.ajax-form > input[type=hidden]').attr('value') + return { action, file } +} From 0656601f1bf289b030bf6264f8bfff73731adc1f Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 22:01:34 -0300 Subject: [PATCH 36/79] fix: change detection if message has media or if the quoted message has --- src/services/functions/miscellaneous.js | 8 ++++---- src/services/functions/stickers.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index d80147f..f6f56ce 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -92,7 +92,7 @@ export async function dice (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toFile (msg) { - if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { await msg.react(reactions.error) const header = '☠️🤖' @@ -127,8 +127,8 @@ export async function toFile (msg) { } if (isImage && isAnimated) { - await msg.reply({ media, caption: 'Espere um pouco que vou converter esse *webp* formato melhor para você...' }) - await msg.react(reactions.loading) + await msg.reply({ media, caption: 'Espere um pouco que vou converter esse *webp* para um formato melhor para você...' }) + await msg.react(reactions.wait) const tempUrl = await getTempUrl(media) const url = await webpToMp4(tempUrl) @@ -148,7 +148,7 @@ export async function toFile (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function toUrl (msg) { - if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { await msg.react(reactions.error) const header = '☠️🤖' diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 20f4063..1861723 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -97,7 +97,7 @@ export async function textSticker3 (msg) { */ export async function removeBg (msg) { await msg.react(reactions.wait) - if (!msg.hasMedia || (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { + if (!msg.hasMedia && (msg.hasQuotedMsg && !msg.quotedMsg.hasMedia)) { await msg.react(reactions.error) const header = '☠️🤖' From ec75921cac29b42d467de01d150178cda6514836 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 22:23:06 -0300 Subject: [PATCH 37/79] fix: change validator to not detect body '...' as function --- src/validators/message.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/validators/message.js b/src/validators/message.js index a05ea0f..2d095ac 100644 --- a/src/validators/message.js +++ b/src/validators/message.js @@ -111,7 +111,8 @@ export default async (msg) => { } } const prefixesWithFallback = await importFresh('config/bot.js').then(config => config.prefixesWithFallback) - if (prefixesWithFallback.includes(aux.prefix) === false) { + console.log('here', msg.aux.originalBody) + if (prefixesWithFallback.includes(aux.prefix) === false && msg.aux.originalBody !== '...') { if (!aux.hasOriginalFunction) { await msg.react(reactions.confused) } From 02847e698d0d5ed5b74a89b326b9828444047137 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 23:37:58 -0300 Subject: [PATCH 38/79] chore: remove console.log statement in unhandledRejection event --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index 3b32dee..6d178a2 100644 --- a/src/index.js +++ b/src/index.js @@ -155,7 +155,6 @@ process.stdout.write('\x1B[2J\x1B[0f') // catch unhandled rejections and errors to avoid crashing process.on('unhandledRejection', (err) => { - console.log('unhandledRejection') // Connection Closed try connectToWhatsApp if (err.message.includes('Connection Closed')) { logger.fatal('Connection Closed AAAAAAAAAAA') From d8e3476e5d2f4a4d8e3095409daf63027e9548dd Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 23:38:13 -0300 Subject: [PATCH 39/79] feat: add getDBUrl function --- src/db.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/db.js b/src/db.js index d42b374..4774397 100644 --- a/src/db.js +++ b/src/db.js @@ -48,7 +48,6 @@ doLogin() // /** * Check if the API is online - * * @returns {boolean} true if the API is online */ export function isOnline () { @@ -57,16 +56,22 @@ export function isOnline () { /** * Get the token - * * @returns {string} token */ export function getToken () { return token } +/** + * Get the Database URL + * @returns {string} dbUrl + */ +export function getDBUrl () { + return dbUrl +} + /** * Load the commands from the database - * * @returns {object} commands */ export async function loadCommands () { From b70a5244e21e2e68972ef319694fdfbd49ddbeed Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 23:38:26 -0300 Subject: [PATCH 40/79] chore: remove console logs --- src/validators/message.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/validators/message.js b/src/validators/message.js index 2d095ac..769a670 100644 --- a/src/validators/message.js +++ b/src/validators/message.js @@ -111,7 +111,6 @@ export default async (msg) => { } } const prefixesWithFallback = await importFresh('config/bot.js').then(config => config.prefixesWithFallback) - console.log('here', msg.aux.originalBody) if (prefixesWithFallback.includes(aux.prefix) === false && msg.aux.originalBody !== '...') { if (!aux.hasOriginalFunction) { await msg.react(reactions.confused) From adb83e614fb9d0fa709f8ded6b46b92132525777 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Fri, 2 Feb 2024 23:40:50 -0300 Subject: [PATCH 41/79] fix: remove double function declaration --- src/db.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/db.js b/src/db.js index 4774397..5372800 100644 --- a/src/db.js +++ b/src/db.js @@ -62,14 +62,6 @@ export function getToken () { return token } -/** - * Get the Database URL - * @returns {string} dbUrl - */ -export function getDBUrl () { - return dbUrl -} - /** * Load the commands from the database * @returns {object} commands From b48ac9f4501f35581bc502af4ec992ad30e51a74 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 00:34:01 -0300 Subject: [PATCH 42/79] feat: mvp of vip bot system --- src/services/events/messagesUpsert.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 96062cd..d433c67 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -48,6 +48,27 @@ export default async (upsert) => { msg ) + // TODO: improve bot vip system + if (msg.bot.name === 'DeadByte - VIP') { + const sender = msg.aux.db.contact.attributes + const hasDonated = sender?.hasDonated === true + if (!hasDonated) { + await msg.react('💎') + let message = '❌ - Você não é um VIP! 😢\n\n' + message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' + message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' + message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' + await msg.reply(message) + + // wait 3 seconds and block the user + setTimeout(async () => { + await socket.updateBlockStatus(msg.from, 'block') + }, 5000) + + return + } + } + const checkDisabled = await importFresh('validators/checkDisabled.js') const isEnabled = await checkDisabled.default(msg) if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) From 072db0ddc63f407b709df67a4492d7ee9ced80fb Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 00:37:48 -0300 Subject: [PATCH 43/79] fix: only works on pv --- src/services/events/messagesUpsert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index d433c67..d6c29c1 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -49,7 +49,7 @@ export default async (upsert) => { ) // TODO: improve bot vip system - if (msg.bot.name === 'DeadByte - VIP') { + if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP') { const sender = msg.aux.db.contact.attributes const hasDonated = sender?.hasDonated === true if (!hasDonated) { From ae197a00e16f1579946b492776effc2e3628fba7 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 03:38:04 -0300 Subject: [PATCH 44/79] feat: add function to force update --- src/db.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/db.js b/src/db.js index 5372800..47cb5ec 100644 --- a/src/db.js +++ b/src/db.js @@ -167,6 +167,17 @@ setInterval(() => { }) }, 60_000) +/** + * Force a contact update on the database + * @param {import('whatsapp-web.js').Contact} contact + * @returns {Promise} - The updated contact + */ +export async function forceContactUpdate (contact) { + const id = contact.id.replace('@s.whatsapp.net', '@c.us') + delete contactsCache[id] + return await findOrCreateContact(contact) +} + /** * Find or create a chat on the database */ From 4acb3d1ed764ab9868ba5f1e3e8ef9badefd2480 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 03:38:20 -0300 Subject: [PATCH 45/79] feat: add function to set preferences --- src/services/commands/meta.js | 10 ++++ src/services/functions/meta.js | 87 ++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/services/commands/meta.js create mode 100644 src/services/functions/meta.js diff --git a/src/services/commands/meta.js b/src/services/commands/meta.js new file mode 100644 index 0000000..77f2c41 --- /dev/null +++ b/src/services/commands/meta.js @@ -0,0 +1,10 @@ +/** + * Miscelanius Bot Commands + * @param {import('../../types.d.ts').WWebJSMessage} msg + * @returns {Object} + */ +export default (msg) => { + return { + set: /^(set|definir|defina)$/.test(msg.aux.function) + } +} diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js new file mode 100644 index 0000000..c8f5e1d --- /dev/null +++ b/src/services/functions/meta.js @@ -0,0 +1,87 @@ +import { getDBUrl, getToken, forceContactUpdate } from '../../db.js' +import relativeTime from 'dayjs/plugin/relativeTime.js' +import spintax from '../../utils/spintax.js' +import { textSticker } from './stickers.js' +import 'dayjs/locale/pt-br.js' +import dayjs from 'dayjs' + +dayjs.locale('pt-br') +dayjs.extend(relativeTime) + +// +// ================================ Main Functions ================================= +// +/** + * Send the menu + * @param {import('../../types.d.ts').WWebJSMessage} msg + */ +export async function set (msg) { + await msg.react('⚙️') + + const preferences = { + pack: 'stickerName', + pacote: 'stickerName', + author: 'stickerAuthor', + autor: 'stickerAuthor', + nome: 'stickerAuthor', + name: 'stickerAuthor' + } + + const examples = { + pacote: 'DeadByte.com.br', + nome: 'bot de figurinhas' + } + + const avaliablePreferences = Object.keys(preferences) + const examplesPrefences = Object.keys(examples) + + const prefence = msg.body.split(' ')[0] + // value is the rest of the message + const value = msg.body.split(' ').slice(1).join(' ') + + if (!prefence || !value) { + let message = '❌ - Para usar este comando você deve informar *o que* ' + message += 'você está definindo e *o valor* que você quer definir\n\n' + message += '*Exemplos:*' + const mono = '```' + for (const pref of examplesPrefences) { + message += `\n${mono}${msg.aux.prefix}set ${pref} ${examples[pref]}${mono}` + } + return msg.reply(message) + } + + if (!avaliablePreferences.includes(prefence)) { + let message = '❌ - Preferência inválida\n\n' + message += '*Preferências disponíveis:*' + const mono = '```' + for (const pref of examplesPrefences) { + message += `\n${mono}${pref}${mono}` + } + return msg.reply(message) + } + + const id = msg.aux.db.contact.id + const preferenceObject = msg.aux.db.contact.attributes.preferences ?? {} + preferenceObject[preferences[prefence]] = value + // PUT /api/contacts/:id + await fetch(`${getDBUrl()}/contacts/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getToken()}` + }, + body: JSON.stringify({ + data: { + preferences: preferenceObject + } + }) + }) + msg.aux.db.contact = await forceContactUpdate(msg.contact) + await msg.react('✅') + msg.body = spintax('{Clique|Clica} {{nessa|nesta} figurinha|{nesse|neste} sticker} para {você |vc |}ver {{o que|oq} {mudou|alterou}|como ficou|o resultado}') + await textSticker(msg) +} + +// +// ================================== Helper Functions ================================== +// From 60da359bffc1d77ed973d6b08772f68013244a20 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 03:38:47 -0300 Subject: [PATCH 46/79] fix: add support to work with local functions --- src/services/events/messagesUpsert.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index d6c29c1..451078f 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -42,14 +42,15 @@ export default async (upsert) => { if (!handlerModule) return logger.debug('handlerModule is undefined') await msg.sendSeen() - msg.aux.db = await saveActionToDB( - handlerModule.type, - handlerModule.command, - msg - ) + + try { + msg.aux.db = await saveActionToDB(handlerModule.type, handlerModule.command, msg) + } catch (error) { + logger.trace('Error saving action to DB', error) + } // TODO: improve bot vip system - if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP') { + if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP' && msg.aux.db) { const sender = msg.aux.db.contact.attributes const hasDonated = sender?.hasDonated === true if (!hasDonated) { From c3c88db9aee85ee0b6e7be671087b5cfbce1111f Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 03:39:20 -0300 Subject: [PATCH 47/79] fix: change the default overwrite to work --- src/services/functions/stickers.js | 33 +++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 1861723..c79f127 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -17,7 +17,7 @@ const sock = getSocket() * @param {string} stickerName * @param {string} stickerAuthor */ -export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite = true) { +export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite = false) { await msg.react(reactions.wait) const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() @@ -158,7 +158,7 @@ export async function stealSticker (msg) { if (!msg.hasQuotedMsg || !msg.quotedMsg.hasMedia) { if (msg.hasMedia && (msg.type === 'image' || msg.type === 'video' || msg.type === 'sticker')) { msg.body = '' - return await stickerCreator(msg, stickerName, stickerAuthor, false) + return await stickerCreator(msg, stickerName, stickerAuthor, true) } await msg.react(reactions.error) @@ -175,7 +175,7 @@ export async function stealSticker (msg) { const media = await msg.downloadMedia(true) if (!media) throw new Error('Error downloading media') - await sendMediaAsSticker(msg, media, stickerName, stickerAuthor) + await sendMediaAsSticker(msg, media, stickerName, stickerAuthor, true) await msg.react(reactions.success) } @@ -244,8 +244,7 @@ export async function stickerLyPack (msg) { const limit = getStickerLimit(isStickerGroup) if (!msg.body) { - await msg.reply('🤖 - Para usar o *!pack* você precisa enviar um código de pacote do sticker.ly.\nEx: *!pack 2RY2AQ*') - throw new Error('No search term') + return await msg.reply('🤖 - Para usar o *!pack* você precisa enviar um código de pacote do sticker.ly.\nEx: *!pack 2RY2AQ*') } await msg.react(reactions.wait) @@ -253,7 +252,7 @@ export async function stickerLyPack (msg) { // if the term is a pack id, send the pack const packRegex = /^[a-zA-Z0-9]{6}$/ if (!packRegex.test(msg.body)) { - await msg.reply('🤖 - Para usar o *!pack* você precisa enviar um código de pacote do sticker.ly.\nEx: *!pack 2RY2AQ*') + return await msg.reply('🤖 - Para usar o *!pack* você precisa enviar um código de pacote do sticker.ly.\nEx: *!pack 2RY2AQ*') } const packId = msg.body.toUpperCase() @@ -301,13 +300,23 @@ export async function stickerLyPack (msg) { * @param {import ('whatsapp-web.js').MessageMedia} media - The media to send as a sticker. * @param {string} [stickerName='DeadByte.com.br'] - The name of the sticker. * @param {string} [stickerAuthor='bot de figurinhas'] - The author of the sticker. - * @param {boolean} [overwrite=true] - Whether to overwrite the sticker pack or not. + * @param {boolean} [overwrite=false] - Whether to overwrite the sticker pack or not. * @returns {Promise} A Promise that resolves with the Message object of the sent sticker. */ -async function sendMediaAsSticker (msg, media, author, pack, overwrite = true) { +async function sendMediaAsSticker (msg, media, author, pack, overwrite = false) { + // parse sticker name and author + // author = author || (overwrite ? 'DeadByte.com.br' : undefined) + // pack = pack || (overwrite ? 'bot de figurinhas' : undefined) + const authorFromDb = msg.aux.db.contact.attributes?.preferences?.stickerAuthor + const packFromDb = msg.aux.db.contact.attributes?.preferences?.stickerName + if (!overwrite) { + author = authorFromDb || 'DeadByte.com.br' + pack = packFromDb || 'bot de figurinhas' + } + const stickerMedia = await Util.formatToWebpSticker(media, { - author: author || (overwrite ? 'DeadByte.com.br' : undefined), - pack: pack || (overwrite ? 'bot de figurinhas' : undefined) + author, + pack }, false) const buffer = Buffer.from(stickerMedia.data, 'base64') @@ -315,8 +324,8 @@ async function sendMediaAsSticker (msg, media, author, pack, overwrite = true) { if (buffer.byteLength > 1_000_000) { media = await compressMediaBuffer(buffer, media) media = await Util.formatToWebpSticker(media, { - author: author || (overwrite ? 'DeadByte.com.br' : undefined), - pack: pack || (overwrite ? 'bot de figurinhas' : undefined) + author, + pack }, false) } const firstKey = Object.keys(msg.raw.message)[0] From de65078023f211dd52c64b020203a8f3305b318f Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 03:46:15 -0300 Subject: [PATCH 48/79] feat: add support for empty preferences --- src/services/functions/meta.js | 5 ++--- src/services/functions/stickers.js | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index c8f5e1d..9e53bfd 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -36,10 +36,9 @@ export async function set (msg) { const examplesPrefences = Object.keys(examples) const prefence = msg.body.split(' ')[0] - // value is the rest of the message const value = msg.body.split(' ').slice(1).join(' ') - if (!prefence || !value) { + if (!prefence) { let message = '❌ - Para usar este comando você deve informar *o que* ' message += 'você está definindo e *o valor* que você quer definir\n\n' message += '*Exemplos:*' @@ -62,7 +61,7 @@ export async function set (msg) { const id = msg.aux.db.contact.id const preferenceObject = msg.aux.db.contact.attributes.preferences ?? {} - preferenceObject[preferences[prefence]] = value + preferenceObject[preferences[prefence]] = value || 'undefined' // PUT /api/contacts/:id await fetch(`${getDBUrl()}/contacts/${id}`, { method: 'PUT', diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index c79f127..45ed6d6 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -313,6 +313,8 @@ async function sendMediaAsSticker (msg, media, author, pack, overwrite = false) author = authorFromDb || 'DeadByte.com.br' pack = packFromDb || 'bot de figurinhas' } + author = author === 'undefined' ? undefined : author + pack = pack === 'undefined' ? undefined : pack const stickerMedia = await Util.formatToWebpSticker(media, { author, From 5ad7e8add60f8c1646ae04fd43657907a140d0ab Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sat, 3 Feb 2024 17:04:19 -0300 Subject: [PATCH 49/79] fix: remove left over debug message --- src/services/functions/admin-commands.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/services/functions/admin-commands.js b/src/services/functions/admin-commands.js index 5ed1880..15b9408 100644 --- a/src/services/functions/admin-commands.js +++ b/src/services/functions/admin-commands.js @@ -17,9 +17,4 @@ export async function debug (msg) { const debugEmoji = '🐛' await msg.react(debugEmoji) // Debug code goes here - - const readMore = '​'.repeat(783) - const message = `Dead${readMore}Byte` - - await msg.reply(message) } From 5b34a4470e5ba8e9da5de992f5de0dc86e4bf330 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:05:05 -0300 Subject: [PATCH 50/79] fix: do not parse empty message --- src/validators/messageType.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/validators/messageType.js b/src/validators/messageType.js index 8fea4e0..ec5478b 100644 --- a/src/validators/messageType.js +++ b/src/validators/messageType.js @@ -45,6 +45,10 @@ const types = { * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg */ export default (msg) => { + if (!msg.message) { + logger.warn('Message has no message', msg) + return types.UNKNOWN + } const keysToIgnore = ['messageContextInfo'] const hasKeys = Object.keys(msg).length > 1 let firstKey = Object.keys(msg)[0] From b648f44b63d65959bb37a44afcf28a242299638f Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:05:41 -0300 Subject: [PATCH 51/79] feat: add new queue and anti-spam system --- src/services/events/messagesUpsert.js | 127 +++++------ src/services/queue.js | 298 ++++++++++++++++++-------- 2 files changed, 261 insertions(+), 164 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 451078f..697b677 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -1,7 +1,7 @@ import importFresh from '../../utils/importFresh.js' import { saveActionToDB } from '../../db.js' import { getSocket } from '../../index.js' -import { camelCase } from 'change-case' +import { addToQueue } from '../queue.js' import logger from '../../logger.js' // // ================================ Variables ================================= @@ -15,85 +15,74 @@ import logger from '../../logger.js' * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.upsert']} upsert */ export default async (upsert) => { - logger.trace('messages.upsert', upsert) - if (!upsert.messages) return // ignore if there are no messages - let msg = upsert.messages[0] - if (msg.key.fromMe) return // ignore self messages - const meta = await importFresh('meta/message.js') - msg = meta.default(msg) + logger.trace('messages.upsert\n' + JSON.stringify(upsert, null, 2)) + if (upsert.type === 'append') return // TODO: handle unread messages - if (msg.type === 'revoked') { - // TODO: send random "Deus viu o que você apagou" sticker - await msg.sendSeen() - return await msg.reply('👀 - Eu vi o que você apagou') - } - if (msg.type === 'edited') { - await msg.sendSeen() - await msg.react('👀') - return await msg.reply('👀 - {Haha eu|Kkkk eu|Eu} {vi|sei} {oq|o que} {tava antes|tu tinha escrito}{ ein| kk|!|!!!}') - } + for (let msg of upsert.messages) { + if (msg.key.fromMe) return // ignore self messages + + const meta = await importFresh('meta/message.js') + msg = meta.default(msg) - const socket = getSocket() - await socket.sendPresenceUpdate('available') - const messageParser = await importFresh('validators/message.js') - const handlerModule = await messageParser.default(msg) - logger.trace('handlerModule: ', handlerModule) + if (msg.type === 'revoked') { + // TODO: send random "Deus viu o que você apagou" sticker + return + } + if (msg.type === 'edited') { + await msg.sendSeen() + return await msg.react('✏️') + } - if (!handlerModule) return logger.debug('handlerModule is undefined') + const socket = getSocket() + await socket.sendPresenceUpdate('available') + const messageParser = await importFresh('validators/message.js') + const handlerModule = await messageParser.default(msg) + logger.trace('handlerModule: ', handlerModule) - await msg.sendSeen() + if (!handlerModule) return logger.debug('handlerModule is undefined') - try { - msg.aux.db = await saveActionToDB(handlerModule.type, handlerModule.command, msg) - } catch (error) { - logger.trace('Error saving action to DB', error) - } + try { + msg.aux.db = await saveActionToDB(handlerModule.type, handlerModule.command, msg) + } catch (error) { + logger.trace('Error saving action to DB', error) + } - // TODO: improve bot vip system - if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP' && msg.aux.db) { - const sender = msg.aux.db.contact.attributes - const hasDonated = sender?.hasDonated === true - if (!hasDonated) { - await msg.react('💎') - let message = '❌ - Você não é um VIP! 😢\n\n' - message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' - message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' - message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' - await msg.reply(message) + // TODO: improve bot vip system + if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP' && msg.aux.db) { + const sender = msg.aux.db.contact.attributes + const hasDonated = sender?.hasDonated === true + if (!hasDonated) { + await msg.react('💎') + let message = '❌ - Você não é um VIP! 😢\n\n' + message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' + message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' + message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' + await msg.reply(message) - // wait 3 seconds and block the user - setTimeout(async () => { - await socket.updateBlockStatus(msg.from, 'block') - }, 5000) + // wait 3 seconds and block the user + setTimeout(async () => { + await socket.updateBlockStatus(msg.from, 'block') + }, 5000) - return + return + } } - } - const checkDisabled = await importFresh('validators/checkDisabled.js') - const isEnabled = await checkDisabled.default(msg) - if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) + const checkDisabled = await importFresh('validators/checkDisabled.js') + const isEnabled = await checkDisabled.default(msg) + if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${handlerModule.command} - Disabled`) - const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') - const isOwnerOnly = await checkOwnerOnly.default(msg) - if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${handlerModule.command} - Restricted to admins`) + const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') + const isOwnerOnly = await checkOwnerOnly.default(msg) + if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${handlerModule.command} - Restricted to admins`) - // TODO: implement queue system - const moduleName = handlerModule.type - const functionName = handlerModule.command - const module = await importFresh(`services/functions/${moduleName}.js`) - const camelCaseFunctionName = camelCase(functionName) - try { - await module[camelCaseFunctionName](msg) - } catch (error) { - logger.error(`Error with command ${camelCaseFunctionName}`, error) - const readMore = '​'.repeat(783) - const prefix = msg.aux.prefix ?? '!' - msg.react('❌') - let message = `❌ - Ocorreu um erro inesperado com o comando *${prefix}${msg.aux.function}*\n\n` - message += 'Se for possível, tira um print e manda para meu administrador nesse grupo aqui: ' - message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR\n\n' - message += `${readMore}\n${error}` - msg.reply(message) + // TODO: implement queue system + const moduleName = handlerModule.type + const functionName = handlerModule.command + await addToQueue(moduleName, functionName, msg) + // const { isSpam, messagesOnQueue } = await addToQueue(moduleName, functionName, msg) + // if (isSpam) return logger.warn(`${msg.from} - ${handlerModule.command} - Spam detected`) + // if (messagesOnQueue > 1) return logger.info(`${msg.from} - ${handlerModule.command} - Queued`) + // logger.info(`${msg.from} - ${handlerModule.command} - Processing`) } } diff --git a/src/services/queue.js b/src/services/queue.js index c173846..0af2de0 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -1,128 +1,236 @@ import importFresh from '../utils/importFresh.js' import logger from '../logger.js' -import { getClient } from '../index.js' +import { getSocket } from '../index.js' import { camelCase } from 'change-case' // // ===================================== Variables ====================================== // +/** + * @typedef {Object} Message + * @property {any} message - The message content. + */ + +/** + * @typedef {Object} QueueItem + * @property {number} waitUntil - The timestamp to wait until. + * @property {string} moduleName - The name of the module. + * @property {string} functionName - The name of the function. + * @property {Message} message - The message object. + */ -const client = getClient() -const queue = [] -const waitTimeMax = 300 -const waitTimeMin = 0 -const waitTimeMultiplier = 100 -let waitTime = waitTimeMax // initial wait time +/** + * @typedef {Object} Chat + * @property {boolean} isBusy - Indicates if the chat is busy. + * @property {number} lastEvent - The timestamp of the last event. + * @property {Array} queue - The queue of items. + * @property {number} spamWarning - The number of spam warnings. + * @property {number} lastSpamWarning - The timestamp of the last spam warning. + */ +/** + * @typedef {Object.} Queue + */ + +/** + * The queue of messages. + * @type {Queue} + */ +const queue = {} +const socket = getSocket() // // ==================================== Main Function ==================================== // +async function processQueue () { + if (Object.keys(queue).length > 0) { + // pick the first key from the queue, pick the first item from the queue + // reconstruct the message object with the current key at the end + const firstChatInQueueKey = Object.keys(queue)[0] + const firstChatInQueue = queue[firstChatInQueueKey] + delete queue[firstChatInQueueKey] + queue[firstChatInQueueKey] = firstChatInQueue // add it back to the queue + if (firstChatInQueue.queue.length === 0) { + return waitAndProcessQueue() // instantly process the next message + } + if (firstChatInQueue.isBusy) { + return waitAndProcessQueue(0, 0) // instantly process the next message + } + const firstMessageInQueue = firstChatInQueue.queue[0] + firstChatInQueue.queue = firstChatInQueue.queue.slice(1) + if (Date.now() < firstMessageInQueue.waitUntil) { + firstChatInQueue.queue.unshift(firstMessageInQueue) + return waitAndProcessQueue(0, 0) // instantly process the next message + } + firstChatInQueue.isBusy = true + // do not await this, so that we can process the next message in the queue + executeQueueItem(firstMessageInQueue.moduleName, firstMessageInQueue.functionName, firstMessageInQueue.message).then(() => { + queue[firstChatInQueueKey].isBusy = false + queue[firstChatInQueueKey].lastEvent = Date.now() + }) + } + + // // await random time between 0,5 and 1,5 seconds + return await waitAndProcessQueue() +} +processQueue() // start the queue processing +// +// ================================== Helper Functions ================================== +// /** - * Add a message to queue and return an array with the queue length and user queue length - * @param {import('whatsapp-web.js').ClientInfo} userId - * @param {string} moduleName - Name of the module to be imported e.g. 'sticker' - * @param {string} functionName - Name of the function to be called e.g. 'stickerText' - * @param {import('whatsapp-web.js').Message} msg - Message object - * @returns {Array} [queueLength, userQueueLength] - * + * Add the message to the queue + * @param {string} moduleName + * @param {string} functionName + * @param {Message} msg + * @returns {Promise<{waitUntil: number, messagesOnQueue: number, isSpam: boolean}>} */ -function addToQueue (userId, moduleName, functionName, msg) { - // if is a group, bypass the queue - if (msg.aux.chat.isGroup) { - bypassQueue(moduleName, functionName, msg) - return [0, 0] +export async function addToQueue (moduleName, functionName, msg) { + const id = msg.from + if (!queue[id]) { + initializeQueueForUser(id) } - const userIndex = queue.findIndex((user) => user.wid === userId) - if (userIndex !== -1) { - queue[userIndex].messagesQueue.push({ moduleName, functionName, message: msg }) - return [getQueueLength(), queue[userIndex].messagesQueue.length] + + const messagesOnQueue = queue[id].queue.length + const waitUntil = Date.now() + (messagesOnQueue * 2000) + const spamThreshold = 6 + + if (messagesOnQueue >= spamThreshold - 1) { + const spamWarningResult = await handleSpamWarning(id, spamThreshold, msg) + if (spamWarningResult.isSpam) { + return spamWarningResult + } } - queue.push({ - wid: userId, - messagesQueue: [{ moduleName, functionName, message: msg }] - }) - return [getQueueLength(), 1] + return addMessageToQueue(id, waitUntil, moduleName, functionName, msg) } -async function bypassQueue (moduleName, functionName, msg) { - const module = await importFresh(`services/functions/${moduleName}.js`) - const camelCaseFunctionName = camelCase(functionName) - module[camelCaseFunctionName](msg) +/** + * Initialize the queue for the user + * @param {string} id + * @returns {void} + */ +function initializeQueueForUser (id) { + queue[id] = { + isBusy: false, + lastEvent: Date.now(), + queue: [], + spamWarning: 0, + lastSpamWarning: 0 + } } -async function processQueue () { - // set the wait time for the next round based on the queue length - const newWaitTime = Math.max(waitTimeMax - (queue.length * waitTimeMultiplier), waitTimeMin) - // noise between -150ms and 150ms to make the wait time more human-like - const noise = Math.floor(Math.random() * 300) - 150 - setWaitTime(newWaitTime + noise) - - if (queue.length === 0) return setTimeout(processQueue, waitTime) // if the queue is empty, wait and try again - - const user = queue.shift() // get the first user on the queue - - const currentMessage = user.messagesQueue.shift() // get the first message of that user - - /** @type {{moduleName: string, functionName: string, message: import('whatsapp-web.js').Message}} */ - const { moduleName, functionName, message: msg } = currentMessage - - const number = await client.getFormattedNumber(msg.from) - const camelCaseFunctionName = camelCase(functionName) - logger.info(`🛫 - ${number} - ${moduleName}.${camelCaseFunctionName}()`) - - try { - const module = await importFresh(`services/functions/${moduleName}.js`) // import the module - logger.debug(module) - - const fnPromisse = module[camelCaseFunctionName](msg) - fnPromisse.then((_result) => { - if (user.messagesQueue.length > 0) { - queue.push(user) // if there are more messages on the user queue, push it back to the queue - } else { - // mark the chat as read after 5 seconds - setTimeout(() => { - msg.aux.chat.sendSeen() - }, 5_000) - } - }).catch((err) => { - logger.error(err) - msg.react('❌') - }) - } catch (err) { - logger.fatal('Error executing module', moduleName, camelCaseFunctionName) - logger.error(err) +/** + * Handle the spam warning + * @param {string} id + * @param {number} spamThreshold + * @param {Message} msg + * @returns {Promise<{isSpam: boolean, messagesOnQueue: number}>} + */ +async function handleSpamWarning (id, spamThreshold, msg) { + if (Date.now() - queue[id].lastSpamWarning > 30_000) { + queue[id].spamWarning++ + queue[id].lastSpamWarning = Date.now() + + const { emoji, message } = getSpamWarningMessage(queue[id].spamWarning, spamThreshold) + + await wait(15_000) + await msg.react(emoji) + await msg.reply(message) + + if (queue[id].spamWarning >= 4) { + await wait(5_000) + await socket.updateBlockStatus(id, 'block') + } + return { isSpam: true, messagesOnQueue: queue[id].queue.length } } - - setTimeout(processQueue, waitTime) // wait and process the next item on the queue + return { isSpam: false } } -processQueue() - -// -// ================================== Helper Functions ================================== -// -export function setWaitTime (time) { - // make sure the wait time is not lower than 1ms - const safeTime = Math.max(time, 1) - waitTime = safeTime +/** + * Get the spam warning message + * @param {number} spamWarning + * @param {number} spamThreshold + * @returns {{emoji: string, message: string}} + */ +function getSpamWarningMessage (spamWarning, spamThreshold) { + let emoji = '⚠️' + let message = '' + switch (spamWarning) { + case 4: + emoji = '🚫' + message = '🚫 - Você foi bloqueado por _spamming_ o bot! 😡' + break + case 3: + emoji = '🚨' + message = '🚨 - *ÚLTIMO AVISO!!!*\n\nNa próxima vez que você _spammar_ o bot te darei block!' + break + case 2: + message = `⚠️ - *ATENÇÃO!!!*\n\nJá avisei uma vez 😡, não _spamme_ o bot, máximo de ${spamThreshold} mensagens por minuto.\n\nSe continuar, você será bloqueado!` + break + default: + message = `⚠️ - *ATENÇÃO!!!*\n\nNão _spamme_ o bot, máximo de ${spamThreshold} mensagens por minuto.\n\nSe continuar, você será bloqueado!` + } + return { emoji, message } } -export function getWaitTime () { - return waitTime +/** + * Add the message to the queue + * @param {string} id + * @param {number} waitUntil + * @param {string} moduleName + * @param {string} functionName + * @param {Message} msg + * @returns {{waitUntil: number, messagesOnQueue: number}} + */ +function addMessageToQueue (id, waitUntil, moduleName, functionName, msg) { + queue[id].queue.push({ + waitUntil, + moduleName, + functionName, + message: msg + }) + + return { waitUntil, messagesOnQueue: queue[id].queue.length } } /** - * Get the number of messages on the queue, by user or total messages - * @returns {number} - * @param {string} by - 'user' or 'messages' - * @throws {Error} Invalid parameter + * Wait for the given amount of time + * @param {number} ms + * @returns {Promise} */ -export function getQueueLength (by = 'messages') { - if (by === 'user') return queue.length - if (by === 'messages') return queue.reduce((acc, user) => acc + user.messagesQueue.length, 0) +async function wait (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} - throw new Error('Invalid parameter') +async function waitAndProcessQueue (min = 500, max = 2500) { + const waitTime = Math.floor(Math.random() * (max - min + 1)) + min + await wait(waitTime) + processQueue() } -export { addToQueue, processQueue } +export async function executeQueueItem (moduleName, functionName, msg) { + // console.log('Executing queue item', moduleName, functionName, msg) + await msg.sendSeen() + const checkDisabled = await importFresh('validators/checkDisabled.js') + const isEnabled = await checkDisabled.default(msg) + if (!isEnabled) return logger.info(`⛔ - ${msg.from} - ${functionName} - Disabled`) + + const checkOwnerOnly = await importFresh('validators/checkOwnerOnly.js') + const isOwnerOnly = await checkOwnerOnly.default(msg) + if (isOwnerOnly) return logger.info(`🛂 - ${msg.from} - ${functionName} - Restricted to admins`) + + const module = await importFresh(`services/functions/${moduleName}.js`) + const camelCaseFunctionName = camelCase(functionName) + try { + await module[camelCaseFunctionName](msg) + } catch (error) { + logger.error(`Error with command ${camelCaseFunctionName}`, error) + const readMore = '​'.repeat(783) + const prefix = msg.aux.prefix ?? '!' + msg.react('❌') + let message = `❌ - Ocorreu um erro inesperado com o comando *${prefix}${msg.aux.function}*\n\n` + message += 'Se for possível, tira um print e manda para meu administrador nesse grupo aqui: ' + message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR\n\n' + message += `${readMore}\n${error}` + msg.reply(message) + } +} From 80413ec814bfcc6c84ebcdacd76069e7b005fdfe Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:06:20 -0300 Subject: [PATCH 52/79] fix: change logs to single string --- src/services/events/call.js | 2 +- src/services/events/connectionUpdate.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/events/call.js b/src/services/events/call.js index ebcaa29..ad010f6 100644 --- a/src/services/events/call.js +++ b/src/services/events/call.js @@ -14,7 +14,7 @@ export default async (event) => { const emoji = '📞' const call = event[0] if (call.status === 'offer') { - logger.info(`${emoji} - Incoming call`, call) + logger.info(`${emoji} - Incoming call\n` + JSON.stringify(call, null, 2)) const sock = getSocket() await sock.rejectCall(call.id, call.from) diff --git a/src/services/events/connectionUpdate.js b/src/services/events/connectionUpdate.js index e46d215..0bf8ce0 100644 --- a/src/services/events/connectionUpdate.js +++ b/src/services/events/connectionUpdate.js @@ -7,7 +7,7 @@ import { connectToWhatsApp } from '../../index.js' * @param {import('@whiskeysockets/baileys').BaileysEventMap['connection.update']} update */ export default async (update) => { - logger.trace('Connection updated', update) + logger.trace('Connection updated\n' + JSON.stringify(update)) if (global.qr !== update.qr) { global.qr = update.qr } From 37de50eb6a59324a07ef45e820db7312daa206f0 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:06:39 -0300 Subject: [PATCH 53/79] chore: simplify logger --- src/logger.js | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/logger.js b/src/logger.js index e46362f..4784bc2 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,29 +1,14 @@ import pino from 'pino' -const transport = pino.transport({ - targets: [ - { - level: 'warn', - target: 'pino/file', - options: { - destination: 'errors.log' - } - }, - { - level: 'trace', - target: 'pino-pretty', - options: {} - } - ] -}) - /** * @type {import('pino').Logger} * @see https://getpino.io/#/ */ export default pino( { - level: 'info' - }, - transport + level: 'info', + transport: { + target: 'pino-pretty' + } + } ) From 05dcaf2d3d434c4ce49dd1c4c6fefd92125d6c77 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:07:03 -0300 Subject: [PATCH 54/79] feat: improve whatsapp boot strategy --- src/index.js | 53 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index 6d178a2..3d0584d 100644 --- a/src/index.js +++ b/src/index.js @@ -5,12 +5,12 @@ import * as baileys from '@whiskeysockets/baileys' import { defineCommand, runMain } from 'citty' import { apiKey } from './config/api.js' import { dotCase } from 'change-case' +import NodeCache from 'node-cache' import bot from './config/bot.js' import logger from './logger.js' import * as db from './db.js' import fs from 'fs/promises' import pino from 'pino' - let globalArgs = {} const main = defineCommand({ @@ -79,7 +79,7 @@ const main = defineCommand({ } }) -const store = undefined +export const store = undefined runMain(main) /** @@ -90,19 +90,18 @@ export function getArgs () { return globalArgs } -/** - * Baileys socket or null if not connected -*/ -// @type {import('@whiskeysockets/baileys').WSocket | null} -let socket = null - /** * Grabs the socket * @returns {import('./types').WSocket} - */ +*/ export function getSocket () { return socket } +let socket = null + +// external map to store retry counts of messages when decryption/encryption fails +// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts +const msgRetryCounterCache = new NodeCache() export async function connectToWhatsApp () { // if no API KEY, kill the process @@ -114,15 +113,23 @@ export async function connectToWhatsApp () { logger.info('Connecting to WhatsApp...') const { state, saveCreds } = await baileys.useMultiFileAuthState(`./src/temp/${bot.name}`) + const { version, isLatest } = await baileys.fetchLatestBaileysVersion() + logger.info(`Baileys version: v${version.join('.')} (latest: ${isLatest})`) socket = baileys.makeWASocket({ - printQRInTerminal: true, + version, logger: pino({ level: 'fatal' }), - auth: state, + printQRInTerminal: true, + auth: { + creds: state.creds, + keys: baileys.makeCacheableSignalKeyStore(state.keys, logger) + }, + msgRetryCounterCache, + markOnlineOnConnect: true, browser: ['DeadByte', 'Safari', '3.0'], generateHighQualityLinkPreview: true, - shouldIgnoreJid: jid => baileys.isJidBroadcast(jid), // TODO: make a stories downloader - getMessage: async key => { return { } } + shouldIgnoreJid: jid => baileys.isJidBroadcast(jid), // TODO: make a stories downloader, + getMessage }) store?.bind(socket.ev) @@ -144,12 +151,31 @@ export async function connectToWhatsApp () { }) }) socket.ev.on('creds.update', saveCreds) + socket.ev.on('messaging-history.set', async (history) => { + const { chats, contacts, messages, isLatest } = history + logger.info(`Loaded ${chats.length} chats, ${contacts.length} contacts and ${messages.length} messages (latest: ${isLatest})`) + }) logger.info('Client initialized!') await db.findCurrentBot(socket) // find the current bot on the database return socket } +/** + * Retrieves a message from the store based on the provided key. + * @param {import('@whiskeysockets/baileys').WAMessageKey} key - The key of the message to retrieve. + * @returns {Promise} The retrieved message content, or undefined if not found. + */ +export async function getMessage (key) { + if (store) { + const msg = await store.loadMessage(key.remoteJid, key.id) + return msg?.message || undefined + } + + // only if store is present + return baileys.proto.Message.fromObject({}) +} + // clear terminal process.stdout.write('\x1B[2J\x1B[0f') @@ -158,6 +184,7 @@ process.on('unhandledRejection', (err) => { // Connection Closed try connectToWhatsApp if (err.message.includes('Connection Closed')) { logger.fatal('Connection Closed AAAAAAAAAAA') + console.log(err) process.exit(0) // kill the process and pm2 will restart it } else { logger.fatal(err) From 1c612c8e130f457eb9fed67a035ffbc341f7c488 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Sun, 4 Feb 2024 23:07:22 -0300 Subject: [PATCH 55/79] feat: detect messages updates --- src/services/events/messagesUpdate.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/services/events/messagesUpdate.js diff --git a/src/services/events/messagesUpdate.js b/src/services/events/messagesUpdate.js new file mode 100644 index 0000000..e5b8aff --- /dev/null +++ b/src/services/events/messagesUpdate.js @@ -0,0 +1,25 @@ +import logger from '../../logger.js' +import { getAggregateVotesInPollMessage } from '@whiskeysockets/baileys' +import { getMessage } from '../../index.js' + +/** + * Connection state has been updated -- WS closed, opened, connecting etc. + * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.update']} event + */ +export default async (event) => { + logger.trace('Messages updated\n' + JSON.stringify(event, null, 2)) + for (const { key, update } of event) { + if (update.pollUpdates) { + const pollCreation = await getMessage(key) + if (pollCreation) { + console.log( + 'got poll update, aggregation: ', + getAggregateVotesInPollMessage({ + message: pollCreation, + pollUpdates: update.pollUpdates + }) + ) + } + } + } +} From 6152db9607617d214f08a9ef6905b78ebf59e630 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 5 Feb 2024 00:54:04 -0300 Subject: [PATCH 56/79] chore: buff response speed --- src/services/queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/queue.js b/src/services/queue.js index 0af2de0..188ad0c 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -201,7 +201,7 @@ async function wait (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } -async function waitAndProcessQueue (min = 500, max = 2500) { +async function waitAndProcessQueue (min = 250, max = 1500) { const waitTime = Math.floor(Math.random() * (max - min + 1)) + min await wait(waitTime) processQueue() From f739e072b274912b8c5ab482a860f08fba9bba49 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 5 Feb 2024 16:44:34 -0300 Subject: [PATCH 57/79] chore: slow down bot responses --- src/services/queue.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/queue.js b/src/services/queue.js index 188ad0c..7e43be3 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -90,10 +90,10 @@ export async function addToQueue (moduleName, functionName, msg) { } const messagesOnQueue = queue[id].queue.length - const waitUntil = Date.now() + (messagesOnQueue * 2000) + const waitUntil = Date.now() + (messagesOnQueue * 3000) const spamThreshold = 6 - if (messagesOnQueue >= spamThreshold - 1) { + if (messagesOnQueue >= spamThreshold) { const spamWarningResult = await handleSpamWarning(id, spamThreshold, msg) if (spamWarningResult.isSpam) { return spamWarningResult @@ -201,7 +201,7 @@ async function wait (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } -async function waitAndProcessQueue (min = 250, max = 1500) { +async function waitAndProcessQueue (min = 1000, max = 3000) { const waitTime = Math.floor(Math.random() * (max - min + 1)) + min await wait(waitTime) processQueue() From 7c7993a060e84d895a1e7d54a867ecb2c785f719 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 14:22:40 -0300 Subject: [PATCH 58/79] chore: remove peacock colors --- .vscode/settings.json | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 36cc1d0..7a73a41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,22 +1,2 @@ { - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#14e8a2", - "activityBar.background": "#14e8a2", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#bb4ff0", - "activityBarBadge.foreground": "#15202b", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#14e8a2", - "statusBar.background": "#10b981", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#0c8a60", - "statusBarItem.remoteBackground": "#10b981", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#10b981", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#10b98199", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#10b981" } \ No newline at end of file From 728cae1c125383efbfad883ea1d49f1041aa8141 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 14:22:58 -0300 Subject: [PATCH 59/79] fix: temp disable send seen --- src/meta/message.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/meta/message.js b/src/meta/message.js index c4fe735..1270723 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -234,8 +234,9 @@ async function sendSeen () { * Message object itself * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ - const msg = this - await socket.readMessages([msg.key]) + // const msg = this + // await socket.readMessages([msg.key]) + // temp disable } /** From 449991f0b408f5ae8b5a3a5928fbdf5605bb39b5 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 14:23:33 -0300 Subject: [PATCH 60/79] fix: temp log every message to detect last message before ban --- src/services/events/messagesUpsert.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 697b677..55d2a02 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -15,6 +15,7 @@ import logger from '../../logger.js' * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.upsert']} upsert */ export default async (upsert) => { + console.log('messages.upsert\n' + JSON.stringify(upsert, null, 2)) logger.trace('messages.upsert\n' + JSON.stringify(upsert, null, 2)) if (upsert.type === 'append') return // TODO: handle unread messages From ae55d78daf822689f2b7afe51d6491b05b52f99c Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 19:55:25 -0300 Subject: [PATCH 61/79] fix: temp enable vip only to 7041 and 5852 --- src/services/events/messagesUpsert.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 55d2a02..012303c 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -49,21 +49,22 @@ export default async (upsert) => { } // TODO: improve bot vip system - if (!msg.isGroup && msg.bot.name === 'DeadByte - VIP' && msg.aux.db) { + const vipBots = ['DeadByte - 5852', 'DeadByte - 7041', 'DeadByte - VIP'] + if (!msg.isGroup && vipBots.includes(msg.bot.name) && msg.aux.db) { const sender = msg.aux.db.contact.attributes const hasDonated = sender?.hasDonated === true if (!hasDonated) { - await msg.react('💎') - let message = '❌ - Você não é um VIP! 😢\n\n' - message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' - message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' - message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' - await msg.reply(message) + // await msg.react('💎') + // let message = '❌ - Você não é um VIP! 😢\n\n' + // message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' + // message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' + // message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' + // await msg.reply(message) - // wait 3 seconds and block the user - setTimeout(async () => { - await socket.updateBlockStatus(msg.from, 'block') - }, 5000) + // // wait 3 seconds and block the user + // setTimeout(async () => { + // await socket.updateBlockStatus(msg.from, 'block') + // }, 5000) return } From 2e1898850820d010e9d39bef22196dd367848c8b Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 20:29:31 -0300 Subject: [PATCH 62/79] fix: temp disable reaction --- src/meta/message.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/meta/message.js b/src/meta/message.js index 1270723..a30a234 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -161,13 +161,13 @@ async function react (reaction) { * Message object itself * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ - const msg = this - await socket.sendMessage(msg.key.remoteJid, { - react: { - text: spintax(reaction), - key: msg.key - } - }) + // const msg = this + // await socket.sendMessage(msg.key.remoteJid, { + // react: { + // text: spintax(reaction), + // key: msg.key + // } + // }) } /** From f77c22292505c3921b317fa039f0b191a2a9272b Mon Sep 17 00:00:00 2001 From: sergiooak Date: Thu, 8 Feb 2024 22:38:07 -0300 Subject: [PATCH 63/79] fix: temp active vip only to all bots --- src/services/events/messagesUpsert.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 012303c..b3d64fe 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -50,7 +50,8 @@ export default async (upsert) => { // TODO: improve bot vip system const vipBots = ['DeadByte - 5852', 'DeadByte - 7041', 'DeadByte - VIP'] - if (!msg.isGroup && vipBots.includes(msg.bot.name) && msg.aux.db) { + // if (!msg.isGroup && vipBots.includes(msg.bot.name) && msg.aux.db) { + if (!msg.isGroup && msg.aux.db) { const sender = msg.aux.db.contact.attributes const hasDonated = sender?.hasDonated === true if (!hasDonated) { From 2545ddb1da243060ffd22259cd96951ec5c39445 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 12 Feb 2024 02:55:00 -0300 Subject: [PATCH 64/79] feat: activate bot trough site --- src/services/commands/meta.js | 1 + src/services/events/messagesUpsert.js | 32 +++++----- src/services/functions/meta.js | 87 +++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 14 deletions(-) diff --git a/src/services/commands/meta.js b/src/services/commands/meta.js index 77f2c41..2b2d365 100644 --- a/src/services/commands/meta.js +++ b/src/services/commands/meta.js @@ -5,6 +5,7 @@ */ export default (msg) => { return { + activate: /^(ativa|ativar|active|activate)$/.test(msg.aux.function), set: /^(set|definir|defina)$/.test(msg.aux.function) } } diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index b3d64fe..2c01b58 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -53,22 +53,26 @@ export default async (upsert) => { // if (!msg.isGroup && vipBots.includes(msg.bot.name) && msg.aux.db) { if (!msg.isGroup && msg.aux.db) { const sender = msg.aux.db.contact.attributes - const hasDonated = sender?.hasDonated === true - if (!hasDonated) { - // await msg.react('💎') - // let message = '❌ - Você não é um VIP! 😢\n\n' - // message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' - // message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' - // message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' - // await msg.reply(message) + if (!sender.queue?.data && msg.aux.db.command.slug !== 'activate') { + console.log(`⛔ - ${msg.from} - ${handlerModule.command} - Not queued`) + return // user not passed through the queue + } + // const hasDonated = sender?.hasDonated === true + // if (!hasDonated) { + // // await msg.react('💎') + // // let message = '❌ - Você não é um VIP! 😢\n\n' + // // message += 'Desculpe, não localizei nenhuma doação em seu nome.\n\n' + // // message += '*Se isso for um erro ou se você deseja se tornar um VIP, entre em contato no grupo de suporte:*\n' + // // message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR' + // // await msg.reply(message) - // // wait 3 seconds and block the user - // setTimeout(async () => { - // await socket.updateBlockStatus(msg.from, 'block') - // }, 5000) + // // // wait 3 seconds and block the user + // // setTimeout(async () => { + // // await socket.updateBlockStatus(msg.from, 'block') + // // }, 5000) - return - } + // return + // } } const checkDisabled = await importFresh('validators/checkDisabled.js') diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index 9e53bfd..9772fbe 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -8,6 +8,11 @@ import dayjs from 'dayjs' dayjs.locale('pt-br') dayjs.extend(relativeTime) +const obfuscateMap = [ + 'a', 'D', 'e', 'A', 'd', 'F', 'g', 'H', 'i', 'J', 'k', 'L', 'm', 'N', 'o', 'P', + 'q', 'R', 'S', 't', 'u', 'V', 'w', 'X', 'y', 'Z', 'B', 'Y', 'T', 'E', 'c', 'C' +] + // // ================================ Main Functions ================================= // @@ -81,6 +86,88 @@ export async function set (msg) { await textSticker(msg) } +export async function activate (msg) { +// msg.body = DeAdFR\n\nSe o dead não responder em até 1 minuto envie a mensagem novamente!\n\n Pra facilitar é só clicar no botão de novo: DeadByte.com.br/fila-de-acesso +// code = DeAdFR + + const code = msg.body.split('\n')[0].trim() + const queueId = deobfuscateQueueId(code) + if (queueId === false) { + await msg.react('❌') + return msg.reply('❌ - Código inválido') + } + + // GET /api/queues/:id + const response = await fetch(`${getDBUrl()}/queues/${queueId}?populate=*`, { + headers: { + Authorization: `Bearer ${getToken()}` + } + }) + const queue = await response.json() + if (queue.data.contact) { + await msg.react('⚠️') + return msg.reply('⚠️ - Código de ativação já utilizado') + } + + console.log(msg.aux.db.contact.id) + // PUT /api/queues/:id + await fetch(`${getDBUrl()}/queues/${queueId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getToken()}` + }, + body: JSON.stringify({ + data: { + contact: msg.aux.db.contact.id + } + }) + }) + + msg.aux.db.contact = await forceContactUpdate(msg.contact) + await msg.react('⚡') + await msg.reply(`⚡ - {Prontinho|Pronto|Tudo pronto} ${msg.pushname}{, o|!\n\nO|!!!\n\nO} {DeadByte|dead|bot} {{já esta|tá} ativo|{já foi|foi} ativado} {para você|pra vc|pra tu}{!|!!|!!!}`) +} + // // ================================== Helper Functions ================================== // +/** + * Obfuscate a number to a string + * @param {number} number + * @returns {string} + */ +function obfuscateQueueId (number) { + return parseInt(number).toString(16).padStart(6, '0').split('').map((char, index) => { + const shift = (parseInt(char, 16) + index + 1) % obfuscateMap.length + return obfuscateMap[shift] + }).join('') +} + +/** + * Deobfuscate a string to a number + * @param {string} encodedStr + * @returns {number | boolean} + */ +function deobfuscateQueueId (encodedStr) { + let foundInt + try { + // Check for invalid input + if (!encodedStr.split('').every(char => obfuscateMap.includes(char))) { + return 'Invalid input' + } + + const hexStr = encodedStr.split('').map((char, index) => { + let shift = obfuscateMap.indexOf(char) - (index + 1) + while (shift < 0) { + shift += obfuscateMap.length // Correct negative shift + } + return shift.toString(16) + }).join('') + + foundInt = parseInt(hexStr, 16) + } catch (error) { + return false + } + return obfuscateQueueId(foundInt) === encodedStr ? foundInt : false +} From 099f7d4df23b2736f09c734fd258438a8ac23f70 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 12 Feb 2024 03:06:19 -0300 Subject: [PATCH 65/79] feat: log error --- src/services/queue.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/queue.js b/src/services/queue.js index 7e43be3..e20f428 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -223,7 +223,8 @@ export async function executeQueueItem (moduleName, functionName, msg) { try { await module[camelCaseFunctionName](msg) } catch (error) { - logger.error(`Error with command ${camelCaseFunctionName}`, error) + logger.error(`Error with command ${camelCaseFunctionName}`) + console.error(error) const readMore = '​'.repeat(783) const prefix = msg.aux.prefix ?? '!' msg.react('❌') From a19a4774cf8c99b2c664aa1edbd056422c87cc80 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 12 Feb 2024 03:08:45 -0300 Subject: [PATCH 66/79] fix: log queue --- src/services/functions/meta.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index 9772fbe..79577ed 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -104,7 +104,8 @@ export async function activate (msg) { } }) const queue = await response.json() - if (queue.data.contact) { + console.log('queue', queue) + if (queue.data?.contact) { await msg.react('⚠️') return msg.reply('⚠️ - Código de ativação já utilizado') } From 1fd1a387829a0d43caebcfbe165358d15c9e00b6 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Mon, 12 Feb 2024 03:18:22 -0300 Subject: [PATCH 67/79] fix --- src/services/functions/meta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index 79577ed..b9cce8e 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -105,7 +105,7 @@ export async function activate (msg) { }) const queue = await response.json() console.log('queue', queue) - if (queue.data?.contact) { + if (queue.data.contact) { await msg.react('⚠️') return msg.reply('⚠️ - Código de ativação já utilizado') } From b36f1cc7f32a0c5165fce6f813bb74fdff3d00d3 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 14:56:56 -0300 Subject: [PATCH 68/79] feat: reactivate reactions --- src/meta/message.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/meta/message.js b/src/meta/message.js index a30a234..47081f7 100644 --- a/src/meta/message.js +++ b/src/meta/message.js @@ -161,13 +161,15 @@ async function react (reaction) { * Message object itself * @type {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ - // const msg = this - // await socket.sendMessage(msg.key.remoteJid, { - // react: { - // text: spintax(reaction), - // key: msg.key - // } - // }) + const msg = this + await wait(Math.floor(Math.random() * 1000)) // 0-1000ms delay + await socket.sendMessage(msg.key.remoteJid, { + react: { + text: spintax(reaction), + key: msg.key + } + }) + await wait(Math.floor(Math.random() * 1000)) // 0-1000ms delay } /** @@ -329,3 +331,12 @@ function extractLinks (string) { if (!responseArray.length) return undefined return responseArray } + +/** + * Wait for the given amount of time + * @param {number} ms + * @returns {Promise} + */ +async function wait (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} From b283feb233464e98b2b266e8402d77c417fb6399 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 14:57:17 -0300 Subject: [PATCH 69/79] perf: stickers now only send wait reaction --- src/services/functions/stickers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 45ed6d6..447ee04 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -41,7 +41,7 @@ export async function stickerCreator (msg, stickerName, stickerAuthor, overwrite await sendMediaAsSticker(msg, stickerMedia, stickerName, stickerAuthor, overwrite) } - await msg.react(reactions.success) + await msg.react() } /** @@ -57,7 +57,7 @@ export async function textSticker (msg) { media = await Util.formatToWebpSticker(media, {}, false) await sendMediaAsSticker(msg, media) - await msg.react(reactions.success) + await msg.react() } /** From 6ecbd950d1ec9c13abd346464f1cb47a8bad817b Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 15:14:13 -0300 Subject: [PATCH 70/79] fix: disable read more trick --- src/services/functions/menu.js | 4 ++-- src/services/queue.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index bd304b6..2dfa23e 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -69,8 +69,8 @@ export async function menu (msg) { // Tell About prefix message += 'Os seguintes prefixos são aceitos para os comandos: *! . # /*\n\n' - const readMore = '​'.repeat(783) - message += readMore + // const readMore = '​'.repeat(783) + // message += readMore // const menuEmojis = '{📋|🗒️|📜}' // message += '```•·········• ' + menuEmojis + ' •·········•```\n\n' diff --git a/src/services/queue.js b/src/services/queue.js index e20f428..e5ffe81 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -225,13 +225,13 @@ export async function executeQueueItem (moduleName, functionName, msg) { } catch (error) { logger.error(`Error with command ${camelCaseFunctionName}`) console.error(error) - const readMore = '​'.repeat(783) + // const readMore = '​'.repeat(783) const prefix = msg.aux.prefix ?? '!' msg.react('❌') let message = `❌ - Ocorreu um erro inesperado com o comando *${prefix}${msg.aux.function}*\n\n` message += 'Se for possível, tira um print e manda para meu administrador nesse grupo aqui: ' message += 'https://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR\n\n' - message += `${readMore}\n${error}` + message += JSON.stringify(error, null, 2) msg.reply(message) } } From 9c542dc28d4d1afbd18f8279836adb3357498bf8 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 15:14:34 -0300 Subject: [PATCH 71/79] fix: disable log --- src/services/events/messagesUpsert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 2c01b58..38eb853 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -15,7 +15,7 @@ import logger from '../../logger.js' * @param {import('@whiskeysockets/baileys').BaileysEventMap['messages.upsert']} upsert */ export default async (upsert) => { - console.log('messages.upsert\n' + JSON.stringify(upsert, null, 2)) + // console.log('messages.upsert\n' + JSON.stringify(upsert, null, 2)) logger.trace('messages.upsert\n' + JSON.stringify(upsert, null, 2)) if (upsert.type === 'append') return // TODO: handle unread messages From 54ff59286cfd440235ba81a15cb751148a2d34d8 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 15:34:53 -0300 Subject: [PATCH 72/79] perf: reactions emojis loaded via db --- .eslintrc.json | 3 +++ src/services/functions/admin-commands.js | 5 +++-- .../functions/artificial-intelligence.js | 14 ++++++------- src/services/functions/groups.js | 20 +++++++++---------- src/services/functions/menu.js | 9 ++++++--- src/services/functions/meta.js | 11 ++++++---- src/services/functions/miscellaneous.js | 19 +++++++++--------- src/services/functions/statistics.js | 10 +++++----- src/services/functions/stickers.js | 12 +++++------ src/services/functions/tools.js | 8 ++++---- 10 files changed, 61 insertions(+), 50 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1b9d851..1adf73a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,9 @@ ], "no-unmodified-loop-condition": [ "off" + ], + "no-irregular-whitespace": [ + "off" ] } } \ No newline at end of file diff --git a/src/services/functions/admin-commands.js b/src/services/functions/admin-commands.js index 15b9408..78838fa 100644 --- a/src/services/functions/admin-commands.js +++ b/src/services/functions/admin-commands.js @@ -14,7 +14,8 @@ dayjs.extend(relativeTime) * @param {import('../../types').WWebJSMessage} msg */ export async function debug (msg) { - const debugEmoji = '🐛' - await msg.react(debugEmoji) // Debug code goes here + + // Then react to the message + await msg.react(msg.aux.db.command.emoji) } diff --git a/src/services/functions/artificial-intelligence.js b/src/services/functions/artificial-intelligence.js index c236ceb..3378cba 100644 --- a/src/services/functions/artificial-intelligence.js +++ b/src/services/functions/artificial-intelligence.js @@ -39,11 +39,11 @@ export async function gpt (msg) { await msg.reply(data.result) await msg.aux.chat.clearState() - await msg.react('🧠') + await msg.react(msg.aux.db.command.emoji) } catch (error) { await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') await msg.aux.chat.clearState() - await msg.react('❌') + await msg.react(reactions.error) } } @@ -84,11 +84,11 @@ export async function bot (msg) { await msg.reply(data.result) await msg.aux.chat.clearState() - await msg.react('🧠') + await msg.react(msg.aux.db.command.emoji) } catch (error) { await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') await msg.aux.chat.clearState() - await msg.react('❌') + await msg.react(reactions.error) } } @@ -125,7 +125,7 @@ export async function emojify (msg) { const response = completion.choices[0]?.message?.content await msg.reply(response) - await msg.react('😀') + await msg.react(msg.aux.db.command.emoji) } /** @@ -166,7 +166,7 @@ export async function translate (msg) { const response = completion.choices[0]?.message?.content await msg.reply(response) - await msg.react('🌐') + await msg.react(msg.aux.db.command.emoji) } /** @@ -213,5 +213,5 @@ export async function calculate (msg) { const response = completion.choices[0]?.message?.content await msg.reply(response) - await msg.react('😀') + await msg.react(msg.aux.db.command.emoji) } diff --git a/src/services/functions/groups.js b/src/services/functions/groups.js index c2f0b87..3dba3a9 100644 --- a/src/services/functions/groups.js +++ b/src/services/functions/groups.js @@ -33,7 +33,7 @@ export async function ban (msg) { } await socket.groupParticipantsUpdate(msg.from, target, 'remove') - await msg.react('🔨') + await msg.react(msg.aux.db.command.emoji) } export async function unban (msg) { @@ -58,7 +58,7 @@ export async function unban (msg) { if (hasQuotedMsg) { target.push(await msg.quotedMsg.sender) } await socket.groupParticipantsUpdate(msg.from, target, 'add') - await msg.react('🔄') + await msg.react(msg.aux.db.command.emoji) } /** @@ -78,7 +78,7 @@ export async function promote (msg) { return await msg.reply('para usar o !promove *você* precisa ser admin') } await socket.groupParticipantsUpdate(msg.from, msg.aux.mentions, 'promote') - await msg.react('↗️') + await msg.react(msg.aux.db.command.emoji) } /** @@ -99,7 +99,7 @@ export async function demote (msg) { } await socket.groupParticipantsUpdate(msg.from, msg.aux.mentions, 'demote') - await msg.react('↘️') + await msg.react(msg.aux.db.command.emoji) } /** @@ -120,7 +120,7 @@ export async function giveaway (msg) { message = hasText ? `${message} de *${msg.body.trim()}*!` : message + '!' await socket.sendMessage(msg.from, { text: spintax(message), mentions: [winner.id] }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) - await msg.react(spintax('{🎉|🎊|🥳}')) + await msg.react(msg.aux.db.command.emoji) } /** @@ -142,7 +142,7 @@ export async function giveawayAdminsOnly (msg) { message = hasText ? `${message} *${text.trim()}*!` : message + '!' await socket.sendMessage(msg.from, { text: spintax(message), mentions: [winner.id] }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) - await msg.react('🎉') + await msg.react(msg.aux.db.command.emoji) } /** @@ -162,7 +162,7 @@ export async function markAllMembers (msg) { text: msg.body ? `📣 - ${msg.body}` : '📣', mentions: participants }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) - await msg.react('📣') + await msg.react(msg.aux.db.command.emoji) } /** @@ -175,7 +175,7 @@ export async function callAdmins (msg) { text: '👑 - Atenção administradores!', mentions: admins }, { ephemeralExpiration: msg.raw.message[Object.keys(msg.raw.message)[0]].contextInfo?.expiration || undefined }) - await msg.react('👑') + await msg.react(msg.aux.db.command.emoji) } /** @@ -183,7 +183,6 @@ export async function callAdmins (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function closeGroup (msg) { - await msg.react('🔒') if (!msg.aux.isBotAdmin) { return await msg.reply('para usar o !fechar *o bot* precisa ser admin') } @@ -192,6 +191,7 @@ export async function closeGroup (msg) { return await msg.reply('para usar o !fechar *você* precisa ser admin') } await socket.groupSettingUpdate(msg.from, 'announcement') + await msg.react(msg.aux.db.command.emoji) } /** @@ -199,7 +199,6 @@ export async function closeGroup (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function openGroup (msg) { - await msg.react('🔓') if (!msg.aux.isBotAdmin) { return await msg.reply('para usar o !abrir *o bot* precisa ser admin') } @@ -209,4 +208,5 @@ export async function openGroup (msg) { } await socket.groupSettingUpdate(msg.from, 'not_announcement') + await msg.react(msg.aux.db.command.emoji) } diff --git a/src/services/functions/menu.js b/src/services/functions/menu.js index 2dfa23e..f953f8f 100644 --- a/src/services/functions/menu.js +++ b/src/services/functions/menu.js @@ -1,5 +1,6 @@ import { MessageMedia } from '../../meta/messageMedia.js' import relativeTime from 'dayjs/plugin/relativeTime.js' +import reactions from '../../config/reactions.js' import spintax from '../../utils/spintax.js' import { getCommands } from '../../db.js' import 'dayjs/locale/pt-br.js' @@ -16,7 +17,7 @@ dayjs.extend(relativeTime) * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function menu (msg) { - await msg.react('📜') + await msg.react(reactions.wait) let commandGroups = await getCommands() @@ -98,15 +99,16 @@ export async function menu (msg) { message = message.trim().replace(/\n$/, '').trim() // await msg.reply(JSON.stringify(msg.aux, null, 2)) await msg.reply({ media, caption: spintax(message) }, undefined) + await msg.react(reactions.success) } export async function menuGroup (msg) { + await msg.react(reactions.wait) let commandGroups = await getCommands() // pick only the command group where slug == groups commandGroups = commandGroups.filter(c => c.slug === 'groups') - - await msg.react(commandGroups[0].emoji) + const emoji = commandGroups[0].emoji // if there is image, send it let media @@ -159,6 +161,7 @@ export async function menuGroup (msg) { // await msg.reply(JSON.stringify(msg.aux, null, 2)) // await msg.reply(spintax(message)) await msg.reply(spintax(message), undefined, { media }) + await msg.react(emoji) } // diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index b9cce8e..3c05c65 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -1,5 +1,6 @@ import { getDBUrl, getToken, forceContactUpdate } from '../../db.js' import relativeTime from 'dayjs/plugin/relativeTime.js' +import reactions from '../../config/reactions.js' import spintax from '../../utils/spintax.js' import { textSticker } from './stickers.js' import 'dayjs/locale/pt-br.js' @@ -21,8 +22,6 @@ const obfuscateMap = [ * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function set (msg) { - await msg.react('⚙️') - const preferences = { pack: 'stickerName', pacote: 'stickerName', @@ -64,6 +63,7 @@ export async function set (msg) { return msg.reply(message) } + await msg.react(reactions.wait) const id = msg.aux.db.contact.id const preferenceObject = msg.aux.db.contact.attributes.preferences ?? {} preferenceObject[preferences[prefence]] = value || 'undefined' @@ -81,9 +81,9 @@ export async function set (msg) { }) }) msg.aux.db.contact = await forceContactUpdate(msg.contact) - await msg.react('✅') msg.body = spintax('{Clique|Clica} {{nessa|nesta} figurinha|{nesse|neste} sticker} para {você |vc |}ver {{o que|oq} {mudou|alterou}|como ficou|o resultado}') await textSticker(msg) + await msg.react(msg.aux.db.command.emoji) } export async function activate (msg) { @@ -106,6 +106,9 @@ export async function activate (msg) { const queue = await response.json() console.log('queue', queue) if (queue.data.contact) { + // TODO: if the current contact give sucessful feedback + // If is another contact, tell the user that the code is already used + // And send then to the queue again await msg.react('⚠️') return msg.reply('⚠️ - Código de ativação já utilizado') } @@ -126,8 +129,8 @@ export async function activate (msg) { }) msg.aux.db.contact = await forceContactUpdate(msg.contact) - await msg.react('⚡') await msg.reply(`⚡ - {Prontinho|Pronto|Tudo pronto} ${msg.pushname}{, o|!\n\nO|!!!\n\nO} {DeadByte|dead|bot} {{já esta|tá} ativo|{já foi|foi} ativado} {para você|pra vc|pra tu}{!|!!|!!!}`) + await msg.react(msg.aux.db.command.emoji) } // diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index f6f56ce..54bd3b4 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -33,13 +33,13 @@ export async function uptime (msg) { const uptimeString = secondsToDhms(uptime) const clock = '{⏳|⌚|⏰|⏱️|⏲️|🕰️|🕛|🕧|🕐|🕜|🕑|🕝}' - await msg.react(spintax(clock)) // react with random clock emoji const saudation = `{${spintax(clock)}} - {Olá|Oi|Oie|E aí} ${msg.pushname || 'usuário'} tudo {jóia|bem}?` const part1 = '{Eu estou|Estou|O bot {está|ta|tá}|O DeadByte {está|ta|tá}} {online|on|ligado}{ direto|} {a|á|tem}{:|} ' const message = `${saudation}\n\n${part1}*${uptimeString}*` await msg.reply(message) + await msg.react(msg.aux.db.command.emoji) } /** @@ -58,8 +58,6 @@ export async function react (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function dice (msg) { - await msg.react('🎲') - const fullCommand = msg.aux.function const regex = /(?\d*)d(?\d+)(?[\+\-\*\/]\d+)?/i @@ -85,6 +83,7 @@ export async function dice (msg) { }) } await msg.reply(message) + await msg.react(msg.aux.db.command.emoji) } /** @@ -140,7 +139,7 @@ export async function toFile (msg) { await msg.reply({ media, caption: 'DeadByte.com.br - bot de figurinhas' }) } - await msg.react('🗂️') + await msg.react(msg.aux.db.command.emoji) } /** @@ -160,11 +159,11 @@ export async function toUrl (msg) { return await msg.reply(message) } - await msg.react('🔗') const media = msg.hasQuotedMsg ? await msg.downloadMedia(true) : await msg.downloadMedia() if (!media) throw new Error('Error downloading media') const tempUrl = (await getTempUrl(media)) + await msg.react(reactions.wait) let message = '🔗 - ' message += '{Aqui está|Toma ai|Confira aqui|Veja só|Prontinho ta aí} ' message += '{a url temporária|o link temporário|o endereço temporário} ' @@ -172,6 +171,7 @@ export async function toUrl (msg) { message += `${tempUrl}\n\n` message += '{Válido por {apenas|}|Com {validade|vigência} de|Por um período de} {3|03|três} dias' await msg.reply(message) + await msg.react(msg.aux.db.command.emoji) } /** @@ -179,8 +179,6 @@ export async function toUrl (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function ping (msg) { - await msg.react('🏓') - let message = '🏓 - Pong!\n\n' // const usersInQueue = getQueueLength('user') @@ -203,6 +201,7 @@ export async function ping (msg) { } await msg.reply(spintax(message)) + await msg.react(msg.aux.db.command.emoji) } /** @@ -236,7 +235,7 @@ export async function speak (msg) { input = input.slice(0, inputLimit) } - await msg.react('🗣️') + await msg.react(reactions.wait) // await msg.aux.chat.sendStateRecording() const voices = ['onyx', 'echo', 'fable', 'nova', 'shimmer'] @@ -264,6 +263,7 @@ export async function speak (msg) { const media = new MessageMedia('audio/ogg; codecs=opus', buffer.toString('base64'), 'DeadByte' + Date.now() + '.opus' , buffer.length) await msg.reply({ media }, undefined, { ptt: true }) + await msg.react(msg.aux.db.command.emoji) } /** @@ -284,7 +284,7 @@ export async function transcribe (msg) { return } - await msg.react('🎙️') + await msg.react(reactions.wait) // await msg.aux.chat.sendStateTyping() // save file to temp folder @@ -301,6 +301,7 @@ export async function transcribe (msg) { }) fs.unlinkSync(nomalizedFilePath) await msg.reply(`🎙️ - ${transcription.trim()}`) + await msg.react(msg.aux.db.command.emoji) } // diff --git a/src/services/functions/statistics.js b/src/services/functions/statistics.js index ea6181f..3a68d55 100644 --- a/src/services/functions/statistics.js +++ b/src/services/functions/statistics.js @@ -59,7 +59,7 @@ export async function stats (msg) { message = formatCommands(commands, msg, message) await waitForMinimumTime(startedAt) - await reactAndReply(msg, emojis, reply, message) + await reactAndReply(msg, msg.aux.db.command.emoji, reply, message) } /** @@ -107,7 +107,7 @@ export async function botStats (msg) { message += 'Veja as estatísticas completas em tempo real no site:\ndeadbyte.com.br/stats\n\n' await waitForMinimumTime(startedAt) - await reactAndReply(msg, emojis, reply, message) + await reactAndReply(msg, msg.aux.db.command.emoji, reply, message) } /** @@ -151,7 +151,7 @@ export async function weekStats (msg) { message = formatCommands(commands, msg, message) await waitForMinimumTime(startedAt) - await reactAndReply(msg, emojis, reply, message) + await reactAndReply(msg, msg.aux.db.command.emoji, reply, message) } /** @@ -195,7 +195,7 @@ export async function dayStats (msg) { message = formatCommands(commands, msg, message) await waitForMinimumTime(startedAt) - await reactAndReply(msg, emojis, reply, message) + await reactAndReply(msg, msg.aux.db.command.emoji, reply, message) } /** @@ -236,7 +236,7 @@ export async function hourStats (msg) { message = formatCommands(commands, msg, message) await waitForMinimumTime(startedAt) - await reactAndReply(msg, emojis, reply, message) + await reactAndReply(msg, msg.aux.db.command.emoji, reply, message) } // // ================================== Helper Functions ================================== diff --git a/src/services/functions/stickers.js b/src/services/functions/stickers.js index 447ee04..ff16fc9 100644 --- a/src/services/functions/stickers.js +++ b/src/services/functions/stickers.js @@ -72,7 +72,7 @@ export async function textSticker2 (msg) { media = await Util.formatToWebpSticker(media, {}, false) await sendMediaAsSticker(msg, media) - await msg.react(reactions.success) + await msg.react() } /** @@ -87,7 +87,7 @@ export async function textSticker3 (msg) { media = await Util.formatToWebpSticker(media, {}, false) await sendMediaAsSticker(msg, media) - await msg.react(reactions.success) + await msg.react() } /** @@ -131,7 +131,7 @@ export async function removeBg (msg) { if (msg.body) stickerMedia = await overlaySubtitle(msg.body, stickerMedia).catch((e) => logger.error(e)) || stickerMedia await sendMediaAsSticker(msg, stickerMedia) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } /** @@ -176,7 +176,7 @@ export async function stealSticker (msg) { if (!media) throw new Error('Error downloading media') await sendMediaAsSticker(msg, media, stickerName, stickerAuthor, true) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } /** @@ -228,7 +228,7 @@ export async function stickerLySearch (msg) { } await sendStickers(stickersPaginated, msg) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } /** @@ -285,7 +285,7 @@ export async function stickerLyPack (msg) { } await sendStickers(stickersPaginated, msg) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } // diff --git a/src/services/functions/tools.js b/src/services/functions/tools.js index 86328ae..4d8dd59 100644 --- a/src/services/functions/tools.js +++ b/src/services/functions/tools.js @@ -37,7 +37,7 @@ export async function qrImageCreator (msg) { url } }) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } catch (error) { logger.error(error) await msg.reply('Erro ao criar QR Code') @@ -64,7 +64,7 @@ export async function qrTextCreator (msg) { const response = await fetch(url) const data = await response.json() await msg.reply('```' + data.result.string + '```') - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } catch (error) { logger.error(error) await msg.reply('Erro ao criar QR Code') @@ -92,7 +92,7 @@ export async function qrReader (msg) { await msg.react(reactions.error) return await msg.reply('❌ Só consigo ler QR Codes em imagens') } - await msg.react(reactions.wait) + // await msg.react(reactions.wait) // a clock doing a full circle // const spinner = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'] // let spinnerIndex = 0 @@ -138,5 +138,5 @@ export async function qrReader (msg) { await msg.reply(`✅ - ${qrData.result}`) // await wait(1000) // await reply.edit(`✅ - ${qrData.result}`) - await msg.react(reactions.success) + await msg.react(msg.aux.db.command.emoji) } From 8b95cf4ca06989e27679face0d1eab69d89a5348 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 15:36:29 -0300 Subject: [PATCH 73/79] fix: comment out unused code --- src/services/events/messagesUpsert.js | 2 +- .../functions/artificial-intelligence.js | 350 +++++++++--------- 2 files changed, 176 insertions(+), 176 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 38eb853..4ecdfe2 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -49,7 +49,7 @@ export default async (upsert) => { } // TODO: improve bot vip system - const vipBots = ['DeadByte - 5852', 'DeadByte - 7041', 'DeadByte - VIP'] + // const vipBots = ['DeadByte - 5852', 'DeadByte - 7041', 'DeadByte - VIP'] // if (!msg.isGroup && vipBots.includes(msg.bot.name) && msg.aux.db) { if (!msg.isGroup && msg.aux.db) { const sender = msg.aux.db.contact.attributes diff --git a/src/services/functions/artificial-intelligence.js b/src/services/functions/artificial-intelligence.js index 3378cba..fcdfef6 100644 --- a/src/services/functions/artificial-intelligence.js +++ b/src/services/functions/artificial-intelligence.js @@ -1,50 +1,50 @@ -import reactions from '../../config/reactions.js' -import { createUrl } from '../../config/api.js' -import fetch from 'node-fetch' +// import reactions from '../../config/reactions.js' +// import { createUrl } from '../../config/api.js' +// import fetch from 'node-fetch' /** * Use chat gpt * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function gpt (msg) { - await msg.react(reactions.wait) - if (!msg.body) { - return msg.reply('Para utilizar o *!gpt* mande uma mensagem junto com o comando.') - } - - const messages = msg.aux.history.map(msg => { - return { - role: msg._data.self === 'out' ? 'assistant' : 'user', - content: msg.body - } - }) - - await msg.aux.chat.sendStateTyping() - const url = await createUrl('artificial-intelligence', 'gpt', {}) - // POST request to the API with messages on json body - try { - const timeout = setTimeout(() => { - throw new Error('Timeout') - }, 30_000) - - const res = await fetch(url, { - method: 'POST', - body: JSON.stringify({ messages }), - headers: { 'Content-Type': 'application/json' } - }) - - clearTimeout(timeout) - - const data = await res.json() - - await msg.reply(data.result) - await msg.aux.chat.clearState() - await msg.react(msg.aux.db.command.emoji) - } catch (error) { - await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') - await msg.aux.chat.clearState() - await msg.react(reactions.error) - } + // await msg.react(reactions.wait) + // if (!msg.body) { + // return msg.reply('Para utilizar o *!gpt* mande uma mensagem junto com o comando.') + // } + + // const messages = msg.aux.history.map(msg => { + // return { + // role: msg._data.self === 'out' ? 'assistant' : 'user', + // content: msg.body + // } + // }) + + // await msg.aux.chat.sendStateTyping() + // const url = await createUrl('artificial-intelligence', 'gpt', {}) + // // POST request to the API with messages on json body + // try { + // const timeout = setTimeout(() => { + // throw new Error('Timeout') + // }, 30_000) + + // const res = await fetch(url, { + // method: 'POST', + // body: JSON.stringify({ messages }), + // headers: { 'Content-Type': 'application/json' } + // }) + + // clearTimeout(timeout) + + // const data = await res.json() + + // await msg.reply(data.result) + // await msg.aux.chat.clearState() + // await msg.react(msg.aux.db.command.emoji) + // } catch (error) { + // await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') + // await msg.aux.chat.clearState() + // await msg.react(reactions.error) + // } } /** @@ -52,44 +52,44 @@ export async function gpt (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function bot (msg) { - await msg.react(reactions.wait) - if (!msg.body) { - return msg.reply('Para utilizar o *!gpt* mande uma mensagem junto com o comando.') - } - - const messages = msg.aux.history.map(msg => { - return { - role: msg._data.self === 'out' ? 'assistant' : 'user', - content: msg.body - } - }) - - await msg.aux.chat.sendStateTyping() - const url = await createUrl('artificial-intelligence', 'bot', {}) - // POST request to the API with messages on json body - try { - const timeout = setTimeout(() => { - throw new Error('Timeout') - }, 30_000) - - const res = await fetch(url, { - method: 'POST', - body: JSON.stringify({ messages }), - headers: { 'Content-Type': 'application/json' } - }) - - clearTimeout(timeout) - - const data = await res.json() - - await msg.reply(data.result) - await msg.aux.chat.clearState() - await msg.react(msg.aux.db.command.emoji) - } catch (error) { - await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') - await msg.aux.chat.clearState() - await msg.react(reactions.error) - } + // await msg.react(reactions.wait) + // if (!msg.body) { + // return msg.reply('Para utilizar o *!gpt* mande uma mensagem junto com o comando.') + // } + + // const messages = msg.aux.history.map(msg => { + // return { + // role: msg._data.self === 'out' ? 'assistant' : 'user', + // content: msg.body + // } + // }) + + // await msg.aux.chat.sendStateTyping() + // const url = await createUrl('artificial-intelligence', 'bot', {}) + // // POST request to the API with messages on json body + // try { + // const timeout = setTimeout(() => { + // throw new Error('Timeout') + // }, 30_000) + + // const res = await fetch(url, { + // method: 'POST', + // body: JSON.stringify({ messages }), + // headers: { 'Content-Type': 'application/json' } + // }) + + // clearTimeout(timeout) + + // const data = await res.json() + + // await msg.reply(data.result) + // await msg.aux.chat.clearState() + // await msg.react(msg.aux.db.command.emoji) + // } catch (error) { + // await msg.reply('❌ - Aconteceu um erro inesperado, tente novamente mais tarde.\nznSe possivel, reporte o erro para o desenvolvedor no grupo:\nhttps://chat.whatsapp.com/CBlkOiMj4fM3tJoFeu2WpR') + // await msg.aux.chat.clearState() + // await msg.react(reactions.error) + // } } /** @@ -97,35 +97,35 @@ export async function bot (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function emojify (msg) { - await msg.react(reactions.wait) + // await msg.react(reactions.wait) - if (!msg.body && !msg.hasQuotedMsg) return msg.reply('Para utilizar o *!emojify* mande uma mensagem junto com o comando.\nOu responda a uma mensagem com o comando.') + // if (!msg.body && !msg.hasQuotedMsg) return msg.reply('Para utilizar o *!emojify* mande uma mensagem junto com o comando.\nOu responda a uma mensagem com o comando.') - const messages = [ - { - role: 'user', - content: msg.hasQuotedMsg ? msg.aux.quotedMsg.body : msg.body - } - ] + // const messages = [ + // { + // role: 'user', + // content: msg.hasQuotedMsg ? msg.aux.quotedMsg.body : msg.body + // } + // ] - const prompt = { - role: 'system', - content: 'I want you to translate the sentences I wrote into emojis. The use will write the sentence, and you will express it with emojis. I just want you to express it with emojis. I don\'t want you to reply with anything but emoji tranlation' - } + // const prompt = { + // role: 'system', + // content: 'I want you to translate the sentences I wrote into emojis. The use will write the sentence, and you will express it with emojis. I just want you to express it with emojis. I don\'t want you to reply with anything but emoji tranlation' + // } - messages.unshift(prompt) // Add prompt object at the beginning of messages array + // messages.unshift(prompt) // Add prompt object at the beginning of messages array - const completion = await openai.chat.completions.create({ - model: 'gpt-3.5-turbo', - max_tokens: 4096 / 8, - temperature: 0, - messages - }) + // const completion = await openai.chat.completions.create({ + // model: 'gpt-3.5-turbo', + // max_tokens: 4096 / 8, + // temperature: 0, + // messages + // }) - const response = completion.choices[0]?.message?.content + // const response = completion.choices[0]?.message?.content - await msg.reply(response) - await msg.react(msg.aux.db.command.emoji) + // await msg.reply(response) + // await msg.react(msg.aux.db.command.emoji) } /** @@ -133,40 +133,40 @@ export async function emojify (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function translate (msg) { - await msg.react(reactions.wait) - const prompt = { - role: 'system', - content: `Returns the sentence translated into the output language. - - Automatically detect the input language. - The output language will be english if the input language is portuguese, and portuguese if the input language is english. - Except if the user specify the output language, saying something like "translate es" or "translate chinese "something"". - - Do not interact with the user, just return the translations of what the user said. - Localize the translations, adapt slang and other things to the language feel natural. - - Prefix the response with a flag representing th output language, like "🇪🇸" or or "🇧🇷" or "🇺🇸" etc.. - Example: '🇪🇸 - "Hola, ¿cómo estás?"' or '🇧🇷 - "Oi, tudo bem?"' or '🇺🇸 - "Hi, how are you?"' - ` - } - - const messageToTranslate = msg.hasQuotedMsg ? msg.aux.quotedMsg.body : msg.body - - const messages = [prompt, { - role: 'user', - content: `translate ${msg.body ? msg.body + ' ' : ''}"${messageToTranslate}"` - }] - - const completion = await openai.chat.completions.create({ - model: 'gpt-3.5-turbo', - max_tokens: 4096 / 4, - temperature: 0, - messages - }) - - const response = completion.choices[0]?.message?.content - await msg.reply(response) - await msg.react(msg.aux.db.command.emoji) + // await msg.react(reactions.wait) + // const prompt = { + // role: 'system', + // content: `Returns the sentence translated into the output language. + + // Automatically detect the input language. + // The output language will be english if the input language is portuguese, and portuguese if the input language is english. + // Except if the user specify the output language, saying something like "translate es" or "translate chinese "something"". + + // Do not interact with the user, just return the translations of what the user said. + // Localize the translations, adapt slang and other things to the language feel natural. + + // Prefix the response with a flag representing th output language, like "🇪🇸" or or "🇧🇷" or "🇺🇸" etc.. + // Example: '🇪🇸 - "Hola, ¿cómo estás?"' or '🇧🇷 - "Oi, tudo bem?"' or '🇺🇸 - "Hi, how are you?"' + // ` + // } + + // const messageToTranslate = msg.hasQuotedMsg ? msg.aux.quotedMsg.body : msg.body + + // const messages = [prompt, { + // role: 'user', + // content: `translate ${msg.body ? msg.body + ' ' : ''}"${messageToTranslate}"` + // }] + + // const completion = await openai.chat.completions.create({ + // model: 'gpt-3.5-turbo', + // max_tokens: 4096 / 4, + // temperature: 0, + // messages + // }) + + // const response = completion.choices[0]?.message?.content + // await msg.reply(response) + // await msg.react(msg.aux.db.command.emoji) } /** @@ -174,44 +174,44 @@ export async function translate (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function calculate (msg) { - await msg.react(reactions.wait) - - const messages = msg.aux.history.map(msg => { - return { - role: msg._data.self === 'out' ? 'assistant' : 'user', - content: msg.body - } - }) - - const prompt = { - role: 'system', - content: `I want you to act like a mathematician - I will type mathematical expressions and you will respond with the result of calculating the expression - I want you to answer the line by line calculations - Do not write explanations - Always wrap the result in * like *18* or *x = 2* - If you need to explain something, always do it in portuguese, but avoid it if possible - - Example: - 2 + 2 * 8 - 2 + (2 * 8) - 2 + 16 - ---------- - *18* - ` - } - - messages.unshift(prompt) // Add prompt object at the beginning of messages array - - const completion = await openai.chat.completions.create({ - model: 'gpt-3.5-turbo', - max_tokens: 4096 / 8, - temperature: 0, - messages - }) - - const response = completion.choices[0]?.message?.content - - await msg.reply(response) - await msg.react(msg.aux.db.command.emoji) + // await msg.react(reactions.wait) + + // const messages = msg.aux.history.map(msg => { + // return { + // role: msg._data.self === 'out' ? 'assistant' : 'user', + // content: msg.body + // } + // }) + + // const prompt = { + // role: 'system', + // content: `I want you to act like a mathematician + // I will type mathematical expressions and you will respond with the result of calculating the expression + // I want you to answer the line by line calculations + // Do not write explanations + // Always wrap the result in * like *18* or *x = 2* + // If you need to explain something, always do it in portuguese, but avoid it if possible + + // Example: + // 2 + 2 * 8 + // 2 + (2 * 8) + // 2 + 16 + // ---------- + // *18* + // ` + // } + + // messages.unshift(prompt) // Add prompt object at the beginning of messages array + + // const completion = await openai.chat.completions.create({ + // model: 'gpt-3.5-turbo', + // max_tokens: 4096 / 8, + // temperature: 0, + // messages + // }) + + // const response = completion.choices[0]?.message?.content + + // await msg.reply(response) + // await msg.react(msg.aux.db.command.emoji) } From 3135ad29a21a1dfab0205bb39962f0e61718d07f Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 15:46:41 -0300 Subject: [PATCH 74/79] fix: remove logs --- src/index.js | 2 +- src/services/events/messagesUpsert.js | 2 +- src/services/functions/meta.js | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 3d0584d..996a15c 100644 --- a/src/index.js +++ b/src/index.js @@ -184,7 +184,7 @@ process.on('unhandledRejection', (err) => { // Connection Closed try connectToWhatsApp if (err.message.includes('Connection Closed')) { logger.fatal('Connection Closed AAAAAAAAAAA') - console.log(err) + console.error(err) process.exit(0) // kill the process and pm2 will restart it } else { logger.fatal(err) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 4ecdfe2..1f0e481 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -54,7 +54,7 @@ export default async (upsert) => { if (!msg.isGroup && msg.aux.db) { const sender = msg.aux.db.contact.attributes if (!sender.queue?.data && msg.aux.db.command.slug !== 'activate') { - console.log(`⛔ - ${msg.from} - ${handlerModule.command} - Not queued`) + console.warn(`⛔ - ${msg.from} - ${handlerModule.command} - Not queued`) return // user not passed through the queue } // const hasDonated = sender?.hasDonated === true diff --git a/src/services/functions/meta.js b/src/services/functions/meta.js index 3c05c65..78f28d1 100644 --- a/src/services/functions/meta.js +++ b/src/services/functions/meta.js @@ -104,7 +104,6 @@ export async function activate (msg) { } }) const queue = await response.json() - console.log('queue', queue) if (queue.data.contact) { // TODO: if the current contact give sucessful feedback // If is another contact, tell the user that the code is already used @@ -113,7 +112,6 @@ export async function activate (msg) { return msg.reply('⚠️ - Código de ativação já utilizado') } - console.log(msg.aux.db.contact.id) // PUT /api/queues/:id await fetch(`${getDBUrl()}/queues/${queueId}`, { method: 'PUT', From f1225a9c23689127c13d63d93a1e0cef6c5fd54a Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 19:35:17 -0300 Subject: [PATCH 75/79] perf: improve ping --- src/services/functions/miscellaneous.js | 44 ++++++++++++++----------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 54bd3b4..10e8efa 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -179,25 +179,31 @@ export async function toUrl (msg) { * @param {import('../../types.d.ts').WWebJSMessage} msg */ export async function ping (msg) { - let message = '🏓 - Pong!\n\n' - - // const usersInQueue = getQueueLength('user') - // const messagesInQueue = getQueueLength('messages') - // if (usersInQueue || messagesInQueue) { - // message += `{Atualmente|No momento|{Nesse|Neste}{ exato|} momento} tem *${usersInQueue} ${usersInQueue > 1 ? 'usuários' : 'usuário'}* na fila com *${messagesInQueue} ${messagesInQueue > 1 ? 'mensagens' : 'mensagem'}* ao todo!\n\n` - // } - let lag = msg.lag / 1000 lag = Math.max(lag, 0) // if lag is negative, set it to 0 lag = isNaN(lag) ? 0 : lag const ping = Date.now() - msg.startedAt const delayString = convertToHumanReadable(ping, lag, 'ms') - message += `Essa mensagem demorou *${delayString}* para ser respondida` + let message = `Essa mensagem demorou *${delayString}* para ser respondida` + + message += '\n\n```━━━━━━━━━━ 🏓 ━━━━━━━━━━```\n\n' + + // TODO: get medium speed for each message from server + // message += '📶 - Velocidade atual: 0\n' + // message += '👥 - Usuários esta semana: 0\n' + // message += '💬 - Chats na última hora: 0\n' + message += '📩 - Mensagens na última hora: 0\n' + + message += '🕒 - Online direto há: ' + const uptime = process.uptime() + const uptimeString = secondsToDhms(uptime) + message += uptimeString - if (lag > 0) { + if (lag >= 1) { + message += '\n\n```━━━━━━━━━━ ⚠️ ━━━━━━━━━━```\n\n' const lagString = convertToHumanReadable(lag, 0, 's') - message += `\n\nO WhatsApp demorou *${lagString}* para entregar essa mensagem pra mim!` + message += `O WhatsApp demorou *${lagString}* para entregar essa mensagem pra mim!` } await msg.reply(spintax(message)) @@ -328,14 +334,14 @@ function secondsToDhms (seconds) { // add suffixe "dia" or "dias" if days > 0 and singular or plural // "hora" or "horas" if hours > 0 and singular or plural etc... - const days = d > 0 ? `${d === 1 ? 'dia' : 'dias'}` : '' - const hours = h > 0 ? `${h === 1 ? 'hora' : 'horas'}` : '' - const minutes = m > 0 ? `${m === 1 ? 'minuto' : 'minutos'}` : '' - const secondsString = `${s === 1 ? 'segundo' : 'segundos'}` - // from left to right, get the first non empty string - const array = [days, hours, minutes, secondsString].filter(s => s !== '') - const suffix = array[0] - string += ` ${suffix}` + // const days = d > 0 ? `${d === 1 ? 'dia' : 'dias'}` : '' + // const hours = h > 0 ? `${h === 1 ? 'hora' : 'horas'}` : '' + // const minutes = m > 0 ? `${m === 1 ? 'minuto' : 'minutos'}` : '' + // const secondsString = `${s === 1 ? 'segundo' : 'segundos'}` + // // from left to right, get the first non empty string + // const array = [days, hours, minutes, secondsString].filter(s => s !== '') + // const suffix = array[0] + // string += ` ${suffix}` return string } From 788096459752b07eed5f7011dd7ee25cdc5fd555 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 19:47:37 -0300 Subject: [PATCH 76/79] chore: add types --- src/services/queue.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/services/queue.js b/src/services/queue.js index e5ffe81..44e311e 100644 --- a/src/services/queue.js +++ b/src/services/queue.js @@ -201,12 +201,25 @@ async function wait (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } +/** + * Wait for a random amount of time and process the queue + * @param {number} min + * @param {number} max + * @returns {Promise} + */ async function waitAndProcessQueue (min = 1000, max = 3000) { const waitTime = Math.floor(Math.random() * (max - min + 1)) + min await wait(waitTime) processQueue() } +/** + * Execute the queue item + * @param {string} moduleName + * @param {string} functionName + * @param {Message} msg + * @returns {Promise} + */ export async function executeQueueItem (moduleName, functionName, msg) { // console.log('Executing queue item', moduleName, functionName, msg) await msg.sendSeen() From eabb8ec640118936775ba7de12d76e087344d516 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 22:47:15 -0300 Subject: [PATCH 77/79] fix: use global in the right way --- src/index.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index 996a15c..ef22b7b 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,6 @@ import logger from './logger.js' import * as db from './db.js' import fs from 'fs/promises' import pino from 'pino' -let globalArgs = {} const main = defineCommand({ meta: { @@ -46,7 +45,7 @@ const main = defineCommand({ } }, run ({ args }) { - globalArgs = args + global.args = args bot.name = args.name logger.info(`Starting bot "${args.name}"`) bot.useStore = !args['no-store'] @@ -82,14 +81,6 @@ const main = defineCommand({ export const store = undefined runMain(main) -/** - * Grabs CLI args - * @returns {object} - */ -export function getArgs () { - return globalArgs -} - /** * Grabs the socket * @returns {import('./types').WSocket} From 057e8d2367906e5a6f2266719280ecd7961e334c Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 22:47:37 -0300 Subject: [PATCH 78/79] fix: disable zero --- src/services/functions/miscellaneous.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/functions/miscellaneous.js b/src/services/functions/miscellaneous.js index 10e8efa..eb532f8 100644 --- a/src/services/functions/miscellaneous.js +++ b/src/services/functions/miscellaneous.js @@ -193,7 +193,7 @@ export async function ping (msg) { // message += '📶 - Velocidade atual: 0\n' // message += '👥 - Usuários esta semana: 0\n' // message += '💬 - Chats na última hora: 0\n' - message += '📩 - Mensagens na última hora: 0\n' + // message += '📩 - Mensagens na última hora: 0\n' message += '🕒 - Online direto há: ' const uptime = process.uptime() From 7b16e0d6da4632e0b307182bf988b8c490033990 Mon Sep 17 00:00:00 2001 From: sergiooak Date: Tue, 13 Feb 2024 22:47:51 -0300 Subject: [PATCH 79/79] fix: temp do not send online --- src/services/events/messagesUpsert.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/events/messagesUpsert.js b/src/services/events/messagesUpsert.js index 1f0e481..f488cdd 100644 --- a/src/services/events/messagesUpsert.js +++ b/src/services/events/messagesUpsert.js @@ -34,8 +34,8 @@ export default async (upsert) => { return await msg.react('✏️') } - const socket = getSocket() - await socket.sendPresenceUpdate('available') + // const socket = getSocket() + // await socket.sendPresenceUpdate('available') const messageParser = await importFresh('validators/message.js') const handlerModule = await messageParser.default(msg) logger.trace('handlerModule: ', handlerModule)