bbb / bot /lib /handler.js
Nexchan's picture
dolla
72ee6f0
// command-handler.js
import fs from "fs";
import path from "path";
import { pathToFileURL } from "url";
/**
* Load semua command dari folder (rekursif)
* Struktur file command:
* export default {
* command: ["ping", "p"],
* description: "...",
* run: async (m, { conn }) => {}
* }
*/
export async function loadCommands(dir) {
const commands = new Map();
async function loadDir(folder) {
const files = fs.readdirSync(folder);
for (const file of files) {
const full = path.join(folder, file);
const stat = fs.statSync(full);
if (stat.isDirectory()) {
// masuk folder lagi (rekursif)
await loadDir(full);
continue;
}
if (!file.endsWith(".js")) continue;
try {
const fileUrl = pathToFileURL(full).href;
const cmdModule = (await import(fileUrl)).default;
if (!cmdModule || !cmdModule.command) continue;
const aliases = Array.isArray(cmdModule.command)
? cmdModule.command
: [cmdModule.command];
for (const alias of aliases) {
const key = String(alias).toLowerCase();
commands.set(key, cmdModule);
}
} catch (err) {
console.error(`❌ Gagal load command: ${full}`, err);
}
}
}
await loadDir(dir);
return commands;
}
/**
* Helper untuk ambil teks utama dari message
*/
function getMessageText(msg) {
return (
msg.message?.conversation ||
msg.message?.extendedTextMessage?.text ||
msg.message?.imageMessage?.caption ||
msg.message?.videoMessage?.caption ||
msg.message?.buttonsResponseMessage?.selectedButtonId ||
msg.message?.listResponseMessage?.singleSelectReply?.selectedRowId ||
""
);
}
/**
* Handler utama command
* - parsing prefix, command, args
* - bikin wrapper "m" dengan m.reply, m.send, dll
* - jalanin plugin.run(...)
*/
export async function handleCommand(sock, msg, commands) {
try {
const from = msg.key.remoteJid;
const isGroup = from.endsWith("@g.us");
const sender = msg.key.participant || msg.key.remoteJid;
const body = getMessageText(msg);
if (!body) return;
// global.prefix bisa string / regex
const prefixMatch =
typeof global.prefix === "string"
? body.startsWith(global.prefix)
? [global.prefix]
: null
: body.match(global.prefix);
if (!prefixMatch) return; // bukan command
const prefix = prefixMatch[0];
const [rawCmd, ...args] = body
.slice(prefix.length)
.trim()
.split(/\s+/);
if (!rawCmd) return;
const command = rawCmd.toLowerCase();
const plugin = commands.get(command);
if (!plugin) return;
// Data tambahan
const quoted =
msg.message?.extendedTextMessage?.contextInfo?.quotedMessage || null;
const mentionedJid =
msg.message?.extendedTextMessage?.contextInfo?.mentionedJid || [];
// =========================================
// Wrapper "m" untuk dikirim ke plugin
// =========================================
const m = {
...msg,
from,
sender,
isGroup,
body,
text: body,
args,
prefix,
command,
quoted,
mentionedJid,
/**
* Reply text sederhana
* @param {string} text
* @param {object} options - opsi tambahan sendMessage
*/
reply: async (text, options = {}) => {
return sock.sendMessage(
from,
{ text: String(text) },
{ quoted: msg, ...options }
);
},
/**
* Kirim pesan custom / media
* contoh:
* m.send({ image: buffer, caption: "hai" })
*/
send: async (content, options = {}) => {
return sock.sendMessage(from, content, { quoted: msg, ...options });
},
/**
* React ke pesan
* @param {string} emoji
*/
react: async (emoji) => {
try {
return sock.sendMessage(from, {
react: { text: emoji, key: msg.key },
});
} catch (err) {
console.error("❌ Gagal react:", err);
}
},
/**
* Reply error dengan format rapi
*/
error: async (err) => {
console.error("❌ Plugin error:", err);
const message =
typeof err === "string" ? err : err?.message || "Terjadi kesalahan";
return m.reply("⚠️ Error: " + message);
},
};
// =========================================
// Jalankan plugin
await plugin.run(m, { conn: sock, sock });
} catch (e) {
console.error("❌ Error handleCommand:", e);
}
}