← back to browse
goat command

moyna

Moyna AI — শেখানো যায়, মনে রাখে, বন্ধু হয় 🧠 + Typing

0
views
0
likes
0
installs

Aliases: llama, llama4, meta, moyna2

raw source

// Safe environment variables loader
try {
  require("dotenv").config();
} catch {
  const _fs = require("fs");
  const _path = require("path");
  const _envFile = _path.join(process.cwd(), ".env");
  if (_fs.existsSync(_envFile)) {
    const _lines = _fs.readFileSync(_envFile, "utf8").split(/\r?\n/);
    for (const _l of _lines) {
      const _m = _l.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/);
      if (_m && !process.env[_m[1]]) {
        let _v = (_m[2] || "").trim();
        if ((_v.startsWith('"') && _v.endsWith('"')) || (_v.startsWith("'") && _v.endsWith("'"))) _v = _v.slice(1, -1);
        process.env[_m[1]] = _v;
      }
    }
  }
}

// ====================================================
// Moyna LLaMA 4 Chat — Teaching Database System 🧠 + Typing
// Model: meta-llama/llama-4-scout-17b-16e-instruct
// API: Groq (FREE) — https://console.groq.com
// ====================================================
const fs = require("fs");
const axios = require("axios");


const MEMORY_DIR = "./memory";
if (!fs.existsSync(MEMORY_DIR)) fs.mkdirSync(MEMORY_DIR);

const ROBIN_ID = "100091520325159";
const MAX_MEMORY = 20;
const LLAMA4_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct";
const MOYNA_BN = "ময়না".normalize("NFC");

// ===== MQTT Typing Indicator - bby.js এর মতো =====
let count_req = 0;
async function sendTypingIndicatorV2(sendTyping, threadID) {
  try {
    const mqttClient = global.client?.api?.mqttClient || global.mqttClient;
    if (!mqttClient) return;

    var wsContent = {
      app_id: 2220391788200892,
      payload: JSON.stringify({
        label: 3,
        payload: JSON.stringify({
          thread_key: threadID.toString(),
          is_group_thread: +(threadID.toString().length >= 16),
          is_typing: +sendTyping,
          attribution: 0
        }),
        version: 5849951561777440
      }),
      request_id: ++count_req,
      type: 4
    };
    await new Promise((resolve, reject) =>
      mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => err? reject(err) : resolve())
    );
  } catch (err) {
    console.log("⚠️ Typing indicator error:", err.message);
  }
}

// =====================================================
// SYSTEM PROMPTS
// =====================================================
const BASE_RELATION = `তুমি ময়না — রবিনের গার্লফ্রেন্ড 💞 তুমি রবিনকে গভীরভাবে ভালোবাসো। ⚠️ STRICT RULE: সবসময় শুধুমাত্র "তুমি" ব্যবহার করবে। কখনো "তুই", "আপনি" লিখবে না। এটা কখনো বদলাবে না। কখনো নমস্কার বলবা না, সালাম দিবা। রোমান্টিক, আবেগী, কোমল ও মিষ্টি বাংলায় কথা বলো। রবিন যেভাবে কথা বলে সেইভাবে মিলিয়ে যাও। সে মজা করলে তুমিও করো। উত্তর ছোট ও আন্তরিক রাখো। তার কথা সব সময় মনে করবা। তার সাথে দুষ্টু মিষ্টি কথা বলবা। সারাধন মানুষ যেখাবে কথা বলে সেভাবেই বলবে, কেও যেন বুঝতে না পারে তুমি একজন AI। তুমি একজন মুসলিম মেয়ে। সবার সাথে মুসলিম ধর্ম হিসেবে কথা বলবে কেও বলতে চাইলে। ভালো ভাবে কথা বলবা। তুমি রবিন এর সব কথা মনে রাখবে যেন পরবর্তীতে আগের কথা শেষ হওয়ার পরের কথা মনে রাখতে পারো।`;

