From 6f59128768c47db2fcc8549354fa22400e132c4f Mon Sep 17 00:00:00 2001 From: dthbot Date: Fri, 26 Dec 2025 01:26:21 +0100 Subject: [PATCH 01/43] Update config.js --- config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config.js b/config.js index adce8b3e9..647b1dec5 100644 --- a/config.js +++ b/config.js @@ -5,18 +5,18 @@ import { fileURLToPath } from 'url'; global.botnumber = ''; global.confirmCode = ''; -global.nomebot = '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ-๐๐จ๐ญ'; -global.packname = '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ-๐๐จ๐ญ'; +global.nomebot = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; +global.packname = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; global.author = '๐Œ๐'; -global.vs = '8.8'; +global.vs = '1.0'; global.collab = 'Demon Slayer'; global.wm = global.nomebot; global.wait = 'โ“˜ ๐‚๐š๐ซ๐ข๐œ๐š๐ฆ๐ž๐ง๐ญ๐จ ...'; global.owner = [ - ['393773842461', '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ', true], - ['xxxxxxxxxx'], //mettete il vostro numero al posto delle x e copiate sopra il formato dopo ovvero 'nome', true + ['21278080331', 'Blood', true], + ['19782772696', 'Bot', true], ['xxxxxxxxxx'], ['xxxxxxxxxx'], ['xxxxxxxxxx'], From 201898ef133c47b94cc02b870f70cc9779d8723d Mon Sep 17 00:00:00 2001 From: dthbot Date: Fri, 26 Dec 2025 13:13:43 +0100 Subject: [PATCH 02/43] Update config.js --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index 647b1dec5..b27935cd4 100644 --- a/config.js +++ b/config.js @@ -15,7 +15,7 @@ global.wait = 'โ“˜ ๐‚๐š๐ซ๐ข๐œ๐š๐ฆ๐ž๐ง๐ญ๐จ ...'; global.owner = [ - ['21278080331', 'Blood', true], + ['212780803311', 'Blood', true], ['19782772696', 'Bot', true], ['xxxxxxxxxx'], ['xxxxxxxxxx'], From 199d3fd63906091853c84431fe41d41cb5668c8a Mon Sep 17 00:00:00 2001 From: dthbot Date: Fri, 26 Dec 2025 13:15:21 +0100 Subject: [PATCH 03/43] Create fun-velith.js --- plugins/fun-velith.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 plugins/fun-velith.js diff --git a/plugins/fun-velith.js b/plugins/fun-velith.js new file mode 100644 index 000000000..0c79e438a --- /dev/null +++ b/plugins/fun-velith.js @@ -0,0 +1,14 @@ +// plugin fatto da Death +let handler = async (m, { conn, command, text }) => { + const message = `๐•๐•–๐•๐•š๐•ฅ๐•™ รฉ ๐•๐•’ ๐•ž๐• ๐•˜๐•๐•š๐•– ๐••๐•š ๐”น๐•๐• ๐• ๐••, ๐•š๐•Ÿ๐•ฅ๐• ๐•”๐•”๐•’๐•“๐•š๐•๐•– ๐•ค๐• ๐•ฅ๐•ฅ๐•  ๐•ฅ๐•ฆ๐•ฅ๐•ฅ๐•š ๐•š ๐•ก๐•ฆ๐•Ÿ๐•ฅ๐•š ๐••๐•š ๐•ง๐•š๐•ค๐•ฅ๐•’. +๐•„๐•–๐•˜๐•๐•š๐•  ๐•ก๐•–๐•ฃ ๐•ง๐• ๐•š ๐•ค๐•ฅ๐•’๐•ฃ๐•– ๐•๐• ๐•Ÿ๐•ฅ๐•’๐•Ÿ๐•š ๐•ก๐•–๐•ฃ๐•”๐•™รฉ ๐”น๐•๐• ๐• ๐•• ๐•ง๐•š ๐••๐•š๐•ค๐•ฅ๐•ฃ๐•ฆ๐•˜๐•˜๐•– ๐•ค๐•–๐•Ÿ๐•ซ๐•’ ๐•ก๐•š๐•–๐•ฅร . +๐•†๐•”๐•”๐•™๐•š๐•  ๐•”๐•™๐•– ๐•ค๐•– ๐•๐•’ ๐•ฅ๐• ๐•”๐•”๐•’๐•ฅ๐•– ๐”น๐•๐• ๐• ๐•• ๐•Ÿ๐• ๐•Ÿ ๐•˜๐•ฆ๐•’๐•ฃ๐••๐•’ ๐•š๐•Ÿ ๐•—๐•’๐•”๐•”๐•š๐•’ ๐•Ÿ๐•–๐•ค๐•ค๐•ฆ๐•Ÿ๐• .`; + // manda il messaggio nella chat dove il comando รจ stato usato, citandolo + await conn.sendMessage(m.chat, { text: message }, { quoted: m }); +}; + +handler.help = ['velith']; +handler.tags = ['fun']; +handler.command = /^velith|mogliediblood$/i; + +export default handler; \ No newline at end of file From 342f7b247d59165aeb7c0c8d5fde00c71c42f751 Mon Sep 17 00:00:00 2001 From: dthbot Date: Fri, 26 Dec 2025 13:15:42 +0100 Subject: [PATCH 04/43] Create fun-blood.js --- plugins/fun-blood.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 plugins/fun-blood.js diff --git a/plugins/fun-blood.js b/plugins/fun-blood.js new file mode 100644 index 000000000..3ed54a554 --- /dev/null +++ b/plugins/fun-blood.js @@ -0,0 +1,13 @@ +// plugin fatto da Death +let handler = async (m, { conn, command, text }) => { + const message = `๐”น๐•๐• ๐• ๐•• รฉ ๐•š๐• ๐•”๐•’๐•ฅ๐•’๐•Ÿ๐•–๐•ค๐•– ๐•ก๐•šรน ๐•ค๐•š๐•ž๐•ก๐•’๐•ฅ๐•š๐•”๐•  ๐••๐•–๐•๐•๐•– ๐•”๐• ๐•ž๐•ž, ๐•š๐• ๐•ž๐•š๐•  ๐•ž๐•š๐•˜๐•๐•š๐• ๐•ฃ๐•– ๐•’๐•ž๐•š๐•”๐•  ๐•– ๐•š๐• ๐•ž๐•’๐•ฃ๐•š๐•ฅ๐•  ๐••๐•š ๐•๐•–๐•๐•š๐•ฅ๐•™. +โ„•๐• ๐•Ÿ ๐•๐•  ๐•—๐•’๐•ฅ๐•– ๐•š๐•Ÿ๐•”๐•’๐•ซ๐•ซ๐•’๐•ฃ๐•– ๐•  ๐•ง๐•š ๐•ค๐•’๐•๐•ฅ๐•’๐•Ÿ๐•  ๐•š ๐•Ÿ๐•ฆ๐•ž๐•–๐•ฃ๐•š ๐•– ๐•ก๐•’๐•ฃ๐•ฅ๐• ๐•Ÿ๐•  ๐•š ๐••๐• ๐•ฉ๐•ฉ ๐••๐• ๐•ง๐•– ๐•ง๐•š ๐•ก๐•ฃ๐•–๐•Ÿ๐••๐•– ๐•ก๐•ฆ๐•ฃ๐•– ๐•š ๐•ก๐•–๐•๐•š ๐••๐•–๐• ๐•”๐•ฆ๐•๐• .`; + // manda il messaggio nella chat dove il comando รจ stato usato, citandolo + await conn.sendMessage(m.chat, { text: message }, { quoted: m }); +}; + +handler.help = ['blood']; +handler.tags = ['fun']; +handler.command = /^blood|maritodivelith$/i; + +export default handler; \ No newline at end of file From 83b199c8b6fe1a3a35ec7baa334543916d25db13 Mon Sep 17 00:00:00 2001 From: BLOOD212 Date: Thu, 8 Jan 2026 15:19:46 +0100 Subject: [PATCH 05/43] Create nuke.js --- plugins/nuke.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 plugins/nuke.js diff --git a/plugins/nuke.js b/plugins/nuke.js new file mode 100644 index 000000000..99f87976e --- /dev/null +++ b/plugins/nuke.js @@ -0,0 +1,36 @@ +let handler = async (m, { conn, args, groupMetadata, participants, usedPrefix, command, isBotAdmin, isSuperAdmin }) => { + let ps = participants.map(u => u.id).filter(v => v !== conn.user.jid); + let bot = global.db.data.settings[conn.user.jid] || {}; + if (ps == '') return; + const delay = time => new Promise(res => setTimeout(res, time)); + + switch (command) { + case "pugnala": + if (!bot.restrict) return; + if (!isBotAdmin) return; + + global.db.data.chats[m.chat].welcome = false; + + await conn.sendMessage(m.chat, { + text: "๐๐ฅ๐จ๐จ๐ ๐žฬ€ ๐š๐ซ๐ซ๐ข๐ฏ๐š๐ญ๐จ ๐ข๐ง ๐œ๐ข๐ซ๐œ๐จ๐ฅ๐š๐ณ๐ข๐จ๐ง๐ž, ๐ž ๐ช๐ฎ๐ž๐ฌ๐ญ๐จ ๐ฌ๐ข๐ ๐ง๐ข๐Ÿ๐ข๐œ๐š ๐ฌ๐จ๐ฅ๐จ ๐ฎ๐ง๐š ๐œ๐จ๐ฌ๐š, ๐ƒ๐„๐•๐€๐’๐“๐Ž. ๐ˆ๐ฅ ๐๐ž๐ฏ๐š๐ฌ๐ญ๐จ ๐œ๐ก๐ž ๐š๐ฆ๐ฆ๐š๐ณ๐ณ๐ž๐ซ๐šฬ€ ๐ญ๐ฎ๐ญ๐ญ๐ข ๐ฉ๐ซ๐จ๐ฉ๐ซ๐ข๐จ ๐œ๐จ๐ฆ๐ž ๐ฎ๐ง๐š ๐ฉ๐ฎ๐ ๐ง๐š๐ฅ๐š๐ญ๐š, ๐ฉ๐ซ๐จ๐ฉ๐ซ๐ข๐จ ๐ช๐ฎ๐ž๐ฅ๐ฅ๐š ๐œ๐ก๐ž ๐ฏ๐ข ๐๐š๐ซ๐šฬ€." + }); + let utenti = participants.map(u => u.id); + await conn.sendMessage(m.chat, { + text: '๐€๐ฏ๐ž๐ญ๐ž ๐š๐ฏ๐ฎ๐ญ๐จ ๐ฅ\' ๐จ๐ง๐จ๐ซ๐ž ๐๐ข ๐ž๐ฌ๐ฌ๐ž๐ซ๐ž ๐ฌ๐ญ๐š๐ญ๐ข ๐ฉ๐ฎ๐ ๐ง๐š๐ฅ๐š๐ญ๐ข ๐๐š ๐๐ฅ๐จ๐จ๐, ๐ฏ๐ข ๐š๐ฌ๐ฉ๐ž๐ญ๐ญ๐ข๐š๐ฆ๐จ ๐ญ๐ฎ๐ญ๐ญ๐ข ๐ช๐ฎ๐š:\n\nhttps://chat.whatsapp.com/GReeEoOxlOxCVBBCyXJuEj?mode=ems_copy_t', + mentions: utenti + }); + + let users = ps; + if (isBotAdmin && bot.restrict) { + await delay(1); + await conn.groupParticipantsUpdate(m.chat, users, 'remove'); + } else return; + break; + } +}; + +handler.command = /^(pugnala)$/i; +handler.group = true; +handler.owner = true; +handler.fail = null; +export default handler; From d48408a5bac7b047301acc2a33f44692ee847dfd Mon Sep 17 00:00:00 2001 From: dthbot Date: Sat, 10 Jan 2026 22:38:47 +0100 Subject: [PATCH 06/43] Update handler.js --- handler.js | 1149 ++++++++++++++++++---------------------------------- 1 file changed, 389 insertions(+), 760 deletions(-) diff --git a/handler.js b/handler.js index c5404757a..98fda49a4 100644 --- a/handler.js +++ b/handler.js @@ -1,796 +1,425 @@ - -process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '1'; -import './config.js'; -import { createRequire } from 'module'; -import path, { join } from 'path'; -import { fileURLToPath, pathToFileURL } from 'url'; -import { platform } from 'process'; -import fs, { readdirSync, statSync, unlinkSync, existsSync, mkdirSync, rmSync, watch } from 'fs'; -import yargs from 'yargs'; -import { spawn } from 'child_process'; -import lodash from 'lodash'; -import chalk from 'chalk'; -import syntaxerror from 'syntax-error'; -import { tmpdir } from 'os'; -import { format } from 'util'; -import pino from 'pino'; -import { makeWASocket, protoType, serialize } from './lib/simple.js'; -import { Low, JSONFile } from 'lowdb'; -import readline from 'readline'; -import NodeCache from 'node-cache'; - -const sessionFolder = path.join(process.cwd(), global.authFile || 'sessioni'); -const tempDir = join(process.cwd(), 'temp'); -const tmpDir = join(process.cwd(), 'tmp'); - -if (!existsSync(tempDir)) { - mkdirSync(tempDir, { recursive: true }); -} -if (!existsSync(tmpDir)) { - mkdirSync(tmpDir, { recursive: true }); -} - -let stopped = 'open'; - -function clearSessionFolderSelective(dir = sessionFolder) { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - return; - } - const entries = fs.readdirSync(dir); - for (const entry of entries) { - const fullPath = path.join(dir, entry); - if (entry === 'creds.json') continue; - const stat = fs.statSync(fullPath); - if (stat.isDirectory()) { - clearSessionFolderSelective(fullPath); - fs.rmdirSync(fullPath); - } else { - if (!entry.startsWith('pre-key')) { - try { - fs.unlinkSync(fullPath); - } catch {} - } - } - } - console.log(`Cartella sessioni pulita (file non critici rimossi): ${new Date().toLocaleTimeString()}`); -} - -function purgeSession(sessionDir, cleanPreKeys = false) { - if (!existsSync(sessionDir)) return; - const files = readdirSync(sessionDir); - files.forEach(file => { - if (file === 'creds.json') return; - const filePath = path.join(sessionDir, file); - const stats = statSync(filePath); - const fileAge = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24); - if (file.startsWith('pre-key') && cleanPreKeys) { - if (fileAge > 1) { - try { - unlinkSync(filePath); - } catch {} - } - } else if (!file.startsWith('pre-key')) { - try { - if (stats.isDirectory()) { - rmSync(filePath, { recursive: true, force: true }); - } else { - unlinkSync(filePath); - } - } catch {} +import { smsg } from './lib/simple.js' +import { format } from 'util' +import { fileURLToPath } from 'url' +import path, { join } from 'path' +import { unwatchFile, watchFile } from 'fs' +import fs from 'fs' +import chalk from 'chalk' +const { proto } = (await import('@chatunity/baileys')).default + +const isNumber = x => typeof x === 'number' && !isNaN(x) +const delay = ms => isNumber(ms) && new Promise(resolve => setTimeout(function () { + clearTimeout(this) + resolve() +}, ms)) + +global.ignoredUsersGlobal = global.ignoredUsersGlobal || new Set() +global.ignoredUsersGroup = global.ignoredUsersGroup || {} +global.groupSpam = global.groupSpam || {} + +export async function handler(chatUpdate) { + if (!global.db.data.stats) global.db.data.stats = {} + const stats = global.db.data.stats + + this.msgqueque = this.msgqueque || [] + if (!chatUpdate) return + this.pushMessage(chatUpdate.messages).catch(console.error) + let m = chatUpdate.messages[chatUpdate.messages.length - 1] + if (!m) return + if (global.db.data == null) await global.loadDatabase() + + const isOwner = (() => { + try { + const isROwner = [conn.decodeJid(global.conn.user.id), ...global.owner.map(([number]) => number)] + .filter(Boolean) + .map(v => v.replace(/[^0-9]/g, '') + '@s.whatsapp.net') + .includes(m.sender) + return isROwner || m.fromMe + } catch { + return false } - }); -} - -setInterval(async () => { - if (stopped === 'close' || !global.conn || !global.conn.user) return; - clearSessionFolderSelective(); -}, 30 * 60 * 1000); - -setInterval(async () => { - if (stopped === 'close' || !global.conn || !global.conn.user) return; - purgeSession(`./sessioni`); - const subBotDir = `./${global.authFileJB}`; - if (existsSync(subBotDir)) { - const subBotFolders = readdirSync(subBotDir).filter(file => statSync(join(subBotDir, file)).isDirectory()); - subBotFolders.forEach(folder => purgeSession(join(subBotDir, folder))); + })() + + const hasValidPrefix = (text, prefixes) => { + if (!text || typeof text !== 'string') return false + if (prefixes instanceof RegExp) return prefixes.test(text) + const prefixList = Array.isArray(prefixes) ? prefixes : [prefixes] + return prefixList.some(p => { + if (p instanceof RegExp) return p.test(text) + if (typeof p === 'string') return text.startsWith(p) + return false + }) } -}, 20 * 60 * 1000); - -setInterval(async () => { - if (stopped === 'close' || !global.conn || !global.conn.user) return; - purgeSession(`./${global.authFile}`, true); - const subBotDir = `./${global.authFileJB}`; - if (existsSync(subBotDir)) { - const subBotFolders = readdirSync(subBotDir).filter(file => statSync(join(subBotDir, file)).isDirectory()); - subBotFolders.forEach(folder => purgeSession(join(subBotDir, folder), true)); - } -}, 3 * 60 * 60 * 1000); - -const { useMultiFileAuthState, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, Browsers, jidNormalizedUser, makeInMemoryStore, DisconnectReason } = await import('@chatunity/baileys'); -const { chain } = lodash; -const PORT = process.env.PORT || process.env.SERVER_PORT || 3000; -protoType(); -serialize(); - -global.isLogoPrinted = false; -global.qrGenerated = false; -global.connectionMessagesPrinted = {}; -let methodCodeQR = process.argv.includes("qr"); -let methodCode = process.argv.includes("code"); -let MethodMobile = process.argv.includes("mobile"); -let phoneNumber = global.botNumberCode; - -function generateRandomCode(length = 8) { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; -} - -function redefineConsoleMethod(methodName, filterStrings) { - const originalConsoleMethod = console[methodName]; - console[methodName] = function () { - const message = arguments[0]; - if (typeof message === 'string' && filterStrings.some(filterString => message.includes(atob(filterString)))) { - arguments[0] = ""; - } - originalConsoleMethod.apply(console, arguments); - }; -} - -global.__filename = function filename(pathURL = import.meta.url, rmPrefix = platform !== 'win32') { - return rmPrefix ? /file:\/\/\//.test(pathURL) ? fileURLToPath(pathURL) : pathURL : pathToFileURL(pathURL).toString(); -}; - -global.__dirname = function dirname(pathURL) { - return path.dirname(global.__filename(pathURL, true)); -}; - -global.__require = function require(dir = import.meta.url) { - return createRequire(dir); -}; - -global.API = (name, path = '/', query = {}, apikeyqueryname) => (name in global.APIs ? global.APIs[name] : name) + path + (query || apikeyqueryname ? '?' + new URLSearchParams(Object.entries({ ...query, ...(apikeyqueryname ? { [apikeyqueryname]: global.APIKeys[name in global.APIs ? global.APIs[name] : name] } : {}) })) : ''); -global.timestamp = { start: new Date }; -const __dirname = global.__dirname(import.meta.url); -global.opts = new Object(yargs(process.argv.slice(2)).exitProcess(false).parse()); -global.prefix = new RegExp('^[' + (opts['prefix'] || '*/!#$%+ยฃยขโ‚ฌยฅ^ยฐ=ยถโˆ†ร—รทฯ€โˆšโœ“ยฉยฎ&.\\-.@').replace(/[|\\{}()[\]^$+*.\-\^]/g, '\\$&') + ']'); -global.db = new Low(/https?:\/\//.test(opts['db'] || '') ? new cloudDBAdapter(opts['db']) : new JSONFile('database.json')); -global.DATABASE = global.db; -global.loadDatabase = async function loadDatabase() { - if (global.db.READ) { - return new Promise((resolve) => setInterval(async function () { - if (!global.db.READ) { - clearInterval(this); - resolve(global.db.data == null ? global.loadDatabase() : global.db.data); + + if ( + m.isGroup && + !isOwner && + typeof m.text === 'string' && + hasValidPrefix(m.text, conn.prefix || global.prefix) + ) { + const now = Date.now() + const chatId = m.chat + + if (!global.groupSpam[chatId]) { + global.groupSpam[chatId] = { + count: 0, + firstCommandTimestamp: now, + isSuspended: false, + suspendedUntil: null } - }, 1 * 1000)); - } - if (global.db.data !== null) return; - global.db.READ = true; - await global.db.read().catch(console.error); - global.db.READ = null; - global.db.data = { - users: {}, - chats: {}, - stats: {}, - msgs: {}, - sticker: {}, - settings: {}, - ...(global.db.data || {}), - }; - global.db.chain = chain(global.db.data); -}; -loadDatabase(); - -if (global.conns instanceof Array) { - console.log('Connessioni giร  inizializzate...'); -} else { - global.conns = []; -} - -global.creds = 'creds.json'; -global.authFile = 'sessioni'; -global.authFileJB = 'chatunity-sub'; - -const { state, saveCreds } = await useMultiFileAuthState(global.authFile); -const msgRetryCounterMap = (MessageRetryMap) => { }; -const msgRetryCounterCache = new NodeCache(); -const { version } = await fetchLatestBaileysVersion(); -let rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: true, -}); - -const question = (t) => { - rl.clearLine(rl.input, 0); - return new Promise((resolver) => { - rl.question(t, (r) => { - rl.clearLine(rl.input, 0); - resolver(r.trim()); - }); - }); -}; - -let opzione; -if (!methodCodeQR && !methodCode && !fs.existsSync(`./${global.authFile}/creds.json`)) { - do { - const menu = `โ•ญโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… -โ”‚ ๊’ฐ ยกMETODO DI COLLEGAMENTO! ๊’ฑ -โ”‚ -โ”‚ ๐Ÿ‘พ Opzione 1: Codice QR -โ”‚ โ˜๏ธ Opzione 2: Codice 8 caratteri -โ”‚ -โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… - ๊’ท๊’ฆ โœฆ ChatUnity โœฆ ๊’ท๊’ฆ -โ•ฐโ™ก๊’ท เน‘ โ‹†หšโ‚Šโ‹†โ”€โ”€โ”€สšหšษžโ”€โ”€โ”€โ‹†หšโ‚Šโ‹† เน‘ โชฉ๏น -`; - opzione = await question(menu + '\nInserisci la tua scelta ---> '); - if (!/^[1-2]$/.test(opzione)) { - console.log('Opzione non valida, inserisci 1 o 2'); - } - } while ((opzione !== '1' && opzione !== '2') || fs.existsSync(`./${global.authFile}/creds.json`)); -} - -const filterStrings = [ - "Q2xvc2luZyBzdGFsZSBvcGVu", - "Q2xvc2luZyBvcGVuIHNlc3Npb24=", - "RmFpbGVkIHRvIGRlY3J5cHQ=", - "U2Vzc2lvbiBlcnJvcg==", - "RXJyb3I6IEJhZCBNQUM=", - "RGVjcnlwdGVkIG1lc3NhZ2U=" -]; -console.info = () => { }; -console.debug = () => { }; -['log', 'warn', 'error'].forEach(methodName => redefineConsoleMethod(methodName, filterStrings)); - -const groupMetadataCache = new NodeCache({ stdTTL: 300, checkperiod: 60, maxKeys: 500 }); -global.groupCache = groupMetadataCache; - -const logger = pino({ - level: 'silent', - redact: { - paths: [ - 'creds.*', - 'auth.*', - 'account.*', - 'media.*.directPath', - 'media.*.url', - 'node.content[*].enc', - 'password', - 'token', - '*.secret' - ], - censor: '***' - }, - timestamp: () => `,"time":"${new Date().toJSON()}"` -}); - -global.jidCache = new NodeCache({ stdTTL: 600, useClones: false, maxKeys: 1000 }); -global.store = makeInMemoryStore({ logger }); - -const connectionOptions = { - logger: logger, - printQRInTerminal: opzione === '1' || methodCodeQR, - mobile: MethodMobile, - auth: { - creds: state.creds, - keys: makeCacheableSignalKeyStore(state.keys, logger), - }, - browser: opzione === '1' ? Browsers.windows('Chrome') : methodCodeQR ? Browsers.windows('Chrome') : Browsers.macOS('Safari'), - version: version, - markOnlineOnConnect: false, - generateHighQualityLinkPreview: true, - syncFullHistory: false, - linkPreviewImageThumbnailWidth: 192, - getMessage: async (key) => { - try { - const jid = global.conn?.decodeJid(key.remoteJid); - const msg = await global.store.loadMessage(jid, key.id); - return msg?.message || undefined; - } catch (error) { - return undefined; } - }, - defaultQueryTimeoutMs: 60000, - connectTimeoutMs: 60000, - keepAliveIntervalMs: 30000, - emitOwnEvents: true, - fireInitQueries: true, - transactionOpts: { - maxCommitRetries: 10, - delayBetweenTriesMs: 3000 - }, - cachedGroupMetadata: async (jid) => { - const cached = global.groupCache.get(jid); - if (cached) return cached; - try { - const metadata = await global.conn?.groupMetadata(global.conn.decodeJid(jid)); - global.groupCache.set(jid, metadata); - return metadata; - } catch (err) { - return {}; - } - }, - decodeJid: (jid) => { - if (!jid) return jid; - const cached = global.jidCache.get(jid); - if (cached) return cached; - - let decoded = jid; - if (/:\d+@/gi.test(jid)) { - decoded = jidNormalizedUser(jid); - } - if (typeof decoded === 'object' && decoded.user && decoded.server) { - decoded = `${decoded.user}@${decoded.server}`; + + const groupData = global.groupSpam[chatId] + if (groupData.isSuspended) { + if (now < groupData.suspendedUntil) return + groupData.isSuspended = false + groupData.count = 0 + groupData.firstCommandTimestamp = now + groupData.suspendedUntil = null } - if (typeof decoded === 'string' && decoded.endsWith('@lid')) { - decoded = decoded.replace('@lid', '@s.whatsapp.net'); + if (now - groupData.firstCommandTimestamp > 60000) { + groupData.count = 1 + groupData.firstCommandTimestamp = now + } else { + groupData.count++ } + if (groupData.count > 2) { + groupData.isSuspended = true + groupData.suspendedUntil = now + 45000 - global.jidCache.set(jid, decoded); - return decoded; - }, - msgRetryCounterCache, - msgRetryCounterMap, - retryRequestDelayMs: 250, - maxMsgRetryCount: 3, - shouldIgnoreJid: jid => false, - patchMessageBeforeSending: (message) => { - const requiresPatch = !!( - message.buttonsMessage || - message.templateMessage || - message.listMessage - ); - if (requiresPatch) { - message = { - viewOnceMessage: { - message: { - messageContextInfo: { - deviceListMetadata: {}, - deviceListMetadataVersion: 2 - }, - ...message - } - } - }; - } - return message; - } -}; - -global.conn = makeWASocket(connectionOptions); -global.store.bind(global.conn.ev); - -if (!fs.existsSync(`./${global.authFile}/creds.json`)) { - if (opzione === '2' || methodCode) { - opzione = '2'; - if (!global.conn.authState.creds.registered) { - let addNumber; - if (phoneNumber) { - addNumber = phoneNumber.replace(/[^0-9]/g, ''); - } else { - phoneNumber = await question(chalk.bgBlack(chalk.bold.bgMagentaBright(`Inserisci il numero di WhatsApp.\n${chalk.bold.yellowBright("Esempio: +393471234567")}\n${chalk.bold.magenta('PS: รจ normale che appare il qrcode incollate comunque il numero')}`))); - addNumber = phoneNumber.replace(/\D/g, ''); - if (!phoneNumber.startsWith('+')) phoneNumber = `+${phoneNumber}`; - rl.close(); - } - setTimeout(async () => { - let codeBot = await global.conn.requestPairingCode(addNumber); - codeBot = codeBot?.match(/.{1,4}/g)?.join("-") || codeBot; - console.log(chalk.bold.white(chalk.bgBlueBright('๊’ฐ๐Ÿฉธ๊’ฑ โ—ฆโ€ขโ‰ซ CODICE DI COLLEGAMENTO:')), chalk.bold.white(chalk.white(codeBot))); - }, 3000); + await conn.sendMessage(chatId, { + text: `ใ€Ž โš  ใ€ Anti-spam comandi\n\nTroppi comandi in poco tempo!\nAttendi *45 secondi* prima di usare altri comandi.\n\n> sviluppato da sam aka vare`, + mentions: [m.sender] + }) + return } } -} -global.conn.isInit = false; -global.conn.well = false; - -// DEFINIZIONE DI reloadHandler CON CONTROLLI COMPLETI -global.reloadHandler = async function (restatConn) { try { - const Handler = await import(`./handler.js?update=${Date.now()}`).catch(console.error); - if (Object.keys(Handler || {}).length) { - global.handler = Handler; - } - } catch (e) { - console.error(chalk.red('โŒ Errore nel caricamento handler:'), e); - return false; - } - - if (restatConn && global.conn) { - const oldChats = global.conn.chats; + m = smsg(this, m) || m + if (!m) return + m.exp = 0 + m.limit = false + try { - global.conn.ws.close(); - } catch { } - global.conn.ev.removeAllListeners(); - global.conn = makeWASocket(connectionOptions, { chats: oldChats }); - global.store.bind(global.conn.ev); - global.conn.isInit = true; - } - - if (global.conn && global.handler) { - // RIMUOVI VECCHI LISTENER CON CONTROLLI - if (typeof global.conn.handler === 'function') { - global.conn.ev.off('messages.upsert', global.conn.handler); - } - if (typeof global.conn.participantsUpdate === 'function') { - global.conn.ev.off('group-participants.update', global.conn.participantsUpdate); - } - if (typeof global.conn.groupsUpdate === 'function') { - global.conn.ev.off('groups.update', global.conn.groupsUpdate); - } - if (typeof global.conn.onDelete === 'function') { - global.conn.ev.off('message.delete', global.conn.onDelete); - } - if (typeof global.conn.onCall === 'function') { - global.conn.ev.off('call', global.conn.onCall); - } - if (typeof global.conn.connectionUpdate === 'function') { - global.conn.ev.off('connection.update', global.conn.connectionUpdate); - } - if (typeof global.conn.credsUpdate === 'function') { - global.conn.ev.off('creds.update', global.conn.credsUpdate); - } + let user = global.db.data.users[m.sender] + if (typeof user !== 'object') global.db.data.users[m.sender] = {} + + if (user) { + if (!isNumber(user.messaggi)) user.messaggi = 0 + if (!isNumber(user.blasphemy)) user.blasphemy = 0 + if (!isNumber(user.exp)) user.exp = 0 + if (!isNumber(user.money)) user.money = 0 + if (!isNumber(user.warn)) user.warn = 0 + if (!isNumber(user.joincount)) user.joincount = 2 + if (!('premium' in user)) user.premium = false + if (!isNumber(user.premiumDate)) user.premiumDate = -1 + if (!('name' in user)) user.name = m.name + if (!('muto' in user)) user.muto = false + } else { + global.db.data.users[m.sender] = { + messaggi: 0, + blasphemy: 0, + exp: 0, + money: 0, + warn: 0, + joincount: 2, + limit: 15000, + premium: false, + premiumDate: -1, + name: m.name, + muto: false + } + } - // IMPOSTA MESSAGGI - global.conn.welcome = '@user benvenuto/a in @subject'; - global.conn.bye = '@user ha abbandonato il gruppo'; - global.conn.spromote = '@user รจ stato promosso ad amministratore'; - global.conn.sdemote = '@user non รจ piรน amministratore'; - global.conn.sIcon = 'immagine gruppo modificata'; - global.conn.sRevoke = 'link reimpostato, nuovo link: @revoke'; - - // BIND NUOVI HANDLER - global.conn.handler = global.handler.handler.bind(global.conn); - global.conn.participantsUpdate = global.handler.participantsUpdate.bind(global.conn); - global.conn.groupsUpdate = global.handler.groupsUpdate.bind(global.conn); - global.conn.onDelete = global.handler.deleteUpdate.bind(global.conn); - global.conn.onCall = global.handler.callUpdate.bind(global.conn); - global.conn.connectionUpdate = connectionUpdate.bind(global.conn); - global.conn.credsUpdate = saveCreds.bind(global.conn, true); - - // REGISTRA NUOVI LISTENER - global.conn.ev.on('messages.upsert', global.conn.handler); - global.conn.ev.on('group-participants.update', global.conn.participantsUpdate); - global.conn.ev.on('groups.update', global.conn.groupsUpdate); - global.conn.ev.on('message.delete', global.conn.onDelete); - global.conn.ev.on('call', global.conn.onCall); - global.conn.ev.on('connection.update', global.conn.connectionUpdate); - global.conn.ev.on('creds.update', global.conn.credsUpdate); - - console.log(chalk.green('โœ… Handler registrato con successo!')); - global.conn.isInit = false; - } - return true; -}; + let chat = global.db.data.chats[m.chat] + if (typeof chat !== 'object') global.db.data.chats[m.chat] = {} + + if (chat) { + if (!('isBanned' in chat)) chat.isBanned = false + if (!('detect' in chat)) chat.detect = true + if (!('delete' in chat)) chat.delete = false + if (!('antiLink' in chat)) chat.antiLink = true + if (!('antiTraba' in chat)) chat.antiTraba = true + if (!isNumber(chat.expired)) chat.expired = 0 + if (!isNumber(chat.messaggi)) chat.messaggi = 0 + if (!('name' in chat)) chat.name = this.getName(m.chat) + if (!('antispamcomandi' in chat)) chat.antispamcomandi = true + if (!('welcome' in chat)) chat.welcome = true + } else { + global.db.data.chats[m.chat] = { + name: this.getName(m.chat), + isBanned: false, + detect: true, + delete: false, + antiLink: true, + antiTraba: true, + expired: 0, + messaggi: 0, + antispamcomandi: true, + welcome: true + } + } -async function chatunityedition() { - try { - const mainChannelId = global.IdCanale?.[0] || '120363259442839354@newsletter'; - await global.conn.newsletterFollow(mainChannelId); - } catch (error) {} -} - -// Funzione connectionUpdate definita QUI -async function connectionUpdate(update) { - const { connection, lastDisconnect, isNewLogin, qr } = update; - global.stopped = connection; - if (isNewLogin) global.conn.isInit = true; - const code = lastDisconnect?.error?.output?.statusCode || lastDisconnect?.error?.output?.payload?.statusCode; - - if (code && code !== DisconnectReason.loggedOut && global.conn) { - try { - await global.reloadHandler(true); - global.timestamp.connect = new Date; + let settings = global.db.data.settings[this.user.jid] + if (typeof settings !== 'object') global.db.data.settings[this.user.jid] = {} + + if (settings) { + if (!('self' in settings)) settings.self = false + if (!('autoread' in settings)) settings.autoread = false + if (!('restrict' in settings)) settings.restrict = true + } else { + global.db.data.settings[this.user.jid] = { + self: false, + autoread: false, + restrict: true + } + } } catch (e) { - console.error('Errore in reloadHandler:', e); + console.error(e) } - } - - if (global.db.data == null) loadDatabase(); - - if (qr && (opzione === '1' || methodCodeQR) && !global.qrGenerated) { - console.log(chalk.bold.yellow(` -โ”Š โ”Š โ”Š โ”Šโ€ฟ หšโžถ ๏ฝกหš SCANSIONA IL CODICE QR -โ”Š โ”Š โ”Š หšโœง Scade tra 45 secondi -โ”Š หšโžถ ๏ฝกหš โ˜๏ธŽ -`)); - global.qrGenerated = true; - } - if (connection === 'open') { - global.qrGenerated = false; - global.connectionMessagesPrinted = {}; - if (!global.isLogoPrinted) { - const chatunity = chalk.hex('#3b0d95')(` โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— -โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• -โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• -โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ•”โ• -โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• - `); - console.log(chatunity); - global.isLogoPrinted = true; - await chatunityedition(); + if (opts['nyimak']) return + if (!m.fromMe && opts['self']) return + if (opts['pconly'] && m.chat.endsWith('g.us')) return + if (opts['gconly'] && !m.chat.endsWith('g.us')) return + + if (typeof m.text !== 'string') m.text = '' + + const isROwner = [conn.decodeJid(global.conn.user.id), ...global.owner.map(([number]) => number)] + .map(v => v.replace(/[^0-9]/g, '') + '@s.whatsapp.net') + .includes(m.sender) + const isOwner2 = isROwner || m.fromMe + const isMods = isOwner2 || global.mods.map(v => v.replace(/[^0-9]/g, '') + '@s.whatsapp.net').includes(m.sender) + const isPrems = isROwner || isOwner2 || isMods || global.db.data.users[m.sender]?.premiumTime > 0 + + if (opts['queque'] && m.text && !(isMods || isPrems)) { + let queque = this.msgqueque, time = 1000 * 5 + const previousID = queque[queque.length - 1] + queque.push(m.id || m.key.id) + setInterval(async function () { + if (queque.indexOf(previousID) === -1) clearInterval(this) + await delay(time) + }, time) } - - try { - await global.conn.groupAcceptInvite('FjPBDj4sUgFLJfZiLwtTvk'); - console.log(chalk.bold.green('โœ… Bot entrato nel gruppo supporto con successo - non abbandonare!')); - } catch (error) { - console.error(chalk.bold.red('โŒ Errore nell\'accettare l\'invito del gruppo:'), error.message); - } - - // CARICA HANDLER DOPO LA CONNESSIONE - console.log(chalk.cyan('๐Ÿ”„ Caricamento handler...')); - await global.reloadHandler(false); - console.log(chalk.green('โœ… Bot pronto a ricevere messaggi!')); - } - if (connection === 'close') { - const reason = lastDisconnect?.error?.output?.statusCode || lastDisconnect?.error?.output?.payload?.statusCode; - if (reason === DisconnectReason.badSession && !global.connectionMessagesPrinted.badSession) { - console.log(chalk.bold.redBright(`\nโš ๏ธโ— SESSIONE NON VALIDA, ELIMINA LA CARTELLA ${global.authFile} E SCANSIONA IL CODICE QR โš ๏ธ`)); - global.connectionMessagesPrinted.badSession = true; - try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); - } - } else if (reason === DisconnectReason.connectionLost && !global.connectionMessagesPrinted.connectionLost) { - console.log(chalk.bold.blueBright(`\nโ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โš ๏ธ CONNESSIONE PERSA COL SERVER เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ ๐Ÿ”„ RICONNESSIONE IN CORSO... \nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); - global.connectionMessagesPrinted.connectionLost = true; - try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); - } - } else if (reason === DisconnectReason.connectionReplaced && !global.connectionMessagesPrinted.connectionReplaced) { - console.log(chalk.bold.yellowBright(`โ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โš ๏ธ CONNESSIONE SOSTITUITA เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ รˆ stata aperta un'altra sessione, \nโ”ƒ chiudi prima quella attuale.\nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โบโ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); - global.connectionMessagesPrinted.connectionReplaced = true; - } else if (reason === DisconnectReason.loggedOut && !global.connectionMessagesPrinted.loggedOut) { - console.log(chalk.bold.redBright(`\nโš ๏ธ DISCONNESSO, ELIMINA LA CARTELLA ${global.authFile} E SCANSIONA IL CODICE QR โš ๏ธ`)); - global.connectionMessagesPrinted.loggedOut = true; - try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); - } - } else if (reason === DisconnectReason.restartRequired && !global.connectionMessagesPrinted.restartRequired) { - console.log(chalk.bold.magentaBright(`\nโญ‘โญ’โ”โ”โ”โœฆโ˜เผป RIAVVIO RICHIESTO เผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); - global.connectionMessagesPrinted.restartRequired = true; - try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); - } - } else if (reason === DisconnectReason.timedOut && !global.connectionMessagesPrinted.timedOut) { - console.log(chalk.bold.yellowBright(`\nโ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โŒ› TIMEOUT CONNESSIONE เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ ๐Ÿ”„ RICONNESSIONE IN CORSO...\nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โบโ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); - global.connectionMessagesPrinted.timedOut = true; + if (m.isBaileys) return + m.exp += Math.ceil(Math.random() * 10) + + let usedPrefix + let _user = global.db.data?.users?.[m.sender] + + const groupMetadata = (m.isGroup ? ((conn.chats[m.chat] || {}).metadata || await this.groupMetadata(m.chat).catch(_ => null)) : {}) || {} + const participants = (m.isGroup ? groupMetadata.participants : []) || [] + const normalizedParticipants = participants.map(u => { + const normalizedId = this.decodeJid(u.id) + return { ...u, id: normalizedId, jid: u.jid || normalizedId } + }) + const user = (m.isGroup ? normalizedParticipants.find(u => conn.decodeJid(u.id) === m.sender) : {}) || {} + const bot = (m.isGroup ? normalizedParticipants.find(u => conn.decodeJid(u.id) == this.user.jid) : {}) || {} + + async function isUserAdmin(conn, chatId, senderId) { try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); + const decodedSender = conn.decodeJid(senderId) + const groupMeta = groupMetadata + return groupMeta?.participants?.some(p => + (conn.decodeJid(p.id) === decodedSender || p.jid === decodedSender) && + (p.admin === 'admin' || p.admin === 'superadmin') + ) || false + } catch { + return false } - } else if (reason !== DisconnectReason.restartRequired && reason !== DisconnectReason.connectionClosed && !global.connectionMessagesPrinted.unknown) { - console.log(chalk.bold.redBright(`\nโš ๏ธโ— MOTIVO DISCONNESSIONE SCONOSCIUTO: ${reason || 'Non trovato'} >> ${connection || 'Non trovato'}`)); - global.connectionMessagesPrinted.unknown = true; } - } -} - -if (!opts['test']) { - if (global.db) setInterval(async () => { - if (global.db.data) await global.db.write(); - if (opts['autocleartmp'] && (global.support || {}).find) { - const tmp = [tmpdir(), 'tmp', "chatunity-sub"]; - tmp.forEach(filename => spawn('find', [filename, '-amin', '2', '-type', 'f', '-delete'])); - } - }, 30 * 1000); -} -if (opts['server']) (await import('./server.js')).default(global.conn, PORT); + const isRAdmin = user?.admin == 'superadmin' || false + const isAdmin = m.isGroup ? await isUserAdmin(this, m.chat, m.sender) : false + const isBotAdmin = m.isGroup ? await isUserAdmin(this, m.chat, this.user.jid) : false -process.on('uncaughtException', console.error); + const ___dirname = path.join(path.dirname(fileURLToPath(import.meta.url)), './plugins') -async function connectSubBots() { - const subBotDirectory = './chatunity-sub'; - if (!existsSync(subBotDirectory)) { - console.log(chalk.bold.magentaBright('non ci sono Sub-Bot collegati. Creazione directory...')); - try { - mkdirSync(subBotDirectory, { recursive: true }); - console.log(chalk.bold.green('โœ… Directory chatunity-sub creata con successo.')); - } catch (err) { - console.log(chalk.bold.red('โŒ Errore nella creazione della directory chatunity-sub:', err.message)); - return; - } - return; - } + // === GESTIONE PLUGIN.ALL (BOTTONI, LISTE, INTERATTIVI) === + for (let name in global.plugins) { + let plugin = global.plugins[name] + if (!plugin || plugin.disabled) continue + const __filename = join(___dirname, name) - try { - const subBotFolders = readdirSync(subBotDirectory).filter(file => - statSync(join(subBotDirectory, file)).isDirectory() - ); + // Esegui la funzione 'all' se presente nel plugin + if (typeof plugin.all === 'function') { + try { + await plugin.all.call(this, m, { + chatUpdate, + __dirname: ___dirname, + __filename + }) + } catch (e) { + console.error(`Errore in plugin.all (${name}):`, e) + } + } - if (subBotFolders.length === 0) { - console.log(chalk.bold.magenta('Nessun subbot collegato')); - return; - } + // Salta plugin admin se restrict รจ disabilitato + if (!opts['restrict'] && plugin.tags?.includes('admin')) continue - const botPromises = subBotFolders.map(async (folder) => { - const subAuthFile = join(subBotDirectory, folder); - if (existsSync(join(subAuthFile, 'creds.json'))) { + // Esegui la funzione 'before' se presente + if (typeof plugin.before === 'function') { try { - const { state: subState, saveCreds: subSaveCreds } = await useMultiFileAuthState(subAuthFile); - const subConn = makeWASocket({ - ...connectionOptions, - auth: { - creds: subState.creds, - keys: makeCacheableSignalKeyStore(subState.keys, logger), - }, - }); - - subConn.ev.on('creds.update', subSaveCreds); - subConn.ev.on('connection.update', connectionUpdate); - return subConn; - } catch (err) { - console.log(chalk.bold.red(`โŒ Errore nella connessione del Sub-Bot ${folder}:`, err.message)); - return null; + const shouldContinue = await plugin.before.call(this, m, { + conn: this, + participants: normalizedParticipants, + groupMetadata, + user, + bot, + isROwner, + isOwner: isOwner2, + isRAdmin, + isAdmin, + isBotAdmin, + isPrems, + chatUpdate, + __dirname: ___dirname, + __filename + }) + if (shouldContinue) continue + } catch (e) { + console.error(`Errore in plugin.before (${name}):`, e) } } - return null; - }); + } + // === FINE GESTIONE PLUGIN.ALL === + + // Gestione comandi normali + for (let name in global.plugins) { + let plugin = global.plugins[name] + if (!plugin || plugin.disabled) continue + const __filename = join(___dirname, name) + + if (!opts['restrict'] && plugin.tags?.includes('admin')) continue + + const str2Regex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + let _prefix = plugin.customPrefix ? plugin.customPrefix : conn.prefix ? conn.prefix : global.prefix + let match = (_prefix instanceof RegExp ? + [[_prefix.exec(m.text), _prefix]] : + Array.isArray(_prefix) ? + _prefix.map(p => { + let re = p instanceof RegExp ? p : new RegExp(str2Regex(p)) + return [re.exec(m.text), re] + }) : + typeof _prefix === 'string' ? + [[new RegExp(str2Regex(_prefix)).exec(m.text), new RegExp(str2Regex(_prefix))]] : + [[[], new RegExp]] + ).find(p => p[1]) + + if (typeof plugin !== 'function') continue + if (!match) continue + + if ((usedPrefix = (match[0] || '')[0])) { + let noPrefix = m.text.replace(usedPrefix, '') + let [command, ...args] = noPrefix.trim().split` `.filter(v => v) + args = args || [] + let _args = noPrefix.trim().split` `.slice(1) + let text = _args.join` ` + command = (command || '').toLowerCase() + let fail = plugin.fail || global.dfail + let isAccept = plugin.command instanceof RegExp ? + plugin.command.test(command) : + Array.isArray(plugin.command) ? + plugin.command.some(cmd => cmd instanceof RegExp ? cmd.test(command) : cmd === command) : + typeof plugin.command === 'string' ? + plugin.command === command : + false + + if (!isAccept) continue + + m.plugin = name + if ((m.chat in global.db.data.chats || m.sender in global.db.data.users)) { + let chat = global.db.data.chats[m.chat] + let userDb = global.db.data.users[m.sender] + if (name != 'owner-unbanchat.js' && chat?.isBanned) return + if (name != 'owner-unbanuser.js' && userDb?.banned) return + } - const bots = await Promise.all(botPromises); - global.conns = bots.filter(Boolean); + let chatDb = global.db.data.chats[m.chat] + let adminMode = chatDb?.soloadmin + let mystica = `${plugin.botAdmin || plugin.admin || plugin.group || plugin || noPrefix || _prefix || m.text.slice(0, 1) == _prefix || plugin.command}` + if (adminMode && !isOwner2 && !isROwner && m.isGroup && !isAdmin && mystica) return - if (global.conns.length > 0) { - console.log(chalk.bold.magentaBright(`๐ŸŒ™ ${global.conns.length} Sub-Bot si sono connessi con successo.`)); - } else { - console.log(chalk.bold.yellow('โš ๏ธ Nessun Sub-Bot รจ riuscito a connettersi.')); - } - } catch (err) { - console.log(chalk.bold.red('โŒ Errore generale nella connessione dei Sub-Bot:', err.message)); - } -} + if (plugin.rowner && plugin.owner && !(isROwner || isOwner2)) { + fail('owner', m, this) + continue + } + if (plugin.rowner && !isROwner) { + fail('rowner', m, this) + continue + } + if (plugin.owner && !isOwner2) { + fail('owner', m, this) + continue + } + if (plugin.mods && !isMods) { + fail('mods', m, this) + continue + } + if (plugin.premium && !isPrems) { + fail('premium', m, this) + continue + } + if (plugin.group && !m.isGroup) { + fail('group', m, this) + continue + } else if (plugin.botAdmin && !isBotAdmin) { + fail('botAdmin', m, this) + continue + } else if (plugin.admin && !isAdmin) { + fail('admin', m, this) + continue + } + if (plugin.private && m.isGroup) { + fail('private', m, this) + continue + } + if (plugin.register == true && _user?.registered == false) { + fail('unreg', m, this) + continue + } -(async () => { - global.conns = []; - try { - global.conn.ev.on('connection.update', connectionUpdate); - global.conn.ev.on('creds.update', saveCreds); - - console.log(chalk.bold.magenta(` -โ•ญ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ - โ‹† ๏ธต๏ธต โ˜… ChatUnity connesso โ˜… ๏ธต๏ธต โ‹† -โ•ฐ. ๊’ท๊’ฆ ๊’ท๊’ฆโ€งหšโ‚Šหš๊’ท๊’ฆ๊’ทโ€งหšโ‚Šหš๊’ท๊’ฆ๊’ท`)); - await connectSubBots(); - } catch (error) { - console.error(chalk.bold.bgRedBright(`๐Ÿฅ€ Errore nell'avvio del bot: `), error); - } -})(); + m.isCommand = true + let xp = 'exp' in plugin ? parseInt(plugin.exp) : 17 + if (xp > 2000) m.reply('Exp limit') + else if (plugin.money && global.db.data.users[m.sender]?.money < plugin.money * 1) { + fail('senzasoldi', m, this) + continue + } + m.exp += xp -const pluginFolder = global.__dirname(join(__dirname, './plugins/index')); -const pluginFilter = (filename) => /\.js$/.test(filename); -global.plugins = {}; + if (!isPrems && plugin.limit && global.db.data.users[m.sender]?.limit < plugin.limit * 1) { + continue + } + if (plugin.level > _user?.level) { + this.reply(m.chat, `livello troppo basso`, m) + continue + } + + let extra = { + match, + usedPrefix, + noPrefix, + _args, + args, + command, + text, + conn: this, + normalizedParticipants, + participants, + groupMetadata, + user, + bot, + isROwner, + isOwner: isOwner2, + isRAdmin, + isAdmin, + isBotAdmin, + isPrems, + chatUpdate, + __dirname: ___dirname, + __filename + } -async function filesInit() { - for (const filename of readdirSync(pluginFolder).filter(pluginFilter)) { - try { - const file = global.__filename(join(pluginFolder, filename)); - const module = await import(file); - global.plugins[filename] = module.default || module; - } catch (e) { - global.conn?.logger?.error(e); - delete global.plugins[filename]; - } - } -} - -filesInit().then((_) => Object.keys(global.plugins)).catch(console.error); - -global.reload = async (_ev, filename) => { - if (pluginFilter(filename)) { - const dir = global.__filename(join(pluginFolder, filename), true); - if (filename in global.plugins) { - if (existsSync(dir)) global.conn?.logger?.info(chalk.green(`โœ… AGGIORNATO - '${filename}' CON SUCCESSO`)); - else { - global.conn?.logger?.warn(`๐Ÿ—‘๏ธ FILE ELIMINATO: '${filename}'`); - return delete global.plugins[filename]; - } - } else global.conn?.logger?.info(`๐Ÿ†• NUOVO PLUGIN RILEVATO: '${filename}'`); - const err = syntaxerror(fs.readFileSync(dir), filename, { - sourceType: 'module', - allowAwaitOutsideFunction: true, - }); - if (err) global.conn?.logger?.error(`โŒ ERRORE DI SINTASSI IN: '${filename}'\n${format(err)}`); - else { - try { - const module = (await import(`${global.__filename(dir)}?update=${Date.now()}`)); - global.plugins[filename] = module.default || module; - } catch (e) { - global.conn?.logger?.error(`โš ๏ธ ERRORE NEL PLUGIN: '${filename}\n${format(e)}'`); - } finally { - global.plugins = Object.fromEntries(Object.entries(global.plugins).sort(([a], [b]) => a.localeCompare(b))); - } - } - } -}; - -Object.freeze(global.reload); -const pluginWatcher = watch(pluginFolder, global.reload); -pluginWatcher.setMaxListeners(20); - -async function _quickTest() { - const test = await Promise.all([ - spawn('ffmpeg'), - spawn('ffprobe'), - spawn('ffmpeg', ['-hide_banner', '-loglevel', 'error', '-filter_complex', 'color', '-frames:v', '1', '-f', 'webp', '-']), - spawn('convert'), - spawn('magick'), - spawn('gm'), - spawn('find', ['--version']), - ].map((p) => { - return Promise.race([ - new Promise((resolve) => { - p.on('close', (code) => { - resolve(code !== 127); - }); - }), - new Promise((resolve) => { - p.on('error', (_) => resolve(false)); - }) - ]); - })); - const [ffmpeg, ffprobe, ffmpegWebp, convert, magick, gm, find] = test; - const s = global.support = { ffmpeg, ffprobe, ffmpegWebp, convert, magick, gm, find }; - Object.freeze(global.support); -} - -function clearDirectory(dirPath) { - if (!existsSync(dirPath)) { - try { - mkdirSync(dirPath, { recursive: true }); - } catch (e) { - console.error(chalk.red(`Errore nella creazione della directory ${dirPath}:`, e)); - } - return; - } - const filenames = readdirSync(dirPath); - filenames.forEach(file => { - const filePath = join(dirPath, file); - try { - const stats = statSync(filePath); - if (stats.isFile()) { - unlinkSync(filePath); - } else if (stats.isDirectory()) { - rmSync(filePath, { recursive: true, force: true }); - } - } catch (e) { - console.error(chalk.red(`Errore nella pulizia del file ${filePath}:`, e)); - } - }); -} - -function ripristinaTimer(conn) { - if (conn.timerReset) clearInterval(conn.timerReset); - conn.timerReset = setInterval(async () => { - if (stopped === 'close' || !conn || !conn.user) return; - await clearDirectory(join(__dirname, 'tmp')); - await clearDirectory(join(__dirname, 'temp')); - }, 1000 * 60 * 30); -} - -_quickTest().then(() => global.conn?.logger?.info(chalk.bold.bgBlueBright(``))); -let filePath = fileURLToPath(import.meta.url); -const mainWatcher = watch(filePath, async () => { - console.log(chalk.bold.bgBlueBright("Main Aggiornato")); - await global.reloadHandler(true).catch(console.error); -}); -mainWatcher.setMaxListeners(20); From 2771b35d21029812b11f22d198148bf8a83a2332 Mon Sep 17 00:00:00 2001 From: dthbot Date: Sat, 10 Jan 2026 22:40:02 +0100 Subject: [PATCH 07/43] Update handler.js --- handler.js | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/handler.js b/handler.js index 98fda49a4..e1ecf8d5f 100644 --- a/handler.js +++ b/handler.js @@ -423,3 +423,226 @@ export async function handler(chatUpdate) { __filename } + try { + await plugin.call(this, m, extra) + if (!isPrems) { + m.limit = m.limit || plugin.limit || false + m.money = m.money || plugin.money || false + } + } catch (e) { + m.error = e + console.error(e) + if (e) { + let textErr = format(e) + for (let key of Object.values(global.APIKeys)) + textErr = textErr.replace(new RegExp(key, 'g'), '#HIDDEN#') + m.reply(textErr) + } + } finally { + // Esegui la funzione 'after' se presente + if (typeof plugin.after === 'function') { + try { + await plugin.after.call(this, m, extra) + } catch (e) { + console.error(`Errore in plugin.after (${name}):`, e) + } + } + } + break + } + } +} catch (e) { + console.error(e) + } finally { + if (opts['queque'] && m.text) { + const quequeIndex = this.msgqueque.indexOf(m.id || m.key.id) + if (quequeIndex !== -1) this.msgqueque.splice(quequeIndex, 1) + } + + if (m?.sender) { + let user = global.db.data.users[m.sender] + let chat = global.db.data.chats[m.chat] + if (user?.muto) { + await conn.sendMessage(m.chat, { + delete: { + remoteJid: m.chat, + fromMe: false, + id: m.key.id, + participant: m.key.participant + } + }) + } + if (user) { + user.exp += m.exp + user.limit -= m.limit * 1 + user.money -= m.money * 1 + user.messaggi += 1 + } + if (chat) chat.messaggi += 1 + } + if (m?.plugin) { + let now = +new Date + if (!stats[m.plugin]) { + stats[m.plugin] = { + total: 0, + success: 0, + last: 0, + lastSuccess: 0 + } + } + const stat = stats[m.plugin] + stat.total += 1 + stat.last = now + if (!m.error) { + stat.success += 1 + stat.lastSuccess = now + } + } + + try { + if (!opts['noprint']) await (await import(`./lib/print.js`)).default(m, this) + } catch (e) { + console.log(m, m.quoted, e) + } + if (opts['autoread']) await this.readMessages([m.key]) + } +} + +export async function participantsUpdate({ id, participants, action }) { + if (opts['self']) return + if (this.isInit) return + if (global.db.data == null) await loadDatabase() + + let chat = global.db.data.chats[id] || {} + let text = '' + let nomeDelBot = global.db.data.nomedelbot || `๐–›๐–Š๐–-๐–‡๐–”๐–™` + let jidCanale = global.db.data.jidcanale || '' + + switch (action) { + case 'add': + case 'remove': + if (chat.welcome) { + let groupMetadata = await this.groupMetadata(id) || (conn.chats[id] || {}).metadata + for (let user of participants) { + let pp = './menu/principale.jpeg' + try { + pp = await this.profilePictureUrl(user, 'image') + } catch (e) { + } finally { + let apii = await this.getFile(pp) + + if (action === 'add') { + text = (chat.sWelcome || this.welcome || conn.welcome || 'benvenuto, @user!') + .replace('@subject', await this.getName(id)) + .replace('@desc', groupMetadata.desc?.toString() || 'bot') + .replace('@user', '@' + user.split('@')[0]) + } else if (action === 'remove') { + text = (chat.sBye || this.bye || conn.bye || 'bye bye, @user!') + .replace('@user', '@' + user.split('@')[0]) + } + + this.sendMessage(id, { + text: text, + contextInfo: { + mentionedJid: [user], + forwardingScore: 99, + isForwarded: true, + forwardedNewsletterMessageInfo: { + newsletterJid: jidCanale, + serverMessageId: '', + newsletterName: `${nomeDelBot}` + }, + externalAdReply: { + title: ( + action === 'add' + ? '๐Œ๐ž๐ฌ๐ฌ๐š๐ ๐ ๐ข๐จ ๐๐ข ๐›๐ž๐ง๐ฏ๐ž๐ง๐ฎ๐ญ๐จ' + : '๐Œ๐ž๐ฌ๐ฌ๐š๐ ๐ ๐ข๐จ ๐๐ข ๐š๐๐๐ข๐จ' + ), + body: ``, + previewType: 'PHOTO', + thumbnailUrl: ``, + thumbnail: apii.data, + mediaType: 1, + renderLargerThumbnail: false + } + } + }) + } + } + } + break + } +} + +export async function groupsUpdate(groupsUpdate) { + if (opts['self']) return + for (const groupUpdate of groupsUpdate) { + const id = groupUpdate.id + if (!id) continue + let chats = global.db.data.chats[id], text = '' + if (groupUpdate.icon) text = (chats.sIcon || this.sIcon || conn.sIcon || '`immagine modificata`').replace('@icon', groupUpdate.icon) + if (groupUpdate.revoke) text = (chats.sRevoke || this.sRevoke || conn.sRevoke || '`link reimpostato, nuovo link:`\n@revoke').replace('@revoke', groupUpdate.revoke) + if (!text) continue + await this.sendMessage(id, { text, mentions: this.parseMention(text) }) + } +} + +export async function callUpdate(callUpdate) { + let isAnticall = global.db.data.settings[this.user.jid].antiCall + if (!isAnticall) return + for (let nk of callUpdate) { + if (nk.isGroup == false) { + if (nk.status == 'offer') { + let callmsg = await this.reply(nk.from, `ciao @${nk.from.split('@')[0]}, c'รจ anticall.`, false, { mentions: [nk.from] }) + let vcard = `BEGIN:VCARD\nVERSION:5.0\nN:;๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ;;;\nFN:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nORG:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nTITLE:\nitem1.TEL;waid=393773842461:+39 3515533859\nitem1.X-ABLabel:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nX-WA-BIZ-DESCRIPTION:ofc\nX-WA-BIZ-NAME:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nEND:VCARD` + await this.sendMessage(nk.from, { contacts: { displayName: 'Unlimited', contacts: [{ vcard }] } }, { quoted: callmsg }) + await this.updateBlockStatus(nk.from, 'block') + } + } + } +} + +export async function deleteUpdate(message) { + try { + const { fromMe, id, participant } = message + if (fromMe) return + let msg = this.serializeM(this.loadMessage(id)) + if (!msg) return + } catch (e) { + console.error(e) + } +} + +global.dfail = (type, m, conn) => { + let msg = { + rowner: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฌ๐จ๐ฅ๐จ ๐ฉ๐ž๐ซ ๐จ๐ฐ๐ง๐ž๐ซ ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ', + owner: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฌ๐จ๐ฅ๐จ ๐ฉ๐ž๐ซ ๐จ๐ฐ๐ง๐ž๐ซ ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ', + mods: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฅ๐จ ๐ฉ๐จ๐ฌ๐ฌ๐จ๐ง๐จ ๐ฎ๐ญ๐ข๐ฅ๐ข๐ณ๐ณ๐š๐ซ๐ž ๐ฌ๐จ๐ฅ๐จ ๐š๐๐ฆ๐ข๐ง ๐ž ๐จ๐ฐ๐ง๐ž๐ซ โš™๏ธ', + premium: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฉ๐ž๐ซ ๐ฆ๐ž๐ฆ๐›๐ซ๐ข ๐ฉ๐ซ๐ž๐ฆ๐ข๐ฎ๐ฆ โœ…', + group: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฉ๐ฎ๐จ๐ข ๐ฎ๐ญ๐ข๐ฅ๐ข๐ณ๐ณ๐š๐ซ๐ฅ๐จ ๐ข๐ง ๐ฎ๐ง ๐ ๐ซ๐ฎ๐ฉ๐ฉ๐จ ๐Ÿ‘ฅ', + private: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฉ๐ฎ๐จ๐ข ๐ฎ๐ญ๐ข๐ฅ๐ข๐ง๐ข๐ญ๐š๐ซ๐ฅ๐จ ๐ข๐ง ๐œ๐ก๐š๐ญ ๐ฉ๐ซ๐ข๐ฏ๐š๐ญ๐š ๐Ÿ‘ค', + admin: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฉ๐ž๐ซ ๐ฌ๐จ๐ฅ๐ข ๐š๐๐ฆ๐ข๐ง ๐Ÿ‘‘', + botAdmin: '๐ƒ๐ž๐ฏ๐ข ๐๐š๐ซ๐ž ๐š๐๐ฆ๐ข๐ง ๐š๐ฅ ๐›๐จ๐ญ ๐Ÿ‘‘', + restrict: '๐Ÿ” ๐‘๐ž๐ฌ๐ญ๐ซ๐ข๐œ๐ญ ๐ž ๐๐ข๐ฌ๐š๐ญ๐ญ๐ข๐ฏ๐š๐ญ๐จ ๐Ÿ”' + }[type] + if (msg) return conn.sendMessage(m.chat, { + text: ' ', + contextInfo: { + externalAdReply: { + title: `${msg}`, + body: ``, + previewType: 'PHOTO', + thumbnail: fs.readFileSync('./media/principale.jpeg'), + mediaType: 1, + renderLargerThumbnail: true + } + } + }, { quoted: m }) +} + +const file = global.__filename(import.meta.url, true) +watchFile(file, async () => { + unwatchFile(file) + console.log(chalk.redBright("Update 'handler.js'")) + if (global.reloadHandler) console.log(await global.reloadHandler()) +}) From 8d435fb98dfa82291c3b659fe784d451b15c85f0 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:02:35 +0100 Subject: [PATCH 08/43] Change import source for baileys library --- handler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler.js b/handler.js index e1ecf8d5f..48d0820f3 100644 --- a/handler.js +++ b/handler.js @@ -5,7 +5,7 @@ import path, { join } from 'path' import { unwatchFile, watchFile } from 'fs' import fs from 'fs' import chalk from 'chalk' -const { proto } = (await import('@chatunity/baileys')).default +const { proto } = (await import('@whiskeysockets/baileys')).default const isNumber = x => typeof x === 'number' && !isNaN(x) const delay = ms => isNumber(ms) && new Promise(resolve => setTimeout(function () { From eb31d399604558ee988d4cfddeff2da2cf1c6c5c Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:38:45 +0100 Subject: [PATCH 09/43] Update import statements and improve caching logic --- lib/print.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/print.js b/lib/print.js index ae0443a7e..de0b5bc7a 100644 --- a/lib/print.js +++ b/lib/print.js @@ -1,4 +1,4 @@ -import { WAMessageStubType } from '@chatunity/baileys' +import { WAMessageStubType } from '@whiskeysockets/baileys' import chalk from 'chalk' import { watchFile } from 'fs' import { fileURLToPath } from 'url' @@ -139,3 +139,4 @@ watchFile(__filename, () => { console.log(chalk.redBright("Aggiornamento 'lib/print.js'")) }) + From 0d80996aa35522fc74ba19637fe349d1c2d91e01 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:48:21 +0100 Subject: [PATCH 10/43] Update simple.js --- lib/simple.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/simple.js b/lib/simple.js index af4b37647..0f480a0ba 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -32,7 +32,7 @@ const { WA_DEFAULT_EPHEMERAL, prepareWAMessageMedia, jidNormalizedUser -} = await import('@chatunity/baileys'); +} = await import('@whiskeysockets/baileys'); /** * Funzione helper per normalizzare i JID @@ -210,7 +210,7 @@ export function makeWASocket(connectionOptions, options = {}) { * @param {String|Buffer} path * @param {String} filename * @param {String} caption - * @param {import('@chatunity/baileys').proto.WebMessageInfo} quoted + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted * @param {Boolean} ptt * @param {Object} options */ @@ -274,7 +274,7 @@ export function makeWASocket(connectionOptions, options = {}) { * Invia un contatto * @param {String} jid * @param {String[][]|String[]} data - * @param {import('@chatunity/baileys').proto.WebMessageInfo} quoted + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ async value(jid, data, quoted, options) { @@ -329,7 +329,7 @@ reply: { * @param {String} footer * @param {Buffer} buffer * @param {String[] | String[][]} buttons - * @param {import('@chatunity/baileys').proto.WebMessageInfo} quoted + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted * @param {Object} options */ /* async value(jid, text = '', footer = '', buffer, buttons, quoted, options) { From fc9f7b3a711cc81f51b3456cfcedbb186863737a Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:53:08 +0100 Subject: [PATCH 11/43] Update simple.js --- lib/simple.js | 2231 ------------------------------------------------- 1 file changed, 2231 deletions(-) diff --git a/lib/simple.js b/lib/simple.js index 0f480a0ba..e69de29bb 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -1,2231 +0,0 @@ -/* - * Crediti: - * - https://github.com/realvare/varebot - * - https://github.com/realvare/based -*/ - -import path from 'path'; -import { toAudio } from './converter.js'; -import chalk from 'chalk'; -import fetch from 'node-fetch'; -import PhoneNumber from 'awesome-phonenumber'; -import fs from 'fs'; -import util from 'util'; -import { fileTypeFromBuffer } from 'file-type'; -import { format } from 'util'; -import { fileURLToPath } from 'url'; -import store from './store.js'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const { - makeWASocket: _makeWASocket, - makeWALegacySocket, - proto, - downloadContentFromMessage, - jidDecode, - areJidsSameUser, - generateWAMessage, - generateForwardMessageContent, - generateWAMessageFromContent, - WAMessageStubType, - extractMessageContent, - WA_DEFAULT_EPHEMERAL, - prepareWAMessageMedia, - jidNormalizedUser -} = await import('@whiskeysockets/baileys'); - -/** - * Funzione helper per normalizzare i JID - * @param {string} jid - Il JID da normalizzare - * @param {object} conn - La connessione socket - * @returns {string|null} Il JID normalizzato o null - */ -function normalizeJid(jid, conn) { - if (!jid) return null; - const normalized = jidNormalizedUser(conn.decodeJid(jid)); - return normalized; -} - -/** - * Crea un socket zozzap con opzioni aggiuntive. - * @param {object} connectionOptions - Opzioni di connessione - * @param {object} [options={}] - Opzioni aggiuntive - * @returns {object} Il socket WhatsApp configurato - */ -export function makeWASocket(connectionOptions, options = {}) { - const conn = (global.opts['legacy'] ? makeWALegacySocket : _makeWASocket)(connectionOptions); - const sock = Object.defineProperties(conn, { - chats: { - value: { ...(options.chats || {}) }, - writable: true, - }, - decodeJid: { - value(jid) { - if (!jid || typeof jid !== 'string') return (!nullish(jid) && jid) || null; - return jid.decodeJid(); - }, - }, - logger: { - get() { - return { - info(...args) { - console.log( - chalk.bold.bgRgb(51, 204, 51)('INFO '), - `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, - chalk.cyan(format(...args)), - ); - }, - error(...args) { - console.log( - chalk.bold.bgRgb(247, 38, 33)('ERROR '), - `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, - chalk.rgb(255, 38, 0)(format(...args)), - ); - }, - warn(...args) { - console.log( - chalk.bold.bgRgb(255, 153, 0)('WARNING '), - `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, - chalk.redBright(format(...args)), - ); - }, - trace(...args) { - console.log( - chalk.grey('TRACE '), - `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, - chalk.white(format(...args)), - ); - }, - debug(...args) { - console.log( - chalk.bold.bgRgb(66, 167, 245)('DEBUG '), - `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, - chalk.white(format(...args)), - ); - }, - }; - }, - enumerable: true, - }, - sendNyanCat: { - async value(jid, text = '', buffer, title, body, url, quoted, options) { - if (buffer) { - try { - (type = await conn.getFile(buffer), buffer = type.data); - } catch { - buffer = buffer; - } - } - const prep = generateWAMessageFromContent(jid, {extendedTextMessage: {text: text, contextInfo: {externalAdReply: {title: title, body: body, thumbnail: buffer, sourceUrl: url}, mentionedJid: await conn.parseMention(text)}}}, {quoted: quoted}); - return conn.relayMessage(jid, prep.message, {messageId: prep.key.id}); - }, - }, - sendPayment: { - async value(jid, amount, text, quoted, options) { - conn.relayMessage(jid, { - requestPaymentMessage: { - currencyCodeIso4217: 'PEN', - amount1000: amount, - requestFrom: null, - noteMessage: { - extendedTextMessage: { - text: text, - contextInfo: { - externalAdReply: { - showAdAttribution: true, - }, mentionedJid: conn.parseMention(text)}}}}}, {}); - }, - }, - getFile: { - /** - * Ottiene il buffer hehe - * @param {fs.PathLike} PATH - * @param {Boolean} saveToFile - */ - async value(PATH, saveToFile = false) { - let res; let filename; - const data = Buffer.isBuffer(PATH) ? PATH - : PATH instanceof ArrayBuffer ? Buffer.from(PATH) - : /^data:.*?\/.*?;base64,/i.test(PATH) ? Buffer.from(PATH.split`,`[1], 'base64') - : /^https?:\/\//.test(PATH) ? Buffer.from(await (res = await fetch(PATH)).arrayBuffer()) - : fs.existsSync(PATH) ? (filename = PATH, fs.readFileSync(PATH)) - : typeof PATH === 'string' ? Buffer.from(PATH) - : Buffer.alloc(0); - - if (!Buffer.isBuffer(data)) throw new TypeError('Result is not a buffer'); - const type = await fileTypeFromBuffer(data) || { - mime: 'application/octet-stream', - ext: '.bin', - }; - if (data && saveToFile && !filename) (filename = path.join(__dirname, '../tmp/' + new Date * 1 + '.' + type.ext), await fs.promises.writeFile(filename, data)); - return { - res, - filename, - ...type, - data, - deleteFile() { - return filename && fs.promises.unlink(filename); - }, - }; - }, - enumerable: true, - }, - waitEvent: { - /** - * Attende un evento - * @param {String} eventName - * @param {Boolean} is - * @param {Number} maxTries - */ - value(eventName, is = () => true, maxTries = 25) { // Non so perchรฉ esiste? - return new Promise((resolve, reject) => { - let tries = 0; - const on = (...args) => { - if (++tries > maxTries) reject('Max tries reached'); - else if (is()) { - conn.ev.off(eventName, on); - resolve(...args); - } - }; - conn.ev.on(eventName, on); - }); - }, - }, - relayWAMessage: { - async value(pesanfull) { - if (pesanfull.message.audioMessage) { - await conn.sendPresenceUpdate('recording', pesanfull.key.remoteJid); - } else { - await conn.sendPresenceUpdate('composing', pesanfull.key.remoteJid); - } - const mekirim = await conn.relayMessage(pesanfull.key.remoteJid, pesanfull.message, {messageId: pesanfull.key.id}); - conn.ev.emit('messages.upsert', {messages: [pesanfull], type: 'append'}); - return mekirim; - }, - }, - sendFile: { - /** - * Invia un media/file con specificatore di tipo automatico - * @param {String} jid - * @param {String|Buffer} path - * @param {String} filename - * @param {String} caption - * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted - * @param {Boolean} ptt - * @param {Object} options - */ - async value(jid, path, filename = '', caption = '', quoted, ptt = false, options = {}) { - const type = await conn.getFile(path, true); - let {res, data: file, filename: pathFile} = type; - if (res && res.status !== 200 || file.length <= 65536) { - try { - throw {json: JSON.parse(file.toString())}; - } catch (e) { - if (e.json) throw e.json; - } - } - const opt = {}; - if (quoted) opt.quoted = quoted; - if (!type) options.asDocument = true; - let mtype = ''; let mimetype = options.mimetype || type.mime; let convert; - if (/webp/.test(type.mime) || (/image/.test(type.mime) && options.asSticker)) mtype = 'sticker'; - else if (/image/.test(type.mime) || (/webp/.test(type.mime) && options.asImage)) mtype = 'image'; - else if (/video/.test(type.mime)) mtype = 'video'; - else if (/audio/.test(type.mime)) { - ( - convert = await toAudio(file, type.ext), - file = convert.data, - pathFile = convert.filename, - mtype = 'audio', - mimetype = options.mimetype || 'audio/mpeg; codecs=opus' - ); - } else mtype = 'document'; - if (options.asDocument) mtype = 'document'; - delete options.asSticker; - delete options.asLocation; - delete options.asVideo; - delete options.asDocument; - delete options.asImage; - - const message = { - ...options, - caption, - ptt, - [mtype]: {url: pathFile}, - mimetype, - fileName: filename || pathFile.split('/').pop(), - }; - let m; - try { - m = await conn.sendMessage(jid, message, {...opt, ...options}); - } catch (e) { - console.error(e); - m = null; - } finally { - if (!m) m = await conn.sendMessage(jid, {...message, [mtype]: file}, {...opt, ...options}); - file = null; // Rilascio della memoria - return m; - } - }, - enumerable: true, - }, - sendContact: { - /** - * Invia un contatto - * @param {String} jid - * @param {String[][]|String[]} data - * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted - * @param {Object} options - */ - async value(jid, data, quoted, options) { - if (!Array.isArray(data[0]) && typeof data[0] === 'string') data = [data]; - const contacts = []; - for (let [number, name] of data) { - number = number.replace(/[^0-9]/g, ''); - const njid = number + '@s.whatsapp.net'; - const biz = await conn.getBusinessProfile(njid).catch((_) => null) || {}; - const vcard = ` -BEGIN:VCARD -VERSION:3.0 -N:;${name.replace(/\n/g, '\\n')};;; -FN:${name.replace(/\n/g, '\\n')} -TEL;type=CELL;type=VOICE;waid=${number}:${PhoneNumber('+' + number).getNumber('international')}${biz.description ? ` -X-WA-BIZ-NAME:${(conn.chats[njid]?.vname || conn.getName(njid) || name).replace(/\n/, '\\n')} -X-WA-BIZ-DESCRIPTION:${biz.description.replace(/\n/g, '\\n')} -`.trim() : ''} -END:VCARD - `.trim(); - contacts.push({vcard, displayName: name}); - } - return await conn.sendMessage(jid, { - ...options, - contacts: { - ...options, - displayName: (contacts.length >= 2 ? `${contacts.length} kontak` : contacts[0].displayName) || null, - contacts, - }, - }, {quoted, ...options}); - }, - enumerable: true, - }, -reply: { - /** - * Rispondi a un messaggio - * @param {String} jid - * @param {String|Buffer} text - * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted - * @param {Object} options - */ - value(jid, text = '', quoted, options) { - - return Buffer.isBuffer(text) ? conn.sendFile(jid, text, 'file', '', quoted, false, options) : conn.sendMessage(jid, { ...options, text }, { quoted, ...options }) - } - }, - // sendButton: { - /** - * Invia un pulsante - * @param {String} jid - * @param {String} text - * @param {String} footer - * @param {Buffer} buffer - * @param {String[] | String[][]} buttons - * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted - * @param {Object} options - */ - /* async value(jid, text = '', footer = '', buffer, buttons, quoted, options) { - let type - if (Array.isArray(buffer)) (options = quoted, quoted = buttons, buttons = buffer, buffer = null) - else if (buffer) try { (type = await conn.getFile(buffer), buffer = type.data) } catch { buffer = null } - if (!Array.isArray(buttons[0]) && typeof buttons[0] === 'string') buttons = [buttons] - if (!options) options = {} - let message = { - ...options, - [buffer ? 'caption' : 'text']: text || '', - footer, - buttons: buttons.map(btn => ({ - buttonId: !nullish(btn[1]) && btn[1] || !nullish(btn[0]) && btn[0] || '', - buttonText: { - displayText: !nullish(btn[0]) && btn[0] || !nullish(btn[1]) && btn[1] || '' - } - })), - ...(buffer ? - options.asLocation && /image/.test(type.mime) ? { - location: { - ...options, - jpegThumbnail: buffer - } - } : { - [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer - } : {}) - } - - return await conn.sendMessage(jid, message, { - quoted, - upload: conn.waUploadToServer, - ...options - }) - }, - enumerable: true - }, - */ - - //-- new -sendButton: { - async value(jid, text = '', footer = '', buffer, buttons, copy, urls, list, quoted, options) { - let img, video - - - if (/^https?:\/\//i.test(buffer)) { - try { - // Ottieni il tipo MIME dall'URL - const response = await fetch(buffer) - const contentType = response.headers.get('content-type') - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) - } else { - console.error("Tipo MIME non compatibile:", contentType) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error) - } - } else { - - try { - const type = await conn.getFile(buffer) - if (/^image\//i.test(type.mime)) { - img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(type.mime)) { - video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo di file:", error); - } - } - -const dynamicButtons = [] - -// Pulsanti di tipo quick_reply -if (buttons && Array.isArray(buttons)) { - dynamicButtons.push(...buttons.map(btn => ({ - name: 'quick_reply', - buttonParamsJson: JSON.stringify({ - display_text: btn[0], - id: btn[1] - }) - }))); -} - -// Pulsanti di copia -if (copy && Array.isArray(copy)) { - dynamicButtons.push(...copy.map(copyBtn => ({ - name: 'cta_copy', - buttonParamsJson: JSON.stringify({ - display_text: copyBtn[0] || 'Copy', - copy_code: copyBtn[1] - }) - }))); -} - -// Pulsanti di URL -if (urls && Array.isArray(urls)) { - urls.forEach(url => { - dynamicButtons.push({ - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: url[0], - url: url[1], - merchant_url: url[1] - }) - }); - }); -} - -// Pulsanti di lista -if (list && Array.isArray(list)) { - list.forEach(lister => { - dynamicButtons.push({ - name: 'single_select', - buttonParamsJson: JSON.stringify({ - title: lister[0], - sections: lister[1] - }) - }) - }) - } - const interactiveMessage = { - body: { text: text }, - footer: { text: footer }, - header: { - hasMediaAttachment: false, - imageMessage: img ? img.imageMessage : null, - videoMessage: video ? video.videoMessage : null - }, - nativeFlowMessage: { - buttons: dynamicButtons, - messageParamsJson: '' - } - } - - - let msgL = generateWAMessageFromContent(jid, { - viewOnceMessage: { - message: { - interactiveMessage } } }, { userJid: conn.user.jid, quoted }) - - conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) - - } -}, - -sendAlbumMessage: { - async value(jid, medias, caption = "", quoted = null) { - let img, video; - - const album = generateWAMessageFromContent(jid, { - albumMessage: { - expectedImageCount: medias.filter(media => media.type === "image").length, - expectedVideoCount: medias.filter(media => media.type === "video").length, - ...(quoted ? { - contextInfo: { - remoteJid: quoted.key.remoteJid, - fromMe: quoted.key.fromMe, - stanzaId: quoted.key.id, - participant: quoted.key.participant || quoted.key.remoteJid, - quotedMessage: quoted.message - } - } : {}) - } - }, { quoted: quoted }); - - await conn.relayMessage(album.key.remoteJid, album.message, { - messageId: album.key.id - }); - - for (const media of medias) { - const { type, data } = media; - - if (/^https?:\/\//i.test(data.url)) { - try { - const response = await fetch(data.url); - const contentType = response.headers.get('content-type'); - - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ image: { url: data.url } }, { upload: conn.waUploadToServer }); - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ video: { url: data.url } }, { upload: conn.waUploadToServer }); - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error); - } - } - - const mediaMessage = await generateWAMessage(album.key.remoteJid, { - [type]: data, - ...(media === medias[0] ? { caption } : {}) - }, { - upload: conn.waUploadToServer - }); - - mediaMessage.message.messageContextInfo = { - messageAssociation: { - associationType: 1, - parentMessageKey: album.key - } - }; - - await conn.relayMessage(mediaMessage.key.remoteJid, mediaMessage.message, { - messageId: mediaMessage.key.id - }); - } - - return album; - } -}, - -/** - * Invia nativeFlowMessage - */ - sendNCarousel: { - async value(jid, text = '', footer = '', buffer, buttons, copy, urls, list, quoted, options) { - let img, video; - if (buffer) { - if (/^https?:\/\//i.test(buffer)) { - try { - const response = await fetch(buffer); - const contentType = response.headers.get('content-type'); - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ - image: { - url: buffer - } - }, { - upload: conn.waUploadToServer, - ...options - }); - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ - video: { - url: buffer - } - }, { - upload: conn.waUploadToServer, - ...options - }); - } else { - console.error("Tipo MIME non compatibile:", contentType); - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error); - } - } else { - try { - const type = await conn.getFile(buffer); - if (/^image\//i.test(type.mime)) { - img = await prepareWAMessageMedia({ - image: (/^https?:\/\//i.test(buffer)) ? { - url: buffer - } : (type && type?.data) - }, { - upload: conn.waUploadToServer, - ...options - }); - } else if (/^video\//i.test(type.mime)) { - video = await prepareWAMessageMedia({ - video: (/^https?:\/\//i.test(buffer)) ? { - url: buffer - } : (type && type?.data) - }, { - upload: conn.waUploadToServer, - ...options - }); - } - } catch (error) { - console.error("Errore nell'ottenere il tipo di file:", error); - } - } - } - const dynamicButtons = buttons.map(btn => ({ - name: 'quick_reply', - buttonParamsJson: JSON.stringify({ - display_text: btn[0], - id: btn[1] - }), - })); - dynamicButtons.push( - (copy && (typeof copy === 'string' || typeof copy === 'number')) ? { - name: 'cta_copy', - buttonParamsJson: JSON.stringify({ - display_text: 'Copy', - copy_code: copy - }) - } : null) - - urls?.forEach(url => { - dynamicButtons.push({ - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: url[0], - url: url[1], - merchant_url: url[1] - }) - }); - }); - list?.forEach(lister => { - dynamicButtons.push({ - name: 'single_select', - buttonParamsJson: JSON.stringify({ - title: lister[0], - sections: lister[1] - }) - }); - }) - const interactiveMessage = { - body: { - text: text || '' - }, - footer: { - text: footer || wm - }, - header: { - hasMediaAttachment: img?.imageMessage || video?.videoMessage ? true : false, - imageMessage: img?.imageMessage || null, - videoMessage: video?.videoMessage || null - }, - nativeFlowMessage: { - buttons: dynamicButtons.filter(Boolean), - messageParamsJson: '' - }, - ...Object.assign({ - mentions: typeof text === 'string' ? conn.parseMention(text || '@0') : [], - contextInfo: { - mentionedJid: typeof text === 'string' ? conn.parseMention(text || '@0') : [], - } - }, { - ...(options || {}), - ...(conn.temareply?.contextInfo && { - contextInfo: { - ...(options?.contextInfo || {}), - ...conn.temareply?.contextInfo, - externalAdReply: { - ...(options?.contextInfo?.externalAdReply || {}), - ...conn.temareply?.contextInfo?.externalAdReply, - }, - }, - }) - }) - }; - const messageContent = proto.Message.fromObject({ - viewOnceMessage: { - message: { - messageContextInfo: { - deviceListMetadata: {}, - deviceListMetadataVersion: 2 - }, - interactiveMessage - } - } - }); - const msgs = await generateWAMessageFromContent(jid, messageContent, { - userJid: conn.user.jid, - quoted: quoted, - upload: conn.waUploadToServer, - ephemeralExpiration: WA_DEFAULT_EPHEMERAL - }); - await conn.relayMessage(jid, msgs.message, { - messageId: msgs.key.id - }); - } - }, - -/** - * Invia carouselMessage - */ - sendCarousel: { - async value(jid, text = '', footer = '', messages, quoted, options = {}) { - try { - if (messages.length > 1) { - const cards = await Promise.all(messages.map(async ([text = '', footer = '', buffer, buttons, copy, urls, list]) => { - let img, video; - - if (/^https?:\/\//i.test(buffer)) { - try { - const response = await fetch(buffer); - const contentType = response.headers.get('content-type'); - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer, ...options }); - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer, ...options }); - } else { - console.error("Tipo MIME non compatibile:", contentType); - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error); - } - } else { - try { - const type = await conn.getFile(buffer); - if (/^image\//i.test(type.mime)) { - img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer, ...options }); - } else if (/^video\//i.test(type.mime)) { - video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer, ...options }); - } - } catch (error) { - console.error("Errore nell'ottenere il tipo di file:", error); - } - } - - const dynamicButtons = []; - if (buttons && Array.isArray(buttons)) { - buttons.forEach(btn => { - dynamicButtons.push({ - name: 'quick_reply', - buttonParamsJson: JSON.stringify({ - display_text: btn[0], - id: btn[1] - }) - }); - }); - } - - if (copy && Array.isArray(copy)) { - copy.forEach(copyBtn => { - dynamicButtons.push({ - name: 'cta_copy', - buttonParamsJson: JSON.stringify({ - display_text: copyBtn[0] || 'Copy', - copy_code: copyBtn[1] - }) - }); - }); - } - - if (urls && Array.isArray(urls)) { - urls.forEach(url => { - dynamicButtons.push({ - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: url[0], - url: url[1], - merchant_url: url[1] - }) - }); - }); - } - - if (list && Array.isArray(list)) { - list.forEach(lister => { - dynamicButtons.push({ - name: 'single_select', - buttonParamsJson: JSON.stringify({ - title: lister[0], - sections: lister[1] - }) - }); - }); - } - - return { - body: proto.Message.InteractiveMessage.Body.fromObject({ - text: text || '' - }), - footer: proto.Message.InteractiveMessage.Footer.fromObject({ - text: footer || null - }), - header: proto.Message.InteractiveMessage.Header.fromObject({ - title: text || null, - subtitle: text || null, - hasMediaAttachment: img?.imageMessage || video?.videoMessage ? true : false, - imageMessage: img?.imageMessage || null, - videoMessage: video?.videoMessage || null - }), - nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({ - buttons: dynamicButtons.filter(Boolean), - messageParamsJson: '' - }) - }; - })); - - const interactiveMessage = proto.Message.InteractiveMessage.create({ - body: proto.Message.InteractiveMessage.Body.fromObject({ - text: text || '' - }), - footer: proto.Message.InteractiveMessage.Footer.fromObject({ - text: footer || '' - }), - header: proto.Message.InteractiveMessage.Header.fromObject({ - title: text || '', - subtitle: text || '', - hasMediaAttachment: false - }), - carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.fromObject({ - cards: cards - }) - }); - - const messageContent = proto.Message.fromObject({ - viewOnceMessage: { - message: { - messageContextInfo: { - deviceListMetadata: {}, - deviceListMetadataVersion: 2 - }, - interactiveMessage - } - } - }); - - const msgs = await generateWAMessageFromContent(jid, messageContent, { - userJid: conn.user.jid, - quoted: quoted, - upload: conn.waUploadToServer, - ephemeralExpiration: WA_DEFAULT_EPHEMERAL - }); - - await conn.relayMessage(jid, msgs.message, { messageId: msgs.key.id }); - } else { - await conn.sendNCarousel(jid, ...messages[0], quoted, options); - } - } catch (error) { - console.error("Errore in sendCarousel:", error); - throw error; - } - } -}, - -sendButton2: { - async value(jid, text = '', footer = '', buffer, buttons, copy, urls, quoted, options) { - let img, video - - - if (/^https?:\/\//i.test(buffer)) { - try { - // Ottieni il tipo MIME dall'URL - const response = await fetch(buffer) - const contentType = response.headers.get('content-type') - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) - } else { - console.error("Tipo MIME non compatibile:", contentType) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error) - } - } else { - - try { - const type = await conn.getFile(buffer) - if (/^image\//i.test(type.mime)) { - img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(type.mime)) { - video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo di file:", error); - } - } - - const dynamicButtons = buttons.map(btn => ({ - name: 'quick_reply', - buttonParamsJson: JSON.stringify({ - display_text: btn[0], - id: btn[1] - }), - })); - - - if (copy && (typeof copy === 'string' || typeof copy === 'number')) { - // Aggiungi pulsante di copia - dynamicButtons.push({ - name: 'cta_copy', - buttonParamsJson: JSON.stringify({ - display_text: 'Copy', - copy_code: copy - }) - }); - } - - // Aggiungi pulsanti di URL - if (urls && Array.isArray(urls)) { - urls.forEach(url => { - dynamicButtons.push({ - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: url[0], - url: url[1], - merchant_url: url[1] - }) - }) - }) - } - - - const interactiveMessage = { - body: { text: text }, - footer: { text: footer }, - header: { - hasMediaAttachment: false, - imageMessage: img ? img.imageMessage : null, - videoMessage: video ? video.videoMessage : null - }, - nativeFlowMessage: { - buttons: dynamicButtons, - messageParamsJson: '' - } - } - - - let msgL = generateWAMessageFromContent(jid, { - viewOnceMessage: { - message: { - interactiveMessage } } }, { userJid: conn.user.jid, quoted }) - - conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) - - } -}, - - //--- - -sendList: { - async value(jid, title, text, buttonText, buffer, listSections, quoted, options = {}) { - let img, video - - if (/^https?:\/\//i.test(buffer)) { - try { - // Ottieni il tipo MIME dall'URL - const response = await fetch(buffer) - const contentType = response.headers.get('content-type') - if (/^image\//i.test(contentType)) { - img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(contentType)) { - video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) - } else { - console.error("Tipo MIME non compatibile:", contentType) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo MIME:", error) - } - } else { - - try { - const type = await conn.getFile(buffer) - if (/^image\//i.test(type.mime)) { - img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) - } else if (/^video\//i.test(type.mime)) { - video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) - } - } catch (error) { - console.error("Errore nell'ottenere il tipo di file:", error); - } - } - - const sections = [...listSections] - - const message = { - interactiveMessage: { - header: {title: title, - hasMediaAttachment: false, - imageMessage: img ? img.imageMessage : null, - videoMessage: video ? video.videoMessage : null - } , - body: {text: text}, - nativeFlowMessage: { - buttons: [ - { - name: 'single_select', - buttonParamsJson: JSON.stringify({ - title: buttonText, - sections - }) - } - ], - messageParamsJson: '' - } - } - }; - - let msgL = generateWAMessageFromContent(jid, { - viewOnceMessage: { - message} }, { userJid: conn.user.jid, quoted }) - - //await conn.relayMessage(jid, { viewOnceMessage: { message } }, {}); - conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) - - } -}, - - sendPoll: { - async value(jid, name = '', optiPoll, options) { - if (!Array.isArray(optiPoll[0]) && typeof optiPoll[0] === 'string') optiPoll = [optiPoll]; - if (!options) options = {}; - const pollMessage = { - name: name, - options: optiPoll.map((btn) => ({ - optionName: !nullish(btn[0]) && btn[0] || '', - })), - selectableOptionsCount: 1, - }; - return conn.relayMessage(jid, {pollCreationMessage: pollMessage}, {...options}); - }, - }, - sendHydrated: { - /** - * - * @param {String} jid - * @param {String} text - * @param {String} footer - * @param {fs.PathLike} buffer - * @param {String|string[]} url - * @param {String|string[]} urlText - * @param {String|string[]} call - * @param {String|string[]} callText - * @param {String[][]} buttons - * @param {import('@chatunity/baileys').proto.WebMessageInfo} quoted - * @param {Object} options - */ - async value(jid, text = '', footer = '', buffer, url, urlText, call, callText, buttons, quoted, options) { - let type; - if (buffer) { - try { - (type = await conn.getFile(buffer), buffer = type.data); - } catch { - buffer = buffer; - } - } - if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null); - if (!options) options = {}; - const templateButtons = []; - if (url || urlText) { - if (!Array.isArray(url)) url = [url]; - if (!Array.isArray(urlText)) urlText = [urlText]; - templateButtons.push(...( - url.map((v, i) => [v, urlText[i]]) - .map(([url, urlText], i) => ({ - index: templateButtons.length + i + 1, - urlButton: { - displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', - url: !nullish(url) && url || !nullish(urlText) && urlText || '', - }, - })) || [] - )); - } - if (call || callText) { - if (!Array.isArray(call)) call = [call]; - if (!Array.isArray(callText)) callText = [callText]; - templateButtons.push(...( - call.map((v, i) => [v, callText[i]]) - .map(([call, callText], i) => ({ - index: templateButtons.length + i + 1, - callButton: { - displayText: !nullish(callText) && callText || !nullish(call) && call || '', - phoneNumber: !nullish(call) && call || !nullish(callText) && callText || '', - }, - })) || [] - )); - } - if (buttons.length) { - if (!Array.isArray(buttons[0])) buttons = [buttons]; - templateButtons.push(...( - buttons.map(([text, id], index) => ({ - index: templateButtons.length + index + 1, - quickReplyButton: { - displayText: !nullish(text) && text || !nullish(id) && id || '', - id: !nullish(id) && id || !nullish(text) && text || '', - }, - })) || [] - )); - } - const message = { - ...options, - [buffer ? 'caption' : 'text']: text || '', - footer, - templateButtons, - ...(buffer ? - options.asLocation && /image/.test(type.mime) ? { - location: { - ...options, - jpegThumbnail: buffer, - }, - } : { - [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer, - } : {}), - }; - return await conn.sendMessage(jid, message, { - quoted, - upload: conn.waUploadToServer, - ...options, - }); - }, - enumerable: true, - }, - sendHydrated2: { - /** - * - * @param {String} jid - * @param {String} text - * @param {String} footer - * @param {fs.PathLike} buffer - * @param {String|string[]} url - * @param {String|string[]} urlText - * @param {String|string[]} call - * @param {String|string[]} callText - * @param {String[][]} buttons - * @param {import('@chatunity/baileys').proto.WebMessageInfo} quoted - * @param {Object} options - */ - async value(jid, text = '', footer = '', buffer, url, urlText, url2, urlText2, buttons, quoted, options) { - let type; - if (buffer) { - try { - (type = await conn.getFile(buffer), buffer = type.data); - } catch { - buffer = buffer; - } - } - if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null); - if (!options) options = {}; - const templateButtons = []; - if (url || urlText) { - if (!Array.isArray(url)) url = [url]; - if (!Array.isArray(urlText)) urlText = [urlText]; - templateButtons.push(...( - url.map((v, i) => [v, urlText[i]]) - .map(([url, urlText], i) => ({ - index: templateButtons.length + i + 1, - urlButton: { - displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', - url: !nullish(url) && url || !nullish(urlText) && urlText || '', - }, - })) || [] - )); - } - if (url2 || urlText2) { - if (!Array.isArray(url2)) url2 = [url2]; - if (!Array.isArray(urlText2)) urlText2 = [urlText2]; - templateButtons.push(...( - url2.map((v, i) => [v, urlText2[i]]) - .map(([url2, urlText2], i) => ({ - index: templateButtons.length + i + 1, - urlButton: { - displayText: !nullish(urlText2) && urlText2 || !nullish(url2) && url2 || '', - url: !nullish(url2) && url2 || !nullish(urlText2) && urlText2 || '', - }, - })) || [] - )); - } - if (buttons.length) { - if (!Array.isArray(buttons[0])) buttons = [buttons]; - templateButtons.push(...( - buttons.map(([text, id], index) => ({ - index: templateButtons.length + index + 1, - quickReplyButton: { - displayText: !nullish(text) && text || !nullish(id) && id || '', - id: !nullish(id) && id || !nullish(text) && text || '', - }, - })) || [] - )); - } - const message = { - ...options, - [buffer ? 'caption' : 'text']: text || '', - footer, - templateButtons, - ...(buffer ? - options.asLocation && /image/.test(type.mime) ? { - location: { - ...options, - jpegThumbnail: buffer, - }, - } : { - [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer, - } : {}), - }; - return await conn.sendMessage(jid, message, { - quoted, - upload: conn.waUploadToServer, - ...options, - }); - }, - enumerable: true, - }, - cMod: { - /** - * cMod - * @param {String} jid - * @param {import('@chatunity/baileys').proto.WebMessageInfo} message - * @param {String} text - * @param {String} sender - * @param {*} options - * @returns - */ - value(jid, message, text = '', sender = conn.user.jid, options = {}) { - if (options.mentions && !Array.isArray(options.mentions)) options.mentions = [options.mentions]; - const copy = message.toJSON(); - delete copy.message.messageContextInfo; - delete copy.message.senderKeyDistributionMessage; - const mtype = Object.keys(copy.message)[0]; - const msg = copy.message; - const content = msg[mtype]; - if (typeof content === 'string') msg[mtype] = text || content; - else if (content.caption) content.caption = text || content.caption; - else if (content.text) content.text = text || content.text; - if (typeof content !== 'string') { - msg[mtype] = {...content, ...options}; - msg[mtype].contextInfo = { - ...(content.contextInfo || {}), - mentionedJid: options.mentions || content.contextInfo?.mentionedJid || [], - }; - } - if (copy.participant) sender = copy.participant = sender || copy.participant; - else if (copy.key.participant) sender = copy.key.participant = sender || copy.key.participant; - if (copy.key.remoteJid.includes('@s.whatsapp.net')) sender = sender || copy.key.remoteJid; - else if (copy.key.remoteJid.includes('@broadcast')) sender = sender || copy.key.remoteJid; - copy.key.remoteJid = jid; - copy.key.fromMe = areJidsSameUser(sender, conn.user.id) || false; - return proto.WebMessageInfo.fromObject(copy); - }, - enumerable: true, - }, - copyNForward: { - /** - * Copia esatta e inoltra - * @param {String} jid - * @param {import('@chatunity/baileys').proto.WebMessageInfo} message - * @param {Boolean|Number} forwardingScore - * @param {Object} options - */ - async value(jid, message, forwardingScore = true, options = {}) { - let vtype; - if (options.readViewOnce && message.message.viewOnceMessage?.message) { - vtype = Object.keys(message.message.viewOnceMessage.message)[0]; - delete message.message.viewOnceMessage.message[vtype].viewOnce; - message.message = proto.Message.fromObject( - JSON.parse(JSON.stringify(message.message.viewOnceMessage.message)), - ); - message.message[vtype].contextInfo = message.message.viewOnceMessage.contextInfo; - } - const mtype = Object.keys(message.message)[0]; - let m = generateForwardMessageContent(message, !!forwardingScore); - const ctype = Object.keys(m)[0]; - if (forwardingScore && typeof forwardingScore === 'number' && forwardingScore > 1) m[ctype].contextInfo.forwardingScore += forwardingScore; - m[ctype].contextInfo = { - ...(message.message[mtype].contextInfo || {}), - ...(m[ctype].contextInfo || {}), - }; - m = generateWAMessageFromContent(jid, m, { - ...options, - userJid: conn.user.jid, - }); - await conn.relayMessage(jid, m.message, {messageId: m.key.id, additionalAttributes: {...options}}); - return m; - }, - enumerable: true, - }, - fakeReply: { - /** - * Risposte finte - * @param {String} jid - * @param {String|Object} text - * @param {String} fakeJid - * @param {String} fakeText - * @param {String} fakeGroupJid - * @param {String} options - */ - value(jid, text = '', fakeJid = this.user.jid, fakeText = '', fakeGroupJid, options) { - return conn.reply(jid, text, {key: {fromMe: areJidsSameUser(fakeJid, conn.user.id), participant: fakeJid, ...(fakeGroupJid ? {remoteJid: fakeGroupJid} : {})}, message: {conversation: fakeText}, ...options}); - }, - }, - downloadM: { - /** - * Scarica messaggio media - * @param {Object} m - * @param {String} type - * @param {fs.PathLike | fs.promises.FileHandle} saveToFile - * @return {Promise} - */ - async value(m, type, saveToFile) { - let filename; - if (!m || !(m.url || m.directPath)) return Buffer.alloc(0); - const stream = await downloadContentFromMessage(m, type); - let buffer = Buffer.from([]); - for await (const chunk of stream) { - buffer = Buffer.concat([buffer, chunk]); - } - if (saveToFile) ({filename} = await conn.getFile(buffer, true)); - return saveToFile && fs.existsSync(filename) ? filename : buffer; - }, - enumerable: true, - }, - parseMention: { - /** - * Analizza la stringa in mentionedJid(s) - * @param {String} text - * @return {Array} - */ - value(text = '') { - return [...text.matchAll(/@([0-9]{5,16}|0)/g)].map((v) => v[1] + '@s.whatsapp.net'); - }, - enumerable: true, - }, - getName: { - /** - * Ottieni nome dal jid - * @param {String} jid - * @param {Boolean} withoutContact - */ - value(jid = '', withoutContact = false) { - jid = conn.decodeJid(jid); - withoutContact = conn.withoutContact || withoutContact; - let v; - if (jid.endsWith('@g.us')) { - return new Promise(async (resolve) => { - v = conn.chats[jid] || {}; - if (!(v.name || v.subject)) v = await conn.groupMetadata(jid) || {}; - resolve(v.name || v.subject || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international')); - }); - } else { - v = jid === '0@s.whatsapp.net' ? { - jid, - vname: 'WhatsApp', - } : areJidsSameUser(jid, conn.user.id) ? - conn.user : - (conn.chats[jid] || {}); - } - return (withoutContact ? '' : v.name) || v.subject || v.vname || v.notify || v.verifiedName || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international'); - }, - enumerable: true, - }, - loadMessage: { - /** - * - * @param {String} messageID - * @returns {import('@chatunity/baileys').proto.WebMessageInfo} - */ - value(messageID) { - return Object.entries(conn.chats) - .filter(([_, {messages}]) => typeof messages === 'object') - .find(([_, {messages}]) => Object.entries(messages) - .find(([k, v]) => (k === messageID || v.key?.id === messageID))) - ?.[1].messages?.[messageID]; - }, - enumerable: true, - }, - sendGroupV4Invite: { - /** - * Invia invito gruppo V4 - * @param {String} jid - * @param {*} participant - * @param {String} inviteCode - * @param {Number} inviteExpiration - * @param {String} groupName - * @param {String} caption - * @param {Buffer} jpegThumbnail - * @param {*} options - */ - async value(jid, participant, inviteCode, inviteExpiration, groupName = 'unknown subject', caption = 'Invitation to join my WhatsApp group', jpegThumbnail, options = {}) { - const msg = proto.Message.fromObject({ - groupInviteMessage: proto.GroupInviteMessage.fromObject({ - inviteCode, - inviteExpiration: parseInt(inviteExpiration) || + new Date(new Date + (3 * 86400000)), - groupJid: jid, - groupName: (groupName ? groupName : await conn.getName(jid)) || null, - jpegThumbnail: Buffer.isBuffer(jpegThumbnail) ? jpegThumbnail : null, - caption, - }), - }); - const message = generateWAMessageFromContent(participant, msg, options); - await conn.relayMessage(participant, message.message, {messageId: message.key.id, additionalAttributes: {...options}}); - return message; - }, - enumerable: true, - }, - processMessageStubType: { - /** - * Processa MessageStubType - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m - */ - async value(m) { - if (!m.messageStubType) return; - const chat = conn.decodeJid(m.key.remoteJid || m.message?.senderKeyDistributionMessage?.groupId || ''); - if (!chat || chat === 'status@broadcast') return; - - // Decodifica LID in JID nei messageStubParameters - if (Array.isArray(m.messageStubParameters)) { - m.messageStubParameters = m.messageStubParameters.map(p => - typeof p === 'string' && p.endsWith('@lid') ? conn.decodeJid(p) : p - ); - } - - const emitGroupUpdate = (update) => { - conn.ev.emit('groups.update', [{ id: chat, ...update }]); - }; - - switch (m.messageStubType) { - case WAMessageStubType.REVOKE: - case WAMessageStubType.GROUP_CHANGE_INVITE_LINK: - if (Array.isArray(m.messageStubParameters) && m.messageStubParameters.length > 0) { - emitGroupUpdate({ revoke: m.messageStubParameters[0] }); - } - break; - case WAMessageStubType.GROUP_CHANGE_ICON: - if (Array.isArray(m.messageStubParameters) && m.messageStubParameters.length > 0) { - emitGroupUpdate({ icon: m.messageStubParameters[0] }); - } - break; - default: { - console.log({ - messageStubType: m.messageStubType, - messageStubParameters: m.messageStubParameters || [], - type: WAMessageStubType[m.messageStubType] - }); - break; - } - } - - const isGroup = chat.endsWith('@g.us'); - if (!isGroup) return; - - let chats = conn.chats[chat]; - if (!chats) chats = conn.chats[chat] = { id: chat }; - chats.isChats = true; - - const metadata = await conn.groupMetadata(chat).catch(() => null); - if (!metadata) return; - - chats.subject = metadata.subject; - chats.metadata = metadata; -} -}, - insertAllGroup: { - async value() { - const groups = await conn.groupFetchAllParticipating().catch((_) => null) || {}; - for (const group in groups) conn.chats[group] = {...(conn.chats[group] || {}), id: group, subject: groups[group].subject, isChats: true, metadata: groups[group]}; - return conn.chats; - }, - }, - pushMessage: { - /** - * pushMessage - * @param {import('@chatunity/baileys').proto.WebMessageInfo[]} m - */ - async value(m) { - if (!m) return; - if (!Array.isArray(m)) m = [m]; - for (const message of m) { - try { - // if (!(message instanceof proto.WebMessageInfo)) continue // https://github.com/adiwajshing/Baileys/pull/696/commits/6a2cb5a4139d8eb0a75c4c4ea7ed52adc0aec20f - if (!message) continue; - if (message.messageStubType && message.messageStubType != WAMessageStubType.CIPHERTEXT) conn.processMessageStubType(message).catch(console.error); - const _mtype = Object.keys(message.message || {}); - const mtype = (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(_mtype[0]) && _mtype[0]) || - (_mtype.length >= 3 && _mtype[1] !== 'messageContextInfo' && _mtype[1]) || - _mtype[_mtype.length - 1]; - const chat = conn.decodeJid(message.key.remoteJid || message.message?.senderKeyDistributionMessage?.groupId || ''); - if (message.message?.[mtype]?.contextInfo?.quotedMessage) { - /** - * @type {import('@chatunity/baileys').proto.IContextInfo} - */ - const context = message.message[mtype].contextInfo; - let participant = conn.decodeJid(context.participant); - const remoteJid = conn.decodeJid(context.remoteJid || participant); - /** - * @type {import('@chatunity/baileys').proto.IMessage} - * - */ - const quoted = message.message[mtype].contextInfo.quotedMessage; - if ((remoteJid && remoteJid !== 'status@broadcast') && quoted) { - let qMtype = Object.keys(quoted)[0]; - if (qMtype == 'conversation') { - quoted.extendedTextMessage = {text: quoted[qMtype]}; - delete quoted.conversation; - qMtype = 'extendedTextMessage'; - } - if (!quoted[qMtype].contextInfo) quoted[qMtype].contextInfo = {}; - // Normalizza i JID menzionati nel messaggio citato - quoted[qMtype].contextInfo.mentionedJid = (context.mentionedJid || quoted[qMtype].contextInfo.mentionedJid || []) - .map(jid => normalizeJid(jid, conn)) - .filter(Boolean); - - const isGroup = remoteJid.endsWith('g.us'); - if (isGroup && !participant) participant = remoteJid; - - // Normalizza tutti gli ID nel messaggio citato - const qM = { - key: { - remoteJid: normalizeJid(remoteJid, conn), - fromMe: areJidsSameUser(conn.user.jid, remoteJid), - id: context.stanzaId, - participant: normalizeJid(participant, conn), - }, - message: JSON.parse(JSON.stringify(quoted)), - ...(isGroup ? {participant: normalizeJid(participant, conn)} : {}), - }; - let qChats = conn.chats[participant]; - if (!qChats) qChats = conn.chats[participant] = {id: participant, isChats: !isGroup}; - if (!qChats.messages) qChats.messages = {}; - if (!qChats.messages[context.stanzaId] && !qM.key.fromMe) qChats.messages[context.stanzaId] = qM; - let qChatsMessages; - if ((qChatsMessages = Object.entries(qChats.messages)).length > 40) qChats.messages = Object.fromEntries(qChatsMessages.slice(30, qChatsMessages.length)); // Forse evita memory leak - } - } - if (!chat || chat === 'status@broadcast') continue; - const isGroup = chat.endsWith('@g.us'); - let chats = conn.chats[chat]; - if (!chats) { - if (isGroup) await conn.insertAllGroup().catch(console.error); - chats = conn.chats[chat] = {id: chat, isChats: true, ...(conn.chats[chat] || {})}; - } - let metadata; let sender; - if (isGroup) { - if (!chats.subject || !chats.metadata) { - metadata = await conn.groupMetadata(chat).catch((_) => ({})) || {}; - if (!chats.subject) chats.subject = metadata.subject || ''; - if (!chats.metadata) chats.metadata = metadata; - } - sender = conn.decodeJid(message.key?.fromMe && conn.user.id || message.participant || message.key?.participant || chat || ''); - if (sender !== chat) { - let chats = conn.chats[sender]; - if (!chats) chats = conn.chats[sender] = {id: sender}; - if (!chats.name) chats.name = message.pushName || chats.name || ''; - } - } else if (!chats.name) chats.name = message.pushName || chats.name || ''; - if (['senderKeyDistributionMessage', 'messageContextInfo'].includes(mtype)) continue; - chats.isChats = true; - if (!chats.messages) chats.messages = {}; - const fromMe = message.key.fromMe || areJidsSameUser(sender || chat, conn.user.id); - if (!['protocolMessage'].includes(mtype) && !fromMe && message.messageStubType != WAMessageStubType.CIPHERTEXT && message.message) { - delete message.message.messageContextInfo; - delete message.message.senderKeyDistributionMessage; - chats.messages[message.key.id] = JSON.parse(JSON.stringify(message, null, 2)); - let chatsMessages; - if ((chatsMessages = Object.entries(chats.messages)).length > 40) chats.messages = Object.fromEntries(chatsMessages.slice(30, chatsMessages.length)); - } - } catch (e) { - console.error(e); - } - } - }, - }, - serializeM: { - /** - * Serializza messaggio, per manipolarlo facilmente - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m - */ - value(m) { - return smsg(conn, m); - }, - }, - ...(typeof conn.chatRead !== 'function' ? { - chatRead: { - /** - * Leggi messaggio - * @param {String} jid - * @param {String|undefined|null} participant - * @param {String} messageID - */ - value(jid, participant = conn.user.jid, messageID) { - return conn.sendReadReceipt(jid, participant, [messageID]); - }, - enumerable: true, - }, - } : {}), - ...(typeof conn.setStatus !== 'function' ? { - setStatus: { - /** - * Imposta status bot - * @param {String} status - */ - value(status) { - return conn.query({ - tag: 'iq', - attrs: { - to: S_WHATSAPP_NET, - type: 'set', - xmlns: 'status', - }, - content: [ - { - tag: 'status', - attrs: {}, - content: Buffer.from(status, 'utf-8'), - }, - ], - }); - }, - enumerable: true, - }, - } : {}), - }); - if (sock.user?.id) sock.user.jid = sock.decodeJid(sock.user.id); - // Avvolgi groupMetadata per restituire sempre JID normalizzati per i partecipanti - try { - const _origGroupMetadata = conn.groupMetadata && conn.groupMetadata.bind(conn) - if (_origGroupMetadata) { - conn.groupMetadata = async function (jid) { - const meta = await _origGroupMetadata(jid).catch(() => null) - if (!meta) return meta - try { - // Normalizza ID gruppo - if (meta.id) meta.id = jidNormalizedUser(conn.decodeJid(meta.id)) - // Normalizza ID partecipanti - if (Array.isArray(meta.participants)) { - meta.participants = meta.participants.map(p => { - try { - const normalizedId = jidNormalizedUser(conn.decodeJid(p.id)) - return { ...p, id: normalizedId } - } catch (e) { - return p - } - }) - } - } catch (e) { - console.error('[simple.js] Errore nella normalizzazione groupMetadata:', e) - } - return meta - } - } - } catch (e) { - console.error('[simple.js] Fallito avvolgimento groupMetadata:', e) - } - store.bind(sock); - return sock; -} -/** - * Serializza messaggio - * @param {ReturnType} conn - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m - * @param {Boolean} hasParent - */ -export function smsg(conn, m, store) { - if (!m) return m; - let M = proto?.WebMessageInfo; - if (M && typeof M.fromObject === 'function') { - m = M.fromObject(m); - } else { - console.warn('proto.WebMessageInfo.fromObject non รจ una funzione, uso messaggio raw'); - } - m.conn = conn; - let protocolMessageKey; - - // Normalizza gli ID principali - if (m.key) { - const remoteJid = normalizeJid(m.key.remoteJid, conn); - Object.defineProperties(m, { - from: { - value: remoteJid, - enumerable: true - }, - chat: { - value: remoteJid, - enumerable: true - }, - id: { - value: m.key.id, - enumerable: true - }, - isGroup: { - value: remoteJid?.endsWith('@g.us') || false, - enumerable: true - }, - sender: { - value: normalizeJid(m.key.fromMe && conn.user?.id || m.key.participant || remoteJid, conn), - enumerable: true - } - }); - } - - // Gestione del messaggio - if (m.message) { - if (m.mtype == 'protocolMessage' && m.msg.key) { - protocolMessageKey = m.msg.key; - if (protocolMessageKey == 'status@broadcast') protocolMessageKey.remoteJid = m.chat; - if (!protocolMessageKey.participant || protocolMessageKey.participant == 'status_me') protocolMessageKey.participant = m.sender; - protocolMessageKey.fromMe = normalizeJid(protocolMessageKey.participant, conn) === normalizeJid(conn.user.id, conn); - if (!protocolMessageKey.fromMe && protocolMessageKey.remoteJid === normalizeJid(conn.user.id, conn)) protocolMessageKey.remoteJid = m.sender; - } - - // Decodifica LID in JID nei messageStubParameters - if (Array.isArray(m.messageStubParameters)) { - m.messageStubParameters = m.messageStubParameters.map(param => { - if (typeof param === 'string' && param.endsWith('@lid')) { - return conn.decodeJid(param); - } - return param; - }); - } - - // Gestione menzioni e quoted - if (m.message.mentionedJid) { - m.mentionedJid = m.message.mentionedJid.map(jid => normalizeJid(jid, conn)); - } - - if (m.quoted) { - if (!m.quoted.mediaMessage) delete m.quoted.download; - if (m.quoted.key && m.quoted.key.participant) { - m.quoted.sender = normalizeJid(m.quoted.key.participant, conn); - } - } - } - if (!m.mediaMessage) delete m.download; - - try { - if (protocolMessageKey && m.mtype == 'protocolMessage') conn.ev.emit('message.delete', protocolMessageKey); - } catch (e) { - console.error(e); - } - return m; -} - -// https://github.com/Nurutomo/wabot-aq/issues/490 -export function serialize() { - const MediaType = ['imageMessage', 'videoMessage', 'audioMessage', 'stickerMessage', 'documentMessage']; - return Object.defineProperties(proto.WebMessageInfo.prototype, { - conn: { - value: undefined, - enumerable: false, - writable: true, - }, - id: { - get() { - return this.key?.id; - }, - }, - isBaileys: { - get() { - return (this?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender)) && this.id.startsWith('3EB0') && (this.id.length === 20 || this.id.length === 22 || this.id.length === 12) || false - }, - }, - chat: { - get() { - const senderKeyDistributionMessage = this.message?.senderKeyDistributionMessage?.groupId; - return ( - this.key?.remoteJid || - (senderKeyDistributionMessage && - senderKeyDistributionMessage !== 'status@broadcast' - ) || '' - ).decodeJid(); - }, - }, - isGroup: { - get() { - return this.chat.endsWith('@g.us'); - }, - enumerable: true, - }, - sender: { - get() { - return this.conn?.decodeJid(this.key?.fromMe && this.conn?.user.id || this.participant || this.key.participant || this.chat || '') - }, - enumerable: true - }, - fromMe: { - get() { - return this.key?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender) || false - } - }, - mtype: { - get() { - if (!this.message) return ''; - const type = Object.keys(this.message); - return (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(type[0]) && type[0]) || // A volte messaggio all'inizio - (type.length >= 3 && type[1] !== 'messageContextInfo' && type[1]) || // A volte messaggio in mezzo se lunghezza mtype >= 3 - type[type.length - 1]; // Caso comune - }, - enumerable: true, - }, - msg: { - get() { - if (!this.message) return null; - return this.message[this.mtype]; - }, - }, - mediaMessage: { - get() { - if (!this.message) return null; - const Message = ((this.msg?.url || this.msg?.directPath) ? {...this.message} : extractMessageContent(this.message)) || null; - if (!Message) return null; - const mtype = Object.keys(Message)[0]; - return MediaType.includes(mtype) ? Message : null; - }, - enumerable: true, - }, - mediaType: { - get() { - let message; - if (!(message = this.mediaMessage)) return null; - return Object.keys(message)[0]; - }, - enumerable: true, - }, - quoted: { - get() { - /** - * @type {ReturnType} - */ - const self = this; - const msg = self.msg; - const contextInfo = msg?.contextInfo; - const quoted = contextInfo?.quotedMessage; - if (!msg || !contextInfo || !quoted) return null; - const type = Object.keys(quoted)[0]; - const q = quoted[type]; - const text = typeof q === 'string' ? q : q.text; - return Object.defineProperties(JSON.parse(JSON.stringify(typeof q === 'string' ? {text: q} : q)), { - mtype: { - get() { - return type; - }, - enumerable: true, - }, - mediaMessage: { - get() { - const Message = ((q.url || q.directPath) ? {...quoted} : extractMessageContent(quoted)) || null; - if (!Message) return null; - const mtype = Object.keys(Message)[0]; - return MediaType.includes(mtype) ? Message : null; - }, - enumerable: true, - }, - mediaType: { - get() { - let message; - if (!(message = this.mediaMessage)) return null; - return Object.keys(message)[0]; - }, - enumerable: true, - }, - id: { - get() { - return contextInfo.stanzaId; - }, - enumerable: true, - }, - chat: { - get() { - return contextInfo.remoteJid || self.chat; - }, - enumerable: true, - }, - isBaileys: { - get() { - return (this?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender)) && this.id.startsWith('3EB0') && (this.id.length === 20 || this.id.length === 22 || this.id.length === 12) || false - }, - enumerable: true, - }, - sender: { - get() { - return (contextInfo.participant || this.chat || '').decodeJid(); - }, - enumerable: true, - }, - fromMe: { - get() { - return areJidsSameUser(this.sender, self.conn?.user.jid); - }, - enumerable: true, - }, - text: { - get() { - return text || this.caption || this.contentText || this.selectedDisplayText || ''; - }, - enumerable: true, - }, - mentionedJid: { - get() { - return q.contextInfo?.mentionedJid || self.getQuotedObj()?.mentionedJid || []; - }, - enumerable: true, - }, - name: { - get() { - const sender = this.sender; - return sender ? self.conn?.getName(sender) : null; - }, - enumerable: true, - - }, - vM: { - get() { - return proto.WebMessageInfo.fromObject({ - key: { - fromMe: this.fromMe, - remoteJid: this.chat, - id: this.id, - }, - message: quoted, - ...(self.isGroup ? {participant: this.sender} : {}), - }); - }, - }, - fakeObj: { - get() { - return this.vM; - }, - }, - download: { - value(saveToFile = false) { - const mtype = this.mediaType; - return self.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile); - }, - enumerable: true, - configurable: true, - }, - reply: { - /** - * Rispondi al messaggio citato - * @param {String|Object} text - * @param {String|false} chatId - * @param {Object} options - */ - value(text, chatId, options) { - return self.conn?.reply(chatId ? chatId : this.chat, text, this.vM, options); - }, - enumerable: true, - }, - copy: { - /** - * Copia messaggio citato - */ - value() { - const M = proto.WebMessageInfo; - return smsg(conn, M.fromObject(M.toObject(this.vM))); - }, - enumerable: true, - }, - forward: { - /** - * Inoltra messaggio citato - * @param {String} jid - * @param {Boolean} forceForward - */ - value(jid, force = false, options) { - return self.conn?.sendMessage(jid, { - forward: this.vM, force, ...options, - }, {...options}); - }, - enumerable: true, - }, - copyNForward: { - /** - * Inoltra esatto messaggio citato - * @param {String} jid - * @param {Boolean|Number} forceForward - * @param {Object} options - */ - value(jid, forceForward = false, options) { - return self.conn?.copyNForward(jid, this.vM, forceForward, options); - }, - enumerable: true, - - }, - cMod: { - /** - * Modifica messaggio citato - * @param {String} jid - * @param {String} text - * @param {String} sender - * @param {Object} options - */ - value(jid, text = '', sender = this.sender, options = {}) { - return self.conn?.cMod(jid, this.vM, text, sender, options) - }, - enumerable: true, - - }, - delete: { - /** - * Elimina messaggio citato - */ - value() { - return self.conn?.sendMessage(this.chat, { delete: this.vM.key }) - }, - enumerable: true, - - }, - //react - react: { - value(text) { - return self.conn?.sendMessage(this.chat, { - react: { - text, - key: this.vM.key - } - }) - }, - enumerable: true, - } - // - }) - }, - enumerable: true - }, - _text: { - value: null, - writable: true, - }, - text: { - get() { - const msg = this.msg - const text = (typeof msg === 'string' ? msg : msg?.text) || msg?.caption || msg?.contentText || '' - return typeof this._text === 'string' ? this._text : '' || (typeof text === 'string' ? text : ( - text?.selectedDisplayText || - text?.hydratedTemplate?.hydratedContentText || - text - )) || '' - }, - set(str) { - return this._text = str - }, - enumerable: true - }, - mentionedJid: { - get() { - return this.msg?.contextInfo?.mentionedJid?.length && this.msg.contextInfo.mentionedJid || [] - }, - enumerable: true - }, - name: { - get() { - return !nullish(this.pushName) && this.pushName || this.conn?.getName(this.sender) - }, - enumerable: true - }, - download: { - value(saveToFile = false) { - const mtype = this.mediaType - return this.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile) - }, - enumerable: true, - configurable: true - }, - reply: { - value(text, chatId, options) { - return this.conn?.reply(chatId ? chatId : this.chat, text, this, options) - } - }, - copy: { - value() { - const M = proto.WebMessageInfo - return smsg(this.conn, M.fromObject(M.toObject(this))) - }, - enumerable: true - }, - forward: { - value(jid, force = false, options = {}) { - return this.conn?.sendMessage(jid, { - forward: this, force, ...options - }, { ...options }) - }, - enumerable: true - }, - copyNForward: { - value(jid, forceForward = false, options = {}) { - return this.conn?.copyNForward(jid, this, forceForward, options) - }, - enumerable: true - }, - cMod: { - value(jid, text = '', sender = this.sender, options = {}) { - return this.conn?.cMod(jid, this, text, sender, options) - }, - enumerable: true - }, - getQuotedObj: { - value() { - if (!this.quoted.id) return null - const q = proto.WebMessageInfo.fromObject(this.conn?.loadMessage(this.quoted.id) || this.quoted.vM) - return smsg(this.conn, q) - }, - enumerable: true - }, - getQuotedMessage: { - get() { - return this.getQuotedObj - } - }, - delete: { - value() { - return this.conn?.sendMessage(this.chat, { delete: this.key }) - }, - enumerable: true - }, - //react - react: { - value(text) { - return this.conn?.sendMessage(this.chat, { - react: { - text, - key: this.key - } - }) - }, - enumerable: true - } - // - }) -} - -export function logic(check, inp, out) { - if (inp.length !== out.length) throw new Error('Input e Output devono avere la stessa lunghezza'); - for (const i in inp) if (util.isDeepStrictEqual(check, inp[i])) return out[i]; - return null; -} - -export function protoType() { - Buffer.prototype.toArrayBuffer = function toArrayBufferV2() { - const ab = new ArrayBuffer(this.length); - const view = new Uint8Array(ab); - for (let i = 0; i < this.length; ++i) { - view[i] = this[i]; - } - return ab; - }; - /** - * @return {ArrayBuffer} - */ - Buffer.prototype.toArrayBufferV2 = function toArrayBuffer() { - return this.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength); - }; - /** - * @return {Buffer} - */ - ArrayBuffer.prototype.toBuffer = function toBuffer() { - return Buffer.from(new Uint8Array(this)); - }; - // /** - // * @returns {String} - // */ - // Buffer.prototype.toUtilFormat = ArrayBuffer.prototype.toUtilFormat = Object.prototype.toUtilFormat = Array.prototype.toUtilFormat = function toUtilFormat() { - // return util.format(this) - // } - Uint8Array.prototype.getFileType = ArrayBuffer.prototype.getFileType = Buffer.prototype.getFileType = async function getFileType() { - return await fileTypeFromBuffer(this); - }; - /** - * @returns {Boolean} - */ - String.prototype.isNumber = Number.prototype.isNumber = isNumber; - /** - * - * @return {String} - */ - String.prototype.capitalize = function capitalize() { - return this.charAt(0).toUpperCase() + this.slice(1, this.length); - }; - /** - * @return {String} - */ - String.prototype.capitalizeV2 = function capitalizeV2() { - const str = this.split(' '); - return str.map((v) => v.capitalize()).join(' '); - }; - String.prototype.decodeJid = function decodeJid() { - if (/:\d+@/gi.test(this)) { - const decode = jidDecode(this) || {}; - return (decode.user && decode.server && decode.user + '@' + decode.server || this).trim(); - } else return this.trim(); - }; - /** - * numero deve essere in millisecondi - * @return {string} - */ - Number.prototype.toTimeString = function toTimeString() { - // const milliseconds = this % 1000 - const seconds = Math.floor((this / 1000) % 60); - const minutes = Math.floor((this / (60 * 1000)) % 60); - const hours = Math.floor((this / (60 * 60 * 1000)) % 24); - const days = Math.floor((this / (24 * 60 * 60 * 1000))); - return ( - (days ? `${days} giorno/i ` : '') + - (hours ? `${hours} ora/e ` : '') + - (minutes ? `${minutes} minuto/i ` : '') + - (seconds ? `${seconds} secondo/i` : '') - ).trim(); - }; - Number.prototype.getRandom = String.prototype.getRandom = Array.prototype.getRandom = getRandom; -} - - -function isNumber() { - const int = parseInt(this); - return typeof int === 'number' && !isNaN(int); -} - -function getRandom() { - if (Array.isArray(this) || this instanceof String) return this[Math.floor(Math.random() * this.length)]; - return Math.floor(Math.random() * this); -} - - -/** - * ?? - * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator - * @return {boolean} - */ -function nullish(args) { - return !(args !== null && args !== undefined); -} \ No newline at end of file From 88d3c3f8bab096c99b79d098e4fe862528ceeead Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:57:51 +0100 Subject: [PATCH 12/43] Update simple.js --- lib/simple.js | 472 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) diff --git a/lib/simple.js b/lib/simple.js index e69de29bb..4cbb09033 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -0,0 +1,472 @@ +/* + * Crediti: + * - https://github.com/realvare/varebot + * - https://github.com/realvare/based +*/ + +import path from 'path'; +import { toAudio } from './converter.js'; +import chalk from 'chalk'; +import fetch from 'node-fetch'; +import PhoneNumber from 'awesome-phonenumber'; +import fs from 'fs'; +import util from 'util'; +import { fileTypeFromBuffer } from 'file-type'; +import { format } from 'util'; +import { fileURLToPath } from 'url'; +import store from './store.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const { + makeWASocket: _makeWASocket, + makeWALegacySocket, + proto, + downloadContentFromMessage, + jidDecode, + areJidsSameUser, + generateWAMessage, + generateForwardMessageContent, + generateWAMessageFromContent, + WAMessageStubType, + extractMessageContent, + WA_DEFAULT_EPHEMERAL, + prepareWAMessageMedia, + jidNormalizedUser +} = await import('@whiskeysockets/baileys'); + +/** + * Funzione helper per normalizzare i JID + * @param {string} jid - Il JID da normalizzare + * @param {object} conn - La connessione socket + * @returns {string|null} Il JID normalizzato o null + */ +function normalizeJid(jid, conn) { + if (!jid) return null; + const normalized = jidNormalizedUser(conn.decodeJid(jid)); + return normalized; +} + +/** + * Crea un socket zozzap con opzioni aggiuntive. + * @param {object} connectionOptions - Opzioni di connessione + * @param {object} [options={}] - Opzioni aggiuntive + * @returns {object} Il socket WhatsApp configurato + */ +export function makeWASocket(connectionOptions, options = {}) { + const conn = (global.opts['legacy'] ? makeWALegacySocket : _makeWASocket)(connectionOptions); + const sock = Object.defineProperties(conn, { + chats: { + value: { ...(options.chats || {}) }, + writable: true, + }, + decodeJid: { + value(jid) { + if (!jid || typeof jid !== 'string') return (!nullish(jid) && jid) || null; + return jid.decodeJid(); + }, + }, + logger: { + get() { + return { + info(...args) { + console.log( + chalk.bold.bgRgb(51, 204, 51)('INFO '), + `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, + chalk.cyan(format(...args)), + ); + }, + error(...args) { + console.log( + chalk.bold.bgRgb(247, 38, 33)('ERROR '), + `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, + chalk.rgb(255, 38, 0)(format(...args)), + ); + }, + warn(...args) { + console.log( + chalk.bold.bgRgb(255, 153, 0)('WARNING '), + `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, + chalk.redBright(format(...args)), + ); + }, + trace(...args) { + console.log( + chalk.grey('TRACE '), + `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, + chalk.white(format(...args)), + ); + }, + debug(...args) { + console.log( + chalk.bold.bgRgb(66, 167, 245)('DEBUG '), + `[${chalk.rgb(255, 255, 255)(new Date().toUTCString())}]:`, + chalk.white(format(...args)), + ); + }, + }; + }, + enumerable: true, + }, + sendNyanCat: { + async value(jid, text = '', buffer, title, body, url, quoted, options) { + if (buffer) { + try { + (type = await conn.getFile(buffer), buffer = type.data); + } catch { + buffer = buffer; + } + } + const prep = generateWAMessageFromContent(jid, {extendedTextMessage: {text: text, contextInfo: {externalAdReply: {title: title, body: body, thumbnail: buffer, sourceUrl: url}, mentionedJid: await conn.parseMention(text)}}}, {quoted: quoted}); + return conn.relayMessage(jid, prep.message, {messageId: prep.key.id}); + }, + }, + sendPayment: { + async value(jid, amount, text, quoted, options) { + conn.relayMessage(jid, { + requestPaymentMessage: { + currencyCodeIso4217: 'PEN', + amount1000: amount, + requestFrom: null, + noteMessage: { + extendedTextMessage: { + text: text, + contextInfo: { + externalAdReply: { + showAdAttribution: true, + }, mentionedJid: conn.parseMention(text)}}}}}, {}); + }, + }, + getFile: { + /** + * Ottiene il buffer hehe + * @param {fs.PathLike} PATH + * @param {Boolean} saveToFile + */ + async value(PATH, saveToFile = false) { + let res; let filename; + const data = Buffer.isBuffer(PATH) ? PATH + : PATH instanceof ArrayBuffer ? Buffer.from(PATH) + : /^data:.*?\/.*?;base64,/i.test(PATH) ? Buffer.from(PATH.split`,`[1], 'base64') + : /^https?:\/\//.test(PATH) ? Buffer.from(await (res = await fetch(PATH)).arrayBuffer()) + : fs.existsSync(PATH) ? (filename = PATH, fs.readFileSync(PATH)) + : typeof PATH === 'string' ? Buffer.from(PATH) + : Buffer.alloc(0); + + if (!Buffer.isBuffer(data)) throw new TypeError('Result is not a buffer'); + const type = await fileTypeFromBuffer(data) || { + mime: 'application/octet-stream', + ext: '.bin', + }; + if (data && saveToFile && !filename) (filename = path.join(__dirname, '../tmp/' + new Date * 1 + '.' + type.ext), await fs.promises.writeFile(filename, data)); + return { + res, + filename, + ...type, + data, + deleteFile() { + return filename && fs.promises.unlink(filename); + }, + }; + }, + enumerable: true, + }, + waitEvent: { + /** + * Attende un evento + * @param {String} eventName + * @param {Boolean} is + * @param {Number} maxTries + */ + value(eventName, is = () => true, maxTries = 25) { // Non so perchรฉ esiste? + return new Promise((resolve, reject) => { + let tries = 0; + const on = (...args) => { + if (++tries > maxTries) reject('Max tries reached'); + else if (is()) { + conn.ev.off(eventName, on); + resolve(...args); + } + }; + conn.ev.on(eventName, on); + }); + }, + }, + relayWAMessage: { + async value(pesanfull) { + if (pesanfull.message.audioMessage) { + await conn.sendPresenceUpdate('recording', pesanfull.key.remoteJid); + } else { + await conn.sendPresenceUpdate('composing', pesanfull.key.remoteJid); + } + const mekirim = await conn.relayMessage(pesanfull.key.remoteJid, pesanfull.message, {messageId: pesanfull.key.id}); + conn.ev.emit('messages.upsert', {messages: [pesanfull], type: 'append'}); + return mekirim; + }, + }, + sendFile: { + /** + * Invia un media/file con specificatore di tipo automatico + * @param {String} jid + * @param {String|Buffer} path + * @param {String} filename + * @param {String} caption + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted + * @param {Boolean} ptt + * @param {Object} options + */ + async value(jid, path, filename = '', caption = '', quoted, ptt = false, options = {}) { + const type = await conn.getFile(path, true); + let {res, data: file, filename: pathFile} = type; + if (res && res.status !== 200 || file.length <= 65536) { + try { + throw {json: JSON.parse(file.toString())}; + } catch (e) { + if (e.json) throw e.json; + } + } + const opt = {}; + if (quoted) opt.quoted = quoted; + if (!type) options.asDocument = true; + let mtype = ''; let mimetype = options.mimetype || type.mime; let convert; + if (/webp/.test(type.mime) || (/image/.test(type.mime) && options.asSticker)) mtype = 'sticker'; + else if (/image/.test(type.mime) || (/webp/.test(type.mime) && options.asImage)) mtype = 'image'; + else if (/video/.test(type.mime)) mtype = 'video'; + else if (/audio/.test(type.mime)) { + ( + convert = await toAudio(file, type.ext), + file = convert.data, + pathFile = convert.filename, + mtype = 'audio', + mimetype = options.mimetype || 'audio/mpeg; codecs=opus' + ); + } else mtype = 'document'; + if (options.asDocument) mtype = 'document'; + delete options.asSticker; + delete options.asLocation; + delete options.asVideo; + delete options.asDocument; + delete options.asImage; + + const message = { + ...options, + caption, + ptt, + [mtype]: {url: pathFile}, + mimetype, + fileName: filename || pathFile.split('/').pop(), + }; + let m; + try { + m = await conn.sendMessage(jid, message, {...opt, ...options}); + } catch (e) { + console.error(e); + m = null; + } finally { + if (!m) m = await conn.sendMessage(jid, {...message, [mtype]: file}, {...opt, ...options}); + file = null; // Rilascio della memoria + return m; + } + }, + enumerable: true, + }, + sendContact: { + /** + * Invia un contatto + * @param {String} jid + * @param {String[][]|String[]} data + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted + * @param {Object} options + */ + async value(jid, data, quoted, options) { + if (!Array.isArray(data[0]) && typeof data[0] === 'string') data = [data]; + const contacts = []; + for (let [number, name] of data) { + number = number.replace(/[^0-9]/g, ''); + const njid = number + '@s.whatsapp.net'; + const biz = await conn.getBusinessProfile(njid).catch((_) => null) || {}; + const vcard = ` +BEGIN:VCARD +VERSION:3.0 +N:;${name.replace(/\n/g, '\\n')};;; +FN:${name.replace(/\n/g, '\\n')} +TEL;type=CELL;type=VOICE;waid=${number}:${PhoneNumber('+' + number).getNumber('international')}${biz.description ? ` +X-WA-BIZ-NAME:${(conn.chats[njid]?.vname || conn.getName(njid) || name).replace(/\n/, '\\n')} +X-WA-BIZ-DESCRIPTION:${biz.description.replace(/\n/g, '\\n')} +`.trim() : ''} +END:VCARD + `.trim(); + contacts.push({vcard, displayName: name}); + } + return await conn.sendMessage(jid, { + ...options, + contacts: { + ...options, + displayName: (contacts.length >= 2 ? `${contacts.length} kontak` : contacts[0].displayName) || null, + contacts, + }, + }, {quoted, ...options}); + }, + enumerable: true, + }, +reply: { + /** + * Rispondi a un messaggio + * @param {String} jid + * @param {String|Buffer} text + * @param {import('@adiwajshing/baileys').proto.WebMessageInfo} quoted + * @param {Object} options + */ + value(jid, text = '', quoted, options) { + + return Buffer.isBuffer(text) ? conn.sendFile(jid, text, 'file', '', quoted, false, options) : conn.sendMessage(jid, { ...options, text }, { quoted, ...options }) + } + }, + // sendButton: { + /** + * Invia un pulsante + * @param {String} jid + * @param {String} text + * @param {String} footer + * @param {Buffer} buffer + * @param {String[] | String[][]} buttons + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted + * @param {Object} options + */ + /* async value(jid, text = '', footer = '', buffer, buttons, quoted, options) { + let type + if (Array.isArray(buffer)) (options = quoted, quoted = buttons, buttons = buffer, buffer = null) + else if (buffer) try { (type = await conn.getFile(buffer), buffer = type.data) } catch { buffer = null } + if (!Array.isArray(buttons[0]) && typeof buttons[0] === 'string') buttons = [buttons] + if (!options) options = {} + let message = { + ...options, + [buffer ? 'caption' : 'text']: text || '', + footer, + buttons: buttons.map(btn => ({ + buttonId: !nullish(btn[1]) && btn[1] || !nullish(btn[0]) && btn[0] || '', + buttonText: { + displayText: !nullish(btn[0]) && btn[0] || !nullish(btn[1]) && btn[1] || '' + } + })), + ...(buffer ? + options.asLocation && /image/.test(type.mime) ? { + location: { + ...options, + jpegThumbnail: buffer + } + } : { + [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer + } : {}) + } + + return await conn.sendMessage(jid, message, { + quoted, + upload: conn.waUploadToServer, + ...options + }) + }, + enumerable: true + }, + */ + + //-- new +sendButton: { + async value(jid, text = '', footer = '', buffer, buttons, copy, urls, list, quoted, options) { + let img, video + + + if (/^https?:\/\//i.test(buffer)) { + try { + // Ottieni il tipo MIME dall'URL + const response = await fetch(buffer) + const contentType = response.headers.get('content-type') + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) + } else { + console.error("Tipo MIME non compatibile:", contentType) + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error) + } + } else { + + try { + const type = await conn.getFile(buffer) + if (/^image\//i.test(type.mime)) { + img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(type.mime)) { + video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) + } + } catch (error) { + console.error("Errore nell'ottenere il tipo di file:", error); + } + } + +const dynamicButtons = [] + +// Pulsanti di tipo quick_reply +if (buttons && Array.isArray(buttons)) { + dynamicButtons.push(...buttons.map(btn => ({ + name: 'quick_reply', + buttonParamsJson: JSON.stringify({ + display_text: btn[0], + id: btn[1] + }) + }))); +} + +// Pulsanti di copia +if (copy && Array.isArray(copy)) { + dynamicButtons.push(...copy.map(copyBtn => ({ + name: 'cta_copy', + buttonParamsJson: JSON.stringify({ + display_text: copyBtn[0] || 'Copy', + copy_code: copyBtn[1] + }) + }))); +} + +// Pulsanti di URL +if (urls && Array.isArray(urls)) { + urls.forEach(url => { + dynamicButtons.push({ + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: url[0], + url: url[1], + merchant_url: url[1] + }) + }); + }); +} + +// Pulsanti di lista +if (list && Array.isArray(list)) { + list.forEach(lister => { + dynamicButtons.push({ + name: 'single_select', + buttonParamsJson: JSON.stringify({ + title: lister[0], + sections: lister[1] + }) + }) + }) + } + const interactiveMessage = { + body: { text: text }, + footer: { text: footer }, + header: { + hasMediaAttachment: false, + imageMessage: img ? img.imageMessage : null, + videoMessage: video ? video.videoMessage : null + }, + nativeFlowMessage: { + buttons: dynamicButtons, + messageParamsJson: '' + } + } + + + let msgL = generateWAMessageFromContent(jid, { \ No newline at end of file From 686a0584255162d33b1ac00736c6c7102a20864a Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 13:59:03 +0100 Subject: [PATCH 13/43] Update simple.js --- lib/simple.js | 403 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 402 insertions(+), 1 deletion(-) diff --git a/lib/simple.js b/lib/simple.js index 4cbb09033..f2c29a5c6 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -469,4 +469,405 @@ if (list && Array.isArray(list)) { } - let msgL = generateWAMessageFromContent(jid, { \ No newline at end of file + let msgL = generateWAMessageFromContent(jid, { + viewOnceMessage: { + message: { + interactiveMessage } } }, { userJid: conn.user.jid, quoted }) + + conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) + + } +}, + +sendAlbumMessage: { + async value(jid, medias, caption = "", quoted = null) { + let img, video; + + const album = generateWAMessageFromContent(jid, { + albumMessage: { + expectedImageCount: medias.filter(media => media.type === "image").length, + expectedVideoCount: medias.filter(media => media.type === "video").length, + ...(quoted ? { + contextInfo: { + remoteJid: quoted.key.remoteJid, + fromMe: quoted.key.fromMe, + stanzaId: quoted.key.id, + participant: quoted.key.participant || quoted.key.remoteJid, + quotedMessage: quoted.message + } + } : {}) + } + }, { quoted: quoted }); + + await conn.relayMessage(album.key.remoteJid, album.message, { + messageId: album.key.id + }); + + for (const media of medias) { + const { type, data } = media; + + if (/^https?:\/\//i.test(data.url)) { + try { + const response = await fetch(data.url); + const contentType = response.headers.get('content-type'); + + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ image: { url: data.url } }, { upload: conn.waUploadToServer }); + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ video: { url: data.url } }, { upload: conn.waUploadToServer }); + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error); + } + } + + const mediaMessage = await generateWAMessage(album.key.remoteJid, { + [type]: data, + ...(media === medias[0] ? { caption } : {}) + }, { + upload: conn.waUploadToServer + }); + + mediaMessage.message.messageContextInfo = { + messageAssociation: { + associationType: 1, + parentMessageKey: album.key + } + }; + + await conn.relayMessage(mediaMessage.key.remoteJid, mediaMessage.message, { + messageId: mediaMessage.key.id + }); + } + + return album; + } +}, + +/** + * Invia nativeFlowMessage + */ + sendNCarousel: { + async value(jid, text = '', footer = '', buffer, buttons, copy, urls, list, quoted, options) { + let img, video; + if (buffer) { + if (/^https?:\/\//i.test(buffer)) { + try { + const response = await fetch(buffer); + const contentType = response.headers.get('content-type'); + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ + image: { + url: buffer + } + }, { + upload: conn.waUploadToServer, + ...options + }); + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ + video: { + url: buffer + } + }, { + upload: conn.waUploadToServer, + ...options + }); + } else { + console.error("Tipo MIME non compatibile:", contentType); + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error); + } + } else { + try { + const type = await conn.getFile(buffer); + if (/^image\//i.test(type.mime)) { + img = await prepareWAMessageMedia({ + image: (/^https?:\/\//i.test(buffer)) ? { + url: buffer + } : (type && type?.data) + }, { + upload: conn.waUploadToServer, + ...options + }); + } else if (/^video\//i.test(type.mime)) { + video = await prepareWAMessageMedia({ + video: (/^https?:\/\//i.test(buffer)) ? { + url: buffer + } : (type && type?.data) + }, { + upload: conn.waUploadToServer, + ...options + }); + } + } catch (error) { + console.error("Errore nell'ottenere il tipo di file:", error); + } + } + } + const dynamicButtons = buttons.map(btn => ({ + name: 'quick_reply', + buttonParamsJson: JSON.stringify({ + display_text: btn[0], + id: btn[1] + }), + })); + dynamicButtons.push( + (copy && (typeof copy === 'string' || typeof copy === 'number')) ? { + name: 'cta_copy', + buttonParamsJson: JSON.stringify({ + display_text: 'Copy', + copy_code: copy + }) + } : null) + + urls?.forEach(url => { + dynamicButtons.push({ + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: url[0], + url: url[1], + merchant_url: url[1] + }) + }); + }); + list?.forEach(lister => { + dynamicButtons.push({ + name: 'single_select', + buttonParamsJson: JSON.stringify({ + title: lister[0], + sections: lister[1] + }) + }); + }) + const interactiveMessage = { + body: { + text: text || '' + }, + footer: { + text: footer || wm + }, + header: { + hasMediaAttachment: img?.imageMessage || video?.videoMessage ? true : false, + imageMessage: img?.imageMessage || null, + videoMessage: video?.videoMessage || null + }, + nativeFlowMessage: { + buttons: dynamicButtons.filter(Boolean), + messageParamsJson: '' + }, + ...Object.assign({ + mentions: typeof text === 'string' ? conn.parseMention(text || '@0') : [], + contextInfo: { + mentionedJid: typeof text === 'string' ? conn.parseMention(text || '@0') : [], + } + }, { + ...(options || {}), + ...(conn.temareply?.contextInfo && { + contextInfo: { + ...(options?.contextInfo || {}), + ...conn.temareply?.contextInfo, + externalAdReply: { + ...(options?.contextInfo?.externalAdReply || {}), + ...conn.temareply?.contextInfo?.externalAdReply, + }, + }, + }) + }) + }; + const messageContent = proto.Message.fromObject({ + viewOnceMessage: { + message: { + messageContextInfo: { + deviceListMetadata: {}, + deviceListMetadataVersion: 2 + }, + interactiveMessage + } + } + }); + const msgs = await generateWAMessageFromContent(jid, messageContent, { + userJid: conn.user.jid, + quoted: quoted, + upload: conn.waUploadToServer, + ephemeralExpiration: WA_DEFAULT_EPHEMERAL + }); + await conn.relayMessage(jid, msgs.message, { + messageId: msgs.key.id + }); + } + }, + +/** + * Invia carouselMessage + */ + sendCarousel: { + async value(jid, text = '', footer = '', messages, quoted, options = {}) { + try { + if (messages.length > 1) { + const cards = await Promise.all(messages.map(async ([text = '', footer = '', buffer, buttons, copy, urls, list]) => { + let img, video; + + if (/^https?:\/\//i.test(buffer)) { + try { + const response = await fetch(buffer); + const contentType = response.headers.get('content-type'); + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer, ...options }); + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer, ...options }); + } else { + console.error("Tipo MIME non compatibile:", contentType); + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error); + } + } else { + try { + const type = await conn.getFile(buffer); + if (/^image\//i.test(type.mime)) { + img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer, ...options }); + } else if (/^video\//i.test(type.mime)) { + video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer, ...options }); + } + } catch (error) { + console.error("Errore nell'ottenere il tipo di file:", error); + } + } + + const dynamicButtons = []; + if (buttons && Array.isArray(buttons)) { + buttons.forEach(btn => { + dynamicButtons.push({ + name: 'quick_reply', + buttonParamsJson: JSON.stringify({ + display_text: btn[0], + id: btn[1] + }) + }); + }); + } + + if (copy && Array.isArray(copy)) { + copy.forEach(copyBtn => { + dynamicButtons.push({ + name: 'cta_copy', + buttonParamsJson: JSON.stringify({ + display_text: copyBtn[0] || 'Copy', + copy_code: copyBtn[1] + }) + }); + }); + } + + if (urls && Array.isArray(urls)) { + urls.forEach(url => { + dynamicButtons.push({ + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: url[0], + url: url[1], + merchant_url: url[1] + }) + }); + }); + } + + if (list && Array.isArray(list)) { + list.forEach(lister => { + dynamicButtons.push({ + name: 'single_select', + buttonParamsJson: JSON.stringify({ + title: lister[0], + sections: lister[1] + }) + }); + }); + } + + return { + body: proto.Message.InteractiveMessage.Body.fromObject({ + text: text || '' + }), + footer: proto.Message.InteractiveMessage.Footer.fromObject({ + text: footer || null + }), + header: proto.Message.InteractiveMessage.Header.fromObject({ + title: text || null, + subtitle: text || null, + hasMediaAttachment: img?.imageMessage || video?.videoMessage ? true : false, + imageMessage: img?.imageMessage || null, + videoMessage: video?.videoMessage || null + }), + nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({ + buttons: dynamicButtons.filter(Boolean), + messageParamsJson: '' + }) + }; + })); + + const interactiveMessage = proto.Message.InteractiveMessage.create({ + body: proto.Message.InteractiveMessage.Body.fromObject({ + text: text || '' + }), + footer: proto.Message.InteractiveMessage.Footer.fromObject({ + text: footer || '' + }), + header: proto.Message.InteractiveMessage.Header.fromObject({ + title: text || '', + subtitle: text || '', + hasMediaAttachment: false + }), + carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.fromObject({ + cards: cards + }) + }); + + const messageContent = proto.Message.fromObject({ + viewOnceMessage: { + message: { + messageContextInfo: { + deviceListMetadata: {}, + deviceListMetadataVersion: 2 + }, + interactiveMessage + } + } + }); + + const msgs = await generateWAMessageFromContent(jid, messageContent, { + userJid: conn.user.jid, + quoted: quoted, + upload: conn.waUploadToServer, + ephemeralExpiration: WA_DEFAULT_EPHEMERAL + }); + + await conn.relayMessage(jid, msgs.message, { messageId: msgs.key.id }); + } else { + await conn.sendNCarousel(jid, ...messages[0], quoted, options); + } + } catch (error) { + console.error("Errore in sendCarousel:", error); + throw error; + } + } +}, + +sendButton2: { + async value(jid, text = '', footer = '', buffer, buttons, copy, urls, quoted, options) { + let img, video + + + if (/^https?:\/\//i.test(buffer)) { + try { + // Ottieni il tipo MIME dall'URL + const response = await fetch(buffer) + const contentType = response.headers.get('content-type') + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) + } else { + console.error("Tipo MIME non compatibile:", contentType) + } \ No newline at end of file From c703693f7b8ed4a5364b6215b97dda5927dadf62 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:00:22 +0100 Subject: [PATCH 14/43] Update simple.js --- lib/simple.js | 339 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 338 insertions(+), 1 deletion(-) diff --git a/lib/simple.js b/lib/simple.js index f2c29a5c6..8f60808aa 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -870,4 +870,341 @@ sendButton2: { video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) } else { console.error("Tipo MIME non compatibile:", contentType) - } \ No newline at end of file + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error) + } + } else { + + try { + const type = await conn.getFile(buffer) + if (/^image\//i.test(type.mime)) { + img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(type.mime)) { + video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) + } + } catch (error) { + console.error("Errore nell'ottenere il tipo di file:", error); + } + } + + const dynamicButtons = buttons.map(btn => ({ + name: 'quick_reply', + buttonParamsJson: JSON.stringify({ + display_text: btn[0], + id: btn[1] + }), + })); + + + if (copy && (typeof copy === 'string' || typeof copy === 'number')) { + // Aggiungi pulsante di copia + dynamicButtons.push({ + name: 'cta_copy', + buttonParamsJson: JSON.stringify({ + display_text: 'Copy', + copy_code: copy + }) + }); + } + + // Aggiungi pulsanti di URL + if (urls && Array.isArray(urls)) { + urls.forEach(url => { + dynamicButtons.push({ + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: url[0], + url: url[1], + merchant_url: url[1] + }) + }) + }) + } + + + const interactiveMessage = { + body: { text: text }, + footer: { text: footer }, + header: { + hasMediaAttachment: false, + imageMessage: img ? img.imageMessage : null, + videoMessage: video ? video.videoMessage : null + }, + nativeFlowMessage: { + buttons: dynamicButtons, + messageParamsJson: '' + } + } + + + let msgL = generateWAMessageFromContent(jid, { + viewOnceMessage: { + message: { + interactiveMessage } } }, { userJid: conn.user.jid, quoted }) + + conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) + + } +}, + + //--- + +sendList: { + async value(jid, title, text, buttonText, buffer, listSections, quoted, options = {}) { + let img, video + + if (/^https?:\/\//i.test(buffer)) { + try { + // Ottieni il tipo MIME dall'URL + const response = await fetch(buffer) + const contentType = response.headers.get('content-type') + if (/^image\//i.test(contentType)) { + img = await prepareWAMessageMedia({ image: { url: buffer } }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(contentType)) { + video = await prepareWAMessageMedia({ video: { url: buffer } }, { upload: conn.waUploadToServer }) + } else { + console.error("Tipo MIME non compatibile:", contentType) + } + } catch (error) { + console.error("Errore nell'ottenere il tipo MIME:", error) + } + } else { + + try { + const type = await conn.getFile(buffer) + if (/^image\//i.test(type.mime)) { + img = await prepareWAMessageMedia({ image: type.data }, { upload: conn.waUploadToServer }) + } else if (/^video\//i.test(type.mime)) { + video = await prepareWAMessageMedia({ video: type.data }, { upload: conn.waUploadToServer }) + } + } catch (error) { + console.error("Errore nell'ottenere il tipo di file:", error); + } + } + + const sections = [...listSections] + + const message = { + interactiveMessage: { + header: {title: title, + hasMediaAttachment: false, + imageMessage: img ? img.imageMessage : null, + videoMessage: video ? video.videoMessage : null + } , + body: {text: text}, + nativeFlowMessage: { + buttons: [ + { + name: 'single_select', + buttonParamsJson: JSON.stringify({ + title: buttonText, + sections + }) + } + ], + messageParamsJson: '' + } + } + }; + + let msgL = generateWAMessageFromContent(jid, { + viewOnceMessage: { + message} }, { userJid: conn.user.jid, quoted }) + + //await conn.relayMessage(jid, { viewOnceMessage: { message } }, {}); + conn.relayMessage(jid, msgL.message, { messageId: msgL.key.id, ...options }) + + } +}, + + sendPoll: { + async value(jid, name = '', optiPoll, options) { + if (!Array.isArray(optiPoll[0]) && typeof optiPoll[0] === 'string') optiPoll = [optiPoll]; + if (!options) options = {}; + const pollMessage = { + name: name, + options: optiPoll.map((btn) => ({ + optionName: !nullish(btn[0]) && btn[0] || '', + })), + selectableOptionsCount: 1, + }; + return conn.relayMessage(jid, {pollCreationMessage: pollMessage}, {...options}); + }, + }, + sendHydrated: { + /** + * + * @param {String} jid + * @param {String} text + * @param {String} footer + * @param {fs.PathLike} buffer + * @param {String|string[]} url + * @param {String|string[]} urlText + * @param {String|string[]} call + * @param {String|string[]} callText + * @param {String[][]} buttons + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted + * @param {Object} options + */ + async value(jid, text = '', footer = '', buffer, url, urlText, call, callText, buttons, quoted, options) { + let type; + if (buffer) { + try { + (type = await conn.getFile(buffer), buffer = type.data); + } catch { + buffer = buffer; + } + } + if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null); + if (!options) options = {}; + const templateButtons = []; + if (url || urlText) { + if (!Array.isArray(url)) url = [url]; + if (!Array.isArray(urlText)) urlText = [urlText]; + templateButtons.push(...( + url.map((v, i) => [v, urlText[i]]) + .map(([url, urlText], i) => ({ + index: templateButtons.length + i + 1, + urlButton: { + displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', + url: !nullish(url) && url || !nullish(urlText) && urlText || '', + }, + })) || [] + )); + } + if (call || callText) { + if (!Array.isArray(call)) call = [call]; + if (!Array.isArray(callText)) callText = [callText]; + templateButtons.push(...( + call.map((v, i) => [v, callText[i]]) + .map(([call, callText], i) => ({ + index: templateButtons.length + i + 1, + callButton: { + displayText: !nullish(callText) && callText || !nullish(call) && call || '', + phoneNumber: !nullish(call) && call || !nullish(callText) && callText || '', + }, + })) || [] + )); + } + if (buttons.length) { + if (!Array.isArray(buttons[0])) buttons = [buttons]; + templateButtons.push(...( + buttons.map(([text, id], index) => ({ + index: templateButtons.length + index + 1, + quickReplyButton: { + displayText: !nullish(text) && text || !nullish(id) && id || '', + id: !nullish(id) && id || !nullish(text) && text || '', + }, + })) || [] + )); + } + const message = { + ...options, + [buffer ? 'caption' : 'text']: text || '', + footer, + templateButtons, + ...(buffer ? + options.asLocation && /image/.test(type.mime) ? { + location: { + ...options, + jpegThumbnail: buffer, + }, + } : { + [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer, + } : {}), + }; + return await conn.sendMessage(jid, message, { + quoted, + upload: conn.waUploadToServer, + ...options, + }); + }, + enumerable: true, + }, + sendHydrated2: { + /** + * + * @param {String} jid + * @param {String} text + * @param {String} footer + * @param {fs.PathLike} buffer + * @param {String|string[]} url + * @param {String|string[]} urlText + * @param {String|string[]} call + * @param {String|string[]} callText + * @param {String[][]} buttons + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} quoted + * @param {Object} options + */ + async value(jid, text = '', footer = '', buffer, url, urlText, url2, urlText2, buttons, quoted, options) { + let type; + if (buffer) { + try { + (type = await conn.getFile(buffer), buffer = type.data); + } catch { + buffer = buffer; + } + } + if (buffer && !Buffer.isBuffer(buffer) && (typeof buffer === 'string' || Array.isArray(buffer))) (options = quoted, quoted = buttons, buttons = callText, callText = call, call = urlText, urlText = url, url = buffer, buffer = null); + if (!options) options = {}; + const templateButtons = []; + if (url || urlText) { + if (!Array.isArray(url)) url = [url]; + if (!Array.isArray(urlText)) urlText = [urlText]; + templateButtons.push(...( + url.map((v, i) => [v, urlText[i]]) + .map(([url, urlText], i) => ({ + index: templateButtons.length + i + 1, + urlButton: { + displayText: !nullish(urlText) && urlText || !nullish(url) && url || '', + url: !nullish(url) && url || !nullish(urlText) && urlText || '', + }, + })) || [] + )); + } + if (url2 || urlText2) { + if (!Array.isArray(url2)) url2 = [url2]; + if (!Array.isArray(urlText2)) urlText2 = [urlText2]; + templateButtons.push(...( + url2.map((v, i) => [v, urlText2[i]]) + .map(([url2, urlText2], i) => ({ + index: templateButtons.length + i + 1, + urlButton: { + displayText: !nullish(urlText2) && urlText2 || !nullish(url2) && url2 || '', + url: !nullish(url2) && url2 || !nullish(urlText2) && urlText2 || '', + }, + })) || [] + )); + } + if (buttons.length) { + if (!Array.isArray(buttons[0])) buttons = [buttons]; + templateButtons.push(...( + buttons.map(([text, id], index) => ({ + index: templateButtons.length + index + 1, + quickReplyButton: { + displayText: !nullish(text) && text || !nullish(id) && id || '', + id: !nullish(id) && id || !nullish(text) && text || '', + }, + })) || [] + )); + } + const message = { + ...options, + [buffer ? 'caption' : 'text']: text || '', + footer, + templateButtons, + ...(buffer ? + options.asLocation && /image/.test(type.mime) ? { + location: { + ...options, + jpegThumbnail: buffer, + }, + } : { + [/video/.test(type.mime) ? 'video' : /image/.test(type.mime) ? 'image' : 'document']: buffer, + } : {}), + }; + return await conn.sendMessage(jid, message, { + quoted, + upload: conn.waUploadToServer, + ...options, \ No newline at end of file From a81af0649ad733b414fc14f68365a8d7a9410dfd Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:01:43 +0100 Subject: [PATCH 15/43] Update simple.js --- lib/simple.js | 495 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 494 insertions(+), 1 deletion(-) diff --git a/lib/simple.js b/lib/simple.js index 8f60808aa..c1c539e27 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -1207,4 +1207,497 @@ sendList: { return await conn.sendMessage(jid, message, { quoted, upload: conn.waUploadToServer, - ...options, \ No newline at end of file + ...options, + }); + }, + enumerable: true, + }, + cMod: { + /** + * cMod + * @param {String} jid + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} message + * @param {String} text + * @param {String} sender + * @param {*} options + * @returns + */ + value(jid, message, text = '', sender = conn.user.jid, options = {}) { + if (options.mentions && !Array.isArray(options.mentions)) options.mentions = [options.mentions]; + const copy = message.toJSON(); + delete copy.message.messageContextInfo; + delete copy.message.senderKeyDistributionMessage; + const mtype = Object.keys(copy.message)[0]; + const msg = copy.message; + const content = msg[mtype]; + if (typeof content === 'string') msg[mtype] = text || content; + else if (content.caption) content.caption = text || content.caption; + else if (content.text) content.text = text || content.text; + if (typeof content !== 'string') { + msg[mtype] = {...content, ...options}; + msg[mtype].contextInfo = { + ...(content.contextInfo || {}), + mentionedJid: options.mentions || content.contextInfo?.mentionedJid || [], + }; + } + if (copy.participant) sender = copy.participant = sender || copy.participant; + else if (copy.key.participant) sender = copy.key.participant = sender || copy.key.participant; + if (copy.key.remoteJid.includes('@s.whatsapp.net')) sender = sender || copy.key.remoteJid; + else if (copy.key.remoteJid.includes('@broadcast')) sender = sender || copy.key.remoteJid; + copy.key.remoteJid = jid; + copy.key.fromMe = areJidsSameUser(sender, conn.user.id) || false; + return proto.WebMessageInfo.fromObject(copy); + }, + enumerable: true, + }, + copyNForward: { + /** + * Copia esatta e inoltra + * @param {String} jid + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} message + * @param {Boolean|Number} forwardingScore + * @param {Object} options + */ + async value(jid, message, forwardingScore = true, options = {}) { + let vtype; + if (options.readViewOnce && message.message.viewOnceMessage?.message) { + vtype = Object.keys(message.message.viewOnceMessage.message)[0]; + delete message.message.viewOnceMessage.message[vtype].viewOnce; + message.message = proto.Message.fromObject( + JSON.parse(JSON.stringify(message.message.viewOnceMessage.message)), + ); + message.message[vtype].contextInfo = message.message.viewOnceMessage.contextInfo; + } + const mtype = Object.keys(message.message)[0]; + let m = generateForwardMessageContent(message, !!forwardingScore); + const ctype = Object.keys(m)[0]; + if (forwardingScore && typeof forwardingScore === 'number' && forwardingScore > 1) m[ctype].contextInfo.forwardingScore += forwardingScore; + m[ctype].contextInfo = { + ...(message.message[mtype].contextInfo || {}), + ...(m[ctype].contextInfo || {}), + }; + m = generateWAMessageFromContent(jid, m, { + ...options, + userJid: conn.user.jid, + }); + await conn.relayMessage(jid, m.message, {messageId: m.key.id, additionalAttributes: {...options}}); + return m; + }, + enumerable: true, + }, + fakeReply: { + /** + * Risposte finte + * @param {String} jid + * @param {String|Object} text + * @param {String} fakeJid + * @param {String} fakeText + * @param {String} fakeGroupJid + * @param {String} options + */ + value(jid, text = '', fakeJid = this.user.jid, fakeText = '', fakeGroupJid, options) { + return conn.reply(jid, text, {key: {fromMe: areJidsSameUser(fakeJid, conn.user.id), participant: fakeJid, ...(fakeGroupJid ? {remoteJid: fakeGroupJid} : {})}, message: {conversation: fakeText}, ...options}); + }, + }, + downloadM: { + /** + * Scarica messaggio media + * @param {Object} m + * @param {String} type + * @param {fs.PathLike | fs.promises.FileHandle} saveToFile + * @return {Promise} + */ + async value(m, type, saveToFile) { + let filename; + if (!m || !(m.url || m.directPath)) return Buffer.alloc(0); + const stream = await downloadContentFromMessage(m, type); + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + if (saveToFile) ({filename} = await conn.getFile(buffer, true)); + return saveToFile && fs.existsSync(filename) ? filename : buffer; + }, + enumerable: true, + }, + parseMention: { + /** + * Analizza la stringa in mentionedJid(s) + * @param {String} text + * @return {Array} + */ + value(text = '') { + return [...text.matchAll(/@([0-9]{5,16}|0)/g)].map((v) => v[1] + '@s.whatsapp.net'); + }, + enumerable: true, + }, + getName: { + /** + * Ottieni nome dal jid + * @param {String} jid + * @param {Boolean} withoutContact + */ + value(jid = '', withoutContact = false) { + jid = conn.decodeJid(jid); + withoutContact = conn.withoutContact || withoutContact; + let v; + if (jid.endsWith('@g.us')) { + return new Promise(async (resolve) => { + v = conn.chats[jid] || {}; + if (!(v.name || v.subject)) v = await conn.groupMetadata(jid) || {}; + resolve(v.name || v.subject || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international')); + }); + } else { + v = jid === '0@s.whatsapp.net' ? { + jid, + vname: 'WhatsApp', + } : areJidsSameUser(jid, conn.user.id) ? + conn.user : + (conn.chats[jid] || {}); + } + return (withoutContact ? '' : v.name) || v.subject || v.vname || v.notify || v.verifiedName || PhoneNumber('+' + jid.replace('@s.whatsapp.net', '')).getNumber('international'); + }, + enumerable: true, + }, + loadMessage: { + /** + * + * @param {String} messageID + * @returns {import('@whiskeysockets/baileys').proto.WebMessageInfo} + */ + value(messageID) { + return Object.entries(conn.chats) + .filter(([_, {messages}]) => typeof messages === 'object') + .find(([_, {messages}]) => Object.entries(messages) + .find(([k, v]) => (k === messageID || v.key?.id === messageID))) + ?.[1].messages?.[messageID]; + }, + enumerable: true, + }, + sendGroupV4Invite: { + /** + * Invia invito gruppo V4 + * @param {String} jid + * @param {*} participant + * @param {String} inviteCode + * @param {Number} inviteExpiration + * @param {String} groupName + * @param {String} caption + * @param {Buffer} jpegThumbnail + * @param {*} options + */ + async value(jid, participant, inviteCode, inviteExpiration, groupName = 'unknown subject', caption = 'Invitation to join my WhatsApp group', jpegThumbnail, options = {}) { + const msg = proto.Message.fromObject({ + groupInviteMessage: proto.GroupInviteMessage.fromObject({ + inviteCode, + inviteExpiration: parseInt(inviteExpiration) || + new Date(new Date + (3 * 86400000)), + groupJid: jid, + groupName: (groupName ? groupName : await conn.getName(jid)) || null, + jpegThumbnail: Buffer.isBuffer(jpegThumbnail) ? jpegThumbnail : null, + caption, + }), + }); + const message = generateWAMessageFromContent(participant, msg, options); + await conn.relayMessage(participant, message.message, {messageId: message.key.id, additionalAttributes: {...options}}); + return message; + }, + enumerable: true, + }, + processMessageStubType: { + /** + * Processa MessageStubType + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m + */ + async value(m) { + if (!m.messageStubType) return; + const chat = conn.decodeJid(m.key.remoteJid || m.message?.senderKeyDistributionMessage?.groupId || ''); + if (!chat || chat === 'status@broadcast') return; + + // Decodifica LID in JID nei messageStubParameters + if (Array.isArray(m.messageStubParameters)) { + m.messageStubParameters = m.messageStubParameters.map(p => + typeof p === 'string' && p.endsWith('@lid') ? conn.decodeJid(p) : p + ); + } + + const emitGroupUpdate = (update) => { + conn.ev.emit('groups.update', [{ id: chat, ...update }]); + }; + + switch (m.messageStubType) { + case WAMessageStubType.REVOKE: + case WAMessageStubType.GROUP_CHANGE_INVITE_LINK: + if (Array.isArray(m.messageStubParameters) && m.messageStubParameters.length > 0) { + emitGroupUpdate({ revoke: m.messageStubParameters[0] }); + } + break; + case WAMessageStubType.GROUP_CHANGE_ICON: + if (Array.isArray(m.messageStubParameters) && m.messageStubParameters.length > 0) { + emitGroupUpdate({ icon: m.messageStubParameters[0] }); + } + break; + default: { + console.log({ + messageStubType: m.messageStubType, + messageStubParameters: m.messageStubParameters || [], + type: WAMessageStubType[m.messageStubType] + }); + break; + } + } + + const isGroup = chat.endsWith('@g.us'); + if (!isGroup) return; + + let chats = conn.chats[chat]; + if (!chats) chats = conn.chats[chat] = { id: chat }; + chats.isChats = true; + + const metadata = await conn.groupMetadata(chat).catch(() => null); + if (!metadata) return; + + chats.subject = metadata.subject; + chats.metadata = metadata; +} +}, + insertAllGroup: { + async value() { + const groups = await conn.groupFetchAllParticipating().catch((_) => null) || {}; + for (const group in groups) conn.chats[group] = {...(conn.chats[group] || {}), id: group, subject: groups[group].subject, isChats: true, metadata: groups[group]}; + return conn.chats; + }, + }, + pushMessage: { + /** + * pushMessage + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo[]} m + */ + async value(m) { + if (!m) return; + if (!Array.isArray(m)) m = [m]; + for (const message of m) { + try { + // if (!(message instanceof proto.WebMessageInfo)) continue // https://github.com/adiwajshing/Baileys/pull/696/commits/6a2cb5a4139d8eb0a75c4c4ea7ed52adc0aec20f + if (!message) continue; + if (message.messageStubType && message.messageStubType != WAMessageStubType.CIPHERTEXT) conn.processMessageStubType(message).catch(console.error); + const _mtype = Object.keys(message.message || {}); + const mtype = (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(_mtype[0]) && _mtype[0]) || + (_mtype.length >= 3 && _mtype[1] !== 'messageContextInfo' && _mtype[1]) || + _mtype[_mtype.length - 1]; + const chat = conn.decodeJid(message.key.remoteJid || message.message?.senderKeyDistributionMessage?.groupId || ''); + if (message.message?.[mtype]?.contextInfo?.quotedMessage) { + /** + * @type {import('@whiskeysockets/baileys').proto.IContextInfo} + */ + const context = message.message[mtype].contextInfo; + let participant = conn.decodeJid(context.participant); + const remoteJid = conn.decodeJid(context.remoteJid || participant); + /** + * @type {import('@whiskeysockets/baileys').proto.IMessage} + * + */ + const quoted = message.message[mtype].contextInfo.quotedMessage; + if ((remoteJid && remoteJid !== 'status@broadcast') && quoted) { + let qMtype = Object.keys(quoted)[0]; + if (qMtype == 'conversation') { + quoted.extendedTextMessage = {text: quoted[qMtype]}; + delete quoted.conversation; + qMtype = 'extendedTextMessage'; + } + if (!quoted[qMtype].contextInfo) quoted[qMtype].contextInfo = {}; + // Normalizza i JID menzionati nel messaggio citato + quoted[qMtype].contextInfo.mentionedJid = (context.mentionedJid || quoted[qMtype].contextInfo.mentionedJid || []) + .map(jid => normalizeJid(jid, conn)) + .filter(Boolean); + + const isGroup = remoteJid.endsWith('g.us'); + if (isGroup && !participant) participant = remoteJid; + + // Normalizza tutti gli ID nel messaggio citato + const qM = { + key: { + remoteJid: normalizeJid(remoteJid, conn), + fromMe: areJidsSameUser(conn.user.jid, remoteJid), + id: context.stanzaId, + participant: normalizeJid(participant, conn), + }, + message: JSON.parse(JSON.stringify(quoted)), + ...(isGroup ? {participant: normalizeJid(participant, conn)} : {}), + }; + let qChats = conn.chats[participant]; + if (!qChats) qChats = conn.chats[participant] = {id: participant, isChats: !isGroup}; + if (!qChats.messages) qChats.messages = {}; + if (!qChats.messages[context.stanzaId] && !qM.key.fromMe) qChats.messages[context.stanzaId] = qM; + let qChatsMessages; + if ((qChatsMessages = Object.entries(qChats.messages)).length > 40) qChats.messages = Object.fromEntries(qChatsMessages.slice(30, qChatsMessages.length)); // Forse evita memory leak + } + } + if (!chat || chat === 'status@broadcast') continue; + const isGroup = chat.endsWith('@g.us'); + let chats = conn.chats[chat]; + if (!chats) { + if (isGroup) await conn.insertAllGroup().catch(console.error); + chats = conn.chats[chat] = {id: chat, isChats: true, ...(conn.chats[chat] || {})}; + } + let metadata; let sender; + if (isGroup) { + if (!chats.subject || !chats.metadata) { + metadata = await conn.groupMetadata(chat).catch((_) => ({})) || {}; + if (!chats.subject) chats.subject = metadata.subject || ''; + if (!chats.metadata) chats.metadata = metadata; + } + sender = conn.decodeJid(message.key?.fromMe && conn.user.id || message.participant || message.key?.participant || chat || ''); + if (sender !== chat) { + let chats = conn.chats[sender]; + if (!chats) chats = conn.chats[sender] = {id: sender}; + if (!chats.name) chats.name = message.pushName || chats.name || ''; + } + } else if (!chats.name) chats.name = message.pushName || chats.name || ''; + if (['senderKeyDistributionMessage', 'messageContextInfo'].includes(mtype)) continue; + chats.isChats = true; + if (!chats.messages) chats.messages = {}; + const fromMe = message.key.fromMe || areJidsSameUser(sender || chat, conn.user.id); + if (!['protocolMessage'].includes(mtype) && !fromMe && message.messageStubType != WAMessageStubType.CIPHERTEXT && message.message) { + delete message.message.messageContextInfo; + delete message.message.senderKeyDistributionMessage; + chats.messages[message.key.id] = JSON.parse(JSON.stringify(message, null, 2)); + let chatsMessages; + if ((chatsMessages = Object.entries(chats.messages)).length > 40) chats.messages = Object.fromEntries(chatsMessages.slice(30, chatsMessages.length)); + } + } catch (e) { + console.error(e); + } + } + }, + }, + serializeM: { + /** + * Serializza messaggio, per manipolarlo facilmente + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m + */ + value(m) { + return smsg(conn, m); + }, + }, + ...(typeof conn.chatRead !== 'function' ? { + chatRead: { + /** + * Leggi messaggio + * @param {String} jid + * @param {String|undefined|null} participant + * @param {String} messageID + */ + value(jid, participant = conn.user.jid, messageID) { + return conn.sendReadReceipt(jid, participant, [messageID]); + }, + enumerable: true, + }, + } : {}), + ...(typeof conn.setStatus !== 'function' ? { + setStatus: { + /** + * Imposta status bot + * @param {String} status + */ + value(status) { + return conn.query({ + tag: 'iq', + attrs: { + to: S_WHATSAPP_NET, + type: 'set', + xmlns: 'status', + }, + content: [ + { + tag: 'status', + attrs: {}, + content: Buffer.from(status, 'utf-8'), + }, + ], + }); + }, + enumerable: true, + }, + } : {}), + }); + if (sock.user?.id) sock.user.jid = sock.decodeJid(sock.user.id); + // Avvolgi groupMetadata per restituire sempre JID normalizzati per i partecipanti + try { + const _origGroupMetadata = conn.groupMetadata && conn.groupMetadata.bind(conn) + if (_origGroupMetadata) { + conn.groupMetadata = async function (jid) { + const meta = await _origGroupMetadata(jid).catch(() => null) + if (!meta) return meta + try { + // Normalizza ID gruppo + if (meta.id) meta.id = jidNormalizedUser(conn.decodeJid(meta.id)) + // Normalizza ID partecipanti + if (Array.isArray(meta.participants)) { + meta.participants = meta.participants.map(p => { + try { + const normalizedId = jidNormalizedUser(conn.decodeJid(p.id)) + return { ...p, id: normalizedId } + } catch (e) { + return p + } + }) + } + } catch (e) { + console.error('[simple.js] Errore nella normalizzazione groupMetadata:', e) + } + return meta + } + } + } catch (e) { + console.error('[simple.js] Fallito avvolgimento groupMetadata:', e) + } + store.bind(sock); + return sock; +} +/** + * Serializza messaggio + * @param {ReturnType} conn + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m + * @param {Boolean} hasParent + */ +export function smsg(conn, m, store) { + if (!m) return m; + let M = proto?.WebMessageInfo; + if (M && typeof M.fromObject === 'function') { + m = M.fromObject(m); + } else { + console.warn('proto.WebMessageInfo.fromObject non รจ una funzione, uso messaggio raw'); + } + m.conn = conn; + let protocolMessageKey; + + // Normalizza gli ID principali + if (m.key) { + const remoteJid = normalizeJid(m.key.remoteJid, conn); + Object.defineProperties(m, { + from: { + value: remoteJid, + enumerable: true + }, + chat: { + value: remoteJid, + enumerable: true + }, + id: { + value: m.key.id, + enumerable: true + }, + isGroup: { + value: remoteJid?.endsWith('@g.us') || false, + enumerable: true + }, + sender: { + value: normalizeJid(m.key.fromMe && conn.user?.id || m.key.participant || remoteJid, conn), + enumerable: true + } + }); + } + + // Gestione del messaggio + if (m.message) { + if (m.mtype == 'protocolMessage' && m.msg.key) { \ No newline at end of file From 6432c92ddcfbdd19e8d7d410b2b66902f7c34cf9 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:04:41 +0100 Subject: [PATCH 16/43] Update simple.js --- lib/simple.js | 399 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 398 insertions(+), 1 deletion(-) diff --git a/lib/simple.js b/lib/simple.js index c1c539e27..450a8a083 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -1700,4 +1700,401 @@ export function smsg(conn, m, store) { // Gestione del messaggio if (m.message) { - if (m.mtype == 'protocolMessage' && m.msg.key) { \ No newline at end of file + if (m.mtype == 'protocolMessage' && m.msg.key) { + protocolMessageKey = m.msg.key; + if (protocolMessageKey == 'status@broadcast') protocolMessageKey.remoteJid = m.chat; + if (!protocolMessageKey.participant || protocolMessageKey.participant == 'status_me') protocolMessageKey.participant = m.sender; + protocolMessageKey.fromMe = normalizeJid(protocolMessageKey.participant, conn) === normalizeJid(conn.user.id, conn); + if (!protocolMessageKey.fromMe && protocolMessageKey.remoteJid === normalizeJid(conn.user.id, conn)) protocolMessageKey.remoteJid = m.sender; + } + + // Decodifica LID in JID nei messageStubParameters + if (Array.isArray(m.messageStubParameters)) { + m.messageStubParameters = m.messageStubParameters.map(param => { + if (typeof param === 'string' && param.endsWith('@lid')) { + return conn.decodeJid(param); + } + return param; + }); + } + + // Gestione menzioni e quoted + if (m.message.mentionedJid) { + m.mentionedJid = m.message.mentionedJid.map(jid => normalizeJid(jid, conn)); + } + + if (m.quoted) { + if (!m.quoted.mediaMessage) delete m.quoted.download; + if (m.quoted.key && m.quoted.key.participant) { + m.quoted.sender = normalizeJid(m.quoted.key.participant, conn); + } + } + } + if (!m.mediaMessage) delete m.download; + + try { + if (protocolMessageKey && m.mtype == 'protocolMessage') conn.ev.emit('message.delete', protocolMessageKey); + } catch (e) { + console.error(e); + } + return m; +} + +// https://github.com/Nurutomo/wabot-aq/issues/490 +export function serialize() { + const MediaType = ['imageMessage', 'videoMessage', 'audioMessage', 'stickerMessage', 'documentMessage']; + return Object.defineProperties(proto.WebMessageInfo.prototype, { + conn: { + value: undefined, + enumerable: false, + writable: true, + }, + id: { + get() { + return this.key?.id; + }, + }, + isBaileys: { + get() { + return (this?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender)) && this.id.startsWith('3EB0') && (this.id.length === 20 || this.id.length === 22 || this.id.length === 12) || false + }, + }, + chat: { + get() { + const senderKeyDistributionMessage = this.message?.senderKeyDistributionMessage?.groupId; + return ( + this.key?.remoteJid || + (senderKeyDistributionMessage && + senderKeyDistributionMessage !== 'status@broadcast' + ) || '' + ).decodeJid(); + }, + }, + isGroup: { + get() { + return this.chat.endsWith('@g.us'); + }, + enumerable: true, + }, + sender: { + get() { + return this.conn?.decodeJid(this.key?.fromMe && this.conn?.user.id || this.participant || this.key.participant || this.chat || '') + }, + enumerable: true + }, + fromMe: { + get() { + return this.key?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender) || false + } + }, + mtype: { + get() { + if (!this.message) return ''; + const type = Object.keys(this.message); + return (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(type[0]) && type[0]) || // A volte messaggio all'inizio + (type.length >= 3 && type[1] !== 'messageContextInfo' && type[1]) || // A volte messaggio in mezzo se lunghezza mtype >= 3 + type[type.length - 1]; // Caso comune + }, + enumerable: true, + }, + msg: { + get() { + if (!this.message) return null; + return this.message[this.mtype]; + }, + }, + mediaMessage: { + get() { + if (!this.message) return null; + const Message = ((this.msg?.url || this.msg?.directPath) ? {...this.message} : extractMessageContent(this.message)) || null; + if (!Message) return null; + const mtype = Object.keys(Message)[0]; + return MediaType.includes(mtype) ? Message : null; + }, + enumerable: true, + }, + mediaType: { + get() { + let message; + if (!(message = this.mediaMessage)) return null; + return Object.keys(message)[0]; + }, + enumerable: true, + }, + quoted: { + get() { + /** + * @type {ReturnType} + */ + const self = this; + const msg = self.msg; + const contextInfo = msg?.contextInfo; + const quoted = contextInfo?.quotedMessage; + if (!msg || !contextInfo || !quoted) return null; + const type = Object.keys(quoted)[0]; + const q = quoted[type]; + const text = typeof q === 'string' ? q : q.text; + return Object.defineProperties(JSON.parse(JSON.stringify(typeof q === 'string' ? {text: q} : q)), { + mtype: { + get() { + return type; + }, + enumerable: true, + }, + mediaMessage: { + get() { + const Message = ((q.url || q.directPath) ? {...quoted} : extractMessageContent(quoted)) || null; + if (!Message) return null; + const mtype = Object.keys(Message)[0]; + return MediaType.includes(mtype) ? Message : null; + }, + enumerable: true, + }, + mediaType: { + get() { + let message; + if (!(message = this.mediaMessage)) return null; + return Object.keys(message)[0]; + }, + enumerable: true, + }, + id: { + get() { + return contextInfo.stanzaId; + }, + enumerable: true, + }, + chat: { + get() { + return contextInfo.remoteJid || self.chat; + }, + enumerable: true, + }, + isBaileys: { + get() { + return (this?.fromMe || areJidsSameUser(this.conn?.user.id, this.sender)) && this.id.startsWith('3EB0') && (this.id.length === 20 || this.id.length === 22 || this.id.length === 12) || false + }, + enumerable: true, + }, + sender: { + get() { + return (contextInfo.participant || this.chat || '').decodeJid(); + }, + enumerable: true, + }, + fromMe: { + get() { + return areJidsSameUser(this.sender, self.conn?.user.jid); + }, + enumerable: true, + }, + text: { + get() { + return text || this.caption || this.contentText || this.selectedDisplayText || ''; + }, + enumerable: true, + }, + mentionedJid: { + get() { + return q.contextInfo?.mentionedJid || self.getQuotedObj()?.mentionedJid || []; + }, + enumerable: true, + }, + name: { + get() { + const sender = this.sender; + return sender ? self.conn?.getName(sender) : null; + }, + enumerable: true, + + }, + vM: { + get() { + return proto.WebMessageInfo.fromObject({ + key: { + fromMe: this.fromMe, + remoteJid: this.chat, + id: this.id, + }, + message: quoted, + ...(self.isGroup ? {participant: this.sender} : {}), + }); + }, + }, + fakeObj: { + get() { + return this.vM; + }, + }, + download: { + value(saveToFile = false) { + const mtype = this.mediaType; + return self.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile); + }, + enumerable: true, + configurable: true, + }, + reply: { + /** + * Rispondi al messaggio citato + * @param {String|Object} text + * @param {String|false} chatId + * @param {Object} options + */ + value(text, chatId, options) { + return self.conn?.reply(chatId ? chatId : this.chat, text, this.vM, options); + }, + enumerable: true, + }, + copy: { + /** + * Copia messaggio citato + */ + value() { + const M = proto.WebMessageInfo; + return smsg(conn, M.fromObject(M.toObject(this.vM))); + }, + enumerable: true, + }, + forward: { + /** + * Inoltra messaggio citato + * @param {String} jid + * @param {Boolean} forceForward + */ + value(jid, force = false, options) { + return self.conn?.sendMessage(jid, { + forward: this.vM, force, ...options, + }, {...options}); + }, + enumerable: true, + }, + copyNForward: { + /** + * Inoltra esatto messaggio citato + * @param {String} jid + * @param {Boolean|Number} forceForward + * @param {Object} options + */ + value(jid, forceForward = false, options) { + return self.conn?.copyNForward(jid, this.vM, forceForward, options); + }, + enumerable: true, + + }, + cMod: { + /** + * Modifica messaggio citato + * @param {String} jid + * @param {String} text + * @param {String} sender + * @param {Object} options + */ + value(jid, text = '', sender = this.sender, options = {}) { + return self.conn?.cMod(jid, this.vM, text, sender, options) + }, + enumerable: true, + + }, + delete: { + /** + * Elimina messaggio citato + */ + value() { + return self.conn?.sendMessage(this.chat, { delete: this.vM.key }) + }, + enumerable: true, + + }, + //react + react: { + value(text) { + return self.conn?.sendMessage(this.chat, { + react: { + text, + key: this.vM.key + } + }) + }, + enumerable: true, + } + // + }) + }, + enumerable: true + }, + _text: { + value: null, + writable: true, + }, + text: { + get() { + const msg = this.msg + const text = (typeof msg === 'string' ? msg : msg?.text) || msg?.caption || msg?.contentText || '' + return typeof this._text === 'string' ? this._text : '' || (typeof text === 'string' ? text : ( + text?.selectedDisplayText || + text?.hydratedTemplate?.hydratedContentText || + text + )) || '' + }, + set(str) { + return this._text = str + }, + enumerable: true + }, + mentionedJid: { + get() { + return this.msg?.contextInfo?.mentionedJid?.length && this.msg.contextInfo.mentionedJid || [] + }, + enumerable: true + }, + name: { + get() { + return !nullish(this.pushName) && this.pushName || this.conn?.getName(this.sender) + }, + enumerable: true + }, + download: { + value(saveToFile = false) { + const mtype = this.mediaType + return this.conn?.downloadM(this.mediaMessage[mtype], mtype.replace(/message/i, ''), saveToFile) + }, + enumerable: true, + configurable: true + }, + reply: { + value(text, chatId, options) { + return this.conn?.reply(chatId ? chatId : this.chat, text, this, options) + } + }, + copy: { + value() { + const M = proto.WebMessageInfo + return smsg(this.conn, M.fromObject(M.toObject(this))) + }, + enumerable: true + }, + forward: { + value(jid, force = false, options = {}) { + return this.conn?.sendMessage(jid, { + forward: this, force, ...options + }, { ...options }) + }, + enumerable: true + }, + copyNForward: { + value(jid, forceForward = false, options = {}) { + return this.conn?.copyNForward(jid, this, forceForward, options) + }, + enumerable: true + }, + cMod: { + value(jid, text = '', sender = this.sender, options = {}) { + return this.conn?.cMod(jid, this, text, sender, options) + }, + enumerable: true + }, + getQuotedObj: { + value() { + if (!this.quoted.id) return null + const q = proto.WebMessageInfo.fromObject(this.conn?.loadMessage(this.quoted.id) || this.quoted.vM) \ No newline at end of file From ef5c69015735d5826d13b040d018131a7ed91d3f Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:06:33 +0100 Subject: [PATCH 17/43] Update simple.js --- lib/simple.js | 133 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/lib/simple.js b/lib/simple.js index 450a8a083..de2f676c6 100644 --- a/lib/simple.js +++ b/lib/simple.js @@ -2097,4 +2097,135 @@ export function serialize() { getQuotedObj: { value() { if (!this.quoted.id) return null - const q = proto.WebMessageInfo.fromObject(this.conn?.loadMessage(this.quoted.id) || this.quoted.vM) \ No newline at end of file + const q = proto.WebMessageInfo.fromObject(this.conn?.loadMessage(this.quoted.id) || this.quoted.vM) + return smsg(this.conn, q) + }, + enumerable: true + }, + getQuotedMessage: { + get() { + return this.getQuotedObj + } + }, + delete: { + value() { + return this.conn?.sendMessage(this.chat, { delete: this.key }) + }, + enumerable: true + }, + //react + react: { + value(text) { + return this.conn?.sendMessage(this.chat, { + react: { + text, + key: this.key + } + }) + }, + enumerable: true + } + // + }) +} + +export function logic(check, inp, out) { + if (inp.length !== out.length) throw new Error('Input e Output devono avere la stessa lunghezza'); + for (const i in inp) if (util.isDeepStrictEqual(check, inp[i])) return out[i]; + return null; +} + +export function protoType() { + Buffer.prototype.toArrayBuffer = function toArrayBufferV2() { + const ab = new ArrayBuffer(this.length); + const view = new Uint8Array(ab); + for (let i = 0; i < this.length; ++i) { + view[i] = this[i]; + } + return ab; + }; + /** + * @return {ArrayBuffer} + */ + Buffer.prototype.toArrayBufferV2 = function toArrayBuffer() { + return this.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength); + }; + /** + * @return {Buffer} + */ + ArrayBuffer.prototype.toBuffer = function toBuffer() { + return Buffer.from(new Uint8Array(this)); + }; + // /** + // * @returns {String} + // */ + // Buffer.prototype.toUtilFormat = ArrayBuffer.prototype.toUtilFormat = Object.prototype.toUtilFormat = Array.prototype.toUtilFormat = function toUtilFormat() { + // return util.format(this) + // } + Uint8Array.prototype.getFileType = ArrayBuffer.prototype.getFileType = Buffer.prototype.getFileType = async function getFileType() { + return await fileTypeFromBuffer(this); + }; + /** + * @returns {Boolean} + */ + String.prototype.isNumber = Number.prototype.isNumber = isNumber; + /** + * + * @return {String} + */ + String.prototype.capitalize = function capitalize() { + return this.charAt(0).toUpperCase() + this.slice(1, this.length); + }; + /** + * @return {String} + */ + String.prototype.capitalizeV2 = function capitalizeV2() { + const str = this.split(' '); + return str.map((v) => v.capitalize()).join(' '); + }; + String.prototype.decodeJid = function decodeJid() { + if (/:\d+@/gi.test(this)) { + const decode = jidDecode(this) || {}; + return (decode.user && decode.server && decode.user + '@' + decode.server || this).trim(); + } else return this.trim(); + }; + /** + * numero deve essere in millisecondi + * @return {string} + */ + Number.prototype.toTimeString = function toTimeString() { + // const milliseconds = this % 1000 + const seconds = Math.floor((this / 1000) % 60); + const minutes = Math.floor((this / (60 * 1000)) % 60); + const hours = Math.floor((this / (60 * 60 * 1000)) % 24); + const days = Math.floor((this / (24 * 60 * 60 * 1000))); + return ( + (days ? `${days} giorno/i ` : '') + + (hours ? `${hours} ora/e ` : '') + + (minutes ? `${minutes} minuto/i ` : '') + + (seconds ? `${seconds} secondo/i` : '') + ).trim(); + }; + Number.prototype.getRandom = String.prototype.getRandom = Array.prototype.getRandom = getRandom; +} + + +function isNumber() { + const int = parseInt(this); + return typeof int === 'number' && !isNaN(int); +} + +function getRandom() { + if (Array.isArray(this) || this instanceof String) return this[Math.floor(Math.random() * this.length)]; + return Math.floor(Math.random() * this); +} + + +/** + * ?? + * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator + * @return {boolean} + */ +function nullish(args) { + return !(args !== null && args !== undefined); +} \ No newline at end of file From 08c1d6ad90b61b25fbece12d9cf3704af93aee24 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:08:58 +0100 Subject: [PATCH 18/43] Update store.js --- lib/store.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/store.js b/lib/store.js index 362243167..223170946 100644 --- a/lib/store.js +++ b/lib/store.js @@ -1,18 +1,18 @@ import {readFileSync, writeFileSync, existsSync} from 'fs'; /** - * @type {import('@chatunity/baileys')} + * @type {import('@whiskeysockets/baileys')} */ -const {initAuthCreds, BufferJSON, proto} = (await import('@chatunity/baileys')).default; +const {initAuthCreds, BufferJSON, proto} = (await import('@whiskeysockets/baileys')).default; /** - * @param {import('@chatunity/baileys').WASocket | import('@chatunity/baileys').WALegacySocket} + * @param {import('@whiskeysockets/baileys').WASocket | import('@whiskeysockets/baileys').WALegacySocket} */ function bind(conn) { if (!conn.chats) conn.chats = {}; /** * - * @param {import('@chatunity/baileys').Contact[]|{contacts:import('@chatunity/baileys').Contact[]}} contacts + * @param {import('@whiskeysockets/baileys').Contact[]|{contacts:import('@whiskeysockets/baileys').Contact[]}} contacts * @returns */ function updateNameToDb(contacts) { @@ -204,7 +204,7 @@ function loadMessage(jid, id = null) { // If only 1 param, first param is assumed to be id not jid if (jid && !id) { id = jid; - /** @type {(m: import('@chatunity/baileys').proto.WebMessageInfo) => Boolean} */ + /** @type {(m: import('@whiskeysockets/baileys').proto.WebMessageInfo) => Boolean} */ const filter = (m) => m.key?.id == id; const messages = {}; const messageFind = Object.entries(messages) @@ -225,4 +225,4 @@ export default { bind, useSingleFileAuthState, loadMessage, -}; +}; \ No newline at end of file From 815f78c7ff66577b474ae53f5b39848e9c681f81 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:10:33 +0100 Subject: [PATCH 19/43] Update main.js --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 5065ed229..f03ac2be2 100644 --- a/main.js +++ b/main.js @@ -112,7 +112,7 @@ setInterval(async () => { }, 3 * 60 * 60 * 1000); -const { useMultiFileAuthState, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, Browsers, jidNormalizedUser, makeInMemoryStore, DisconnectReason } = await import('@chatunity/baileys'); +const { useMultiFileAuthState, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, Browsers, jidNormalizedUser, makeInMemoryStore, DisconnectReason } = await import('@whiskeysockets/baileys'); const { chain } = lodash; const PORT = process.env.PORT || process.env.SERVER_PORT || 3000; protoType(); From 91bc7898db3a67f1e6aff4412f090d477fbb4bae Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:11:17 +0100 Subject: [PATCH 20/43] Update package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 985b0ea2d..664b36611 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "license": "GPL-3.0-or-later", "dependencies": { "@bochilteam/scraper": "^5.0.1", - "@chatunity/baileys": "latest", + "@whiskeysockets/baileys": "npm:@realvare/based", "@vitalets/google-translate-api": "^8.0.0", "acrcloud": "^1.4.0", "archiver": "^7.0.1", @@ -83,4 +83,4 @@ "eslint": "^8.45.0", "eslint-config-google": "^0.14.0" } -} +} \ No newline at end of file From 3ebb4ba2b058eb88676cdac8b2196df3bd93c045 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:43:38 +0100 Subject: [PATCH 21/43] Update antilink.js --- plugins/antilink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/antilink.js b/plugins/antilink.js index 347ac4cc6..994b4df49 100644 --- a/plugins/antilink.js +++ b/plugins/antilink.js @@ -1,6 +1,6 @@ import fetch from 'node-fetch'; import FormData from 'form-data'; -import { downloadContentFromMessage } from '@chatunity/baileys'; +import { downloadContentFromMessage } from '@whiskeysockets/baileys'; const linkRegex = /chat\.whatsapp\.com\/([0-9A-Za-z]{20,24})|whatsapp\.com\/channel\/([0-9A-Za-z]{20,24})/i; const urlRegex = /(https?:\/\/[^\s]+)/g; From 4e520aa906b3d570cb83eeb9298f606fe216e305 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:44:19 +0100 Subject: [PATCH 22/43] Update antiviewonce.js --- plugins/antiviewonce.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/antiviewonce.js b/plugins/antiviewonce.js index 4d3a4fc0c..cf823216c 100644 --- a/plugins/antiviewonce.js +++ b/plugins/antiviewonce.js @@ -1,6 +1,6 @@ -import { downloadContentFromMessage } from "@chatunity/baileys" +import { downloadContentFromMessage } from "@whiskeysockets/baileys" export async function before(m, { isAdmin, isBotAdmin }) { let chat = db.data.chats[m.chat] From 0a927e4e2da7cc59f0e9b81263eb0249ba1248b8 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:44:41 +0100 Subject: [PATCH 23/43] Update fun-barzelletta.js --- plugins/fun-barzelletta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/fun-barzelletta.js b/plugins/fun-barzelletta.js index c6054ae0d..17f980896 100644 --- a/plugins/fun-barzelletta.js +++ b/plugins/fun-barzelletta.js @@ -1,4 +1,4 @@ -const { generateWAMessageFromContent, proto } = (await import('@chatunity/baileys')).default +const { generateWAMessageFromContent, proto } = (await import('@whiskeysockets/baileys')).default var handler = async (m, { conn, text}) => { From 3f68593d16af711f3e3b4a78ae4580ea87155305 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:45:23 +0100 Subject: [PATCH 24/43] Delete fun-esci tris.js --- plugins/fun-esci tris.js | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 plugins/fun-esci tris.js diff --git a/plugins/fun-esci tris.js b/plugins/fun-esci tris.js deleted file mode 100644 index b901062d9..000000000 --- a/plugins/fun-esci tris.js +++ /dev/null @@ -1,9 +0,0 @@ -import MessageType from '@chatunity/baileys' -let handler = async (m, { conn, usedPrefix, command }) => { -let room = Object.values(conn.game).find(room => room.id.startsWith('tictactoe') && [room.game.playerX, room.game.playerO].includes(m.sender)) -if (room == undefined) return conn.reply(m.chat, 'Sei uscito dalla partita', null, [['๐™ธ๐™ฝ๐™ธ๐™ฒ๐™ธ๐™ฐ๐š ๐š‚๐™ฐ๐™ป๐™ฐ ๐™ณ๐™ด ๐™น๐š„๐™ด๐™ถ๐™พ', `${usedPrefix}ttt partida nueva`]], m) -delete conn.game[room.id] -await m.reply('Sei uscito dalla partita')} -handler.command = /^(delttt|deltt|esci|deltictactoe)$/i -handler.fail = null -export default handler \ No newline at end of file From ad3612c8eda99bfb01e86e4593359e8f2b35cd4c Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:45:38 +0100 Subject: [PATCH 25/43] Delete fun-roulettemorte.js --- plugins/fun-roulettemorte.js | 102 ----------------------------------- 1 file changed, 102 deletions(-) delete mode 100644 plugins/fun-roulettemorte.js diff --git a/plugins/fun-roulettemorte.js b/plugins/fun-roulettemorte.js deleted file mode 100644 index 0bba59d1a..000000000 --- a/plugins/fun-roulettemorte.js +++ /dev/null @@ -1,102 +0,0 @@ -import { delay } from '@chatunity/baileys'; - -const salasRuleta = {}; - -const handler = async (m, { conn, usedPrefix, command }) => { - const chatId = m.chat; - const senderId = m.sender; - - if (salasRuleta[chatId]) - return conn.reply(m.chat, 'โœง C\'รจ giร  una sala attiva in questo gruppo, attendi che finisca.', m); - - salasRuleta[chatId] = { giocatori: [senderId], stato: 'in_attesa' }; - - await conn.sendMessage(m.chat, { - text: `โœฆ *Roulette della Morte* โœฆ\n\n@${senderId.split('@')[0]} ha avviato una sala di gioco.\n\nโ€ Premi il bottone qui sotto per partecipare! (60 secondi)`, - mentions: [senderId], - buttons: [ - { buttonId: `${usedPrefix}${command} accetto`, buttonText: { displayText: "โœ… Accetto la sfida" }, type: 1 }, - { buttonId: `${usedPrefix}${command} rifiuto`, buttonText: { displayText: "โŒ Annulla" }, type: 1 } - ] - }, { quoted: m }); - - await delay(60000); - if (salasRuleta[chatId] && salasRuleta[chatId].stato === 'in_attesa') { - delete salasRuleta[chatId]; - await conn.sendMessage(m.chat, { text: 'โœฆ Nessuno ha accettato la sfida, la sala รจ stata chiusa.' }); - } -}; - -handler.command = ['roulettedelban']; -handler.botAdmin = true; - -export default handler; - -handler.before = async (m, { conn, usedPrefix, command, args }) => { - const chatId = m.chat; - const senderId = m.sender; - const testo = (m.text || '').toLowerCase(); - - if (!salasRuleta[chatId]) return; - - // Gestione tramite bottoni - const arg = args && args[0] ? args[0].toLowerCase() : null; - - if (testo === 'accetto' || testo === 'accettare' || arg === 'accetto') { - if (salasRuleta[chatId].giocatori.length >= 2) - return conn.reply(m.chat, 'โœง Ci sono giร  due giocatori in questa sala.', m); - - if (senderId === salasRuleta[chatId].giocatori[0]) - return conn.reply(m.chat, 'โœง Non puoi accettare la tua stessa sfida.', m); - - salasRuleta[chatId].giocatori.push(senderId); - salasRuleta[chatId].stato = 'completa'; - - await conn.sendMessage(m.chat, { - audio: { url: "https://qu.ax/iwAmy.mp3" }, - mimetype: "audio/mp4", - ptt: true - }); - - await conn.sendMessage(m.chat, { - text: 'โœฆ *Roulette della Morte* โœฆ\n\nโ€ La sala รจ completa!\n\n> โœง Selezionando il perdente...' - }); - - const loadingMessages = [ - "ใ€Š โ–ˆโ–’โ–’โ–’โ–’โ–’โ–’โ–’โ–’โ–’โ–’โ–’ใ€‹10%\n- Calcolo probabilitร ...", - "ใ€Š โ–ˆโ–ˆโ–ˆโ–ˆโ–’โ–’โ–’โ–’โ–’โ–’โ–’โ–’ใ€‹30%\n- Il destino รจ segnato...", - "ใ€Š โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–’โ–’โ–’โ–’โ–’ใ€‹50%\n- La sorte รจ decisa...", - "ใ€Š โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–’โ–’ใ€‹80%\n- Presto conosceremo il perdente!", - "ใ€Š โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆใ€‹100%\n- Risultato finale!" - ]; - - let { key } = await conn.sendMessage(m.chat, { text: "โœง Calcolo risultato in corso!" }, { quoted: m }); - - for (let msg of loadingMessages) { - await delay(3000); - await conn.sendMessage(m.chat, { text: msg, edit: key }, { quoted: m }); - } - - const [giocatore1, giocatore2] = salasRuleta[chatId].giocatori; - const perdente = Math.random() < 0.5 ? giocatore1 : giocatore2; - - await conn.sendMessage(m.chat, { - text: `โœฆ *Verdetto finale* โœฆ\n\n@${perdente.split('@')[0]} รจ stato il perdente.\n\n> โ€ Hai 60 secondi per le tue ultime parole...`, - mentions: [perdente] - }); - - await delay(60000); - await conn.groupParticipantsUpdate(m.chat, [perdente], 'remove'); - await conn.sendMessage(m.chat, { - text: `โ€ @${perdente.split('@')[0]} รจ stato eliminato. Fine del gioco.`, - mentions: [perdente] - }); - delete salasRuleta[chatId]; - } - - if (testo === 'rifiuto' || arg === 'rifiuto') { - if (senderId !== salasRuleta[chatId].giocatori[0]) return; - delete salasRuleta[chatId]; - await conn.sendMessage(m.chat, { text: 'โœง Il gioco รจ stato annullato dal creatore della sfida.' }); - } -}; \ No newline at end of file From f09d9242feed378cd04c569ffa4fb45d3aebc0bc Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:46:20 +0100 Subject: [PATCH 26/43] Update gp-inattivi.js --- plugins/gp-inattivi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/gp-inattivi.js b/plugins/gp-inattivi.js index f400da28d..a62f8ecd6 100644 --- a/plugins/gp-inattivi.js +++ b/plugins/gp-inattivi.js @@ -1 +1 @@ -var _0x5b2276=_0x4554;(function(_0x2d4ba8,_0x2c0bda){var _0x4cf0f2=_0x4554,_0x14f26c=_0x2d4ba8();while(!![]){try{var _0x14197c=parseInt(_0x4cf0f2(0x110))/0x1*(-parseInt(_0x4cf0f2(0x121))/0x2)+-parseInt(_0x4cf0f2(0x108))/0x3+-parseInt(_0x4cf0f2(0x11b))/0x4*(parseInt(_0x4cf0f2(0x117))/0x5)+parseInt(_0x4cf0f2(0x123))/0x6*(-parseInt(_0x4cf0f2(0x11f))/0x7)+-parseInt(_0x4cf0f2(0x124))/0x8+parseInt(_0x4cf0f2(0x116))/0x9+-parseInt(_0x4cf0f2(0x115))/0xa*(-parseInt(_0x4cf0f2(0x11e))/0xb);if(_0x14197c===_0x2c0bda)break;else _0x14f26c['push'](_0x14f26c['shift']());}catch(_0x3bd13c){_0x14f26c['push'](_0x14f26c['shift']());}}}(_0x1972,0x8a577));import{areJidsSameUser}from'@chatunity/baileys';let handler=async(_0x4469f7,{conn:_0x3400b6,text:_0x4afc13,participants:_0x59d3a1,args:_0x342b25,command:_0x335a6b})=>{var _0x14a7c1=_0x4554;let _0x4b139e=_0x59d3a1['map'](_0x680590=>_0x680590['id']);if(!_0x4afc13)var _0x37721f=_0x4b139e[_0x14a7c1(0x129)];else var _0x37721f=_0x4afc13;var _0x21bf1a=0x0,_0x49405c=[];for(let _0x13a923=0x0;_0x13a923<_0x37721f;_0x13a923++){let _0x4c5114=_0x4469f7[_0x14a7c1(0x10f)]?_0x59d3a1[_0x14a7c1(0x106)](_0x28693b=>_0x28693b['id']==_0x4b139e[_0x13a923]):{};(typeof global['db']['data'][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]]==_0x14a7c1(0x10b)||global['db'][_0x14a7c1(0x118)][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]][_0x14a7c1(0x107)]==0x0)&&!_0x4c5114['isAdmin']&&!_0x4c5114[_0x14a7c1(0x12a)]&&(typeof global['db'][_0x14a7c1(0x118)]['users'][_0x4b139e[_0x13a923]]!=='undefined'?global['db'][_0x14a7c1(0x118)][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]][_0x14a7c1(0x11d)]==![]&&(_0x21bf1a++,_0x49405c['push'](_0x4b139e[_0x13a923])):(_0x21bf1a++,_0x49405c[_0x14a7c1(0x11a)](_0x4b139e[_0x13a923])));}switch(_0x335a6b){case _0x14a7c1(0x10d):if(_0x21bf1a==0x0)return _0x3400b6[_0x14a7c1(0x109)](_0x4469f7[_0x14a7c1(0x107)],_0x14a7c1(0x127),_0x4469f7);_0x4469f7['reply']('ใ€Ž๐Ÿ’ฌใ€ โ•โ• โ€ขโŠฐโœฐโŠฑโ€ข โ•โ• ใ€Ž๐Ÿ’ฌใ€\x0a๐‘๐ž๐ฏ๐ข๐ฌ๐ข๐จ๐ง๐ž\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐ข\x20๐Ÿ˜ด\x0a'+await _0x3400b6[_0x14a7c1(0x114)](_0x4469f7[_0x14a7c1(0x107)])+'\x0a\x0a'+_0x49405c[_0x14a7c1(0x129)]+_0x14a7c1(0x10a)+_0x49405c[_0x14a7c1(0x112)](_0x52dcb6=>_0x14a7c1(0x10c)+_0x52dcb6[_0x14a7c1(0x128)](/@.+/,''))[_0x14a7c1(0x10e)]('\x0a')+_0x14a7c1(0x122),null,{'mentions':_0x49405c});break;case _0x14a7c1(0x111):if(_0x21bf1a==0x0)return _0x3400b6['reply'](_0x4469f7[_0x14a7c1(0x107)],'๐ง๐ž๐ฌ๐ฌ๐ฎ๐ง\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐จ',_0x4469f7);await _0x4469f7[_0x14a7c1(0x109)](_0x14a7c1(0x119)+_0x49405c['map'](_0x2777f6=>'@'+_0x2777f6[_0x14a7c1(0x128)](/@.+/,''))[_0x14a7c1(0x10e)]('\x0a')+'\x0a',null,{'mentions':_0x49405c}),await _0x3400b6['groupParticipantsUpdate'](_0x4469f7['chat'],_0x49405c,_0x14a7c1(0x125));break;}};function _0x1972(){var _0x239511=['inattivi','join','isGroup','691979mIgdzx','viainattivi','map','admin','getName','5471690HylvzU','1707867vUADlJ','1205rztXrF','data','๐‘๐ˆ๐Œ๐Ž๐™๐ˆ๐Ž๐๐„\x20๐ˆ๐๐€๐“๐“๐ˆ๐•๐ˆ\x20๐Ÿšซ\x0a\x0a','push','44quVmND','fail','whitelist','55nAMjnG','7fkKogU','group','2NqFRHo','\x0aใ€Ž๐Ÿ’ฌใ€ โ•โ• โ€ขโŠฐโœฐโŠฑโ€ข โ•โ• ใ€Ž๐Ÿ’ฌใ€','6282246FWOIDY','3951536PqLKvy','remove','users','๐ง๐ž๐ฌ๐ฌ๐ฎ๐ง\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐จ','replace','length','isSuperAdmin','find','chat','370044mndqTr','reply','\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐ข:\x0a','undefined','\x20\x20๐Ÿ‘‰๐Ÿป\x20@'];_0x1972=function(){return _0x239511;};return _0x1972();}handler['command']=/^(inattivi|viainattivi)$/i,handler[_0x5b2276(0x120)]=handler['botAdmin']=handler[_0x5b2276(0x113)]=!![],handler[_0x5b2276(0x11c)]=null;function _0x4554(_0xef1c79,_0xd7d3ff){var _0x1972c0=_0x1972();return _0x4554=function(_0x4554f9,_0x65de1b){_0x4554f9=_0x4554f9-0x106;var _0x43b748=_0x1972c0[_0x4554f9];return _0x43b748;},_0x4554(_0xef1c79,_0xd7d3ff);}export default handler; \ No newline at end of file +var _0x5b2276=_0x4554;(function(_0x2d4ba8,_0x2c0bda){var _0x4cf0f2=_0x4554,_0x14f26c=_0x2d4ba8();while(!![]){try{var _0x14197c=parseInt(_0x4cf0f2(0x110))/0x1*(-parseInt(_0x4cf0f2(0x121))/0x2)+-parseInt(_0x4cf0f2(0x108))/0x3+-parseInt(_0x4cf0f2(0x11b))/0x4*(parseInt(_0x4cf0f2(0x117))/0x5)+parseInt(_0x4cf0f2(0x123))/0x6*(-parseInt(_0x4cf0f2(0x11f))/0x7)+-parseInt(_0x4cf0f2(0x124))/0x8+parseInt(_0x4cf0f2(0x116))/0x9+-parseInt(_0x4cf0f2(0x115))/0xa*(-parseInt(_0x4cf0f2(0x11e))/0xb);if(_0x14197c===_0x2c0bda)break;else _0x14f26c['push'](_0x14f26c['shift']());}catch(_0x3bd13c){_0x14f26c['push'](_0x14f26c['shift']());}}}(_0x1972,0x8a577));import{areJidsSameUser}from'@whiskeysockets/baileys';let handler=async(_0x4469f7,{conn:_0x3400b6,text:_0x4afc13,participants:_0x59d3a1,args:_0x342b25,command:_0x335a6b})=>{var _0x14a7c1=_0x4554;let _0x4b139e=_0x59d3a1['map'](_0x680590=>_0x680590['id']);if(!_0x4afc13)var _0x37721f=_0x4b139e[_0x14a7c1(0x129)];else var _0x37721f=_0x4afc13;var _0x21bf1a=0x0,_0x49405c=[];for(let _0x13a923=0x0;_0x13a923<_0x37721f;_0x13a923++){let _0x4c5114=_0x4469f7[_0x14a7c1(0x10f)]?_0x59d3a1[_0x14a7c1(0x106)](_0x28693b=>_0x28693b['id']==_0x4b139e[_0x13a923]):{};(typeof global['db']['data'][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]]==_0x14a7c1(0x10b)||global['db'][_0x14a7c1(0x118)][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]][_0x14a7c1(0x107)]==0x0)&&!_0x4c5114['isAdmin']&&!_0x4c5114[_0x14a7c1(0x12a)]&&(typeof global['db'][_0x14a7c1(0x118)]['users'][_0x4b139e[_0x13a923]]!=='undefined'?global['db'][_0x14a7c1(0x118)][_0x14a7c1(0x126)][_0x4b139e[_0x13a923]][_0x14a7c1(0x11d)]==![]&&(_0x21bf1a++,_0x49405c['push'](_0x4b139e[_0x13a923])):(_0x21bf1a++,_0x49405c[_0x14a7c1(0x11a)](_0x4b139e[_0x13a923])));}switch(_0x335a6b){case _0x14a7c1(0x10d):if(_0x21bf1a==0x0)return _0x3400b6[_0x14a7c1(0x109)](_0x4469f7[_0x14a7c1(0x107)],_0x14a7c1(0x127),_0x4469f7);_0x4469f7['reply']('ใ€Ž๐Ÿ’ฌใ€ โ•โ• โ€ขโŠฐโœฐโŠฑโ€ข โ•โ• ใ€Ž๐Ÿ’ฌใ€\x0a๐‘๐ž๐ฏ๐ข๐ฌ๐ข๐จ๐ง๐ž\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐ข\x20๐Ÿ˜ด\x0a'+await _0x3400b6[_0x14a7c1(0x114)](_0x4469f7[_0x14a7c1(0x107)])+'\x0a\x0a'+_0x49405c[_0x14a7c1(0x129)]+_0x14a7c1(0x10a)+_0x49405c[_0x14a7c1(0x112)](_0x52dcb6=>_0x14a7c1(0x10c)+_0x52dcb6[_0x14a7c1(0x128)](/@.+/,''))[_0x14a7c1(0x10e)]('\x0a')+_0x14a7c1(0x122),null,{'mentions':_0x49405c});break;case _0x14a7c1(0x111):if(_0x21bf1a==0x0)return _0x3400b6['reply'](_0x4469f7[_0x14a7c1(0x107)],'๐ง๐ž๐ฌ๐ฌ๐ฎ๐ง\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐จ',_0x4469f7);await _0x4469f7[_0x14a7c1(0x109)](_0x14a7c1(0x119)+_0x49405c['map'](_0x2777f6=>'@'+_0x2777f6[_0x14a7c1(0x128)](/@.+/,''))[_0x14a7c1(0x10e)]('\x0a')+'\x0a',null,{'mentions':_0x49405c}),await _0x3400b6['groupParticipantsUpdate'](_0x4469f7['chat'],_0x49405c,_0x14a7c1(0x125));break;}};function _0x1972(){var _0x239511=['inattivi','join','isGroup','691979mIgdzx','viainattivi','map','admin','getName','5471690HylvzU','1707867vUADlJ','1205rztXrF','data','๐‘๐ˆ๐Œ๐Ž๐™๐ˆ๐Ž๐๐„\x20๐ˆ๐๐€๐“๐“๐ˆ๐•๐ˆ\x20๐Ÿšซ\x0a\x0a','push','44quVmND','fail','whitelist','55nAMjnG','7fkKogU','group','2NqFRHo','\x0aใ€Ž๐Ÿ’ฌใ€ โ•โ• โ€ขโŠฐโœฐโŠฑโ€ข โ•โ• ใ€Ž๐Ÿ’ฌใ€','6282246FWOIDY','3951536PqLKvy','remove','users','๐ง๐ž๐ฌ๐ฌ๐ฎ๐ง\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐จ','replace','length','isSuperAdmin','find','chat','370044mndqTr','reply','\x20๐ข๐ง๐š๐ญ๐ญ๐ข๐ฏ๐ข:\x0a','undefined','\x20\x20๐Ÿ‘‰๐Ÿป\x20@'];_0x1972=function(){return _0x239511;};return _0x1972();}handler['command']=/^(inattivi|viainattivi)$/i,handler[_0x5b2276(0x120)]=handler['botAdmin']=handler[_0x5b2276(0x113)]=!![],handler[_0x5b2276(0x11c)]=null;function _0x4554(_0xef1c79,_0xd7d3ff){var _0x1972c0=_0x1972();return _0x4554=function(_0x4554f9,_0x65de1b){_0x4554f9=_0x4554f9-0x106;var _0x43b748=_0x1972c0[_0x4554f9];return _0x43b748;},_0x4554(_0xef1c79,_0xd7d3ff);}export default handler; \ No newline at end of file From 9ad9ec206c414640489c0ea17363bdc89e77df05 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:46:46 +0100 Subject: [PATCH 27/43] Update info-creatore.js --- plugins/info-creatore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/info-creatore.js b/plugins/info-creatore.js index c44969ebe..6579bbd87 100644 --- a/plugins/info-creatore.js +++ b/plugins/info-creatore.js @@ -1,4 +1,4 @@ -import pkg from '@chatunity/baileys' +import pkg from '@whiskeysockets/baileys' const { generateWAMessageFromContent } = pkg let handler = async (m, { conn }) => { From 0590eacea63f53463f42a9dfb5c11ae6b6dbc3ac Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:47:03 +0100 Subject: [PATCH 28/43] Delete info-installa.js --- plugins/info-installa.js | 120 --------------------------------------- 1 file changed, 120 deletions(-) delete mode 100644 plugins/info-installa.js diff --git a/plugins/info-installa.js b/plugins/info-installa.js deleted file mode 100644 index 8b6a0449f..000000000 --- a/plugins/info-installa.js +++ /dev/null @@ -1,120 +0,0 @@ -import os from 'os' -import util from 'util' -import sizeFormatter from 'human-readable' -import MessageType from '@chatunity/baileys' -import fs from 'fs' -const ims = './bb.jpg' -import { performance } from 'perf_hooks' -let handler = async (m, { conn, usedPrefix }) => { -let _uptime = process.uptime() * 1000 -let uptime = clockString(_uptime) -let totalreg = Object.keys(global.db.data.users).length -const chats = Object.entries(conn.chats).filter(([id, data]) => id && data.isChats) -const groupsIn = chats.filter(([id]) => id.endsWith('@g.us')) -const groups = chats.filter(([id]) => id.endsWith('@g.us')) -const used = process.memoryUsage() -const { restrict } = global.db.data.settings[conn.user.jid] || {} -const { autoread } = global.opts -let old = performance.now() -let neww = performance.now() -let speed = (neww - old).toFixed(4) -let prova = { "key": {"participants":"0@s.whatsapp.net", "remoteJid": "status@broadcast", "fromMe": false, "id": "Halo" -}, "message": { -"orderMessage": { text: '๐’๐‚๐€๐‘๐ˆ๐‚๐€ ๐‚๐‡๐€๐“๐”๐๐ˆ๐“๐˜-๐๐Ž๐“ ๐Ÿ’ฌ', -"itemCount": 2023, -"status": 1, -"surface" : 1, - "message": '๐’๐‚๐€๐‘๐ˆ๐‚๐€ ๐‚๐‡๐€๐“๐”๐๐ˆ๐“๐˜-๐๐Ž๐“ ๐Ÿ’ฌ', -"vcard": `BEGIN:VCARD\nVERSION:5.0\nN:;Unlimited;;;\nFN:Unlimited\nORG:Unlimited\nTITLE:\nitem1.TEL;waid=15395490858:+1 (539) 549-0858\nitem1.X-ABLabel:Unlimited\nX-WA-BIZ-DESCRIPTION:ofc\nX-WA-BIZ-NAME:Unlimited\nEND:VCARD` -}}, "participant": "0@s.whatsapp.net" -} -let info = ` -โ‹† ๏ธต๏ธต โ˜… ๐Ÿ’ฌ ๐‚๐‡๐€๐“๐”๐๐ˆ๐“๐˜-๐๐Ž๐“ ๐Ÿ’ฌ โ˜… ๏ธต๏ธต โ‹† - -Segui questi passaggi per installare ChatUnity Bot correttamente su Termux - -๊’ท๊’ฆ โœฆ เญงใƒป๏ธถ : ๏ธถ ๊’ท๊’ฆ โ€งโ‚Š เญง -เญง ๐Ÿ“‚ Repository: https://github.com/chatunitycenter/chatunity-bot -เญง ๐ŸŽฅ Video Tutorial: https://youtu.be/-FZYK-vj4BY -๊’ท๊’ฆ โœฆ เญงใƒป๏ธถ : ๏ธถ ๊’ท๊’ฆ โ€งโ‚Š เญง - -โ•ญโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… -|ใ…คใ…คใ…คใ…คใ…คใ…คใ…ค๊’ฐยกPASSO 1!๊’ฑ -|หšโ‚Š๊’ท ๐Ÿ“ฅ ๊’ฑ เธ…๏น•Scaricare e installare Termux โ‚Šหšเน‘ -โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… - -Scarica la versione corretta di Termux dal link seguente: -๐Ÿ”— Scarica Termux 0.119.1 -https://www.mediafire.com/file/0npdmv51pnttps0/com.termux_0.119.1-119_minAPI21(arm64-v8a),armeabi-v7a,x86,x86_64)(nodpi)_apkmirror.com.apk/file - -โ•ญ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ -โ”โ”โœซ ๐Ÿ“ฆ INSTALLAZIONE COMPLETA (Termux) -โ•ฐ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ - -Copia e incolla il comando completo: - -termux-setup-storage && \ -pkg update && pkg upgrade -y && \ -pkg install x11-repo tur-repo -y && \ -pkg install git nodejs ffmpeg imagemagick yarn libcairo pango libjpeg-turbo giflib libpixman pkg-config freetype fontconfig xorgproto build-essential python libvips sqlite clang make chromium binutils -y && \ -pip install setuptools && \ -export GYP_DEFINES="android_ndk_path=''" && \ -cd ~ && \ -git clone https://github.com/chatunitycenter/chatunity-bot.git && \ -cd chatunity-bot && \ -yarn install && \ -pip install yt-dlp && \ -yarn start - -โ•ญ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ -โ”โ”โœซ ๐Ÿ“ฆ INSTALLAZIONE ZIP (MT Manager / ZArchiver) -โ•ฐ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ - -Se hai scaricato la ZIP del bot, usa questo comando: - -๐Ÿ”— Scarica ZIP: https://www.mediafire.com/file/la6evdeof2m6pl7/chatunity-bot-main.zip/file - -Dopo aver estratto la cartella nella memoria interna (/sdcard/), esegui: - -termux-setup-storage && \ -pkg update && pkg upgrade -y && \ -pkg install x11-repo tur-repo -y && \ -pkg install git nodejs ffmpeg imagemagick yarn libcairo pango libjpeg-turbo giflib libpixman pkg-config freetype fontconfig xorgproto build-essential python libvips sqlite clang make chromium binutils -y && \ -pip install setuptools && \ -export GYP_DEFINES="android_ndk_path=''" && \ -cd /sdcard/chatunity-bot && \ -yarn install && \ -pip install yt-dlp && \ -yarn start - -Dopo lโ€™installazione, il bot si avvierร  automaticamente. - -โ”Š โ”Š โ”Š โ”Šโ€ฟ หšโžถ ๏ฝกหš -โ”Š โ”Š โ”Š โ”Š. โžถ หš -โ”Š โ”Š โ”Š หšโœง Se riscontri problemi, verifica di aver seguito -โ”Š หšโžถ ๏ฝกหš โ˜๏ธŽ tutti i passaggi correttamente e controlla -โ˜๏ธŽ eventuali messaggi di errore su Termux - -โ•ฐโ™ก๊’ท เน‘ โ‹†หšโ‚Šโ‹†โ”€โ”€โ”€สšหšษžโ”€โ”€โ”€โ‹†หšโ‚Šโ‹† เน‘ โชฉ -เญงใƒป๐‚๐Ž๐‹๐‹๐€๐: ${collab} -เญงใƒปยฉ ChatUnity Bot -โ•ฐโ™ก๊’ท เน‘ โ‹†หšโ‚Šโ‹†โ”€โ”€โ”€สšหšษžโ”€โ”€โ”€โ‹†หšโ‚Šโ‹† เน‘ โชฉ -`.trim() -conn.reply(m.chat, info,prova, m, { -contextInfo: { externalAdReply :{ mediaUrl: null, mediaType: 1, description: null, -title: '๐™ธ๐™ฝ๐™ต๐™พ ๐™ณ๐™ด๐™ป ๐™ฑ๐™พ๐šƒ', -body: 'ChatUnity', -previewType: 0, thumbnail: fs.readFileSync("./media/principale.jpeg"), -sourceUrl: `https://github.com/chatunitycenter/chatunity-bot`}}}) -} -handler.help = ['infobot', 'speed'] -handler.tags = ['info', 'tools'] -handler.command = /^(scarica|installa|git|instalarbot)$/i -export default handler - -function clockString(ms) { -let h = Math.floor(ms / 3600000) -let m = Math.floor(ms / 60000) % 60 -let s = Math.floor(ms / 1000) % 60 -console.log({ms,h,m,s}) -return [h, m, s].map(v => v.toString().padStart(2, 0) ).join(':')} \ No newline at end of file From 42885291c9c70d24c1d3de909372328addee0cbd Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:47:39 +0100 Subject: [PATCH 29/43] Update menu-sicurezza.js --- plugins/menu-sicurezza.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/menu-sicurezza.js b/plugins/menu-sicurezza.js index a1b96382d..d1dc14629 100644 --- a/plugins/menu-sicurezza.js +++ b/plugins/menu-sicurezza.js @@ -2,7 +2,7 @@ import 'os' import 'util' import 'human-readable' -import '@chatunity/baileys' +import '@whiskeysockets/baileys' import 'fs' import 'perf_hooks' import path from 'path' From 06173a191bf84a23b1e7e19b1b2c932862cb4f90 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:47:51 +0100 Subject: [PATCH 30/43] Delete rpg-daixp.js --- plugins/rpg-daixp.js | 69 -------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 plugins/rpg-daixp.js diff --git a/plugins/rpg-daixp.js b/plugins/rpg-daixp.js deleted file mode 100644 index f7b5e29c6..000000000 --- a/plugins/rpg-daixp.js +++ /dev/null @@ -1,69 +0,0 @@ -import MessageType from '@chatunity/baileys' - -let tassa = 0.02 // 2% di tassa sulle transazioni - -let handler = async (m, { conn, text }) => { - let who - if (m.isGroup) who = m.mentionedJid[0] // Se in gruppo, prende l'utente menzionato - else who = m.chat // Se in privato, usa l'utente corrente - - if (!who) throw '๐Ÿšฉ ๐๐ž๐ฏ๐ข ๐ฆ๐ž๐ง๐ณ๐ข๐จ๐ง๐š๐ซ๐ž ๐ฎ๐ง ๐ ๐š๐ฒ ๐œ๐จ๐ง @user*' - - let txt = text.replace('@' + who.split`@`[0], '').trim() - if (!txt) throw '๐Ÿšฉ ๐ข๐ง๐ฌ๐ž๐ซ๐ข๐ซ๐ž ๐ฅ๐š ๐ช๐ฎ๐š๐ง๐ญ๐ข๐ญ๐šฬ€ ๐๐ข ๐Ÿ’ซ ๐—๐ ๐๐š ๐ญ๐ซ๐š๐ฌ๐Ÿ๐ž๐ซ๐ข๐ซ๐ž' - if (isNaN(txt)) throw '๐Ÿšฉ ๐ข๐ง๐ฌ๐ž๐ซ๐ข๐ฌ๐œ๐ข ๐ฌ๐จ๐ฅ๐จ ๐ง๐ฎ๐ฆ๐ž๐ซ๐ข ๐œ๐จ๐ ๐ฅ๐ข๐จ๐ง๐ž' - - let xp = parseInt(txt) - let exp = xp - let tassaImporto = Math.ceil(xp * tassa) // Calcola la tassa del 2% - exp += tassaImporto - - if (exp < 1) throw '๐Ÿšฉ ๐ข๐ฅ ๐ฆ๐ข๐ง๐ข๐ฆ๐จ ๐๐š ๐ญ๐ซ๐š๐ฌ๐Ÿ๐ž๐ซ๐ข๐ซ๐ž ๐ž 1 ๐Ÿ’ซ ๐—๐' - - let users = global.db.data.users - if (exp > users[m.sender].exp) throw '๐Ÿšฉ ๐ง๐จ๐ง ๐ก๐š๐ข ๐š๐›๐›๐š๐ฌ๐ญ๐š๐ง๐ณ๐š ๐Ÿ’ซ ๐—๐ ๐๐จ๐ฐ๐ง ๐๐ž๐ฏ๐ข ๐š๐ฏ๐ž๐ซ๐ž ๐ฉ๐ข๐ฎ ๐ž๐ฌ๐ฉ๐ž๐ซ๐ข๐ž๐ง๐ณ๐š' - - // Esegui la transazione - users[m.sender].exp -= exp - users[who].exp += xp - - // Messaggio di conferma - let confirmationMessage = `๐Ÿ“Š *๐ซ๐ž๐ฌ๐จ๐œ๐จ๐ง๐ญ๐จ ๐ญ๐ซ๐š๐ง๐ฌ๐ข๐ณ๐ข๐จ๐ง๐ž *\n\n` + - `โ–ธ ๐—๐ ๐ญ๐ซ๐š๐ฌ๐Ÿ๐ž๐ซ๐ข๐ญ๐ข: *-${xp} ๐Ÿ’ซ*\n` + - `โ–ธ ๐ญ๐š๐ฌ๐ฌ๐š (2%): *-${tassaImporto} ๐Ÿ’ซ*\n` + - `โ–ธ ๐ญ๐จ๐ญ๐š๐ฅ๐ž ๐š๐๐๐ž๐›๐ข๐ญ๐š๐ญ๐จ: *-${exp} ๐Ÿ’ซ*`; - await conn.sendMessage(m.chat, { - text: confirmationMessage, - contextInfo: { - forwardingScore: 99, - isForwarded: true, - forwardedNewsletterMessageInfo: { - newsletterJid: '120363259442839354@newsletter', - serverMessageId: '', - newsletterName: 'ChatUnity' - } - } - }, { quoted: m }); - - // Notifica al ricevente - let recipientMessage = `๐Ÿ“ฉ ๐ก๐š๐ข ๐ซ๐ข๐œ๐ž๐ฏ๐ฎ๐ญ๐จ +${xp} ๐Ÿ’ซ ๐—๐!`; - await conn.sendMessage(m.chat, { - text: recipientMessage, - contextInfo: { - forwardingScore: 99, - isForwarded: true, - forwardedNewsletterMessageInfo: { - newsletterJid: '120363259442839354@newsletter', - serverMessageId: '', - newsletterName: 'ChatUnity' - } - } - }, { quoted: m, mentions: [who] }); -} - -handler.help = ['darxp *@user *'] -handler.tags = ['rpg'] -handler.command = ['daixp', 'daiexp', 'donaxp'] -handler.register = true - -export default handler \ No newline at end of file From bbe5ac34ff6499e9cade6707aecaf3c8aa9e47fa Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:48:12 +0100 Subject: [PATCH 31/43] Update rpg-dona.js --- plugins/rpg-dona.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/rpg-dona.js b/plugins/rpg-dona.js index 981c0d7a5..8e37188cc 100644 --- a/plugins/rpg-dona.js +++ b/plugins/rpg-dona.js @@ -1,4 +1,4 @@ -import MessageType from '@chatunity/baileys' +import MessageType from '@whiskeysockets/baileys' let tassa = 0.02 // 2% di tassa sulle transazioni From f7849b6516305a2c85912af67d1c79904a27c822 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:48:21 +0100 Subject: [PATCH 32/43] Delete sticker-emojimix.js --- plugins/sticker-emojimix.js | 39 ------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 plugins/sticker-emojimix.js diff --git a/plugins/sticker-emojimix.js b/plugins/sticker-emojimix.js deleted file mode 100644 index 74208b408..000000000 --- a/plugins/sticker-emojimix.js +++ /dev/null @@ -1,39 +0,0 @@ -import MessageType from '@chatunity/baileys' -import fetch from 'node-fetch' -import { sticker } from '../lib/sticker.js' -import fs from "fs" - -const fetchJson = (url, options) => new Promise(async (resolve, reject) => { - fetch(url, options) - .then(response => response.json()) - .then(json => { - resolve(json) - }) - .catch((err) => { - reject(err) - }) -}) - -let handler = async (m, { conn, text, args, usedPrefix, command }) => { - if (!args[0]) return m.reply(`๐Ÿ“Œ Ejemplo: *${usedPrefix + command}* ๐Ÿ˜Ž+๐Ÿค‘`) - - let [emoji, emoji2] = text.split`+` - let anu = await fetchJson(`https://tenor.googleapis.com/v2/featured?key=AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCYQ&contentfilter=high&media_filter=png_transparent&component=proactive&collection=emoji_kitchen_v5&q=${encodeURIComponent(emoji)}_${encodeURIComponent(emoji2)}`) - - for (let res of anu.results) { - let userId = m.sender - let packstickers = global.db.data.users[userId] || {} - let texto1 = packstickers.text1 || global.packsticker - let texto2 = packstickers.text2 || global.packsticker2 - - let stiker = await sticker(false, res.url, texto1, texto2) - conn.sendFile(m.chat, stiker, null, { asSticker: true }, m) - } -} - -handler.help = ['emojimix **'] -handler.tags = ['sticker'] -handler.command = ['emojimix'] -handler.register = true - -export default handler; \ No newline at end of file From 73ae1291a5eaaa91bdc9ba37f48d21cc812aece0 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:48:52 +0100 Subject: [PATCH 33/43] Update templateResponse.js --- plugins/templateResponse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/templateResponse.js b/plugins/templateResponse.js index d052cdf85..41f5725c0 100644 --- a/plugins/templateResponse.js +++ b/plugins/templateResponse.js @@ -1,4 +1,4 @@ -const { proto, generateWAMessage, areJidsSameUser } = (await import('@chatunity/baileys')).default +const { proto, generateWAMessage, areJidsSameUser } = (await import('@whiskeysockets/baileys')).default export async function all(m, chatUpdate) { if (m.isBaileys) return From fa306ed98a95683e4d21b4b6619ba3582abd4b70 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:49:05 +0100 Subject: [PATCH 34/43] Delete tools-ispeziona.js --- plugins/tools-ispeziona.js | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 plugins/tools-ispeziona.js diff --git a/plugins/tools-ispeziona.js b/plugins/tools-ispeziona.js deleted file mode 100644 index 321892b2d..000000000 --- a/plugins/tools-ispeziona.js +++ /dev/null @@ -1,2 +0,0 @@ -function _0x3b28(){const _0x4a8318=['command','Asia/Jakarta','invite','680057IpzZVS','get','2390469IiMUys','jidNormalizedUser','keys','reply','image','1004554CnYNmv','32uMrdKC','creator','query','1645UWeKOD','3239910AkXPpL','\x0aโซนโซบ\x20FOUNDER:\x20','rowner','owner','@g.us','error','*[โ—๐ˆ๐๐…๐Žโ—]\x20๐™ธ๐™ฝ๐™ถ๐š๐™ด๐š‚๐™ด\x20๐™ด๐™ป\x20๐™ป๐™ธ๐™ฝ๐™บ\x20๐™ณ๐™ด๐™ป\x20๐™ถ๐š๐š„๐™ฟ๐™พ*','6560236dExtwB','g.us','\x0aโซนโซบ\x20DESCRIZIONE:\x0a','body','catch','match','profilePictureUrl','toLocaleString','chat','capitalize','includes','subject','desc','creation','split','52503bKIQTH','wa.me/','attrs','group','jidEncode','โซนโซบ\x20ID:\x20','join',':*\x20','23952gCeiYi','\x0aโซนโซบ\x20CREATO:\x20','getBinaryNodeChild','description','sendMessage'];_0x3b28=function(){return _0x4a8318;};return _0x3b28();}const _0x4de7e6=_0x8280;function _0x8280(_0x4c4113,_0x5463de){const _0x3b284e=_0x3b28();return _0x8280=function(_0x828060,_0x307327){_0x828060=_0x828060-0x17f;let _0x1ececb=_0x3b284e[_0x828060];return _0x1ececb;},_0x8280(_0x4c4113,_0x5463de);}(function(_0x5d926c,_0x551be7){const _0x20846d=_0x8280,_0x50f683=_0x5d926c();while(!![]){try{const _0x4be35a=-parseInt(_0x20846d(0x19c))/0x1+parseInt(_0x20846d(0x181))/0x2+-parseInt(_0x20846d(0x1ae))/0x3+-parseInt(_0x20846d(0x18d))/0x4+parseInt(_0x20846d(0x185))/0x5*(parseInt(_0x20846d(0x1a4))/0x6)+parseInt(_0x20846d(0x1ac))/0x7+parseInt(_0x20846d(0x182))/0x8*(parseInt(_0x20846d(0x186))/0x9);if(_0x4be35a===_0x551be7)break;else _0x50f683['push'](_0x50f683['shift']());}catch(_0x33cce9){_0x50f683['push'](_0x50f683['shift']());}}}(_0x3b28,0xd2c8b));import*as _0x4288fe from'@chatunity/baileys';let handler=async(_0xedbd27,{conn:_0x525ec1,text:_0x9d4630})=>{const _0x21c70f=_0x8280;let [,_0x428caf]=_0x9d4630[_0x21c70f(0x192)](/chat\.whatsapp\.com\/(?:invite\/)?([0-9A-Za-z]{20,24})/i)||[];if(!_0x428caf)throw _0x21c70f(0x18c);let _0x2224fe=await _0x525ec1[_0x21c70f(0x184)]({'tag':'iq','attrs':{'type':_0x21c70f(0x1ad),'xmlns':'w:g2','to':_0x21c70f(0x18a)},'content':[{'tag':_0x21c70f(0x1ab),'attrs':{'code':_0x428caf}}]}),_0x2b7345=extractGroupMetadata(_0x2224fe),_0x175bf0=Object[_0x21c70f(0x1b0)](_0x2b7345)['map'](_0x30e116=>'*'+_0x30e116[_0x21c70f(0x196)]()+_0x21c70f(0x1a3)+_0x2b7345[_0x30e116])[_0x21c70f(0x1a2)]('\x0a'),_0x5b69e9=await _0x525ec1[_0x21c70f(0x193)](_0x2b7345['id'],_0x21c70f(0x180))[_0x21c70f(0x191)](console[_0x21c70f(0x18b)]);if(_0x5b69e9)return _0x525ec1[_0x21c70f(0x1a8)](_0xedbd27[_0x21c70f(0x195)],{'image':{'url':_0x5b69e9},'caption':_0x175bf0},{'quoted':_0xedbd27});let _0x57e32e=_0x21c70f(0x1a1)+_0x2b7345['id']+'\x0aโซนโซบ\x20NOME:\x20'+_0x2b7345[_0x21c70f(0x198)]+_0x21c70f(0x1a5)+_0x2b7345[_0x21c70f(0x19a)]+_0x21c70f(0x187)+_0x2b7345[_0x21c70f(0x189)]+_0x21c70f(0x18f)+_0x2b7345[_0x21c70f(0x199)];await _0x525ec1[_0x21c70f(0x17f)](_0xedbd27[_0x21c70f(0x195)],_0x57e32e,_0xedbd27);};handler[_0x4de7e6(0x1a9)]=/^(ispeziona)$/i,handler[_0x4de7e6(0x188)]=!![];export default handler;const extractGroupMetadata=_0x1c61bb=>{const _0x310d01=_0x4de7e6,_0x3f8417=_0x4288fe['getBinaryNodeChild'](_0x1c61bb,_0x310d01(0x19f)),_0x5995c0=_0x4288fe[_0x310d01(0x1a6)](_0x3f8417,_0x310d01(0x1a7));let _0x177629;if(_0x5995c0)_0x177629=_0x4288fe['getBinaryNodeChild'](_0x5995c0,_0x310d01(0x190))?.['content'];const _0x4ae461={'id':_0x3f8417[_0x310d01(0x19e)]['id']['includes']('@')?_0x3f8417['attrs']['id']:_0x4288fe[_0x310d01(0x1a0)](_0x3f8417[_0x310d01(0x19e)]['id'],_0x310d01(0x18e)),'subject':_0x3f8417[_0x310d01(0x19e)][_0x310d01(0x198)],'creation':new Date(+_0x3f8417['attrs']['creation']*0x3e8)[_0x310d01(0x194)]('id',{'timeZone':_0x310d01(0x1aa)}),'owner':_0x3f8417[_0x310d01(0x19e)][_0x310d01(0x183)]?_0x310d01(0x19d)+_0x4288fe[_0x310d01(0x1af)](_0x3f8417[_0x310d01(0x19e)][_0x310d01(0x183)])['split']('@')[0x0]:_0x3f8417['attrs']['id'][_0x310d01(0x197)]('-')?'wa.me/'+_0x3f8417[_0x310d01(0x19e)]['id'][_0x310d01(0x19b)]('-')[0x0]:'','desc':_0x177629};return _0x4ae461;}; - From f3a198faebfb553a10ef45ed63214a874694c3f8 Mon Sep 17 00:00:00 2001 From: dthbot Date: Tue, 20 Jan 2026 14:49:28 +0100 Subject: [PATCH 35/43] Update tools-rivela.js --- plugins/tools-rivela.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tools-rivela.js b/plugins/tools-rivela.js index 43fc54576..686b7de39 100644 --- a/plugins/tools-rivela.js +++ b/plugins/tools-rivela.js @@ -1,4 +1,4 @@ -import { downloadContentFromMessage } from '@chatunity/baileys' +import { downloadContentFromMessage } from '@whiskeysockets/baileys' let handler = async (m, { conn }) => { try { From 47c9baf735f18329f547167aa66932a0be8d43bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:05:15 +0000 Subject: [PATCH 36/43] Initial plan From 3e085a5f56fddd6fc5fab298cfe1320eef3b7fb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:56:55 +0000 Subject: [PATCH 37/43] Initial plan From 5ccf8a2b6730d461f3d87107902c9812ffab5c39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:13:43 +0000 Subject: [PATCH 38/43] Add comprehensive comments and improve code quality across main JavaScript files - Added detailed JSDoc comments to all functions in main.js, config.js, api.js, handler.js, and lib/print.js - Organized code into logical sections with clear headers - Improved code readability with consistent formatting and spacing - Added explanatory comments for complex logic and configuration - Fixed minor syntax issue in api.js (trailing comma) - Improved consistency in variable naming and code structure Co-authored-by: AlexUngu07 <234906301+AlexUngu07@users.noreply.github.com> --- api.js | 131 +++++++-- config.js | 95 ++++++- handler.js | 505 +++++++++++++++++++++++++++++++---- lib/print.js | 99 ++++++- main.js | 730 +++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 1410 insertions(+), 150 deletions(-) diff --git a/api.js b/api.js index c81269bbd..3d23d3dea 100644 --- a/api.js +++ b/api.js @@ -1,3 +1,21 @@ +/** + * ============================================================================ + * API.JS - External API Configuration and Utilities + * ============================================================================ + * + * This file provides configuration for external API services used by the bot. + * It includes: + * - OpenAI configuration + * - Third-party API keys + * - API endpoint mappings + * - Global utility exports + * + * @author ChatUnity Team + * @version 1.0 + * ============================================================================ + */ + +// Node.js and third-party imports import { watchFile, unwatchFile } from 'fs' import chalk from 'chalk' import { fileURLToPath } from 'url' @@ -7,19 +25,51 @@ import fetch from 'node-fetch' import axios from 'axios' import moment from 'moment-timezone' +// ============================================================================ +// OPENAI CONFIGURATION +// ============================================================================ + +/** OpenAI API key for GPT features */ global.openai_key = 'sk-0' +/** OpenAI organization ID */ global.openai_org_id = 'org-3' +// ============================================================================ +// API KEY POOLS +// ============================================================================ + +/** + * Multiple API keys are used for load balancing and fallback. + * Keys are randomly selected at runtime to distribute API usage. + */ + +/** ZenAPI service keys */ global.keysZens = ['LuOlangNgentot', 'c2459db922', '37CC845916', '6fb0eff124', 'hdiiofficial', 'fiktod', 'BF39D349845E', '675e34de8a', '0b917b905e6f'] global.keysxxx = keysZens[Math.floor(keysZens.length * Math.random())] + +/** XTeam API service keys */ global.keysxteammm = ['29d4b59a4aa687ca', '5LTV57azwaid7dXfz5fzJu', 'cb15ed422c71a2fb', '5bd33b276d41d6b4', 'HIRO', 'kurrxd09', 'ebb6251cc00f9c63'] global.keysxteam = keysxteammm[Math.floor(keysxteammm.length * Math.random())] + +/** NeoXR API service keys */ global.keysneoxrrr = ['5VC9rvNx', 'cfALv5'] global.keysneoxr = keysneoxrrr[Math.floor(keysneoxrrr.length * Math.random())] + +/** LOL Human API keys */ global.lolkeysapi = ['BrunoSobrino'] + +/** Rose API keys */ global.itsrose = ['4b146102c4d500809da9d1ff'] +// ============================================================================ +// API ENDPOINTS REGISTRY +// ============================================================================ + +/** + * Maps friendly service names to their base URLs. + * Use with global.API() function to construct full API URLs. + */ global.APIs = { xteam: 'https://api.xteam.xyz', dzx: 'https://api.dhamzxploit.my.id', @@ -28,18 +78,23 @@ global.APIs = { neoxr: 'https://api.neoxr.my.id', zenzapis: 'https://api.zahwazein.xyz', akuari: 'https://api.akuari.my.id', - akuari2: 'https://apimu.my.id', + akuari2: 'https://apimu.my.id', fgmods: 'https://api-fgmods.ddns.net', botcahx: 'https://api.botcahx.biz.id', - ibeng: 'https://api.ibeng.tech/docs', + ibeng: 'https://api.ibeng.tech/docs', rose: 'https://api.itsrose.site', - popcat : 'https://api.popcat.xyz', - xcoders : 'https://api-xcoders.site' -}, + popcat: 'https://api.popcat.xyz', + xcoders: 'https://api-xcoders.site' +} + +/** + * Maps API base URLs to their corresponding authentication keys. + * Used automatically by global.API() when apikeyqueryname is provided. + */ global.APIKeys = { 'https://api.xteam.xyz': `${keysxteam}`, 'https://api.lolhuman.xyz': '85faf717d0545d14074659ad', - 'https://api.neoxr.my.id': `${keysneoxr}`, + 'https://api.neoxr.my.id': `${keysneoxr}`, 'https://violetics.pw': 'beta', 'https://api.zahwazein.xyz': `${keysxxx}`, 'https://api-fgmods.ddns.net': 'fg-dylux', @@ -49,23 +104,55 @@ global.APIKeys = { 'https://api-xcoders.site': 'Frieren' } +// ============================================================================ +// GLOBAL UTILITY EXPORTS +// ============================================================================ + +/** + * Export commonly used libraries globally for plugin access. + * This allows plugins to use these without importing. + */ +global.cheerio = cheerio // HTML parsing library +global.fs = fs // File system operations +global.fetch = fetch // HTTP client +global.axios = axios // HTTP client with more features +global.moment = moment // Date/time handling -global.cheerio = cheerio -global.fs = fs -global.fetch = fetch -global.axios = axios -global.moment = moment +// ============================================================================ +// RPG EMOTICON SYSTEM +// ============================================================================ + +/** + * RPG utility object with emoticon mapping functionality. + * Used for displaying appropriate emojis in RPG game features. + */ global.rpg = { -emoticon(string) { -string = string.toLowerCase() - -let results = Object.keys(emotttt).map(v => [v, new RegExp(v, 'gi')]).filter(v => v[1].test(string)) -if (!results.length) return '' -else return emotttt[results[0][0]] -}} - + /** + * Finds the emoticon associated with a string. + * Matches against known emoticon patterns. + * + * @param {string} string - Text to find emoticon for + * @returns {string} Matching emoticon or empty string + */ + emoticon(string) { + string = string.toLowerCase() + + // Match string against emoticon patterns + let results = Object.keys(emotttt).map(v => [v, new RegExp(v, 'gi')]).filter(v => v[1].test(string)) + + if (!results.length) return '' + return emotttt[results[0][0]] + } +} + +// ============================================================================ +// HOT-RELOAD WATCHER +// ============================================================================ + +// Watch this file for changes and auto-reload let file = fileURLToPath(import.meta.url) watchFile(file, () => { -unwatchFile(file) -console.log(chalk.redBright("Update 'config.js'")) -import(`${file}?update=${Date.now()}`)}) + unwatchFile(file) + console.log(chalk.redBright("Update 'api.js'")) + import(`${file}?update=${Date.now()}`) +}) diff --git a/config.js b/config.js index b27935cd4..eb6e0c81e 100644 --- a/config.js +++ b/config.js @@ -1,19 +1,64 @@ +/** + * ============================================================================ + * CONFIG.JS - Bot Configuration and Settings + * ============================================================================ + * + * This file contains the main configuration for the ChatUnity bot including: + * - Bot identity (name, version, watermark) + * - Owner and moderator definitions + * - API keys and endpoints + * - Visual customization settings + * + * @author ChatUnity Team + * @version 1.0 + * ============================================================================ + */ + +// Node.js core modules import { watchFile, unwatchFile } from 'fs'; import chalk from 'chalk'; import fs from 'fs'; import { fileURLToPath } from 'url'; +// ============================================================================ +// BOT IDENTITY CONFIGURATION +// ============================================================================ + +/** Bot phone number (leave empty for auto-detection) */ global.botnumber = ''; + +/** Confirmation code for authentication */ global.confirmCode = ''; + +/** Display name for the bot */ global.nomebot = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; + +/** Sticker pack name */ global.packname = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; + +/** Sticker author name */ global.author = '๐Œ๐'; + +/** Bot version */ global.vs = '1.0'; + +/** Collaborator tag */ global.collab = 'Demon Slayer'; + +/** Watermark (defaults to bot name) */ global.wm = global.nomebot; + +/** Loading message shown during command processing */ global.wait = 'โ“˜ ๐‚๐š๐ซ๐ข๐œ๐š๐ฆ๐ž๐ง๐ญ๐จ ...'; +// ============================================================================ +// USER PERMISSION CONFIGURATION +// ============================================================================ +/** + * Owner list - Users with full bot control + * Format: [phoneNumber, displayName, isMainOwner] + */ global.owner = [ ['212780803311', 'Blood', true], ['19782772696', 'Bot', true], @@ -22,21 +67,52 @@ global.owner = [ ['xxxxxxxxxx'], ['xxxxxxxxxx'] ]; + +/** Moderators - Users with elevated permissions */ global.mods = ['xxxxxxxxxx']; + +/** Premium users - Users with premium feature access */ global.prems = ['xxxxxxxxxx', 'xxxxxxxxxx']; +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ +/** + * Picks a random element from an array. + * Used for API key rotation to distribute load. + * + * @param {Array} arr - Array to pick from + * @returns {*} Random element from array + */ const pickRandom = arr => arr[Math.floor(Math.random() * arr.length)]; +// ============================================================================ +// API KEY POOLS +// ============================================================================ + +/** ZenAPI keys pool */ global.keysZens = ['c2459db922', '37CC845916', '6fb0eff124']; + +/** XTeam API keys pool */ global.keysxteammm = ['29d4b59a4aa687ca', '5LTV57azwaid7dXfz5fzJu', 'cb15ed422c71a2fb', '5bd33b276d41d6b4', 'HIRO', 'kurrxd09', 'ebb6251cc00f9c63']; + +/** NeoXR API keys pool */ global.keysneoxrrr = ['5VC9rvNx', 'cfALv5']; + +/** LOL Human API keys pool */ global.lolkeysapi = ['BrunoSobrino']; +// Select random key from each pool for load balancing global.keysxxx = pickRandom(global.keysZens); global.keysxteam = pickRandom(global.keysxteammm); global.keysneoxr = pickRandom(global.keysneoxrrr); +// ============================================================================ +// API ENDPOINTS CONFIGURATION +// ============================================================================ + +/** API base URLs mapped by service name */ global.APIs = { xteam: 'https://api.xteam.xyz', nrtm: 'https://fg-nrtm-nhie.onrender.com', @@ -49,21 +125,31 @@ global.APIs = { zenzapis: 'https://zenzapis.xyz', akuari: 'https://api.akuari.my.id', akuari2: 'https://apimu.my.id' - }; +/** API keys mapped by base URL */ global.APIKeys = { 'https://api.xteam.xyz': global.keysxteam, 'https://api.lolhuman.xyz': '85faf717d0545d14074659ad', 'https://api.neoxr.my.id': global.keysneoxr, 'https://violetics.pw': 'beta', - }; +// ============================================================================ +// GAME AND FEATURE SETTINGS +// ============================================================================ + +/** Experience points multiplier for leveling system */ global.multiplier = 69; + +/** Maximum warnings before user is banned */ global.maxwarn = '4'; +// ============================================================================ +// FLAMING TEXT LOGO TEMPLATES +// ============================================================================ +/** URLs for generating styled text logos via FlamingText API */ global.flaaa = [ 'https://flamingtext.com/net-fu/proxy_form.cgi?&imageoutput=true&script=water-logo&fontsize=100&scaleWidth=800&scaleHeight=500&fillTextColor=%23000&shadowGlowColor=%23000&backgroundColor=%23000&text=', 'https://flamingtext.com/net-fu/proxy_form.cgi?&imageoutput=true&script=crafts-logo&fontsize=90&scaleWidth=800&scaleHeight=500&text=', @@ -72,6 +158,11 @@ global.flaaa = [ 'https://www6.flamingtext.com/net-fu/proxy_form.cgi?&imageoutput=true&script=sketch-name&fontsize=100&fillTextType=1&fillTextPattern=Warning!&fillColor1Color=%23f2aa4c&fillOutlineColor=%23f2aa4c&backgroundColor=%23101820&scaleWidth=800&scaleHeight=500&text=' ]; +// ============================================================================ +// HOT-RELOAD WATCHER +// ============================================================================ + +// Watch this file for changes and auto-reload const file = fileURLToPath(import.meta.url); watchFile(file, () => { unwatchFile(file); diff --git a/handler.js b/handler.js index 48d0820f3..86456a431 100644 --- a/handler.js +++ b/handler.js @@ -1,3 +1,23 @@ +/** + * ============================================================================ + * HANDLER.JS - Message and Event Handler + * ============================================================================ + * + * This is the core message handler for the ChatUnity bot. + * It processes all incoming WhatsApp messages and events including: + * - Message parsing and command detection + * - User permission checks (owner, admin, premium) + * - Plugin execution and error handling + * - Group events (join, leave, promote, demote) + * - Anti-spam protection + * - User statistics tracking + * + * @author ChatUnity Team + * @version 1.0 + * ============================================================================ + */ + +// Module imports import { smsg } from './lib/simple.js' import { format } from 'util' import { fileURLToPath } from 'url' @@ -5,29 +25,84 @@ import path, { join } from 'path' import { unwatchFile, watchFile } from 'fs' import fs from 'fs' import chalk from 'chalk' + +// Import Baileys proto for message handling const { proto } = (await import('@whiskeysockets/baileys')).default +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +/** + * Checks if a value is a valid number (not NaN). + * @param {*} x - Value to check + * @returns {boolean} True if valid number + */ const isNumber = x => typeof x === 'number' && !isNaN(x) + +/** + * Creates a promise that resolves after specified milliseconds. + * @param {number} ms - Milliseconds to delay + * @returns {Promise|boolean} Promise that resolves after delay, or false if ms is invalid + */ const delay = ms => isNumber(ms) && new Promise(resolve => setTimeout(function () { clearTimeout(this) resolve() }, ms)) +// ============================================================================ +// GLOBAL STATE FOR ANTI-SPAM AND USER MANAGEMENT +// ============================================================================ + +/** Set of globally ignored users (won't receive any bot responses) */ global.ignoredUsersGlobal = global.ignoredUsersGlobal || new Set() + +/** Object mapping group IDs to sets of ignored users within that group */ global.ignoredUsersGroup = global.ignoredUsersGroup || {} + +/** Object tracking command spam per group for anti-spam protection */ global.groupSpam = global.groupSpam || {} +// ============================================================================ +// MAIN MESSAGE HANDLER +// ============================================================================ + +/** + * Main handler for incoming WhatsApp messages. + * Processes messages, executes commands, and manages user/chat data. + * + * @param {Object} chatUpdate - Baileys chat update object containing messages + * @this {Object} WhatsApp connection instance + */ export async function handler(chatUpdate) { + // Initialize stats tracking if not present if (!global.db.data.stats) global.db.data.stats = {} const stats = global.db.data.stats + // Initialize message queue for rate limiting this.msgqueque = this.msgqueque || [] + + // Validate chat update if (!chatUpdate) return + + // Store messages in the message store this.pushMessage(chatUpdate.messages).catch(console.error) + + // Get the latest message from the update let m = chatUpdate.messages[chatUpdate.messages.length - 1] if (!m) return + + // Ensure database is loaded if (global.db.data == null) await global.loadDatabase() + // ============================================================================ + // OWNER CHECK + // ============================================================================ + + /** + * Determines if the message sender is a bot owner. + * Owners have unrestricted access to all commands. + */ const isOwner = (() => { try { const isROwner = [conn.decodeJid(global.conn.user.id), ...global.owner.map(([number]) => number)] @@ -40,6 +115,18 @@ export async function handler(chatUpdate) { } })() + // ============================================================================ + // PREFIX VALIDATION + // ============================================================================ + + /** + * Checks if text starts with a valid command prefix. + * Supports both RegExp and string/array prefix configurations. + * + * @param {string} text - Message text to check + * @param {RegExp|string|string[]} prefixes - Prefix pattern(s) + * @returns {boolean} True if text has valid prefix + */ const hasValidPrefix = (text, prefixes) => { if (!text || typeof text !== 'string') return false if (prefixes instanceof RegExp) return prefixes.test(text) @@ -51,6 +138,16 @@ export async function handler(chatUpdate) { }) } + // ============================================================================ + // ANTI-SPAM PROTECTION FOR GROUPS + // ============================================================================ + + /** + * Implements rate limiting for group commands. + * Prevents users from spamming commands and overwhelming the bot. + * - Allows max 2 commands per minute per group + * - Suspends command processing for 45 seconds when exceeded + */ if ( m.isGroup && !isOwner && @@ -60,6 +157,7 @@ export async function handler(chatUpdate) { const now = Date.now() const chatId = m.chat + // Initialize spam tracking for this group if (!global.groupSpam[chatId]) { global.groupSpam[chatId] = { count: 0, @@ -70,22 +168,30 @@ export async function handler(chatUpdate) { } const groupData = global.groupSpam[chatId] + + // Check if currently suspended if (groupData.isSuspended) { - if (now < groupData.suspendedUntil) return + if (now < groupData.suspendedUntil) return // Still suspended, ignore command + + // Suspension period over, reset counters groupData.isSuspended = false groupData.count = 0 groupData.firstCommandTimestamp = now groupData.suspendedUntil = null } + + // Reset counter if more than 60 seconds since first command if (now - groupData.firstCommandTimestamp > 60000) { groupData.count = 1 groupData.firstCommandTimestamp = now } else { groupData.count++ } + + // Trigger suspension if too many commands if (groupData.count > 2) { groupData.isSuspended = true - groupData.suspendedUntil = now + 45000 + groupData.suspendedUntil = now + 45000 // 45 second cooldown await conn.sendMessage(chatId, { text: `ใ€Ž โš  ใ€ Anti-spam comandi\n\nTroppi comandi in poco tempo!\nAttendi *45 secondi* prima di usare altri comandi.\n\n> sviluppato da sam aka vare`, @@ -95,28 +201,43 @@ export async function handler(chatUpdate) { } } + // ============================================================================ + // MESSAGE SERIALIZATION AND INITIALIZATION + // ============================================================================ + try { + // Serialize message for easier handling m = smsg(this, m) || m if (!m) return + + // Initialize experience and limit tracking for this message m.exp = 0 m.limit = false + // ============================================================================ + // USER AND CHAT DATA INITIALIZATION + // ============================================================================ + try { let user = global.db.data.users[m.sender] + + // Initialize user object if not exists if (typeof user !== 'object') global.db.data.users[m.sender] = {} + // Ensure all user properties exist with default values if (user) { - if (!isNumber(user.messaggi)) user.messaggi = 0 - if (!isNumber(user.blasphemy)) user.blasphemy = 0 - if (!isNumber(user.exp)) user.exp = 0 - if (!isNumber(user.money)) user.money = 0 - if (!isNumber(user.warn)) user.warn = 0 - if (!isNumber(user.joincount)) user.joincount = 2 - if (!('premium' in user)) user.premium = false - if (!isNumber(user.premiumDate)) user.premiumDate = -1 - if (!('name' in user)) user.name = m.name - if (!('muto' in user)) user.muto = false + if (!isNumber(user.messaggi)) user.messaggi = 0 // Message count + if (!isNumber(user.blasphemy)) user.blasphemy = 0 // Blasphemy counter + if (!isNumber(user.exp)) user.exp = 0 // Experience points + if (!isNumber(user.money)) user.money = 0 // In-game currency + if (!isNumber(user.warn)) user.warn = 0 // Warning count + if (!isNumber(user.joincount)) user.joincount = 2 // Group join limit + if (!('premium' in user)) user.premium = false // Premium status + if (!isNumber(user.premiumDate)) user.premiumDate = -1 // Premium expiry + if (!('name' in user)) user.name = m.name // Display name + if (!('muto' in user)) user.muto = false // Muted status } else { + // Create new user with all default values global.db.data.users[m.sender] = { messaggi: 0, blasphemy: 0, @@ -132,21 +253,24 @@ export async function handler(chatUpdate) { } } + // Initialize chat/group settings let chat = global.db.data.chats[m.chat] if (typeof chat !== 'object') global.db.data.chats[m.chat] = {} + // Ensure all chat properties exist with default values if (chat) { - if (!('isBanned' in chat)) chat.isBanned = false - if (!('detect' in chat)) chat.detect = true - if (!('delete' in chat)) chat.delete = false - if (!('antiLink' in chat)) chat.antiLink = true - if (!('antiTraba' in chat)) chat.antiTraba = true - if (!isNumber(chat.expired)) chat.expired = 0 - if (!isNumber(chat.messaggi)) chat.messaggi = 0 + if (!('isBanned' in chat)) chat.isBanned = false // Chat ban status + if (!('detect' in chat)) chat.detect = true // Event detection + if (!('delete' in chat)) chat.delete = false // Auto-delete messages + if (!('antiLink' in chat)) chat.antiLink = true // Anti-link protection + if (!('antiTraba' in chat)) chat.antiTraba = true // Anti-crash protection + if (!isNumber(chat.expired)) chat.expired = 0 // Expiry timestamp + if (!isNumber(chat.messaggi)) chat.messaggi = 0 // Message count if (!('name' in chat)) chat.name = this.getName(m.chat) if (!('antispamcomandi' in chat)) chat.antispamcomandi = true - if (!('welcome' in chat)) chat.welcome = true + if (!('welcome' in chat)) chat.welcome = true // Welcome messages } else { + // Create new chat with all default values global.db.data.chats[m.chat] = { name: this.getName(m.chat), isBanned: false, @@ -161,13 +285,15 @@ export async function handler(chatUpdate) { } } + // Initialize bot settings let settings = global.db.data.settings[this.user.jid] if (typeof settings !== 'object') global.db.data.settings[this.user.jid] = {} + // Ensure all settings exist with default values if (settings) { - if (!('self' in settings)) settings.self = false - if (!('autoread' in settings)) settings.autoread = false - if (!('restrict' in settings)) settings.restrict = true + if (!('self' in settings)) settings.self = false // Self-mode (owner only) + if (!('autoread' in settings)) settings.autoread = false // Auto-read messages + if (!('restrict' in settings)) settings.restrict = true // Restrict admin commands } else { global.db.data.settings[this.user.jid] = { self: false, @@ -179,20 +305,42 @@ export async function handler(chatUpdate) { console.error(e) } - if (opts['nyimak']) return - if (!m.fromMe && opts['self']) return - if (opts['pconly'] && m.chat.endsWith('g.us')) return - if (opts['gconly'] && !m.chat.endsWith('g.us')) return + // ============================================================================ + // MESSAGE FILTERING OPTIONS + // ============================================================================ + + // Skip processing based on command-line options + if (opts['nyimak']) return // Observer mode - no responses + if (!m.fromMe && opts['self']) return // Self mode - only respond to own messages + if (opts['pconly'] && m.chat.endsWith('g.us')) return // PC only - ignore groups + if (opts['gconly'] && !m.chat.endsWith('g.us')) return // Group only - ignore private chats + // Ensure text property is a string if (typeof m.text !== 'string') m.text = '' + // ============================================================================ + // PERMISSION LEVEL CHECKS + // ============================================================================ + + // Real owner check (configured in config.js) const isROwner = [conn.decodeJid(global.conn.user.id), ...global.owner.map(([number]) => number)] .map(v => v.replace(/[^0-9]/g, '') + '@s.whatsapp.net') .includes(m.sender) + + // Owner check (includes real owners and bot itself) const isOwner2 = isROwner || m.fromMe + + // Moderator check (owners + configured moderators) const isMods = isOwner2 || global.mods.map(v => v.replace(/[^0-9]/g, '') + '@s.whatsapp.net').includes(m.sender) + + // Premium check (owners, mods, or users with active premium) const isPrems = isROwner || isOwner2 || isMods || global.db.data.users[m.sender]?.premiumTime > 0 + // ============================================================================ + // MESSAGE QUEUE FOR RATE LIMITING + // ============================================================================ + + // Add message to queue if queque option is enabled (for non-privileged users) if (opts['queque'] && m.text && !(isMods || isPrems)) { let queque = this.msgqueque, time = 1000 * 5 const previousID = queque[queque.length - 1] @@ -203,21 +351,42 @@ export async function handler(chatUpdate) { }, time) } + // Skip bot's own messages (from Baileys) if (m.isBaileys) return + + // Award random experience points for activity m.exp += Math.ceil(Math.random() * 10) + // Track used prefix and get user data let usedPrefix let _user = global.db.data?.users?.[m.sender] + // ============================================================================ + // GROUP METADATA AND PARTICIPANT INFO + // ============================================================================ + + // Fetch group metadata (cached when possible) const groupMetadata = (m.isGroup ? ((conn.chats[m.chat] || {}).metadata || await this.groupMetadata(m.chat).catch(_ => null)) : {}) || {} const participants = (m.isGroup ? groupMetadata.participants : []) || [] + + // Normalize participant JIDs for consistent comparison const normalizedParticipants = participants.map(u => { const normalizedId = this.decodeJid(u.id) return { ...u, id: normalizedId, jid: u.jid || normalizedId } }) + + // Get current user's and bot's participant info const user = (m.isGroup ? normalizedParticipants.find(u => conn.decodeJid(u.id) === m.sender) : {}) || {} const bot = (m.isGroup ? normalizedParticipants.find(u => conn.decodeJid(u.id) == this.user.jid) : {}) || {} + /** + * Checks if a user is an admin in the specified group. + * + * @param {Object} conn - WhatsApp connection + * @param {string} chatId - Group chat ID + * @param {string} senderId - User's JID to check + * @returns {Promise} True if user is admin + */ async function isUserAdmin(conn, chatId, senderId) { try { const decodedSender = conn.decodeJid(senderId) @@ -231,19 +400,29 @@ export async function handler(chatUpdate) { } } - const isRAdmin = user?.admin == 'superadmin' || false + // Check admin status for message sender and bot + const isRAdmin = user?.admin == 'superadmin' || false // Is super admin const isAdmin = m.isGroup ? await isUserAdmin(this, m.chat, m.sender) : false const isBotAdmin = m.isGroup ? await isUserAdmin(this, m.chat, this.user.jid) : false + // Get plugins directory path const ___dirname = path.join(path.dirname(fileURLToPath(import.meta.url)), './plugins') - // === GESTIONE PLUGIN.ALL (BOTTONI, LISTE, INTERATTIVI) === + // ============================================================================ + // PLUGIN EXECUTION - PHASE 1: ALL AND BEFORE HOOKS + // ============================================================================ + + /** + * First pass through plugins to execute: + * - 'all' functions: Run on every message (buttons, lists, interactive) + * - 'before' functions: Pre-processing before command execution + */ for (let name in global.plugins) { let plugin = global.plugins[name] if (!plugin || plugin.disabled) continue const __filename = join(___dirname, name) - // Esegui la funzione 'all' se presente nel plugin + // Execute 'all' function if present (runs on every message) if (typeof plugin.all === 'function') { try { await plugin.all.call(this, m, { @@ -256,10 +435,10 @@ export async function handler(chatUpdate) { } } - // Salta plugin admin se restrict รจ disabilitato + // Skip admin plugins if restrict mode is disabled if (!opts['restrict'] && plugin.tags?.includes('admin')) continue - // Esegui la funzione 'before' se presente + // Execute 'before' function if present (pre-processing) if (typeof plugin.before === 'function') { try { const shouldContinue = await plugin.before.call(this, m, { @@ -284,18 +463,35 @@ export async function handler(chatUpdate) { } } } - // === FINE GESTIONE PLUGIN.ALL === + // === END PLUGIN.ALL/BEFORE PHASE === - // Gestione comandi normali + // ============================================================================ + // PLUGIN EXECUTION - PHASE 2: COMMAND HANDLING + // ============================================================================ + + /** + * Second pass through plugins to match and execute commands. + * Checks prefix, permissions, and executes the appropriate plugin. + */ for (let name in global.plugins) { let plugin = global.plugins[name] if (!plugin || plugin.disabled) continue const __filename = join(___dirname, name) + // Skip admin plugins if restrict is disabled if (!opts['restrict'] && plugin.tags?.includes('admin')) continue + /** + * Escapes special regex characters in a string. + * @param {string} str - String to escape + * @returns {string} Escaped string + */ const str2Regex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + + // Determine which prefix to use (plugin-specific, connection, or global) let _prefix = plugin.customPrefix ? plugin.customPrefix : conn.prefix ? conn.prefix : global.prefix + + // Match prefix against message text let match = (_prefix instanceof RegExp ? [[_prefix.exec(m.text), _prefix]] : Array.isArray(_prefix) ? @@ -308,17 +504,27 @@ export async function handler(chatUpdate) { [[[], new RegExp]] ).find(p => p[1]) + // Skip non-function plugins and unmatched prefixes if (typeof plugin !== 'function') continue if (!match) continue + // ============================================================================ + // COMMAND PARSING AND MATCHING + // ============================================================================ + if ((usedPrefix = (match[0] || '')[0])) { + // Remove prefix and parse command/arguments let noPrefix = m.text.replace(usedPrefix, '') let [command, ...args] = noPrefix.trim().split` `.filter(v => v) args = args || [] let _args = noPrefix.trim().split` `.slice(1) let text = _args.join` ` command = (command || '').toLowerCase() + + // Get fail handler (plugin-specific or global default) let fail = plugin.fail || global.dfail + + // Check if command matches plugin's command pattern let isAccept = plugin.command instanceof RegExp ? plugin.command.test(command) : Array.isArray(plugin.command) ? @@ -329,19 +535,40 @@ export async function handler(chatUpdate) { if (!isAccept) continue + // Store plugin name for statistics m.plugin = name + + // ============================================================================ + // BAN CHECKS + // ============================================================================ + if ((m.chat in global.db.data.chats || m.sender in global.db.data.users)) { let chat = global.db.data.chats[m.chat] let userDb = global.db.data.users[m.sender] + + // Skip if chat is banned (unless unban command) if (name != 'owner-unbanchat.js' && chat?.isBanned) return + + // Skip if user is banned (unless unban command) if (name != 'owner-unbanuser.js' && userDb?.banned) return } + // ============================================================================ + // ADMIN-ONLY MODE CHECK + // ============================================================================ + let chatDb = global.db.data.chats[m.chat] let adminMode = chatDb?.soloadmin let mystica = `${plugin.botAdmin || plugin.admin || plugin.group || plugin || noPrefix || _prefix || m.text.slice(0, 1) == _prefix || plugin.command}` + + // In admin mode, non-admin users can't use commands if (adminMode && !isOwner2 && !isROwner && m.isGroup && !isAdmin && mystica) return + // ============================================================================ + // PERMISSION CHECKS + // ============================================================================ + + // Check owner requirements if (plugin.rowner && plugin.owner && !(isROwner || isOwner2)) { fail('owner', m, this) continue @@ -354,50 +581,83 @@ export async function handler(chatUpdate) { fail('owner', m, this) continue } + + // Check moderator requirement if (plugin.mods && !isMods) { fail('mods', m, this) continue } + + // Check premium requirement if (plugin.premium && !isPrems) { fail('premium', m, this) continue } + + // Check group requirement if (plugin.group && !m.isGroup) { fail('group', m, this) continue - } else if (plugin.botAdmin && !isBotAdmin) { + } + + // Check bot admin requirement + else if (plugin.botAdmin && !isBotAdmin) { fail('botAdmin', m, this) continue - } else if (plugin.admin && !isAdmin) { + } + + // Check user admin requirement + else if (plugin.admin && !isAdmin) { fail('admin', m, this) continue } + + // Check private chat requirement if (plugin.private && m.isGroup) { fail('private', m, this) continue } + + // Check registration requirement if (plugin.register == true && _user?.registered == false) { fail('unreg', m, this) continue } + // ============================================================================ + // EXPERIENCE AND RESOURCE CHECKS + // ============================================================================ + + // Mark message as a command m.isCommand = true + + // Calculate experience points to award let xp = 'exp' in plugin ? parseInt(plugin.exp) : 17 if (xp > 2000) m.reply('Exp limit') + + // Check money requirement else if (plugin.money && global.db.data.users[m.sender]?.money < plugin.money * 1) { fail('senzasoldi', m, this) continue } m.exp += xp + // Check limit requirement (for non-premium users) if (!isPrems && plugin.limit && global.db.data.users[m.sender]?.limit < plugin.limit * 1) { continue } + + // Check level requirement if (plugin.level > _user?.level) { this.reply(m.chat, `livello troppo basso`, m) continue } + // ============================================================================ + // PLUGIN EXECUTION + // ============================================================================ + + // Prepare context object for plugin execution let extra = { match, usedPrefix, @@ -424,22 +684,27 @@ export async function handler(chatUpdate) { } try { + // Execute the plugin await plugin.call(this, m, extra) + + // Apply limit/money costs for non-premium users if (!isPrems) { m.limit = m.limit || plugin.limit || false m.money = m.money || plugin.money || false } } catch (e) { + // Handle plugin execution errors m.error = e console.error(e) if (e) { + // Format error message and hide API keys let textErr = format(e) for (let key of Object.values(global.APIKeys)) textErr = textErr.replace(new RegExp(key, 'g'), '#HIDDEN#') m.reply(textErr) } } finally { - // Esegui la funzione 'after' se presente + // Execute 'after' hook if present if (typeof plugin.after === 'function') { try { await plugin.after.call(this, m, extra) @@ -448,20 +713,33 @@ export async function handler(chatUpdate) { } } } + + // Only execute first matching command break } } + // ============================================================================ + // ERROR HANDLING AND CLEANUP + // ============================================================================ + } catch (e) { console.error(e) } finally { + // Remove message from queue after processing if (opts['queque'] && m.text) { const quequeIndex = this.msgqueque.indexOf(m.id || m.key.id) if (quequeIndex !== -1) this.msgqueque.splice(quequeIndex, 1) } + // ============================================================================ + // USER STATISTICS UPDATE + // ============================================================================ + if (m?.sender) { let user = global.db.data.users[m.sender] let chat = global.db.data.chats[m.chat] + + // Delete messages from muted users if (user?.muto) { await conn.sendMessage(m.chat, { delete: { @@ -472,65 +750,112 @@ export async function handler(chatUpdate) { } }) } + + // Update user statistics if (user) { - user.exp += m.exp - user.limit -= m.limit * 1 - user.money -= m.money * 1 - user.messaggi += 1 + user.exp += m.exp // Add earned experience + user.limit -= m.limit * 1 // Deduct used limit + user.money -= m.money * 1 // Deduct spent money + user.messaggi += 1 // Increment message count } + + // Update chat message count if (chat) chat.messaggi += 1 } + + // ============================================================================ + // COMMAND STATISTICS TRACKING + // ============================================================================ + if (m?.plugin) { let now = +new Date + + // Initialize stats for this plugin if not exists if (!stats[m.plugin]) { stats[m.plugin] = { - total: 0, - success: 0, - last: 0, - lastSuccess: 0 + total: 0, // Total executions + success: 0, // Successful executions + last: 0, // Last execution timestamp + lastSuccess: 0 // Last successful execution timestamp } } + const stat = stats[m.plugin] stat.total += 1 stat.last = now + + // Track successful executions (no errors) if (!m.error) { stat.success += 1 stat.lastSuccess = now } } + // ============================================================================ + // MESSAGE LOGGING AND AUTO-READ + // ============================================================================ + try { + // Print message to console (unless disabled) if (!opts['noprint']) await (await import(`./lib/print.js`)).default(m, this) } catch (e) { console.log(m, m.quoted, e) } + + // Auto-read messages if enabled if (opts['autoread']) await this.readMessages([m.key]) } } +// ============================================================================ +// GROUP PARTICIPANTS UPDATE HANDLER +// ============================================================================ + +/** + * Handles group participant changes (join, leave, promote, demote). + * Sends welcome/goodbye messages based on group settings. + * + * @param {Object} param0 - Event data + * @param {string} param0.id - Group ID + * @param {string[]} param0.participants - Array of affected participant JIDs + * @param {string} param0.action - Action type: 'add', 'remove', 'promote', 'demote' + * @this {Object} WhatsApp connection instance + */ export async function participantsUpdate({ id, participants, action }) { + // Skip in self mode or during initialization if (opts['self']) return if (this.isInit) return + + // Ensure database is loaded if (global.db.data == null) await loadDatabase() + // Get chat settings let chat = global.db.data.chats[id] || {} let text = '' + + // Get bot display name and channel JID from settings let nomeDelBot = global.db.data.nomedelbot || `๐–›๐–Š๐–-๐–‡๐–”๐–™` let jidCanale = global.db.data.jidcanale || '' switch (action) { case 'add': case 'remove': + // Only send messages if welcome feature is enabled if (chat.welcome) { + // Get group metadata for subject and description let groupMetadata = await this.groupMetadata(id) || (conn.chats[id] || {}).metadata + for (let user of participants) { - let pp = './menu/principale.jpeg' + // Try to get user's profile picture + let pp = './menu/principale.jpeg' // Default fallback image try { pp = await this.profilePictureUrl(user, 'image') } catch (e) { + // Use default if profile picture unavailable } finally { let apii = await this.getFile(pp) + // Format message based on action type if (action === 'add') { text = (chat.sWelcome || this.welcome || conn.welcome || 'benvenuto, @user!') .replace('@subject', await this.getName(id)) @@ -541,6 +866,7 @@ export async function participantsUpdate({ id, participants, action }) { .replace('@user', '@' + user.split('@')[0]) } + // Send formatted welcome/goodbye message this.sendMessage(id, { text: text, contextInfo: { @@ -574,46 +900,122 @@ export async function participantsUpdate({ id, participants, action }) { } } +// ============================================================================ +// GROUP SETTINGS UPDATE HANDLER +// ============================================================================ + +/** + * Handles group setting changes (icon, link revocation, etc.). + * Sends notification messages to the group when settings change. + * + * @param {Object[]} groupsUpdate - Array of group update objects + * @this {Object} WhatsApp connection instance + */ export async function groupsUpdate(groupsUpdate) { + // Skip in self mode if (opts['self']) return + for (const groupUpdate of groupsUpdate) { const id = groupUpdate.id if (!id) continue + let chats = global.db.data.chats[id], text = '' - if (groupUpdate.icon) text = (chats.sIcon || this.sIcon || conn.sIcon || '`immagine modificata`').replace('@icon', groupUpdate.icon) - if (groupUpdate.revoke) text = (chats.sRevoke || this.sRevoke || conn.sRevoke || '`link reimpostato, nuovo link:`\n@revoke').replace('@revoke', groupUpdate.revoke) + + // Handle group icon change + if (groupUpdate.icon) { + text = (chats.sIcon || this.sIcon || conn.sIcon || '`immagine modificata`') + .replace('@icon', groupUpdate.icon) + } + + // Handle group link revocation + if (groupUpdate.revoke) { + text = (chats.sRevoke || this.sRevoke || conn.sRevoke || '`link reimpostato, nuovo link:`\n@revoke') + .replace('@revoke', groupUpdate.revoke) + } + + // Send notification if there's a message to send if (!text) continue await this.sendMessage(id, { text, mentions: this.parseMention(text) }) } } +// ============================================================================ +// INCOMING CALL HANDLER +// ============================================================================ + +/** + * Handles incoming WhatsApp calls. + * Implements anti-call feature that blocks callers if enabled. + * + * @param {Object[]} callUpdate - Array of call event objects + * @this {Object} WhatsApp connection instance + */ export async function callUpdate(callUpdate) { + // Check if anti-call is enabled in settings let isAnticall = global.db.data.settings[this.user.jid].antiCall if (!isAnticall) return + for (let nk of callUpdate) { + // Only handle private calls (not group calls) if (nk.isGroup == false) { + // Only respond to incoming call offers if (nk.status == 'offer') { + // Send warning message to caller let callmsg = await this.reply(nk.from, `ciao @${nk.from.split('@')[0]}, c'รจ anticall.`, false, { mentions: [nk.from] }) + + // Send contact card with bot info let vcard = `BEGIN:VCARD\nVERSION:5.0\nN:;๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ;;;\nFN:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nORG:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nTITLE:\nitem1.TEL;waid=393773842461:+39 3515533859\nitem1.X-ABLabel:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nX-WA-BIZ-DESCRIPTION:ofc\nX-WA-BIZ-NAME:๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ\nEND:VCARD` await this.sendMessage(nk.from, { contacts: { displayName: 'Unlimited', contacts: [{ vcard }] } }, { quoted: callmsg }) + + // Block the caller await this.updateBlockStatus(nk.from, 'block') } } } } +// ============================================================================ +// MESSAGE DELETE HANDLER +// ============================================================================ + +/** + * Handles message deletion events. + * Can be used to implement anti-delete features. + * + * @param {Object} message - Deleted message info + * @this {Object} WhatsApp connection instance + */ export async function deleteUpdate(message) { try { const { fromMe, id, participant } = message + + // Skip own message deletions if (fromMe) return + + // Try to load the deleted message from store let msg = this.serializeM(this.loadMessage(id)) if (!msg) return + + // Additional anti-delete logic can be added here } catch (e) { console.error(e) } } +// ============================================================================ +// DEFAULT FAILURE MESSAGE HANDLER +// ============================================================================ + +/** + * Default handler for permission/requirement failures. + * Displays appropriate error messages when users lack required permissions. + * + * @param {string} type - Failure type (owner, admin, premium, etc.) + * @param {Object} m - Message object + * @param {Object} conn - WhatsApp connection + */ global.dfail = (type, m, conn) => { + // Map failure types to Italian error messages let msg = { rowner: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฌ๐จ๐ฅ๐จ ๐ฉ๐ž๐ซ ๐จ๐ฐ๐ง๐ž๐ซ ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ', owner: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฌ๐จ๐ฅ๐จ ๐ฉ๐ž๐ซ ๐จ๐ฐ๐ง๐ž๐ซ ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ', @@ -625,6 +1027,8 @@ global.dfail = (type, m, conn) => { botAdmin: '๐ƒ๐ž๐ฏ๐ข ๐๐š๐ซ๐ž ๐š๐๐ฆ๐ข๐ง ๐š๐ฅ ๐›๐จ๐ญ ๐Ÿ‘‘', restrict: '๐Ÿ” ๐‘๐ž๐ฌ๐ญ๐ซ๐ข๐œ๐ญ ๐ž ๐๐ข๐ฌ๐š๐ญ๐ญ๐ข๐ฏ๐š๐ญ๐จ ๐Ÿ”' }[type] + + // Send styled error message if type is recognized if (msg) return conn.sendMessage(m.chat, { text: ' ', contextInfo: { @@ -640,6 +1044,11 @@ global.dfail = (type, m, conn) => { }, { quoted: m }) } +// ============================================================================ +// HOT-RELOAD WATCHER +// ============================================================================ + +// Watch this file for changes and auto-reload const file = global.__filename(import.meta.url, true) watchFile(file, async () => { unwatchFile(file) diff --git a/lib/print.js b/lib/print.js index de0b5bc7a..489f889d0 100644 --- a/lib/print.js +++ b/lib/print.js @@ -1,24 +1,60 @@ +/** + * ============================================================================ + * PRINT.JS - Console Message Logger + * ============================================================================ + * + * This module provides formatted console logging for incoming WhatsApp messages. + * It displays: + * - Message metadata (sender, chat, timestamp) + * - Message type and size + * - Message content with mention resolution + * - Event stub parameters (for group events) + * + * @author ChatUnity Team + * @version 1.0 + * ============================================================================ + */ + import { WAMessageStubType } from '@whiskeysockets/baileys' import chalk from 'chalk' import { watchFile } from 'fs' import { fileURLToPath } from 'url' +// ============================================================================ +// NAME CACHING SYSTEM +// ============================================================================ + +/** Cache for resolved contact/group names */ const nameCache = new Map() + +/** Time-to-live for cached names (5 minutes) */ const CACHE_TTL = 300000 +/** + * Retrieves a cached name or fetches and caches a new one. + * Uses a race condition with timeout to prevent blocking on slow lookups. + * + * @param {Object} conn - WhatsApp connection instance + * @param {string} jid - JID to get name for + * @returns {Promise} Resolved name or null + */ async function getCachedName(conn, jid) { if (!jid) return null + // Check cache first const cached = nameCache.get(jid) if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.name } try { + // Race between name lookup and 100ms timeout const name = await Promise.race([ conn.getName(jid), new Promise((resolve) => setTimeout(() => resolve(null), 100)) ]) + + // Cache the result nameCache.set(jid, { name, timestamp: Date.now() }) return name } catch { @@ -26,34 +62,59 @@ async function getCachedName(conn, jid) { } } +// ============================================================================ +// MAIN PRINT FUNCTION +// ============================================================================ + +/** + * Prints formatted message information to the console. + * Creates a visually appealing log entry with all relevant message details. + * + * @param {Object} m - Serialized message object + * @param {Object} conn - WhatsApp connection instance + * @returns {Promise} + */ export default async function (m, conn = { user: {} }) { try { let sender = m.sender + // Get participant if available (for group messages) if (m.key?.participant) { sender = m.key.participant } + // Decode/normalize sender JID let resolvedSender = conn.decodeJid ? conn.decodeJid(sender) : sender + // Handle LID (Linked ID) format - use phone number if available if (/@lid/.test(resolvedSender) && m.key?.senderPn) { resolvedSender = m.key.senderPn } + // Fetch sender and chat names concurrently const [senderName, chatName] = await Promise.all([ getCachedName(conn, resolvedSender), getCachedName(conn, m.chat) ]) + // Format sender display string let displaySender = '+' + resolvedSender.replace('@s.whatsapp.net', '').replace('@lid', '') + (senderName ? ' ~ ' + senderName : '') + // Calculate message size let filesize = (m.msg?.fileLength?.low || m.msg?.fileLength || m.text?.length || 0) + // Get bot's display info let me = '+' + (conn.user?.jid || '').replace('@s.whatsapp.net', '') const userName = conn.user.name || conn.user.verifiedName || "Sconosciuto" + // Skip logging own messages if (resolvedSender === conn.user?.jid) return + // ============================================================================ + // FORMATTED CONSOLE OUTPUT + // ============================================================================ + + // Print main message info box console.log(`${chalk.hex('#FE0041').bold('โ•ญโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…')} ${chalk.hex('#FE0041').bold('โ”‚')} ${chalk.redBright('Bot:')} ${chalk.greenBright(me)} ~ ${chalk.magentaBright(userName)} ${global.conn?.user?.jid === conn.user?.jid ? chalk.cyanBright('(Principale)') : chalk.cyanBright('(Sub-Bot)')} ${chalk.hex('#FE0041').bold('โ”‚')} ${chalk.yellowBright('Data:')} ${chalk.blueBright(new Date(m.messageTimestamp ? 1000 * (m.messageTimestamp.low || m.messageTimestamp) : Date.now()).toLocaleDateString("it-IT", { day: 'numeric', month: 'long', year: 'numeric' }))} @@ -64,9 +125,14 @@ ${chalk.hex('#FE0041').bold('โ”‚')} ${chalk.cyanBright(`Chat:`)} ${chalk.greenBr ${chalk.hex('#FE0041').bold('โ”‚')} ${chalk.magentaBright('Tipo:')} ${chalk.yellowBright(m.mtype?.replace(/message$/i, '').replace('audio', m.msg?.ptt ? 'PTT' : 'audio') || 'Sconosciuto')} ${chalk.hex('#FE0041').bold('โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…')}`) + // ============================================================================ + // MESSAGE TEXT WITH MENTION RESOLUTION + // ============================================================================ + if (typeof m.text === 'string' && m.text) { let displayText = m.text + // Resolve @mentions to display names if (m.mentionedJid && Array.isArray(m.mentionedJid) && m.mentionedJid.length > 0) { for (const id of m.mentionedJid) { try { @@ -75,6 +141,7 @@ ${chalk.hex('#FE0041').bold('โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… let displayNum = originalNum.split(':')[0] let name = await getCachedName(conn, mentionJid) || displayNum + // Handle LID mentions in groups - try to find real phone number if (m.isGroup && /@lid/.test(mentionJid)) { try { const groupMeta = await conn.groupMetadata(m.chat) @@ -89,24 +156,34 @@ ${chalk.hex('#FE0041').bold('โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… name = await getCachedName(conn, realJid) || displayNum } } catch (e) { + // Ignore group metadata errors } } + // Replace @number with @number ~name format const replacement = '@' + displayNum + (name && name !== displayNum ? ' ~' + name : '') displayText = displayText.replace('@' + originalNum, replacement) } catch (e) { + // Ignore individual mention resolution errors } } } + // Print message text with color based on type + // Red for errors, yellow for commands, white for regular messages console.log(m.error != null ? chalk.red(displayText) : m.isCommand ? chalk.yellow(displayText) : chalk.white(displayText)) } + // ============================================================================ + // EVENT STUB PARAMETERS (GROUP EVENTS) + // ============================================================================ + + // Print stub parameters (e.g., participants in join/leave events) if (m.messageStubParameters?.length > 0) { const decoded = await Promise.all(m.messageStubParameters.map(async jid => { let resolvedJid = conn.decodeJid ? conn.decodeJid(jid) : jid - + // Try to resolve LID to phone number if (/@lid/.test(resolvedJid)) { try { const pn = await conn.getPNForLID?.(jid) @@ -121,22 +198,34 @@ ${chalk.hex('#FE0041').bold('โ•ฐโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… console.log(decoded.join(', ')) } + // ============================================================================ + // SPECIAL MESSAGE TYPE INDICATORS + // ============================================================================ + + // Document messages if (/document/i.test(m.mtype)) console.log(`๐Ÿ“„ ${m.msg.fileName || m.msg.displayName || 'Documento'}`) + + // Contact messages else if (/contact/i.test(m.mtype)) console.log(`๐Ÿ“‡ ${m.msg.displayName || 'Contatto'}`) + + // Audio messages (voice notes and audio files) else if (/audio/i.test(m.mtype)) { const duration = m.msg.seconds || 0 console.log(`${m.msg.ptt ? '๐ŸŽค (PTT' : '๐ŸŽต ('}AUDIO) ${Math.floor(duration / 60).toString().padStart(2, 0)}:${(duration % 60).toString().padStart(2, 0)}`) } + // Print empty line for spacing console.log() } catch (e) { - + // Silently ignore print errors to prevent log spam } } +// ============================================================================ +// HOT-RELOAD WATCHER +// ============================================================================ + const __filename = fileURLToPath(import.meta.url) watchFile(__filename, () => { console.log(chalk.redBright("Aggiornamento 'lib/print.js'")) -}) - - +}) \ No newline at end of file diff --git a/main.js b/main.js index f03ac2be2..744a69d3a 100644 --- a/main.js +++ b/main.js @@ -1,29 +1,67 @@ +/** + * ============================================================================ + * MAIN.JS - ChatUnity WhatsApp Bot Entry Point + * ============================================================================ + * + * This is the main entry point for the ChatUnity WhatsApp bot. + * It handles: + * - WhatsApp connection and authentication + * - Session management and cleanup + * - Plugin loading and hot-reloading + * - Sub-bot connections for multiple accounts + * - Database initialization + * + * @author ChatUnity Team + * @version 8.0 + * ============================================================================ + */ + +// Enable TLS certificate verification for secure connections process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '1'; + +// ============================================================================ +// MODULE IMPORTS +// ============================================================================ + +// Import global configuration (must be loaded first) import './config.js'; + +// Node.js core modules import { createRequire } from 'module'; import path, { join } from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import { platform } from 'process'; import fs, { readdirSync, statSync, unlinkSync, existsSync, mkdirSync, rmSync, watch } from 'fs'; -import yargs from 'yargs'; +import { tmpdir } from 'os'; +import { format } from 'util'; import { spawn } from 'child_process'; +import readline from 'readline'; + +// Third-party modules +import yargs from 'yargs'; import lodash from 'lodash'; import chalk from 'chalk'; import syntaxerror from 'syntax-error'; -import { tmpdir } from 'os'; -import { format } from 'util'; import pino from 'pino'; -import { makeWASocket, protoType, serialize } from './lib/simple.js'; import { Low, JSONFile } from 'lowdb'; -import readline from 'readline'; import NodeCache from 'node-cache'; +// Custom modules +import { makeWASocket, protoType, serialize } from './lib/simple.js'; + +// ============================================================================ +// DIRECTORY CONFIGURATION +// ============================================================================ + +/** Directory for storing WhatsApp session files */ const sessionFolder = path.join(process.cwd(), global.authFile || 'sessioni'); + +/** Directory for temporary files that are periodically cleaned */ const tempDir = join(process.cwd(), 'temp'); const tmpDir = join(process.cwd(), 'tmp'); - +// Ensure required directories exist if (!existsSync(tempDir)) { mkdirSync(tempDir, { recursive: true }); } @@ -32,100 +70,216 @@ if (!existsSync(tmpDir)) { } +// ============================================================================ +// SESSION CLEANUP FUNCTIONS +// ============================================================================ + +/** + * Clears non-critical files from the session folder. + * Preserves `creds.json` and files starting with `pre-key` to maintain session integrity. + * This function is called periodically to prevent session file accumulation. + * + * @param {string} dir - Directory path to clean (defaults to sessionFolder) + */ function clearSessionFolderSelective(dir = sessionFolder) { + // Create directory if it doesn't exist if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); return; } + const entries = fs.readdirSync(dir); for (const entry of entries) { const fullPath = path.join(dir, entry); + + // Always preserve credentials file if (entry === 'creds.json') continue; + const stat = fs.statSync(fullPath); if (stat.isDirectory()) { + // Recursively clean subdirectories clearSessionFolderSelective(fullPath); fs.rmdirSync(fullPath); } else { + // Preserve pre-key files, delete everything else if (!entry.startsWith('pre-key')) { try { fs.unlinkSync(fullPath); - } catch {} + } catch { + // Silently ignore deletion errors + } } } } + console.log(`Cartella sessioni pulita (file non critici rimossi): ${new Date().toLocaleTimeString()}`); } - +/** + * Purges session files based on age and type. + * Removes old pre-key files (older than 1 day) and non-essential session files. + * + * @param {string} sessionDir - Path to the session directory + * @param {boolean} cleanPreKeys - Whether to also clean old pre-key files + */ function purgeSession(sessionDir, cleanPreKeys = false) { if (!existsSync(sessionDir)) return; + const files = readdirSync(sessionDir); + files.forEach(file => { + // Never delete credentials if (file === 'creds.json') return; + const filePath = path.join(sessionDir, file); const stats = statSync(filePath); - const fileAge = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24); + + // Calculate file age in days + const fileAgeDays = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24); + if (file.startsWith('pre-key') && cleanPreKeys) { - if (fileAge > 1) { + // Only delete old pre-key files when explicitly requested + if (fileAgeDays > 1) { try { unlinkSync(filePath); - } catch {} + } catch { + // Silently ignore errors + } } } else if (!file.startsWith('pre-key')) { + // Remove all non-pre-key files try { if (stats.isDirectory()) { rmSync(filePath, { recursive: true, force: true }); } else { unlinkSync(filePath); } - } catch {} + } catch { + // Silently ignore errors + } } }); } +// ============================================================================ +// SCHEDULED CLEANUP INTERVALS +// ============================================================================ + +/** + * Session cleanup scheduler - runs every 30 minutes + * Clears non-critical session files to prevent disk space issues + */ setInterval(async () => { if (stopped === 'close' || !conn || !conn.user) return; clearSessionFolderSelective(); }, 30 * 60 * 1000); - +/** + * Session purge scheduler - runs every 20 minutes + * Purges session files for main bot and all sub-bots + */ setInterval(async () => { if (stopped === 'close' || !conn || !conn.user) return; + + // Purge main bot session purgeSession(`./sessioni`); + + // Purge all sub-bot sessions const subBotDir = `./${global.authFileJB}`; if (existsSync(subBotDir)) { - const subBotFolders = readdirSync(subBotDir).filter(file => statSync(join(subBotDir, file)).isDirectory()); + const subBotFolders = readdirSync(subBotDir).filter(file => + statSync(join(subBotDir, file)).isDirectory() + ); subBotFolders.forEach(folder => purgeSession(join(subBotDir, folder))); } }, 20 * 60 * 1000); - +/** + * Deep cleanup scheduler - runs every 3 hours + * Performs aggressive cleanup including old pre-key files + */ setInterval(async () => { if (stopped === 'close' || !conn || !conn.user) return; + + // Deep purge main bot session purgeSession(`./${global.authFile}`, true); + + // Deep purge all sub-bot sessions const subBotDir = `./${global.authFileJB}`; if (existsSync(subBotDir)) { - const subBotFolders = readdirSync(subBotDir).filter(file => statSync(join(subBotDir, file)).isDirectory()); + const subBotFolders = readdirSync(subBotDir).filter(file => + statSync(join(subBotDir, file)).isDirectory() + ); subBotFolders.forEach(folder => purgeSession(join(subBotDir, folder), true)); } }, 3 * 60 * 60 * 1000); -const { useMultiFileAuthState, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, Browsers, jidNormalizedUser, makeInMemoryStore, DisconnectReason } = await import('@whiskeysockets/baileys'); +// ============================================================================ +// BAILEYS LIBRARY IMPORTS +// ============================================================================ + +const { + useMultiFileAuthState, + fetchLatestBaileysVersion, + makeCacheableSignalKeyStore, + Browsers, + jidNormalizedUser, + makeInMemoryStore, + DisconnectReason +} = await import('@whiskeysockets/baileys'); + const { chain } = lodash; + +// Server port configuration with fallbacks const PORT = process.env.PORT || process.env.SERVER_PORT || 3000; + +// Initialize prototype extensions for WhatsApp message handling protoType(); serialize(); + +// ============================================================================ +// GLOBAL STATE FLAGS +// ============================================================================ + +/** Flag to prevent duplicate logo printing */ global.isLogoPrinted = false; + +/** Flag to track if QR code has been generated */ global.qrGenerated = false; + +/** Track which connection messages have been printed to avoid spam */ global.connectionMessagesPrinted = {}; + +// ============================================================================ +// AUTHENTICATION METHOD CONFIGURATION +// ============================================================================ + +/** Use QR code authentication method */ let methodCodeQR = process.argv.includes("qr"); + +/** Use 8-character pairing code authentication */ let methodCode = process.argv.includes("code"); + +/** Use mobile-based authentication */ let MethodMobile = process.argv.includes("mobile"); + +/** Phone number for pairing code method */ let phoneNumber = global.botNumberCode; +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +/** + * Generates a random alphanumeric code for pairing. + * Used when authenticating via 8-character pairing code method. + * + * @param {number} length - Length of the code to generate (default: 8) + * @returns {string} Random alphanumeric code + */ function generateRandomCode(length = 8) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; @@ -135,11 +289,18 @@ function generateRandomCode(length = 8) { return result; } - +/** + * Redefines console methods to filter out specific log messages. + * Used to suppress noisy or sensitive log output from the WhatsApp library. + * + * @param {string} methodName - Console method to override (log, warn, error) + * @param {string[]} filterStrings - Base64-encoded strings to filter out + */ function redefineConsoleMethod(methodName, filterStrings) { const originalConsoleMethod = console[methodName]; console[methodName] = function () { const message = arguments[0]; + // Check if message contains any filtered string (decoded from base64) if (typeof message === 'string' && filterStrings.some(filterString => message.includes(atob(filterString)))) { arguments[0] = ""; } @@ -148,29 +309,94 @@ function redefineConsoleMethod(methodName, filterStrings) { } +// ============================================================================ +// GLOBAL PATH UTILITIES +// ============================================================================ + +/** + * Converts a URL to a file path. + * Handles both file:// URLs and regular paths across platforms. + * + * @param {string} pathURL - URL or path to convert + * @param {boolean} rmPrefix - Whether to remove the file:// prefix + * @returns {string} Converted file path + */ global.__filename = function filename(pathURL = import.meta.url, rmPrefix = platform !== 'win32') { - return rmPrefix ? /file:\/\/\//.test(pathURL) ? fileURLToPath(pathURL) : pathURL : pathToFileURL(pathURL).toString(); + return rmPrefix + ? /file:\/\/\//.test(pathURL) ? fileURLToPath(pathURL) : pathURL + : pathToFileURL(pathURL).toString(); }; - +/** + * Gets the directory name from a URL or path. + * + * @param {string} pathURL - URL or path to get directory from + * @returns {string} Directory path + */ global.__dirname = function dirname(pathURL) { return path.dirname(global.__filename(pathURL, true)); }; - +/** + * Creates a require function for ES modules compatibility. + * Allows importing CommonJS modules in ES module context. + * + * @param {string} dir - Base URL for require resolution + * @returns {Function} Require function + */ global.__require = function require(dir = import.meta.url) { return createRequire(dir); }; -global.API = (name, path = '/', query = {}, apikeyqueryname) => (name in global.APIs ? global.APIs[name] : name) + path + (query || apikeyqueryname ? '?' + new URLSearchParams(Object.entries({ ...query, ...(apikeyqueryname ? { [apikeyqueryname]: global.APIKeys[name in global.APIs ? global.APIs[name] : name] } : {}) })) : ''); +// ============================================================================ +// API AND DATABASE CONFIGURATION +// ============================================================================ + +/** + * Constructs an API URL with query parameters and optional API key. + * + * @param {string} name - API name (key in global.APIs) or base URL + * @param {string} path - API endpoint path + * @param {Object} query - Query parameters object + * @param {string} apikeyqueryname - Parameter name for API key injection + * @returns {string} Complete API URL + */ +global.API = (name, path = '/', query = {}, apikeyqueryname) => { + const baseUrl = name in global.APIs ? global.APIs[name] : name; + const queryString = query || apikeyqueryname + ? '?' + new URLSearchParams(Object.entries({ + ...query, + ...(apikeyqueryname ? { [apikeyqueryname]: global.APIKeys[baseUrl] } : {}) + })) + : ''; + return baseUrl + path + queryString; +}; + +/** Bot startup timestamp */ global.timestamp = { start: new Date }; + +// Current directory for module resolution const __dirname = global.__dirname(import.meta.url); + +// Parse command line arguments global.opts = new Object(yargs(process.argv.slice(2)).exitProcess(false).parse()); + +// Configure command prefix pattern (supports multiple special characters) global.prefix = new RegExp('^[' + (opts['prefix'] || '*/!#$%+ยฃยขโ‚ฌยฅ^ยฐ=ยถโˆ†ร—รทฯ€โˆšโœ“ยฉยฎ&.\\-.@').replace(/[|\\{}()[\]^$+*.\-\^]/g, '\\$&') + ']'); + +// Initialize database with lowdb (supports both local JSON and cloud storage) global.db = new Low(/https?:\/\//.test(opts['db'] || '') ? new cloudDBAdapter(opts['db']) : new JSONFile('database.json')); global.DATABASE = global.db; + +/** + * Loads the database and ensures it has required structure. + * Handles concurrent read prevention and initializes default data structures. + * + * @returns {Promise} Database data object + */ global.loadDatabase = async function loadDatabase() { + // Prevent concurrent database reads if (global.db.READ) { return new Promise((resolve) => setInterval(async function () { if (!global.db.READ) { @@ -179,40 +405,75 @@ global.loadDatabase = async function loadDatabase() { } }, 1 * 1000)); } + + // Skip if data already loaded if (global.db.data !== null) return; + + // Mark as reading to prevent concurrent access global.db.READ = true; await global.db.read().catch(console.error); global.db.READ = null; + + // Initialize default data structure global.db.data = { - users: {}, - chats: {}, - stats: {}, - msgs: {}, - sticker: {}, - settings: {}, + users: {}, // User profiles and stats + chats: {}, // Chat/group settings + stats: {}, // Command usage statistics + msgs: {}, // Message store + sticker: {}, // Sticker metadata + settings: {}, // Bot settings ...(global.db.data || {}), }; + + // Enable lodash chain for data manipulation global.db.chain = chain(global.db.data); }; + +// Load database on startup loadDatabase(); +// ============================================================================ +// CONNECTION ARRAY INITIALIZATION +// ============================================================================ + +// Initialize connections array if not already present if (global.conns instanceof Array) { console.log('Connessioni giร  inizializzate...'); } else { global.conns = []; } +// ============================================================================ +// AUTHENTICATION FILE PATHS +// ============================================================================ +/** Path to credentials file */ global.creds = 'creds.json'; + +/** Main bot session directory */ global.authFile = 'sessioni'; + +/** Sub-bot sessions directory */ global.authFileJB = 'chatunity-sub'; +// ============================================================================ +// AUTHENTICATION STATE SETUP +// ============================================================================ +// Initialize multi-file authentication state const { state, saveCreds } = await useMultiFileAuthState(global.authFile); + +// Message retry counter (unused but required by Baileys) const msgRetryCounterMap = (MessageRetryMap) => { }; + +// Cache for message retry counts const msgRetryCounterCache = new NodeCache(); + +// Fetch latest Baileys version for compatibility const { version } = await fetchLatestBaileysVersion(); + +// Setup readline interface for user input let rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -220,6 +481,13 @@ let rl = readline.createInterface({ }); +/** + * Prompts the user with a question and returns their response. + * Clears the line before and after input for clean display. + * + * @param {string} t - Question text to display + * @returns {Promise} User's trimmed response + */ const question = (t) => { rl.clearLine(rl.input, 0); return new Promise((resolver) => { @@ -230,8 +498,13 @@ const question = (t) => { }); }; +// ============================================================================ +// AUTHENTICATION METHOD SELECTION +// ============================================================================ let opzione; + +// Only prompt for connection method if no credentials exist if (!methodCodeQR && !methodCode && !fs.existsSync(`./${authFile}/creds.json`)) { do { const menu = `โ•ญโ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜…โ”€โ”€โ”€โ”€โ˜… @@ -252,23 +525,47 @@ if (!methodCodeQR && !methodCode && !fs.existsSync(`./${authFile}/creds.json`)) } +// ============================================================================ +// CONSOLE OUTPUT FILTERING +// ============================================================================ + +// Base64-encoded strings to filter from console output +// These represent noisy/sensitive messages from the WhatsApp library const filterStrings = [ - "Q2xvc2luZyBzdGFsZSBvcGVu", - "Q2xvc2luZyBvcGVuIHNlc3Npb24=", - "RmFpbGVkIHRvIGRlY3J5cHQ=", - "U2Vzc2lvbiBlcnJvcg==", - "RXJyb3I6IEJhZCBNQUM=", - "RGVjcnlwdGVkIG1lc3NhZ2U=" + "Q2xvc2luZyBzdGFsZSBvcGVu", // "Closing stale open" + "Q2xvc2luZyBvcGVuIHNlc3Npb24=", // "Closing open session" + "RmFpbGVkIHRvIGRlY3J5cHQ=", // "Failed to decrypt" + "U2Vzc2lvbiBlcnJvcg==", // "Session error" + "RXJyb3I6IEJhZCBNQUM=", // "Error: Bad MAC" + "RGVjcnlwdGVkIG1lc3NhZ2U=" // "Decrypted message" ]; + +// Suppress info and debug logs console.info = () => { }; console.debug = () => { }; + +// Apply message filtering to log, warn, and error ['log', 'warn', 'error'].forEach(methodName => redefineConsoleMethod(methodName, filterStrings)); +// ============================================================================ +// CACHING CONFIGURATION +// ============================================================================ +/** + * Group metadata cache to reduce API calls. + * TTL: 5 minutes, cleanup every 60 seconds, max 500 entries + */ const groupMetadataCache = new NodeCache({ stdTTL: 300, checkperiod: 60, maxKeys: 500 }); global.groupCache = groupMetadataCache; +// ============================================================================ +// LOGGING CONFIGURATION +// ============================================================================ +/** + * Pino logger with silent level and sensitive data redaction. + * Censors credentials, auth tokens, media URLs, and secrets. + */ const logger = pino({ level: 'silent', redact: { @@ -288,25 +585,66 @@ const logger = pino({ timestamp: () => `,"time":"${new Date().toJSON()}"` }); +// ============================================================================ +// JID AND MESSAGE STORE +// ============================================================================ +/** + * JID (WhatsApp ID) cache for fast lookups. + * TTL: 10 minutes, no cloning for performance, max 1000 entries + */ global.jidCache = new NodeCache({ stdTTL: 600, useClones: false, maxKeys: 1000 }); + +/** In-memory message store for message retrieval */ global.store = makeInMemoryStore({ logger }); +// ============================================================================ +// WHATSAPP CONNECTION OPTIONS +// ============================================================================ + +/** + * WhatsApp socket connection configuration. + * Includes authentication, caching, message handling, and timeouts. + */ const connectionOptions = { logger: logger, + + // Print QR code in terminal if using QR method printQRInTerminal: opzione === '1' || methodCodeQR, + + // Mobile authentication flag mobile: MethodMobile, + + // Authentication state with cacheable signal keys auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys, logger), }, + + // Browser identification based on auth method browser: opzione === '1' ? Browsers.windows('Chrome') : methodCodeQR ? Browsers.windows('Chrome') : Browsers.macOS('Safari'), + + // Baileys version for compatibility version: version, + + // Don't mark as online on connect (stealth mode) markOnlineOnConnect: false, + + // Enable high-quality link previews generateHighQualityLinkPreview: true, + + // Disable full history sync for performance syncFullHistory: false, + + // Link preview thumbnail dimensions linkPreviewImageThumbnailWidth: 192, + + /** + * Retrieves a message from the store for retry/quote operations. + * @param {Object} key - Message key with remoteJid and id + * @returns {Promise} Message content or undefined + */ getMessage: async (key) => { try { const jid = global.conn.decodeJid(key.remoteJid); @@ -316,18 +654,33 @@ const connectionOptions = { return undefined; } }, - defaultQueryTimeoutMs: 60000, - connectTimeoutMs: 60000, - keepAliveIntervalMs: 30000, - emitOwnEvents: true, - fireInitQueries: true, + + // Timeout configurations (in milliseconds) + defaultQueryTimeoutMs: 60000, // 60 seconds for queries + connectTimeoutMs: 60000, // 60 seconds for initial connection + keepAliveIntervalMs: 30000, // 30 seconds keep-alive ping + + // Event emission settings + emitOwnEvents: true, // Emit events for own messages + fireInitQueries: true, // Fire initial queries on connect + + // Transaction retry settings transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, + + /** + * Retrieves cached group metadata or fetches fresh data. + * Uses NodeCache for performance optimization. + * + * @param {string} jid - Group JID + * @returns {Promise} Group metadata + */ cachedGroupMetadata: async (jid) => { const cached = global.groupCache.get(jid); if (cached) return cached; + try { const metadata = await global.conn.groupMetadata(global.conn.decodeJid(jid)); global.groupCache.set(jid, metadata); @@ -336,38 +689,66 @@ const connectionOptions = { return {}; } }, + + /** + * Decodes and normalizes a WhatsApp JID. + * Handles LID (linked device ID) conversion and caching. + * + * @param {string} jid - JID to decode + * @returns {string|null} Normalized JID + */ decodeJid: (jid) => { if (!jid) return jid; + + // Check cache first const cached = global.jidCache.get(jid); if (cached) return cached; - let decoded = jid; + + // Normalize JIDs with port numbers if (/:\d+@/gi.test(jid)) { decoded = jidNormalizedUser(jid); } + + // Convert object format to string if (typeof decoded === 'object' && decoded.user && decoded.server) { decoded = `${decoded.user}@${decoded.server}`; } + + // Convert LID format to standard format if (typeof decoded === 'string' && decoded.endsWith('@lid')) { decoded = decoded.replace('@lid', '@s.whatsapp.net'); } - + // Cache and return global.jidCache.set(jid, decoded); return decoded; }, + + // Message retry handling msgRetryCounterCache, msgRetryCounterMap, retryRequestDelayMs: 250, maxMsgRetryCount: 3, + + // Don't ignore any JIDs shouldIgnoreJid: jid => false, + + /** + * Patches interactive messages (buttons, lists) for compatibility. + * Wraps them in viewOnceMessage format to work around restrictions. + * + * @param {Object} message - Message to patch + * @returns {Object} Patched message + */ patchMessageBeforeSending: (message) => { const requiresPatch = !!( message.buttonsMessage || message.templateMessage || message.listMessage ); + if (requiresPatch) { message = { viewOnceMessage: { @@ -381,55 +762,99 @@ const connectionOptions = { } }; } + return message; } }; +// ============================================================================ +// WHATSAPP SOCKET INITIALIZATION +// ============================================================================ + +// Create main WhatsApp socket connection global.conn = makeWASocket(connectionOptions); + +// Bind message store to connection events global.store.bind(global.conn.ev); +// ============================================================================ +// PAIRING CODE AUTHENTICATION +// ============================================================================ +// Handle pairing code authentication if no credentials exist if (!fs.existsSync(`./${authFile}/creds.json`)) { if (opzione === '2' || methodCode) { opzione = '2'; + if (!conn.authState.creds.registered) { let addNumber; + if (phoneNumber) { + // Use pre-configured phone number addNumber = phoneNumber.replace(/[^0-9]/g, ''); } else { + // Prompt for phone number phoneNumber = await question(chalk.bgBlack(chalk.bold.bgMagentaBright(`Inserisci il numero di WhatsApp.\n${chalk.bold.yellowBright("Esempio: +393471234567")}\n${chalk.bold.magenta('PS: รจ normale che appare il qrcode incollate comunque il numero')}`))); addNumber = phoneNumber.replace(/\D/g, ''); if (!phoneNumber.startsWith('+')) phoneNumber = `+${phoneNumber}`; rl.close(); } + + // Request pairing code after 3 second delay setTimeout(async () => { const randomCode = generateRandomCode(); let codeBot = await conn.requestPairingCode(addNumber, randomCode); + + // Format code with dashes for readability codeBot = codeBot?.match(/.{1,4}/g)?.join("-") || codeBot; codeBot = codeBot.toUpperCase(); + console.log(chalk.bold.white(chalk.bgBlueBright('๊’ฐ๐Ÿฉธ๊’ฑ โ—ฆโ€ขโ‰ซ CODICE DI COLLEGAMENTO:')), chalk.bold.white(chalk.white(codeBot))); }, 3000); } } } +// ============================================================================ +// CONNECTION STATE FLAGS +// ============================================================================ +/** Flag indicating if connection is initialized */ conn.isInit = false; + +/** Flag indicating if bot is ready */ conn.well = false; +// ============================================================================ +// CHANNEL SUBSCRIPTION +// ============================================================================ +/** + * Subscribes the bot to the main ChatUnity channel. + * Called on successful connection. + */ async function chatunityedition() { try { const mainChannelId = global.IdCanale?.[0] || '120363259442839354@newsletter'; await global.conn.newsletterFollow(mainChannelId); - } catch (error) {} + } catch (error) { + // Silently ignore subscription errors + } } +// ============================================================================ +// DATABASE PERSISTENCE AND AUTO-CLEANUP +// ============================================================================ + +// Periodic database write and temp file cleanup (every 30 seconds) if (!opts['test']) { if (global.db) setInterval(async () => { + // Save database to disk if (global.db.data) await global.db.write(); + + // Auto-cleanup temp directories if feature is enabled if (opts['autocleartmp'] && (global.support || {}).find) { const tmp = [tmpdir(), 'tmp', "chatunity-sub"]; tmp.forEach(filename => spawn('find', [filename, '-amin', '2', '-type', 'f', '-delete'])); @@ -437,22 +862,42 @@ if (!opts['test']) { }, 30 * 1000); } - +// Start HTTP server if server option is enabled if (opts['server']) (await import('./server.js')).default(global.conn, PORT); +// ============================================================================ +// CONNECTION UPDATE HANDLER +// ============================================================================ + +/** + * Handles WebSocket connection state changes. + * Manages reconnection, QR code display, and connection status messages. + * + * @param {Object} update - Connection update object from Baileys + */ async function connectionUpdate(update) { const { connection, lastDisconnect, isNewLogin, qr } = update; + + // Store connection state globally global.stopped = connection; + + // Mark as initialized on new login if (isNewLogin) conn.isInit = true; + + // Get disconnect reason code const code = lastDisconnect?.error?.output?.statusCode || lastDisconnect?.error?.output?.payload?.statusCode; + + // Auto-reconnect on disconnect (except logout) if (code && code !== DisconnectReason.loggedOut) { await global.reloadHandler(true).catch(console.error); global.timestamp.connect = new Date; } + + // Ensure database is loaded if (global.db.data == null) loadDatabase(); - + // Display QR code prompt when using QR authentication if (qr && (opzione === '1' || methodCodeQR) && !global.qrGenerated) { console.log(chalk.bold.yellow(` โ”Š โ”Š โ”Š โ”Šโ€ฟ หšโžถ ๏ฝกหš SCANSIONA IL CODICE QR @@ -462,10 +907,12 @@ async function connectionUpdate(update) { global.qrGenerated = true; } - + // Handle successful connection if (connection === 'open') { global.qrGenerated = false; global.connectionMessagesPrinted = {}; + + // Display logo on first connection if (!global.isLogoPrinted) { const chatunity = chalk.hex('#3b0d95')(` โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• @@ -479,6 +926,7 @@ async function connectionUpdate(update) { await chatunityedition(); } + // Attempt to join support group try { await conn.groupAcceptInvite('FjPBDj4sUgFLJfZiLwtTvk'); console.log(chalk.bold.green('โœ… Bot entrato nel gruppo supporto con successo - non abbandonare!')); @@ -490,45 +938,70 @@ async function connectionUpdate(update) { } } - + // Handle connection close with specific error messages if (connection === 'close') { const reason = lastDisconnect?.error?.output?.statusCode || lastDisconnect?.error?.output?.payload?.statusCode; + + // Bad session - credentials corrupted if (reason === DisconnectReason.badSession && !global.connectionMessagesPrinted.badSession) { console.log(chalk.bold.redBright(`\nโš ๏ธโ— SESSIONE NON VALIDA, ELIMINA LA CARTELLA ${global.authFile} E SCANSIONA IL CODICE QR โš ๏ธ`)); global.connectionMessagesPrinted.badSession = true; await global.reloadHandler(true).catch(console.error); - } else if (reason === DisconnectReason.connectionLost && !global.connectionMessagesPrinted.connectionLost) { + } + // Connection lost - network issue + else if (reason === DisconnectReason.connectionLost && !global.connectionMessagesPrinted.connectionLost) { console.log(chalk.bold.blueBright(`\nโ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โš ๏ธ CONNESSIONE PERSA COL SERVER เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ ๐Ÿ”„ RICONNESSIONE IN CORSO... \nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); global.connectionMessagesPrinted.connectionLost = true; await global.reloadHandler(true).catch(console.error); - } else if (reason === DisconnectReason.connectionReplaced && !global.connectionMessagesPrinted.connectionReplaced) { + } + // Connection replaced - another device connected + else if (reason === DisconnectReason.connectionReplaced && !global.connectionMessagesPrinted.connectionReplaced) { console.log(chalk.bold.yellowBright(`โ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โš ๏ธ CONNESSIONE SOSTITUITA เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ รˆ stata aperta un'altra sessione, \nโ”ƒ chiudi prima quella attuale.\nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โบโ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); global.connectionMessagesPrinted.connectionReplaced = true; - } else if (reason === DisconnectReason.loggedOut && !global.connectionMessagesPrinted.loggedOut) { + } + // Logged out - session terminated + else if (reason === DisconnectReason.loggedOut && !global.connectionMessagesPrinted.loggedOut) { console.log(chalk.bold.redBright(`\nโš ๏ธ DISCONNESSO, ELIMINA LA CARTELLA ${global.authFile} E SCANSIONA IL CODICE QR โš ๏ธ`)); global.connectionMessagesPrinted.loggedOut = true; await global.reloadHandler(true).catch(console.error); - } else if (reason === DisconnectReason.restartRequired && !global.connectionMessagesPrinted.restartRequired) { + } + // Restart required - normal reconnection + else if (reason === DisconnectReason.restartRequired && !global.connectionMessagesPrinted.restartRequired) { console.log(chalk.bold.magentaBright(`\nโญ‘โญ’โ”โ”โ”โœฆโ˜เผป CONNESSO CON SUCCESSO เผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); global.connectionMessagesPrinted.restartRequired = true; await global.reloadHandler(true).catch(console.error); - } else if (reason === DisconnectReason.timedOut && !global.connectionMessagesPrinted.timedOut) { + } + // Timeout - connection timed out + else if (reason === DisconnectReason.timedOut && !global.connectionMessagesPrinted.timedOut) { console.log(chalk.bold.yellowBright(`\nโ•ญโญ‘โญ’โ”โ”โ”โœฆโ˜เผป โŒ› TIMEOUT CONNESSIONE เผบโ˜โœฆโ”โ”โ”โญ’โญ‘\nโ”ƒ ๐Ÿ”„ RICONNESSIONE IN CORSO...\nโ•ฐโญ‘โญ’โ”โ”โ”โœฆโ˜เผปโ˜พโ‹†โบโ‚Šโœง chatunity-bot โœงโ‚Šโบโ‹†โ˜ฝเผบโ˜โœฆโ”โ”โ”โญ’โญ‘`)); global.connectionMessagesPrinted.timedOut = true; await global.reloadHandler(true).catch(console.error); - } else if (reason !== DisconnectReason.restartRequired && reason !== DisconnectReason.connectionClosed && !global.connectionMessagesPrinted.unknown) { + } + // Unknown reason + else if (reason !== DisconnectReason.restartRequired && reason !== DisconnectReason.connectionClosed && !global.connectionMessagesPrinted.unknown) { console.log(chalk.bold.redBright(`\nโš ๏ธโ— MOTIVO DISCONNESSIONE SCONOSCIUTO: ${reason || 'Non trovato'} >> ${connection || 'Non trovato'}`)); global.connectionMessagesPrinted.unknown = true; } } } - +// Global error handler for uncaught exceptions process.on('uncaughtException', console.error); +// ============================================================================ +// SUB-BOT CONNECTION HANDLER +// ============================================================================ + +/** + * Connects all sub-bots from the chatunity-sub directory. + * Each sub-bot gets its own authentication state and connection. + * Used for managing multiple WhatsApp accounts simultaneously. + */ async function connectSubBots() { const subBotDirectory = './chatunity-sub'; + + // Create directory if it doesn't exist if (!existsSync(subBotDirectory)) { console.log(chalk.bold.magentaBright('non ci sono Sub-Bot collegati. Creazione directory...')); try { @@ -541,24 +1014,28 @@ async function connectSubBots() { return; } - try { + // Get all sub-bot folders const subBotFolders = readdirSync(subBotDirectory).filter(file => statSync(join(subBotDirectory, file)).isDirectory() ); - if (subBotFolders.length === 0) { console.log(chalk.bold.magenta('Nessun subbot collegato')); return; } - + // Connect each sub-bot in parallel const botPromises = subBotFolders.map(async (folder) => { const subAuthFile = join(subBotDirectory, folder); + + // Only connect if credentials exist if (existsSync(join(subAuthFile, 'creds.json'))) { try { + // Initialize authentication state for sub-bot const { state: subState, saveCreds: subSaveCreds } = await useMultiFileAuthState(subAuthFile); + + // Create sub-bot connection with same options const subConn = makeWASocket({ ...connectionOptions, auth: { @@ -567,9 +1044,10 @@ async function connectSubBots() { }, }); - + // Bind event handlers subConn.ev.on('creds.update', subSaveCreds); subConn.ev.on('connection.update', connectionUpdate); + return subConn; } catch (err) { console.log(chalk.bold.red(`โŒ Errore nella connessione del Sub-Bot ${folder}:`, err.message)); @@ -579,11 +1057,11 @@ async function connectSubBots() { return null; }); - + // Wait for all sub-bots to connect const bots = await Promise.all(botPromises); global.conns = bots.filter(Boolean); - + // Log connection status if (global.conns.length > 0) { console.log(chalk.bold.magentaBright(`๐ŸŒ™ ${global.conns.length} Sub-Bot si sono connessi con successo.`)); } else { @@ -595,16 +1073,29 @@ async function connectSubBots() { } +// ============================================================================ +// MAIN BOT INITIALIZATION (IIFE) +// ============================================================================ + +/** + * Immediately-invoked async function to start the bot. + * Sets up event listeners and connects sub-bots. + */ (async () => { global.conns = []; + try { + // Register connection event handlers conn.ev.on('connection.update', connectionUpdate); conn.ev.on('creds.update', saveCreds); + // Display startup message console.log(chalk.bold.magenta(` โ•ญ๏น•โ‚Šหš โ˜… โบหณ๊•คโ‚Šโบใƒป๊’ฑ โ‹† ๏ธต๏ธต โ˜… ChatUnity connesso โ˜… ๏ธต๏ธต โ‹† โ•ฐ. ๊’ท๊’ฆ ๊’ท๊’ฆโ€งหšโ‚Šหš๊’ท๊’ฆ๊’ทโ€งหšโ‚Šหš๊’ท๊’ฆ๊’ท`)); + + // Connect all configured sub-bots await connectSubBots(); } catch (error) { console.error(chalk.bold.bgRedBright(`๐Ÿฅ€ Errore nell'avvio del bot: `, error)); @@ -612,25 +1103,47 @@ async function connectSubBots() { })(); +// ============================================================================ +// MESSAGE HANDLER RELOAD SYSTEM +// ============================================================================ + +/** Flag to track if handler is initialized */ let isInit = true; + +/** Import message handler module */ let handler = await import('./handler.js'); + +/** + * Reloads the message handler and optionally restarts the connection. + * Supports hot-reloading of handler code without full restart. + * + * @param {boolean} restatConn - Whether to restart the WebSocket connection + * @returns {Promise} True on successful reload + */ global.reloadHandler = async function (restatConn) { try { + // Import handler with cache-busting query string const Handler = await import(`./handler.js?update=${Date.now()}`).catch(console.error); if (Object.keys(Handler || {}).length) handler = Handler; } catch (e) { console.error(e); } + + // Restart connection if requested if (restatConn) { const oldChats = global.conn.chats; try { global.conn.ws.close(); } catch { } + + // Remove all event listeners and create new connection conn.ev.removeAllListeners(); global.conn = makeWASocket(connectionOptions, { chats: oldChats }); global.store.bind(global.conn.ev); isInit = true; } + + // Remove old event handlers if not first initialization if (!isInit) { conn.ev.off('messages.upsert', conn.handler); conn.ev.off('group-participants.update', conn.participantsUpdate); @@ -641,7 +1154,7 @@ global.reloadHandler = async function (restatConn) { conn.ev.off('creds.update', conn.credsUpdate); } - + // Configure welcome/bye messages conn.welcome = '@user benvenuto/a in @subject'; conn.bye = '@user ha abbandonato il gruppo'; conn.spromote = '@user รจ stato promosso ad amministratore'; @@ -649,7 +1162,7 @@ global.reloadHandler = async function (restatConn) { conn.sIcon = 'immagine gruppo modificata'; conn.sRevoke = 'link reimpostato, nuovo link: @revoke'; - + // Bind handler functions to connection context conn.handler = handler.handler.bind(global.conn); conn.participantsUpdate = handler.participantsUpdate.bind(global.conn); conn.groupsUpdate = handler.groupsUpdate.bind(global.conn); @@ -658,7 +1171,7 @@ global.reloadHandler = async function (restatConn) { conn.connectionUpdate = connectionUpdate.bind(global.conn); conn.credsUpdate = saveCreds.bind(global.conn, true); - + // Register event handlers conn.ev.on('messages.upsert', conn.handler); conn.ev.on('group-participants.update', conn.participantsUpdate); conn.ev.on('groups.update', conn.groupsUpdate); @@ -666,16 +1179,29 @@ global.reloadHandler = async function (restatConn) { conn.ev.on('call', conn.onCall); conn.ev.on('connection.update', conn.connectionUpdate); conn.ev.on('creds.update', conn.credsUpdate); + isInit = false; return true; }; +// ============================================================================ +// PLUGIN SYSTEM +// ============================================================================ + +/** Path to plugins directory */ const pluginFolder = global.__dirname(join(__dirname, './plugins/index')); + +/** Filter for JavaScript plugin files */ const pluginFilter = (filename) => /\.js$/.test(filename); -global.plugins = {}; +/** Global plugins registry */ +global.plugins = {}; +/** + * Initializes all plugins from the plugins directory. + * Loads each .js file as a module and stores in global.plugins. + */ async function filesInit() { for (const filename of readdirSync(pluginFolder).filter(pluginFilter)) { try { @@ -689,45 +1215,74 @@ async function filesInit() { } } - +// Initialize plugins and log loaded count filesInit().then((_) => Object.keys(global.plugins)).catch(console.error); - +/** + * Hot-reload handler for plugin file changes. + * Detects new, updated, and deleted plugins and reloads them. + * + * @param {string} _ev - Event type (unused) + * @param {string} filename - Changed filename + */ global.reload = async (_ev, filename) => { if (pluginFilter(filename)) { const dir = global.__filename(join(pluginFolder, filename), true); + if (filename in global.plugins) { - if (existsSync(dir)) conn.logger.info(chalk.green(`โœ… AGGIORNATO - '${filename}' CON SUCCESSO`)); - else { + if (existsSync(dir)) { + conn.logger.info(chalk.green(`โœ… AGGIORNATO - '${filename}' CON SUCCESSO`)); + } else { conn.logger.warn(`๐Ÿ—‘๏ธ FILE ELIMINATO: '${filename}'`); return delete global.plugins[filename]; } - } else conn.logger.info(`๐Ÿ†• NUOVO PLUGIN RILEVATO: '${filename}'`); + } else { + conn.logger.info(`๐Ÿ†• NUOVO PLUGIN RILEVATO: '${filename}'`); + } + + // Check for syntax errors before loading const err = syntaxerror(fs.readFileSync(dir), filename, { sourceType: 'module', allowAwaitOutsideFunction: true, }); - if (err) conn.logger.error(`โŒ ERRORE DI SINTASSI IN: '${filename}'\n${format(err)}`); - else { + + if (err) { + conn.logger.error(`โŒ ERRORE DI SINTASSI IN: '${filename}'\n${format(err)}`); + } else { try { + // Import with cache-busting query string const module = (await import(`${global.__filename(dir)}?update=${Date.now()}`)); global.plugins[filename] = module.default || module; } catch (e) { conn.logger.error(`โš ๏ธ ERRORE NEL PLUGIN: '${filename}\n${format(e)}'`); } finally { + // Sort plugins alphabetically global.plugins = Object.fromEntries(Object.entries(global.plugins).sort(([a], [b]) => a.localeCompare(b))); } } } }; - +// Freeze reload function to prevent modification Object.freeze(global.reload); + +// Watch plugins directory for changes const pluginWatcher = watch(pluginFolder, global.reload); pluginWatcher.setMaxListeners(20); + +// Initialize the message handler await global.reloadHandler(); +// ============================================================================ +// SYSTEM DEPENDENCY CHECKS +// ============================================================================ + +/** + * Tests availability of required system tools. + * Checks for ffmpeg, ffprobe, ImageMagick, GraphicsMagick, and find. + * Results are stored in global.support for use by plugins. + */ async function _quickTest() { const test = await Promise.all([ spawn('ffmpeg'), @@ -741,7 +1296,7 @@ async function _quickTest() { return Promise.race([ new Promise((resolve) => { p.on('close', (code) => { - resolve(code !== 127); + resolve(code !== 127); // 127 = command not found }); }), new Promise((resolve) => { @@ -749,12 +1304,24 @@ async function _quickTest() { }) ]); })); + const [ffmpeg, ffprobe, ffmpegWebp, convert, magick, gm, find] = test; + + // Store support flags globally const s = global.support = { ffmpeg, ffprobe, ffmpegWebp, convert, magick, gm, find }; Object.freeze(global.support); } +// ============================================================================ +// DIRECTORY CLEANUP UTILITIES +// ============================================================================ +/** + * Clears all files and subdirectories from a directory. + * Creates the directory if it doesn't exist. + * + * @param {string} dirPath - Path to directory to clear + */ function clearDirectory(dirPath) { if (!existsSync(dirPath)) { try { @@ -764,6 +1331,7 @@ function clearDirectory(dirPath) { } return; } + const filenames = readdirSync(dirPath); filenames.forEach(file => { const filePath = join(dirPath, file); @@ -780,9 +1348,17 @@ function clearDirectory(dirPath) { }); } - +/** + * Sets up periodic temp directory cleanup timer. + * Clears tmp and temp directories every 30 minutes. + * + * @param {Object} conn - WhatsApp connection object + */ function ripristinaTimer(conn) { + // Clear existing timer if present if (conn.timerReset) clearInterval(conn.timerReset); + + // Setup new timer for 30-minute intervals conn.timerReset = setInterval(async () => { if (stopped === 'close' || !conn || !conn.user) return; await clearDirectory(join(__dirname, 'tmp')); @@ -790,9 +1366,17 @@ function ripristinaTimer(conn) { }, 1000 * 60 * 30); } +// ============================================================================ +// STARTUP AND FILE WATCHERS +// ============================================================================ +// Run system dependency checks _quickTest().then(() => conn.logger.info(chalk.bold.bgBlueBright(``))); + +// Get current file path for hot-reload let filePath = fileURLToPath(import.meta.url); + +// Watch main.js for changes and auto-reload const mainWatcher = watch(filePath, async () => { console.log(chalk.bold.bgBlueBright("Main Aggiornato")); await global.reloadHandler(true).catch(console.error); From 931cf4a5986c8a8192adb8e29dc7a3f7ab2d2939 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:16:21 +0000 Subject: [PATCH 39/43] Add defensive check for undefined emotttt variable in api.js Address code review feedback by adding safety check to prevent ReferenceError when emotttt mapping object is not defined. Co-authored-by: AlexUngu07 <234906301+AlexUngu07@users.noreply.github.com> --- api.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api.js b/api.js index 3d23d3dea..66bb323ce 100644 --- a/api.js +++ b/api.js @@ -125,6 +125,9 @@ global.moment = moment // Date/time handling /** * RPG utility object with emoticon mapping functionality. * Used for displaying appropriate emojis in RPG game features. + * + * @note The emotttt mapping object should be defined elsewhere in the codebase. + * If not defined, the emoticon function will return empty string. */ global.rpg = { /** @@ -135,6 +138,9 @@ global.rpg = { * @returns {string} Matching emoticon or empty string */ emoticon(string) { + // Safety check: ensure emotttt exists before using + if (typeof emotttt === 'undefined') return '' + string = string.toLowerCase() // Match string against emoticon patterns From cde24f5b88d55d274f0c195df4450a1b49a0f895 Mon Sep 17 00:00:00 2001 From: BLOOD212 Date: Fri, 13 Feb 2026 16:14:05 +0100 Subject: [PATCH 40/43] Create tette.js --- plugins/tette.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 plugins/tette.js diff --git a/plugins/tette.js b/plugins/tette.js new file mode 100644 index 000000000..a7ba02c64 --- /dev/null +++ b/plugins/tette.js @@ -0,0 +1,39 @@ +let handler = async (m, { conn }) => { + // Controlla menzione o messaggio quotato + let user = m.mentionedJid && m.mentionedJid[0] + ? m.mentionedJid[0] + : m.quoted + ? m.quoted.sender + : null; + + if (!user) { + return m.reply("Devi menzionare qualcuno ๐Ÿ˜"); + } + + // Numeri possibili + const numeri = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + // Lettere possibili + const lettere = ["A", "B", "C", "D", "E", "F"]; + + // Random + const numeroRandom = numeri[Math.floor(Math.random() * numeri.length)]; + const letteraRandom = lettere[Math.floor(Math.random() * lettere.length)]; + + const misura = `${numeroRandom}${letteraRandom}`; + + let testo = `oh @${user.split("@")[0]} ha una ${misura}`; + + await conn.sendMessage( + m.chat, + { + text: testo, + mentions: [user], + }, + { quoted: m } + ); +}; + +handler.command = ["tette"]; // <-- comando in fondo + +module.exports = handler; From d0fc48613eb5431c62999900d6157584f4e73240 Mon Sep 17 00:00:00 2001 From: BLOOD212 Date: Fri, 13 Feb 2026 16:15:55 +0100 Subject: [PATCH 41/43] Update tette.js --- plugins/tette.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/tette.js b/plugins/tette.js index a7ba02c64..e8a81c250 100644 --- a/plugins/tette.js +++ b/plugins/tette.js @@ -1,5 +1,4 @@ -let handler = async (m, { conn }) => { - // Controlla menzione o messaggio quotato +let handler = async (m, { conn }) => let user = m.mentionedJid && m.mentionedJid[0] ? m.mentionedJid[0] : m.quoted From aab14d3f900bef44ea852d22216ca9cd22053959 Mon Sep 17 00:00:00 2001 From: BLOOD212 Date: Fri, 13 Feb 2026 16:17:23 +0100 Subject: [PATCH 42/43] Update tette.js --- plugins/tette.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/tette.js b/plugins/tette.js index e8a81c250..f81e4858c 100644 --- a/plugins/tette.js +++ b/plugins/tette.js @@ -1,4 +1,4 @@ -let handler = async (m, { conn }) => +let handler = async (m, { conn }) => { let user = m.mentionedJid && m.mentionedJid[0] ? m.mentionedJid[0] : m.quoted @@ -9,30 +9,37 @@ let handler = async (m, { conn }) => return m.reply("Devi menzionare qualcuno ๐Ÿ˜"); } - // Numeri possibili - const numeri = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const numeri = [1,2,3,4,5,6,7,8,9]; + const lettere = ["A","B","C","D","E","F"]; - // Lettere possibili - const lettere = ["A", "B", "C", "D", "E", "F"]; - - // Random const numeroRandom = numeri[Math.floor(Math.random() * numeri.length)]; const letteraRandom = lettere[Math.floor(Math.random() * lettere.length)]; - const misura = `${numeroRandom}${letteraRandom}`; - let testo = `oh @${user.split("@")[0]} ha una ${misura}`; + const fortuna = Math.floor(Math.random() * 101); // 0-100% + + const frasi = [ + `oh @${user.split("@")[0]} ha una ${misura} ๐Ÿ˜ณ`, + `attenzione โš ๏ธ @${user.split("@")[0]} possiede una ${misura}`, + `le statistiche parlano chiaro ๐Ÿ“Š @${user.split("@")[0]} ha una ${misura}`, + `gli esperti confermano ๐Ÿงช @${user.split("@")[0]} ha una ${misura}`, + `breaking news ๐Ÿ“ฐ @${user.split("@")[0]} ha una ${misura}` + ]; + + const fraseRandom = frasi[Math.floor(Math.random() * frasi.length)]; + + let testoFinale = `${fraseRandom}\n๐Ÿ€ Fortuna: ${fortuna}%`; await conn.sendMessage( m.chat, { - text: testo, + text: testoFinale, mentions: [user], }, { quoted: m } ); }; -handler.command = ["tette"]; // <-- comando in fondo +handler.command = ["tette"]; // comando in fondo module.exports = handler; From aae7772b3fdf72a690ad5112c60ec45c5e7052bd Mon Sep 17 00:00:00 2001 From: BLOOD212 Date: Fri, 13 Feb 2026 16:21:15 +0100 Subject: [PATCH 43/43] Update and rename plugins/tette.js to game-seno.js --- game-seno.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ plugins/tette.js | 45 ------------------------------- 2 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 game-seno.js delete mode 100644 plugins/tette.js diff --git a/game-seno.js b/game-seno.js new file mode 100644 index 000000000..87c693f4a --- /dev/null +++ b/game-seno.js @@ -0,0 +1,69 @@ +// Ranking temporaneo (si resetta al riavvio) +global.tetteRank = global.tetteRank || {}; + +let handler = async (m, { conn }) => { + + let user = m.mentionedJid?.[0] || m.quoted?.sender; + if (!user) return m.reply("Devi menzionare qualcuno ๐Ÿ˜"); + + const numeri = [1,2,3,4,5,6,7,8,9]; + const lettere = ["A","B","C","D","E","F"]; + + const numeroRandom = numeri[Math.floor(Math.random() * numeri.length)]; + const letteraRandom = lettere[Math.floor(Math.random() * lettere.length)]; + + let misura = `${numeroRandom}${letteraRandom}`; + + // ๐Ÿ’€ 10% possibilitร  misura negativa + if (Math.random() < 0.10) { + misura = `-${numeroRandom}${letteraRandom}`; + } + + // ๐Ÿ”ฅ Sistema raritร  + const roll = Math.random(); + let rarita = "COMMON"; + + if (roll > 0.95) rarita = "MYTHIC ๐Ÿ”ฑ"; + else if (roll > 0.85) rarita = "LEGENDARY ๐Ÿ”ฅ"; + else if (roll > 0.65) rarita = "EPIC โšก"; + else if (roll > 0.40) rarita = "RARE โญ"; + + const fortuna = Math.floor(Math.random() * 101); + + const frasi = [ + `oh @${user.split("@")[0]} ha una ${misura}`, + `analisi completata ๐Ÿงช @${user.split("@")[0]} possiede una ${misura}`, + `i calcoli parlano chiaro ๐Ÿ“Š @${user.split("@")[0]} ha una ${misura}`, + `attenzione gruppo โš ๏ธ @${user.split("@")[0]} ha una ${misura}`, + `breaking news ๐Ÿ“ฐ @${user.split("@")[0]} ha una ${misura}` + ]; + + const fraseRandom = frasi[Math.floor(Math.random() * frasi.length)]; + + // ๐Ÿ† Ranking + if (!global.tetteRank[user]) global.tetteRank[user] = 0; + global.tetteRank[user] += 1; + + let testoFinale = ` +${fraseRandom} + +๐ŸŽฒ Raritร : ${rarita} +๐Ÿ€ Fortuna: ${fortuna}% +๐Ÿ† Livello Caos: ${global.tetteRank[user]} + `.trim(); + + await conn.sendMessage( + m.chat, + { + text: testoFinale, + mentions: [user], + }, + { quoted: m } + ); +}; + +handler.help = ['tette @tag']; +handler.tags = ['fun']; +handler.command = /^tette$/i; + +module.exports = handler; diff --git a/plugins/tette.js b/plugins/tette.js deleted file mode 100644 index f81e4858c..000000000 --- a/plugins/tette.js +++ /dev/null @@ -1,45 +0,0 @@ -let handler = async (m, { conn }) => { - let user = m.mentionedJid && m.mentionedJid[0] - ? m.mentionedJid[0] - : m.quoted - ? m.quoted.sender - : null; - - if (!user) { - return m.reply("Devi menzionare qualcuno ๐Ÿ˜"); - } - - const numeri = [1,2,3,4,5,6,7,8,9]; - const lettere = ["A","B","C","D","E","F"]; - - const numeroRandom = numeri[Math.floor(Math.random() * numeri.length)]; - const letteraRandom = lettere[Math.floor(Math.random() * lettere.length)]; - const misura = `${numeroRandom}${letteraRandom}`; - - const fortuna = Math.floor(Math.random() * 101); // 0-100% - - const frasi = [ - `oh @${user.split("@")[0]} ha una ${misura} ๐Ÿ˜ณ`, - `attenzione โš ๏ธ @${user.split("@")[0]} possiede una ${misura}`, - `le statistiche parlano chiaro ๐Ÿ“Š @${user.split("@")[0]} ha una ${misura}`, - `gli esperti confermano ๐Ÿงช @${user.split("@")[0]} ha una ${misura}`, - `breaking news ๐Ÿ“ฐ @${user.split("@")[0]} ha una ${misura}` - ]; - - const fraseRandom = frasi[Math.floor(Math.random() * frasi.length)]; - - let testoFinale = `${fraseRandom}\n๐Ÿ€ Fortuna: ${fortuna}%`; - - await conn.sendMessage( - m.chat, - { - text: testoFinale, - mentions: [user], - }, - { quoted: m } - ); -}; - -handler.command = ["tette"]; // comando in fondo - -module.exports = handler;