diff --git a/api.js b/api.js index c81269bbd..66bb323ce 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,61 @@ 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. + * + * @note The emotttt mapping object should be defined elsewhere in the codebase. + * If not defined, the emoticon function will return empty string. + */ 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) { + // Safety check: ensure emotttt exists before using + if (typeof emotttt === 'undefined') return '' + + 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 adce8b3e9..eb6e0c81e 100644 --- a/config.js +++ b/config.js @@ -1,42 +1,118 @@ +/** + * ============================================================================ + * 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 = ''; -global.nomebot = '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ-๐๐จ๐ญ'; -global.packname = '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ-๐๐จ๐ญ'; + +/** Display name for the bot */ +global.nomebot = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; + +/** Sticker pack name */ +global.packname = '๐๐‹๐ƒ-๐๐‹๐Ž๐Ž๐ƒ'; + +/** Sticker author name */ global.author = '๐Œ๐'; -global.vs = '8.8'; + +/** 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 = [ - ['393773842461', '๐‚๐ก๐š๐ญ๐”๐ง๐ข๐ญ๐ฒ', true], - ['xxxxxxxxxx'], //mettete il vostro numero al posto delle x e copiate sopra il formato dopo ovvero 'nome', true + ['212780803311', 'Blood', true], + ['19782772696', 'Bot', true], ['xxxxxxxxxx'], ['xxxxxxxxxx'], ['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/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/handler.js b/handler.js index c5404757a..86456a431 100644 --- a/handler.js +++ b/handler.js @@ -1,796 +1,1057 @@ +/** + * ============================================================================ + * 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 + * ============================================================================ + */ -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 }); -} +// Module imports +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' -let stopped = 'open'; +// Import Baileys proto for message handling +const { proto } = (await import('@whiskeysockets/baileys')).default -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()}`); -} +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ -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 {} - } - }); -} +/** + * 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) -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))); - } -}, 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; -} +/** + * 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)) -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 STATE FOR ANTI-SPAM AND USER MANAGEMENT +// ============================================================================ -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); - } - }, 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 = []; -} +/** Set of globally ignored users (won't receive any bot responses) */ +global.ignoredUsersGlobal = global.ignoredUsersGlobal || new Set() -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`)); -} +/** Object mapping group IDs to sets of ignored users within that group */ +global.ignoredUsersGroup = global.ignoredUsersGroup || {} -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}`; - } - if (typeof decoded === 'string' && decoded.endsWith('@lid')) { - decoded = decoded.replace('@lid', '@s.whatsapp.net'); - } +/** Object tracking command spam per group for anti-spam protection */ +global.groupSpam = global.groupSpam || {} - 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); - } - } -} +// ============================================================================ +// MAIN MESSAGE HANDLER +// ============================================================================ -global.conn.isInit = false; -global.conn.well = false; +/** + * 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 -// 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; - } + // 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 - if (restatConn && global.conn) { - const oldChats = global.conn.chats; + // 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 { - 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; + 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 + } + })() + + // ============================================================================ + // 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) + 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 + }) } + + // ============================================================================ + // ANTI-SPAM PROTECTION FOR GROUPS + // ============================================================================ - 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); + /** + * 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 && + typeof m.text === 'string' && + hasValidPrefix(m.text, conn.prefix || global.prefix) + ) { + const now = Date.now() + const chatId = m.chat + + // Initialize spam tracking for this group + if (!global.groupSpam[chatId]) { + global.groupSpam[chatId] = { + count: 0, + firstCommandTimestamp: now, + isSuspended: false, + suspendedUntil: null + } } - if (typeof global.conn.connectionUpdate === 'function') { - global.conn.ev.off('connection.update', global.conn.connectionUpdate); + + const groupData = global.groupSpam[chatId] + + // Check if currently suspended + if (groupData.isSuspended) { + 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 } - if (typeof global.conn.credsUpdate === 'function') { - global.conn.ev.off('creds.update', global.conn.credsUpdate); + + // Reset counter if more than 60 seconds since first command + if (now - groupData.firstCommandTimestamp > 60000) { + groupData.count = 1 + groupData.firstCommandTimestamp = now + } else { + groupData.count++ } - - // 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; + // Trigger suspension if too many commands + if (groupData.count > 2) { + groupData.isSuspended = true + 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`, + mentions: [m.sender] + }) + return + } } - return true; -}; -async function chatunityedition() { + // ============================================================================ + // MESSAGE SERIALIZATION AND INITIALIZATION + // ============================================================================ + try { - const mainChannelId = global.IdCanale?.[0] || '120363259442839354@newsletter'; - await global.conn.newsletterFollow(mainChannelId); - } catch (error) {} -} + // 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 -// 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) { + // ============================================================================ + // USER AND CHAT DATA INITIALIZATION + // ============================================================================ + try { - await global.reloadHandler(true); - global.timestamp.connect = new Date; + 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 // 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, + exp: 0, + money: 0, + warn: 0, + joincount: 2, + limit: 15000, + premium: false, + premiumDate: -1, + name: m.name, + muto: false + } + } + + // 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 // 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 // Welcome messages + } else { + // Create new chat with all default values + 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 + } + } + + // 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 // 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, + 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(); - } + // ============================================================================ + // MESSAGE FILTERING OPTIONS + // ============================================================================ - 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); + // 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] + queque.push(m.id || m.key.id) + setInterval(async function () { + if (queque.indexOf(previousID) === -1) clearInterval(this) + await delay(time) + }, time) } + + // Skip bot's own messages (from Baileys) + if (m.isBaileys) return - // CARICA HANDLER DOPO LA CONNESSIONE - console.log(chalk.cyan('๐Ÿ”„ Caricamento handler...')); - await global.reloadHandler(false); - console.log(chalk.green('โœ… Bot pronto a ricevere messaggi!')); - } + // Award random experience points for activity + m.exp += Math.ceil(Math.random() * 10) - 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; + // 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 { - 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 && !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); + } + + // 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') + + // ============================================================================ + // 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) + + // Execute 'all' function if present (runs on every message) + 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) + } } - } 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; - try { - await global.reloadHandler(true); - } catch (e) { - console.error('Errore in reloadHandler:', e); + + // Skip admin plugins if restrict mode is disabled + if (!opts['restrict'] && plugin.tags?.includes('admin')) continue + + // Execute 'before' function if present (pre-processing) + if (typeof plugin.before === 'function') { + try { + 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) + } } - } 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; } - } -} + // === END PLUGIN.ALL/BEFORE PHASE === -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); -} + // ============================================================================ + // 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) -if (opts['server']) (await import('./server.js')).default(global.conn, PORT); + // Skip admin plugins if restrict is disabled + if (!opts['restrict'] && plugin.tags?.includes('admin')) continue -process.on('uncaughtException', console.error); + /** + * 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) ? + _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]) -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; - } + // Skip non-function plugins and unmatched prefixes + if (typeof plugin !== 'function') continue + if (!match) continue - try { - const subBotFolders = readdirSync(subBotDirectory).filter(file => - statSync(join(subBotDirectory, file)).isDirectory() - ); + // ============================================================================ + // 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) ? + plugin.command.some(cmd => cmd instanceof RegExp ? cmd.test(command) : cmd === command) : + typeof plugin.command === 'string' ? + plugin.command === command : + false - if (subBotFolders.length === 0) { - console.log(chalk.bold.magenta('Nessun subbot collegato')); - return; - } + 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 + } + if (plugin.rowner && !isROwner) { + fail('rowner', m, this) + continue + } + if (plugin.owner && !isOwner2) { + 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 + } + + // Check bot admin requirement + else if (plugin.botAdmin && !isBotAdmin) { + fail('botAdmin', m, this) + continue + } + + // 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, + noPrefix, + _args, + args, + command, + text, + conn: this, + normalizedParticipants, + participants, + groupMetadata, + user, + bot, + isROwner, + isOwner: isOwner2, + isRAdmin, + isAdmin, + isBotAdmin, + isPrems, + chatUpdate, + __dirname: ___dirname, + __filename + } - const botPromises = subBotFolders.map(async (folder) => { - const subAuthFile = join(subBotDirectory, folder); - if (existsSync(join(subAuthFile, 'creds.json'))) { 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; + // 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 { + // Execute 'after' hook if present + if (typeof plugin.after === 'function') { + try { + await plugin.after.call(this, m, extra) + } catch (e) { + console.error(`Errore in plugin.after (${name}):`, e) + } + } } + + // Only execute first matching command + break } - return null; - }); + } + // ============================================================================ + // 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) + } - const bots = await Promise.all(botPromises); - global.conns = bots.filter(Boolean); + // ============================================================================ + // 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: { + remoteJid: m.chat, + fromMe: false, + id: m.key.id, + participant: m.key.participant + } + }) + } + + // Update user statistics + if (user) { + 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, // 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 + } + } - 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.')); + // ============================================================================ + // 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) } - } catch (err) { - console.log(chalk.bold.red('โŒ Errore generale nella connessione dei Sub-Bot:', err.message)); + + // Auto-read messages if enabled + if (opts['autoread']) await this.readMessages([m.key]) } } -(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); +// ============================================================================ +// 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) { + // 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)) + .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]) + } + + // Send formatted welcome/goodbye message + 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 } -})(); +} -const pluginFolder = global.__dirname(join(__dirname, './plugins/index')); -const pluginFilter = (filename) => /\.js$/.test(filename); -global.plugins = {}; +// ============================================================================ +// GROUP SETTINGS UPDATE HANDLER +// ============================================================================ -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]; +/** + * 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 = '' + + // 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) }) } } -filesInit().then((_) => Object.keys(global.plugins)).catch(console.error); +// ============================================================================ +// INCOMING CALL HANDLER +// ============================================================================ -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))); +/** + * 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') } } } -}; - -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; +// ============================================================================ +// 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) } - 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 }); +} + +// ============================================================================ +// 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: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฌ๐จ๐ฅ๐จ ๐ฉ๐ž๐ซ ๐จ๐ฐ๐ง๐ž๐ซ ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ', + mods: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฅ๐จ ๐ฉ๐จ๐ฌ๐ฌ๐จ๐ง๐จ ๐ฎ๐ญ๐ข๐ฅ๐ข๐ณ๐ณ๐š๐ซ๐ž ๐ฌ๐จ๐ฅ๐จ ๐š๐๐ฆ๐ข๐ง ๐ž ๐จ๐ฐ๐ง๐ž๐ซ โš™๏ธ', + premium: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฉ๐ž๐ซ ๐ฆ๐ž๐ฆ๐›๐ซ๐ข ๐ฉ๐ซ๐ž๐ฆ๐ข๐ฎ๐ฆ โœ…', + group: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฉ๐ฎ๐จ๐ข ๐ฎ๐ญ๐ข๐ฅ๐ข๐ณ๐ณ๐š๐ซ๐ฅ๐จ ๐ข๐ง ๐ฎ๐ง ๐ ๐ซ๐ฎ๐ฉ๐ฉ๐จ ๐Ÿ‘ฅ', + private: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐ฉ๐ฎ๐จ๐ข ๐ฎ๐ญ๐ข๐ฅ๐ข๐ง๐ข๐ญ๐š๐ซ๐ฅ๐จ ๐ข๐ง ๐œ๐ก๐š๐ญ ๐ฉ๐ซ๐ข๐ฏ๐š๐ญ๐š ๐Ÿ‘ค', + admin: '๐๐ฎ๐ž๐ฌ๐ญ๐จ ๐œ๐จ๐ฆ๐š๐ง๐๐จ ๐žฬ€ ๐ฉ๐ž๐ซ ๐ฌ๐จ๐ฅ๐ข ๐š๐๐ฆ๐ข๐ง ๐Ÿ‘‘', + botAdmin: '๐ƒ๐ž๐ฏ๐ข ๐๐š๐ซ๐ž ๐š๐๐ฆ๐ข๐ง ๐š๐ฅ ๐›๐จ๐ญ ๐Ÿ‘‘', + restrict: '๐Ÿ” ๐‘๐ž๐ฌ๐ญ๐ซ๐ข๐œ๐ญ ๐ž ๐๐ข๐ฌ๐š๐ญ๐ญ๐ข๐ฏ๐š๐ญ๐จ ๐Ÿ”' + }[type] + + // Send styled error message if type is recognized + 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 } - } catch (e) { - console.error(chalk.red(`Errore nella pulizia del file ${filePath}:`, e)); } - }); + }, { quoted: m }) } -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); -} +// ============================================================================ +// HOT-RELOAD WATCHER +// ============================================================================ -_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); +// Watch this file for changes and auto-reload +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()) +}) diff --git a/lib/print.js b/lib/print.js index ae0443a7e..489f889d0 100644 --- a/lib/print.js +++ b/lib/print.js @@ -1,24 +1,60 @@ -import { WAMessageStubType } from '@chatunity/baileys' +/** + * ============================================================================ + * 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,21 +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/lib/simple.js b/lib/simple.js index af4b37647..de2f676c6 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) { @@ -317,7 +317,7 @@ reply: { * @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 }) } }, @@ -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) { @@ -368,13 +368,13 @@ reply: { 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 @@ -468,21 +468,21 @@ if (list && Array.isArray(list)) { } } - + 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, @@ -498,19 +498,19 @@ sendAlbumMessage: { } : {}) } }, { 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)) { @@ -520,7 +520,7 @@ sendAlbumMessage: { console.error("Errore nell'ottenere il tipo MIME:", error); } } - + const mediaMessage = await generateWAMessage(album.key.remoteJid, { [type]: data, ...(media === medias[0] ? { caption } : {}) @@ -621,7 +621,7 @@ sendAlbumMessage: { copy_code: copy }) } : null) - + urls?.forEach(url => { dynamicButtons.push({ name: 'cta_url', @@ -853,12 +853,12 @@ sendAlbumMessage: { } } }, - + 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 @@ -896,7 +896,7 @@ sendButton2: { }), })); - + if (copy && (typeof copy === 'string' || typeof copy === 'number')) { // Aggiungi pulsante di copia dynamicButtons.push({ @@ -937,23 +937,23 @@ sendButton2: { } } - + 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 @@ -984,7 +984,7 @@ sendList: { } const sections = [...listSections] - + const message = { interactiveMessage: { header: {title: title, @@ -1007,11 +1007,11 @@ sendList: { } } }; - + 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 }) @@ -1044,7 +1044,7 @@ sendList: { * @param {String|string[]} call * @param {String|string[]} callText * @param {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, url, urlText, call, callText, buttons, quoted, options) { @@ -1134,7 +1134,7 @@ sendList: { * @param {String|string[]} call * @param {String|string[]} callText * @param {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, url, urlText, url2, urlText2, buttons, quoted, options) { @@ -1216,7 +1216,7 @@ sendList: { /** * cMod * @param {String} jid - * @param {import('@chatunity/baileys').proto.WebMessageInfo} message + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} message * @param {String} text * @param {String} sender * @param {*} options @@ -1254,7 +1254,7 @@ sendList: { /** * Copia esatta e inoltra * @param {String} jid - * @param {import('@chatunity/baileys').proto.WebMessageInfo} message + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} message * @param {Boolean|Number} forwardingScore * @param {Object} options */ @@ -1363,7 +1363,7 @@ sendList: { /** * * @param {String} messageID - * @returns {import('@chatunity/baileys').proto.WebMessageInfo} + * @returns {import('@whiskeysockets/baileys').proto.WebMessageInfo} */ value(messageID) { return Object.entries(conn.chats) @@ -1406,7 +1406,7 @@ sendList: { processMessageStubType: { /** * Processa MessageStubType - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m */ async value(m) { if (!m.messageStubType) return; @@ -1419,7 +1419,7 @@ sendList: { typeof p === 'string' && p.endsWith('@lid') ? conn.decodeJid(p) : p ); } - + const emitGroupUpdate = (update) => { conn.ev.emit('groups.update', [{ id: chat, ...update }]); }; @@ -1470,7 +1470,7 @@ sendList: { pushMessage: { /** * pushMessage - * @param {import('@chatunity/baileys').proto.WebMessageInfo[]} m + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo[]} m */ async value(m) { if (!m) return; @@ -1487,13 +1487,13 @@ sendList: { const chat = conn.decodeJid(message.key.remoteJid || message.message?.senderKeyDistributionMessage?.groupId || ''); if (message.message?.[mtype]?.contextInfo?.quotedMessage) { /** - * @type {import('@chatunity/baileys').proto.IContextInfo} + * @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('@chatunity/baileys').proto.IMessage} + * @type {import('@whiskeysockets/baileys').proto.IMessage} * */ const quoted = message.message[mtype].contextInfo.quotedMessage; @@ -1509,10 +1509,10 @@ sendList: { 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: { @@ -1573,7 +1573,7 @@ sendList: { serializeM: { /** * Serializza messaggio, per manipolarlo facilmente - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m */ value(m) { return smsg(conn, m); @@ -1657,7 +1657,7 @@ sendList: { /** * Serializza messaggio * @param {ReturnType} conn - * @param {import('@chatunity/baileys').proto.WebMessageInfo} m + * @param {import('@whiskeysockets/baileys').proto.WebMessageInfo} m * @param {Boolean} hasParent */ export function smsg(conn, m, store) { @@ -1994,7 +1994,7 @@ export function serialize() { return self.conn?.cMod(jid, this.vM, text, sender, options) }, enumerable: true, - + }, delete: { /** @@ -2004,7 +2004,7 @@ export function serialize() { return self.conn?.sendMessage(this.chat, { delete: this.vM.key }) }, enumerable: true, - + }, //react react: { 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 diff --git a/main.js b/main.js index 5065ed229..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('@chatunity/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); 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 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; 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] 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}) => { 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 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 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 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 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 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 }) => { 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 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' 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; 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 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 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 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 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;}; - 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 {