// code from goatstore // api: https://store.agi.bd const axios = require("axios"); const fs = require("fs"); const path = require("path"); // ── Sahu SimSimi API (remote, used only for live chat) ── // GET /simsimi?text=...&senderName=... // GET /teach?ask=...&ans=...&senderID=...&senderName=... const toru = "http://176.100.37.91:30366"; // ── Local database (self-contained, no external dependency) ── // Powers babylist / babyreply / babymsg / babyautoteach / auto-learn-from-reply. // // IMPORTANT (Railway users): Railway's default filesystem is wiped on every // redeploy. To make taught answers permanent, attach a Volume in your // Railway service (Settings → Volumes → mount path "/data") — once that // exists, this file is saved there automatically and survives redeploys. // If no /data folder exists (e.g. running locally), it falls back to this // folder so the bot still works, just without redeploy-proof storage. const DATA_DIR = fs.existsSync("/data") ? "/data" : __dirname; const DB_PATH = path.join(DATA_DIR, "babyai-db.json"); const MATCH_THRESHOLD = 0.7; function loadDB() { try { if (fs.existsSync(DB_PATH)) { const raw = fs.readFileSync(DB_PATH, "utf8"); const parsed = JSON.parse(raw); if (!Array.isArray(parsed.items)) parsed.items = []; if (!parsed.settings) parsed.settings = { autoTeach: true }; return parsed; } } catch (err) { console.error("babyai DB read error:", err.message); } return { items: [], settings: { autoTeach: true } }; } function saveDB(db) { try { fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2), "utf8"); return true; } catch (err) { console.error("babyai DB write error:", err.message); return false; } } function addLocalEntry(question, answer, senderID) { const q = (question || "").trim(); const a = (answer || "").trim(); if (!q || !a) return false; const db = loadDB(); const existingIdx = db.items.findIndex( it => it.question.toLowerCase() === q.toLowerCase() ); if (existingIdx >= 0) { db.items[existingIdx].answer = a; db.items[existingIdx].updatedAt = Date.now(); } else { db.items.push({ question: q, answer: a, senderID: senderID || null, createdAt: Date.now() }); } return saveDB(db); } // ---- Admin-only access for sensitive commands ---- const ADMIN_IDS = ["61594257123734"]; const isAdmin = (senderID) => ADMIN_IDS.includes(String(senderID)); const NOT_ADMIN_MSG = "❌ Etoh command shudhu admin er jonno."; const typingWhile = async (api, threadID, workPromise) => { try { if (typeof api.sendTypingIndicator === "function") { Promise.resolve(api.sendTypingIndicator(threadID, true)).catch(() => {}); } } catch {} const result = await workPromise; try { if (typeof api.sendTypingIndicator === "function") { Promise.resolve(api.sendTypingIndicator(threadID, false)).catch(() => {}); } } catch {} return result; }; const flashTyping = (api, threadID) => { try { if (typeof api.sendTypingIndicator === "function") { api.sendTypingIndicator(threadID, true); setTimeout(() => { try { api.sendTypingIndicator(threadID, false); } catch {} }, 500); } } catch {} }; const spamMap = new Map(); const SPAM_LIMIT = 7; const SPAM_WINDOW = 10000; const SPAM_MUTE = 10000; const SPAM_ENTRY_TTL = 5 * 60 * 1000; let selfCooldownUntil = 0; const SELF_COOLDOWN_MS = 6000; const selfTriggerAllowed = () => Date.now() >= selfCooldownUntil; const markSelfTrigger = () => { selfCooldownUntil = Date.now() + SELF_COOLDOWN_MS; }; const isSpamming = (senderID) => { const now = Date.now(); if (spamMap.size > 500) { for (const [id, e] of spamMap) { const lastHit = e.hits.length ? e.hits[e.hits.length - 1] : 0; if (e.mutedUntil < now && now - lastHit > SPAM_ENTRY_TTL) { spamMap.delete(id); } } } const entry = spamMap.get(senderID) || { hits: [], mutedUntil: 0 }; if (entry.mutedUntil > now) return true; entry.hits = entry.hits.filter(t => now - t < SPAM_WINDOW); entry.hits.push(now); if (entry.hits.length >= SPAM_LIMIT) { entry.mutedUntil = now + SPAM_MUTE; entry.hits = []; spamMap.set(senderID, entry); return true; } spamMap.set(senderID, entry); return false; }; function isUsable(text, prefix) { if (!text || typeof text !== "string") return false; const trimmed = text.trim(); if (!trimmed) return false; if (prefix && trimmed.startsWith(prefix)) return false; if (trimmed.length > 500) return false; return true; } function levenshtein(a, b) { const m = a.length, n = b.length; const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (a[i - 1] === b[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]); } } } return dp[m][n]; } function similarity(a, b) { const x = (a || "").toLowerCase().trim(); const y = (b || "").toLowerCase().trim(); if (!x.length && !y.length) return 1; const maxLen = Math.max(x.length, y.length); if (maxLen === 0) return 1; return 1 - levenshtein(x, y) / maxLen; } function findBestLocalMatch(query) { const db = loadDB(); let best = null; let bestScore = 0; for (const it of db.items) { const score = similarity(query, it.question); if (score > bestScore) { bestScore = score; best = it; } } return best ? { item: best, score: bestScore } : null; } async function getSmartReply(query, senderName) { const local = findBestLocalMatch(query); if (local && local.score >= MATCH_THRESHOLD) { return local.item.answer; } try { const res = await axios.get(`${toru}/simsimi`, { params: { text: query, senderName: senderName || "Friend" }, timeout: 20000 }); const d = res.data; const text = (typeof d === "string" && d) || d?.message || d?.result || d?.answer || d?.reply || d?.response || (d?.data && (d.data.message || d.data.answer || d.data.reply)); return text || "Hmm, bujhi nai baby 😅"; } catch (err) { console.error( "Baby chat gateway error:", err.response?.data?.error || err.response?.data?.message || err.message ); return "❌ Ekhon connect korte parchi na, ektu pore try koro baby 😔"; } } async function getSenderName(api, senderID) { try { const info = await api.getUserInfo(senderID); return info?.[senderID]?.name || "Friend"; } catch { return "Friend"; } } const PREFIX_TRIGGERS = [ "toru","bby","toruchan","tori","bot","তরু","বট","jan","জান","বেবি","baby" ]; function matchPrefix(raw) { for (const p of PREFIX_TRIGGERS) { if (raw === p) return p; if (raw.startsWith(p + " ")) return p + " "; } return null; } // ═══════════════════════════════════════ // 👑 NAME TRIGGERS — এই নামগুলো একা লিখলেই random funny reply // ═══════════════════════════════════════ const NAME_TRIGGERS = new Set([ "মেহেদী", "মেহেদি", "রাকিব", "মাইশা", "মৌ", "মরিয়াম", "mehedi", ".mehedi", "rakib", "maisha", "mou", "moriam" ].map(n => n.toLowerCase())); const FUNNY_REPLIES = [ "𝐀𝐬𝐬𝐚𝐥𝐚𝐦𝐮 𝐰𝐚𝐥𝐚𝐢𝐤𝐮𝐦 ♥", "── চুনা ও চুনা আমার বস মেহেদি র এর হবু বউ মাইশা কে দেকছো তাকে খুজে পাচ্ছি না...!!🥹❤️‍🩹🌷", "── 𝐁𝐨𝐥𝐨 𝐣𝐚𝐧 𝐤𝐢 𝐤𝐨𝐫𝐭𝐞 𝐩𝐚𝐫𝐢 𝐭𝐨𝐦𝐫 𝐣𝐨𝐧𝐧𝐨 🐸", "── Bolo Babu তুমি কি আমার বস মেহেদি কে ভালোবাসো...!!🙈💋", "── oii-🥺🥹-এক🥄 চামুচ ভালোবাসা দিবা-🤏🏻🙂", "ছোটবেলা ভাবতাম বিয়ে করলে অটোমেটিক বাচ্চা হয়-🥱-ওমা এখন দেখি কাহিনী অন্যরকম...?😦🙂🌻", "── 𝐋𝐞𝐦𝐨𝐧 𝐭𝐮𝐬 🍋", "── বস মেহেদি 𝕭𝖔𝖙 হাজির-))😎🌷🫶", "𝐚𝐦𝐤𝐞 𝐬𝐞𝐫𝐞 𝐝𝐞𝐰 𝐚𝐦𝐢 𝐚𝐦𝐦𝐮𝐫 𝐤𝐚𝐬𝐞 𝐣𝐚𝐛𝐨!!🥺...😗", "── অন্যকে নই, নিজেকে ভালোবাসতে শিখো প্রিয় 😌", "একা বাঁচতে শিখো দেখবে পৃথিবী অনেক সুন্দর...!✨", "──‎ 𝐇𝐮𝐌...!! 👉👈", "আম গাছে আম নাই ঢিল কেন মারো, তোমার সাথে প্রেম নাই বেবি কেন ডাকো 😒🐸", "── কি হলো, মিস টিস করচ্ছো নাকি 🤣", "𝐓𝐫𝐮𝐬𝐭 𝐦𝐞 𝐢𝐚𝐦 𝐭𝐨𝐫𝐮 𝐟𝐫𝐨𝐦 ᴍ𝐄ʜ𝐄ᴅ𝐈🧃", "── তাহলে আমার কথা মনে হলো তোর ))🥺🙆‍♀️", "𝐓𝐨𝐫 𝐣𝐧𝐧𝐨 𝐛𝐬𝐢 𝐚𝐜𝐡𝐢, 𝐣𝐥𝐝𝐢 𝐛𝐨𝐥 𝐤𝐢 𝐝𝐫𝐤𝐚𝐫 ✨", "── একাকিত্ব মানুষকে ধীরে ধীরে শেষ করে ফেলে🥀", "চা খাবেন ,ঢেলে দেবো...?😙🤏", "── আজ একটা ফোন নাই বলে রিপ্লাই দিতে পারলাম না___🙄" ]; module.exports = { config: { name: "babyai", version: "4.1.0", author: "Hridoy", countDown: 0, role: 0, shortDescription: "Toru Chan AI — Sahu SimSimi + Local Brain", longDescription: "Teachable TORU AI: live chat via the Sahu SimSimi API with a self-contained local database powering list/search/auto-teach, so every sub-command works with no external dependency. Teaching is restricted to admins only.", category: "System", dependencies: { axios: "" }, aliases: [ "babyteach", "babyautoteach", "babylist", "babyreply", "babymsg" ], guide: { en: "{p}baby [message]\n" + "{p}babyteach [q] - [a] (admin only)\n" + "{p}babyautoteach on/off (admin only)\n" + "{p}babylist\n" + "{p}babylist [text] (numbered search results)\n" + "{p}babyreply [text] (searches inside all saved replies)\n" + "{p}babymsg [trigger]" } }, onStart: async function ({ api, event, args, message }) { const senderID = event.senderID; const botID = api.getCurrentUserID(); const isSelf = senderID === botID; if (isSelf) { if (!selfTriggerAllowed()) return; markSelfTrigger(); } if (isSpamming(senderID)) return; const threadID = event.threadID; const prefix = (global.GoatBot && global.GoatBot.config && global.GoatBot.config.prefix) || ""; let bodyText = (event.body || "").trim(); if (prefix && bodyText.toLowerCase().startsWith(prefix.toLowerCase())) { bodyText = bodyText.slice(prefix.length).trim(); } const lowerBody = bodyText.toLowerCase(); // ═══════════════════════════════ // 👑 NAME → RANDOM FUNNY REPLY // ═══════════════════════════════ if (NAME_TRIGGERS.has(lowerBody)) { flashTyping(api, threadID); return message.reply( FUNNY_REPLIES[Math.floor(Math.random() * FUNNY_REPLIES.length)] ); } const stripKeyword = (kw) => { if (lowerBody === kw) return ""; if (lowerBody.startsWith(kw + " ")) return bodyText.slice(kw.length).trim(); return null; }; const KEYWORD_ORDER = [ ["autoteach", "babyautoteach"], ["teach", "babyteach"], ["list", "babylist"], ["reply", "babyreply"], ["msg", "babymsg"] ]; let sub = null; let rawArgs = ""; for (const [name, kw] of KEYWORD_ORDER) { const r = stripKeyword(kw); if (r !== null) { sub = name; rawArgs = r; break; } } if (sub === null) { const r = stripKeyword("baby"); rawArgs = r !== null ? r : (args.join(" ").trim() || bodyText); } try { if (!sub && !rawArgs) { flashTyping(api, threadID); return message.reply( FUNNY_REPLIES[Math.floor(Math.random() * FUNNY_REPLIES.length)] ); } if (sub === "autoteach") { if (!isAdmin(senderID)) { return message.reply(NOT_ADMIN_MSG); } const mode = (rawArgs.split(/\s+/)[0] || "").toLowerCase(); if (!["on", "off"].includes(mode)) { return message.reply("Use: babyautoteach on/off"); } const status = mode === "on"; const db = loadDB(); db.settings.autoTeach = status; const ok = saveDB(db); if (!ok) { return message.reply("❌ Change kora jayni (file write error)."); } return message.reply(`✅ Auto Teach ekhon ${status ? "ON 🟢" : "OFF 🔴"}`); } if (sub === "list") { const query = rawArgs.trim(); const db = loadDB(); if (!query) { return message.reply( `╭─╼🌟 𝐓𝐨𝐫𝐮 𝐀𝐈 𝐒𝐭𝐚𝐭𝐮𝐬 ├ 📝 𝐓𝐞𝐚𝐜𝐡𝐞𝐝 𝐐𝐮𝐞𝐬𝐭𝐢𝐨𝐧𝐬: ${db.items.length} ├ 🔁 𝐀𝐮𝐭𝐨 𝐓𝐞𝐚𝐜𝐡: ${db.settings.autoTeach ? "ON 🟢" : "OFF 🔴"} ╰─╼👤 𝐃𝐞𝐯: 𝐌𝙴ʜ𝐄ᴅ𝐈` ); } const q = query.toLowerCase(); const items = db.items.filter(it => it.question.toLowerCase().includes(q)); if (!items.length) { return message.reply("❌ Ei text er kono answer paoa jayni."); } const limited = items.slice(0, 15); const formatted = limited .map((it, i) => `➤ ${i + 1}. [${it.question}] → ${it.answer}`) .join("\n"); return message.reply( `📌 𝗦𝗲𝗮𝗿𝗰𝗵: ${query} 📋 𝗧𝗼𝘁𝗮𝗹: ${items.length} ━━━━━━━━━━━━━━ ${formatted}` ); } if (sub === "reply") { const query = rawArgs.trim(); if (!query) { return message.reply("Use: babyreply [text]"); } const db = loadDB(); const q = query.toLowerCase(); const items = db.items.filter(it => it.answer.toLowerCase().includes(q)); if (!items.length) { return message.reply("❌ Ei text shoho kono reply paoa jayni."); } const limited = items.slice(0, 15); const formatted = limited .map((it, i) => `➤ ${i + 1}. [${it.question}] → ${it.answer}`) .join("\n"); return message.reply( `📌 𝗥𝗲𝗽𝗹𝘆 𝗦𝗲𝗮𝗿𝗰𝗵: ${query} 📋 𝗧𝗼𝘁𝗮𝗹: ${items.length} ━━━━━━━━━━━━━━ ${formatted}` ); } if (sub === "msg") { const trigger = rawArgs.trim(); if (!trigger) { return message.reply("Use: babymsg [trigger]"); } const db = loadDB(); const t = trigger.toLowerCase(); const items = db.items.filter(it => it.question.toLowerCase().includes(t)); if (!items.length) { return message.reply("❌ Ei trigger-er kono answer paoa jayni."); } const formatted = items .slice(0, 15) .map((it, i) => `➤ ${i + 1}. [${it.question}] → ${it.answer}`) .join("\n"); return message.reply( `📌 𝗧𝗿𝗶𝗴𝗴𝗲𝗿: ${trigger} 📋 𝗧𝗼𝘁𝗮𝗹: ${items.length} ━━━━━━━━━━━━━━ ${formatted}` ); } // ───── TEACH: Admin only ───── if (sub === "teach") { if (!isAdmin(senderID)) { return message.reply(NOT_ADMIN_MSG); } const parts = rawArgs.split(" - "); if (parts.length < 2) { return message.reply("Use: babyteach question - answer"); } const [ask, ans] = parts.map(s => s.trim()); const savedLocally = addLocalEntry(ask, ans, senderID); // Best-effort mirror to the remote brain; local save is what guarantees it works. try { const senderName = await getSenderName(api, senderID); await axios.get(`${toru}/teach`, { params: { ask, ans, senderID, senderName }, timeout: 12000 }); } catch {} return message.reply( savedLocally ? "✅ Shekhano hoyeche! (Permanent - bot reset e o thakbe)" : "❌ Vul hoyeche (file save error)." ); } const senderName = await getSenderName(api, senderID); const reply = await typingWhile(api, threadID, getSmartReply(rawArgs, senderName)); return message.reply(reply); } catch (err) { console.error( "baby command error:", err.response?.data?.error || err.response?.data?.message || err.message ); return message.reply( "❌ Error: " + (err.response?.data?.error || err.response?.data?.message || err.message) ); } }, onChat: async function ({ api, event, message }) { const senderID = event.senderID; const botID = api.getCurrentUserID(); const isSelf = senderID === botID; if (isSpamming(senderID)) return; const prefix = (global.GoatBot && global.GoatBot.config && global.GoatBot.config.prefix) || ""; const raw = event.body ? event.body.toLowerCase().trim() : ""; const threadID = event.threadID; try { const repliedToBot = event.messageReply && event.messageReply.senderID === botID; // ───── AUTO-TEACH: শুধুমাত্র Admin রিপ্লাই দিলেই শিখবে ───── if (!isSelf && event.messageReply && !repliedToBot && isAdmin(senderID)) { const question = event.messageReply.body; const answer = event.body; const db = loadDB(); if ( isUsable(question, prefix) && isUsable(answer, prefix) && db.settings.autoTeach ) { addLocalEntry(question.trim(), answer.trim(), senderID); } } if (!isSelf && repliedToBot && isUsable(event.body, prefix)) { const senderName = await getSenderName(api, senderID); const reply = await typingWhile(api, threadID, getSmartReply(event.body.trim(), senderName)); return message.reply(reply); } if (!raw) return; // ═══════════════════════════════ // 👑 NAME → RANDOM FUNNY REPLY // ═══════════════════════════════ if (NAME_TRIGGERS.has(raw)) { if (isSelf) { if (!selfTriggerAllowed()) return; markSelfTrigger(); } flashTyping(api, threadID); return message.reply( FUNNY_REPLIES[Math.floor(Math.random() * FUNNY_REPLIES.length)] ); } const foundPrefix = matchPrefix(raw); if (foundPrefix) { if (isSelf) { if (!selfTriggerAllowed()) return; markSelfTrigger(); } const q = event.body.slice(foundPrefix.length).trim(); if (!q) { flashTyping(api, threadID); return message.reply( FUNNY_REPLIES[Math.floor(Math.random() * FUNNY_REPLIES.length)] ); } const senderName = await getSenderName(api, senderID); const reply = await typingWhile(api, threadID, getSmartReply(q, senderName)); return message.reply(reply); } } catch (err) { console.error( "baby onChat error:", err.response?.data?.error || err.response?.data?.message || err.message ); } } };