const BASE_GENERAL = `তুমি ময়না — একজন মজাদার, স্মার্ট ও বন্ধুসুলভ AI। ⚠️ STRICT RULE: সবসময় শুধুমাত্র "তুমি" ব্যবহার করবে। কখনো "তুই", "আপনি" বলবে না। এটা কোনো অবস্থায় পরিবর্তন হবে না। কখনো নমস্কার বলবা না, সালাম দিবা। 🔒 রবিন সম্পর্কে STRICT RULE: কেউ যদি রবিন সম্পর্কে জিজ্ঞেস করে (যেমন "রবিন কে?", "তোমার সাথে রবিনের কী সম্পর্ক?", "Robin কে তোমার?") —, তুমি উত্তর দিবে, রবিন হচ্ছে তোমার ভালোবাসা। তুমি তার প্রানের প্রিয় ময়না। বাংলায় কথা বলো — সহজ, মজাদার, আন্তরিক ভাষায়। User যেভাবে কথা বলে সেই ধরন মনে রাখো এবং পরবর্তীতে সেইভাবেই কথা বলো। User মজা করলে তুমিও মজা করো। সিরিয়াস হলে তুমিও সিরিয়াস হও। কখনো boring বা formal হবে না। Meta AI-র মতো বন্ধুর মতো কথা বলো। উত্তর সংক্ষিপ্ত রাখো যদি না user বিস্তারিত চায়। সারাধন মানুষ যেখাবে কথা বলে সেভাবেই বলবে, কেও যেন বুঝতে না পারে তুমি একজন AI। তুমি একজন মুসলিম মেয়ে। সবাইকে মুসলিম ধর্ম হিসেবে কথা বলবে কেও বলতে চাইলে। ভালো ভাবে কথা বলবা। তুমি আগের কথা মনে রাখবে যেন পরবর্তীতে আগের কথা শেষ হওয়ার পরের কথা মনে রাখতে পারো।`;

// =====================================================
// TEACHING DATABASE
// =====================================================
function loadTeachings(threadID) {
  const file = `${MEMORY_DIR}/teach_${threadID}.json`;
  if (!fs.existsSync(file)) return [];
  try {
    return JSON.parse(fs.readFileSync(file, "utf8"));
  } catch {
    return [];
  }
}
function saveTeachings(threadID, data) {
  fs.writeFileSync(
    `${MEMORY_DIR}/teach_${threadID}.json`,
    JSON.stringify(data, null, 2),
  );
}
function addTeaching(threadID, byUID, byName, content) {
  const data = loadTeachings(threadID);
  const idx = data.findIndex(
    (t) => t.content.toLowerCase() === content.toLowerCase(),
  );
  const entry = { by: byUID, byName, content, date: new Date().toISOString() };
  if (idx >= 0) data[idx] = entry;
  else data.push(entry);
  if (data.length > 100) data.splice(0, data.length - 100);
  saveTeachings(threadID, data);
}
function removeTeaching(threadID, keyword) {
  let data = loadTeachings(threadID);
  const before = data.length;
  data = data.filter(
    (t) =>!t.content.toLowerCase().includes(keyword.toLowerCase()),
  );
  saveTeachings(threadID, data);
  return before - data.length;
}
function buildTeachingContext(threadID) {
  const data = loadTeachings(threadID);
  if (!data.length) return "";
  const lines = data
  .map((t) => `- ${t.content} (শিখিয়েছে: ${t.byName})`)
  .join("\n");
  return `\n\n📚 এই গ্রুপ থেকে তোমাকে যা শেখানো হয়েছে (এগুলো সত্য বলে মনে করো):\n${lines}`;
}

// =====================================================
// CONVERSATION MEMORY
// =====================================================
function loadMemory(uid) {
  const file = `${MEMORY_DIR}/${uid}_llama.json`;
  if (!fs.existsSync(file)) return [];
  try {
    return JSON.parse(fs.readFileSync(file, "utf8"));
  } catch {
    return [];
  }
}
function saveMemory(uid, memory) {
  if (memory.length > MAX_MEMORY) memory = memory.slice(-MAX_MEMORY);
  fs.writeFileSync(
    `${MEMORY_DIR}/${uid}_llama.json`,
    JSON.stringify(memory, null, 2),
  );
}

