const fs = require("fs-extra"); const path = require("path"); const https = require("https"); // ๐Ÿ”’ anti double trigger lock const running = new Set(); const prefixFile = path.join(__dirname, "prefixData.json"); function ensurePrefixFile() { if (!fs.existsSync(prefixFile)) { fs.writeFileSync(prefixFile, JSON.stringify({}, null, 2)); } } // ---- Shared function: always returns the ACTUAL current prefix for a thread ---- function getCurrentPrefix(threadID) { ensurePrefixFile(); try { const data = JSON.parse(fs.readFileSync(prefixFile)); return data[threadID] || global.GoatBot.config.prefix || "."; } catch (e) { return global.GoatBot.config.prefix || "."; } } function setPrefix(threadID, newPrefix) { ensurePrefixFile(); const data = JSON.parse(fs.readFileSync(prefixFile)); data[threadID] = newPrefix; fs.writeFileSync(prefixFile, JSON.stringify(data, null, 2)); } const loadingSets = [ [ "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ 10%", "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ 30%", "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ 50%", "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑ 70%", "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑ 90%", "๐‹๐จ๐š๐๐ข๐ง๐  ๐๐ซ๐ž๐Ÿ๐ข๐ฑ...\nโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐ 100%" ], [ "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ–กโ–กโ–กโ–กโ–กโ–กโ–กโ–กโ–ก] 10%", "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ– โ– โ–กโ–กโ–กโ–กโ–กโ–กโ–ก] 30%", "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ– โ– โ– โ– โ–กโ–กโ–กโ–กโ–ก] 50%", "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ– โ– โ– โ– โ– โ– โ–กโ–กโ–ก] 70%", "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–ก] 90%", "๐™‡๐™ค๐™–๐™™๐™ž๐™ฃ๐™œ ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ...\n[โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– ] 100%" ], [ "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ–กโ–กโ–กโ–กโ–กโ–กโ–กโ–กโ–ก 10%", "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ—‰โ—‰โ–กโ–กโ–กโ–กโ–กโ–กโ–ก 30%", "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ—‰โ—‰โ—‰โ—‰โ–กโ–กโ–กโ–กโ–ก 50%", "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ–กโ–กโ–ก 70%", "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ–ก 90%", "๐™ป๐š˜๐šŠ๐š๐š’๐š—๐š ๐™ฟ๐š›๐šŽ๐š๐š’๐šก...\nโ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰โ—‰ 100%" ] ]; const gifs = [ "https://i.imgur.com/zex8uo7.gif", "https://i.imgur.com/4ki8eBI.gif", "https://i.imgur.com/AMKQCJc.gif", "https://i.imgur.com/rkjO7YV.gif", "https://i.imgur.com/SgNPn8E.gif", "https://i.imgur.com/u3qB5y2.gif", "https://i.imgur.com/KUFxWlF.gif", "https://i.imgur.com/FV9krHV.gif", "https://i.imgur.com/lFrFMEn.gif", "https://i.imgur.com/KrEez4A.gif" ]; // ================= SHARED: "prefix" -> loading animation + gif + info card ================= // Called from BOTH onStart (normal command dispatch) AND onChat (manual no-prefix // safety net below), so the exact same behaviour fires no matter which path // the framework routes the message through โ€” the user never needs to type the // bot's prefix character first, just the word "prefix" by itself. async function showPrefixInfo({ message, event, api }) { const key = event.threadID; if (running.has(key)) return; running.add(key); setTimeout(() => running.delete(key), 5000); const botPrefix = global.GoatBot.config.prefix || "."; const groupPrefix = getCurrentPrefix(event.threadID); const ping = Date.now() - event.timestamp; const day = new Date().toLocaleString("en-US", { weekday: "long" }); const BOTNAME = global.GoatBot.config.nickNameBot || "KakashiBot"; const textFrames = [ `โ•ญโ”€โ โŸฃ ๐—ฃ๐—ฅ๐—˜๐—™๐—œ๐—ซ ๐—œ๐—ก๐—™๐—ข โŸข โโ”€โ•ฎ โฑ๏ธ ๐—ฃ๐—ถ๐—ป๐—ด : ${ping}ms ๐Ÿ“… ๐——๐—ฎ๐˜† : ${day} ๐Ÿ’  ๐—•๐—ผ๐˜ ๐—ฃ๐—ฟ๐—ฒ๐—ณ๐—ถ๐˜… : ${botPrefix} ๐Ÿ’ฌ ๐—š๐—ฟ๐—ผ๐˜‚๐—ฝ ๐—ฃ๐—ฟ๐—ฒ๐—ณ๐—ถ๐˜… : ${groupPrefix} ๐Ÿค– ๐—•๐—ผ๐˜ ๐—ก๐—ฎ๐—บ๐—ฒ : ${BOTNAME} โ•ฐโ”€โ โŸฃ ๐—ง๐—›๐—”๐—ก๐—ž ๐—ฌ๐—ข๐—จ โŸข โโ”€โ•ฏ`, `โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ โœฆ ๐™‹๐™๐™€๐™๐™„๐™“ ๐™Ž๐™๐˜ผ๐™๐™๐™Ž โœฆ โ”ƒ โ”ฃโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ โ”ƒ โฑ๏ธ ๐™‹๐™ž๐™ฃ๐™œ โžœ ${ping}ms โ”ƒ ๐Ÿ“… ๐˜ฟ๐™–๐™ฎ โžœ ${day} โ”ƒ ๐Ÿ’  ๐™‹๐™ง๐™š๐™›๐™ž๐™ญ โžœ ${botPrefix} โ”ƒ ๐Ÿ’ฌ ๐™‚๐™ง๐™ค๐™ช๐™ฅ โžœ ${groupPrefix} โ”ƒ ๐Ÿค– ๐˜ฝ๐™ค๐™ฉ โžœ ${BOTNAME} โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›`, `โ– โ–‚ โ–ƒ โ–„ ๐๐‘๐„๐…๐ˆ๐— ๐ˆ๐๐…๐Ž โ–„ โ–ƒ โ–‚ โ– โžค ๐๐ข๐ง๐  โ‹ฎ ${ping}ms โžค ๐ƒ๐š๐ฒ โ‹ฎ ${day} โžค ๐๐จ๐ญ ๐๐š๐ฆ๐ž โ‹ฎ ${BOTNAME} โžค ๐๐จ๐ญ ๐๐ซ๐ž๐Ÿ๐ข๐ฑ โ‹ฎ ${botPrefix} โžค ๐†๐ซ๐จ๐ฎ๐ฉ ๐๐ซ๐ž๐Ÿ๐ข๐ฑ โ‹ฎ ${groupPrefix} โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–” โ–”`, ` โŸฆ ๐™ฟ ๐š ๐™ด ๐™ต ๐™ธ ๐š‡ ๐š‚ ๐šƒ ๐™ฐ ๐šƒ ๐š„ ๐š‚ โŸง โŒ ๐™ฟ๐š’๐š—๐š ยป ${ping}ms โŒ ๐™ณ๐šŠ๐šข ยป ${day} โŒ ๐™ฟ๐š›๐šŽ๐š๐š’๐šก ยป ${botPrefix} โŒ ๐™ถ๐š›๐š˜๐šž๐š™ ๐™ฟ๐š›๐šŽ๐š๐š’๐šก ยป ${groupPrefix} โŸฆ ๐™ฟ๐š˜๐š ๐šŽ๐š›๐šŽ๐š ๐š‹๐šข ${BOTNAME} โŸง` ]; const randomLoadingSet = loadingSets[Math.floor(Math.random() * loadingSets.length)]; const randomGifUrl = gifs[Math.floor(Math.random() * gifs.length)]; const randomText = textFrames[Math.floor(Math.random() * textFrames.length)]; const msg = await message.reply(randomLoadingSet[0]); for (let i = 1; i < randomLoadingSet.length; i++) { await new Promise(r => setTimeout(r, 1000)); api.editMessage(randomLoadingSet[i], msg.messageID); } await new Promise(r => setTimeout(r, 700)); api.unsendMessage(msg.messageID); const cacheFolder = path.join(__dirname, "cache"); if (!fs.existsSync(cacheFolder)) fs.mkdirSync(cacheFolder); const gifName = path.basename(randomGifUrl); const gifPath = path.join(cacheFolder, gifName); if (!fs.existsSync(gifPath)) { await new Promise((resolve, reject) => { const file = fs.createWriteStream(gifPath); https.get(randomGifUrl, res => { res.pipe(file); file.on("finish", () => file.close(resolve)); }).on("error", reject); }); } running.delete(key); return api.sendMessage({ body: randomText, attachment: fs.createReadStream(gifPath) }, event.threadID); } // ================= SHARED: "prefix set " ================= async function handleSetPrefix({ message, event, api, args, threadsData }) { const key = event.threadID; if (running.has(key)) return; running.add(key); setTimeout(() => running.delete(key), 5000); const newPrefix = args[1]; if (!newPrefix) { running.delete(key); return message.reply("โŒ | Example: prefix set !"); } // ---- Admin-only check (group admin OR bot admin) ---- let isAdmin = false; try { const threadInfo = await api.getThreadInfo(event.threadID); const isGroupAdmin = threadInfo.adminIDs?.some(a => a.id == event.senderID); const isBotAdmin = global.GoatBot.config.adminBot?.includes(event.senderID); isAdmin = isGroupAdmin || isBotAdmin; } catch (e) { isAdmin = global.GoatBot.config.adminBot?.includes(event.senderID); } if (!isAdmin) { running.delete(key); return message.reply("โŒ | เฆถเงเฆงเงเฆฎเฆพเฆคเงเฆฐ เฆ—เงเฆฐเงเฆช เฆเฆกเฆฎเฆฟเฆจ เฆฌเฆพ เฆฌเฆŸ เฆเฆกเฆฎเฆฟเฆจ เฆชเงเฆฐเง‡เฆซเฆฟเฆ•เงเฆธ เฆชเฆฐเฆฟเฆฌเฆฐเงเฆคเฆจ เฆ•เฆฐเฆคเง‡ เฆชเฆพเฆฐเฆฌเง‡เฅค"); } // ---- Only change THIS thread's prefix, never global ---- setPrefix(event.threadID, newPrefix); if (threadsData) { try { await threadsData.set(event.threadID, newPrefix, "data.prefix"); } catch (e) {} } running.delete(key); return message.reply(`โœ… Prefix changed successfully for this group!\nNew Prefix: ${newPrefix}`); } module.exports = { config: { name: "prefix", version: "21.0", author: "HR ID OY", description: "Full prefix system with random animation + gif โ€” works with no bot prefix needed", category: "Utility", role: 0, // ๐Ÿ”‘ REQUIRED so "prefix" and "prefix set " work WITHOUT the bot prefix usePrefix: false, guide: { en: "prefix -> show prefix info with animation (no bot prefix needed)\nprefix set -> change prefix for this group (admin only)" } }, onStart: async function ({ message, event, api, args, threadsData }) { if (args[0] === "set") { return handleSetPrefix({ message, event, api, args, threadsData }); } return showPrefixInfo({ message, event, api }); }, // ================= NO-PREFIX SAFETY NET ================= // Runs on every message regardless of the framework's usePrefix handling, // so "prefix" / "prefix set " always fires the full flow above even if // usePrefix:false isn't honoured for some reason โ€” and the bare prefix // character alone (e.g. "." or "!") still gets its quick one-line reply. onChat: async function ({ event, message, api, threadsData }) { if (!event.body) return; const body = event.body.trim(); const lower = body.toLowerCase(); // "prefix set " typed with no bot prefix at all const setMatch = lower.match(/^prefix\s+set\s+(\S+)$/); if (setMatch) { return handleSetPrefix({ message, event, api, threadsData, args: ["set", body.split(/\s+/)[2]] }); } // bare word "prefix" typed with no bot prefix at all if (lower === "prefix") { return showPrefixInfo({ message, event, api }); } // ---- ALWAYS read the ACTUAL live prefix for this thread, never hardcoded ---- const currentPrefix = getCurrentPrefix(event.threadID); // Only reply if the ENTIRE message is exactly the current prefix, nothing else if (body === currentPrefix) { return message.reply(""); } } };