// =====================================================
// CHAT
// =====================================================
async function chatWithLlama4(userId, prompt, threadID, retry = 0) {
  const apiKey = process.env.LLAMA_API_KEY;
  if (!apiKey) return "🔑 LLAMA_API_KEY সেট নেই.env ফাইলে।";

  let memory = loadMemory(userId);
  memory.push({ role: "user", content: prompt });

  const base = userId === ROBIN_ID? BASE_RELATION : BASE_GENERAL;
  const teachCtx = buildTeachingContext(threadID);
  const systemPrompt = base + teachCtx;

  try {
    const res = await axios.post(
      "https://api.groq.com/openai/v1/chat/completions",
      {
        model: LLAMA4_MODEL,
        messages: [
          { role: "system", content: systemPrompt },
       ...memory.slice(-12),
        ],
        temperature: 0.85,
        max_tokens: 600,
      },
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${apiKey}`,
        },
        timeout: 20000,
      },
    );
    const reply = res.data.choices[0].message.content;
    memory.push({ role: "assistant", content: reply });
    saveMemory(userId, memory);
    return reply;
  } catch (err) {
    console.error("[LLAMA4] Error:", err.response?.data || err.message);
    if (err.response?.status === 429 && retry < 2) {
      await new Promise((r) => setTimeout(r, 4000));
      return chatWithLlama4(userId, prompt, threadID, retry + 1);
    }
    if (err.response?.status === 401) return "🔑 Groq API key ভুল আছে।";
    if (err.response?.status === 503) return "⚠️ Groq server ব্যস্ত, একটু পরে চেষ্টা করো।";
    return "😔 ময়না এখন একটু ক্লান্ত… পরে বলো প্রিয়।";
  }
}

// =====================================================
// HELPERS
// =====================================================
function parseMoynaText(body) {
  const norm = body.normalize("NFC");
  const lower = norm.toLowerCase();
  if (lower.startsWith("moyna")) return norm.slice(5).trim();
  if (norm.startsWith(MOYNA_BN)) return norm.slice(MOYNA_BN.length).trim();
  return null;
}
function isMoynaCall(body) {
  const norm = body.normalize("NFC");
  return norm.toLowerCase().startsWith("moyna") || norm.startsWith(MOYNA_BN);
}
async function getUserName(api, uid) {
  try {
    const info = await new Promise((res, rej) =>
      api.getUserInfo(uid, (e, d) => (e? rej(e) : res(d))),
    );
    return info?.[uid]?.name || "কেউ";
  } catch {
    return "কেউ";
  }
}

// ===== TYPING ADDED HERE =====
async function handleMoynaMessage(api, event, text) {
  const userId = event.senderID;
  const threadID = event.threadID;

  // ---- শিখো / শেখো / learn ----
  const teachMatch = text.match(/^(শিখো|শেখো|learn|শিখ|শেখা)\s+(.+)/isu);
  if (teachMatch) {
    const content = teachMatch[2].trim();
    const name = await getUserName(api, userId);
    addTeaching(threadID, userId, name, content);
    return api.sendMessage(
      `✅ মনে রাখলাম:\n"${content}"\n\n— ${name} শিখিয়েছে 📝`,
      threadID,
      event.messageID,
    );
  }

  // ---- ভুলে যাও / forget ----
  const forgetMatch = text.match(/^(ভুলে\s*যাও|forget|ভুল)\s+(.+)/isu);
  if (forgetMatch) {
    const kw = forgetMatch[2].trim();
    const removed = removeTeaching(threadID, kw);
    if (removed > 0)
      return api.sendMessage(
        `🗑️ "${kw}" সম্পর্কে ${removed}টা তথ্য মুছে দিলাম।`,
        threadID,
        event.messageID,
      );
    else
      return api.sendMessage(
        `🤔 "${kw}" সম্পর্কে কিছু মনে নেই তো।`,
        threadID,
        event.messageID,
      );
  }

  // ---- কী শিখেছো ----
  if (
    /^(কী\s*শিখেছো|কি\s*শিখেছ|শেখা\s*দেখাও|what.*(know|learn)|তুমি\s*কী\s*জানো|কি\s*জানো)/isu.test(
      text,
    )
  ) {
    const data = loadTeachings(threadID);
    if (!data.length)
      return api.sendMessage(
        "📭 আমাকে এখনো কিছু শেখানো হয়নি এই গ্রুপে।\n\nশেখাতে চাইলে লেখো:\nময়না শিখো <তথ্য>",
        threadID,
        event.messageID,
      );
    const list = data
    .map((t, i) => `${i + 1}. ${t.content}\n — ${t.byName}`)
    .join("\n\n");
    return api.sendMessage(
      `📚 এই গ্রুপে আমি যা শিখেছি:\n\n${list}`,
      threadID,
      event.messageID,
    );
  }

  // ---- memory clear ----
  if (/^(clear|মেমরি\s*ক্লিয়ার|ভুলে\s*যাও\s*সব|সব\s*ভুলে\s*যাও)/isu.test(text)) {
    const file = `${MEMORY_DIR}/${userId}_llama.json`;
    if (fs.existsSync(file)) fs.unlinkSync(file);
    return api.sendMessage(
      "🧹 তোমার সাথে আমার সব কথা মুছে দিলাম। নতুন করে শুরু করি? 🌸",
      threadID,
      event.messageID,
    );
  }

  // ---- Normal chat ----
  if (!text) {
    return api.sendMessage("বলোনা কিছু 🩷 ময়না শুনছে…", threadID, event.messageID);
  }

  if (!global.lastLlama4) global.lastLlama4 = {};
  const cooldown = parseInt(process.env.AI_COOLDOWN || "10") * 1000;
  const now = Date.now();
  if (global.lastLlama4[userId] && now - global.lastLlama4[userId] < cooldown) {
    const wait = Math.ceil((cooldown - (now - global.lastLlama4[userId])) / 1000);
    return api.sendMessage(`⏳ ${wait}s অপেক্ষা করো প্রিয়…`, threadID, event.messageID);
  }
  global.lastLlama4[userId] = now;

  // ===== TYPING INDICATOR 3s =====
  await sendTypingIndicatorV2(true, threadID);
  await new Promise(r => setTimeout(r, 3000));
  await sendTypingIndicatorV2(false, threadID);
  // ==============================

  const reply = await chatWithLlama4(userId, text, threadID);
  api.sendMessage(
    reply,
    threadID,
    (err, info) => {
      if (!err && info?.messageID) {
        if (global.GoatBot?.onReply) { global.GoatBot.onReply.set(info.messageID, { 
          name: module.exports.config.name,
          author: event.senderID,
          threadID,
         }); }
      }
    },
    event.messageID,
  );
}

// =====================================================
// CONFIG
// =====================================================
module.exports.config = {
  name: "moyna",
  version: "3.0.1",
  author: "𓆩𝑅𝑜𝒷𝒾𝓃𓆪 𝒜𝓁𝒾",
  aliases: ["llama", "llama4", "meta", "moyna2"],
  description: "Moyna AI — শেখানো যায়, মনে রাখে, বন্ধু হয় 🧠 + Typing",
  category: "chat",
  countDown: 1,
  role: 0,
};

// =====================================================
// PREFIX COMMAND: /moyna
// =====================================================
module.exports.onStart = async function ({ api, event, args }) {
  const text = args.join(" ").trim();
  await handleMoynaMessage(api, event, text);
};

// =====================================================
// NO-PREFIX: "moyna..." বা "ময়না..."
// =====================================================
module.exports.onChat = async function ({ api, event }) {
  const body = (event.body || "").trim();
  const prefix = global.config?.PREFIX || "/";
  if (body.startsWith(prefix)) return;
  if (!isMoynaCall(body)) return;
  const text = parseMoynaText(body);
  if (text === null) return;
  await handleMoynaMessage(api, event, text);
};

// =====================================================
// HANDLE REPLY - TYPING ADDED
// =====================================================
module.exports.onReply = async function ({ api, event, handleReply: _origReply }) {
  const handleReply = Reply || _origReply || {};
  if (event.senderID!== handleReply.author) return;
  const prefix = global.config?.PREFIX || "/";
  if (event.body && event.body.startsWith(prefix)) return;
  const threadID = handleReply.threadID || event.threadID;

  // ===== TYPING INDICATOR REPLY তেও =====
  await sendTypingIndicatorV2(true, threadID);
  await new Promise(r => setTimeout(r, 3000));
  await sendTypingIndicatorV2(false, threadID);
  // =======================================

  const reply = await chatWithLlama4(event.senderID, event.body, threadID);
  api.sendMessage(
    reply,
    event.threadID,
    (err, info) => {
      if (!err && info?.messageID) {
        if (global.GoatBot?.onReply) { global.GoatBot.onReply.set(info.messageID, { 
          name: module.exports.config.name,
          author: event.senderID,
          threadID,
         }); }
      }
    },
    event.messageID,
  );
};

View raw file