adityajat/SaarvamAI_botPublic · Bot Template

ProfileTelegram

AISarvam AI is a multi‑purpose Telegram bot that lets users ask questions, get explanations, convert text to speech, and translate messages using the Sarvam AI API. It also offers group administration commands such as warn, mute, ban, pin, delete, and announcements, and can clear personal or group AI memory. The bot uses the TeleBotHost TBL framework with Bot, Api, db, and HTTP calls, and provides inline keyboards for language and voice selection.

Ai Assistantaivoicetranslatemoderationadminsarvam
34 commands4 envUpdated 15d agoCreated Sep 14, 2026
Back to folder

commands/@.js

javascript · 657 lines

Raw
1/**#command2name: @3answer: 4keyboard: 5parse_mode: markdown6aliases: 7allow_only_group: false8need_reply: false9is_web: 010#command**/11 12// ==========================================13// SARVAM BOT - SHARED CORE WITH AUTO CALCULATOR14// ==========================================15 16const BOT_OWNER_HANDLE = "@shecallsmecalm";17 18// ==========================================19// BASIC HELPERS20// ==========================================21 22function isGroupChatNow() {23  return !!(chat && (chat.type === "group" || chat.type === "supergroup"));24}25 26function isAnonymousAdminMessageNow() {27  return !!(28    isGroupChatNow() &&29    update &&30    update.message &&31    update.message.sender_chat &&32    update.message.chat &&33    update.message.sender_chat.id === update.message.chat.id34  );35}36 37async function userIsAdmin(userId, chatId) {38  if (!userId || !chatId) return false;39  try {40    const r = await Api.call("getChatMember", {41      chat_id: chatId,42      user_id: userId43    });44    if (!r || !r.ok || !r.result) return false;45    return r.result.status === "creator" || r.result.status === "administrator";46  } catch (e) {47    return false;48  }49}50 51async function callerIsAdmin() {52  if (!isGroupChatNow()) return false;53  if (isAnonymousAdminMessageNow()) return true;54  const uid = update && update.message && update.message.from && update.message.from.id;55  return uid ? await userIsAdmin(uid, chat.id) : false;56}57 58async function targetIsAdmin(userId) {59  if (!isGroupChatNow() || !userId) return false;60  return await userIsAdmin(userId, chat.id);61}62 63// ==========================================64// USER DISPLAY65// ==========================================66 67function userDisplayName(u) {68  if (!u) return "User";69  let name = u.first_name || u.username || "User";70  if (u.last_name) name += " " + u.last_name;71  return name;72}73 74function htmlEscape(text) {75  return String(text || "")76    .replace(/&/g, "&amp;")77    .replace(/</g, "&lt;")78    .replace(/>/g, "&gt;")79    .replace(/"/g, "&quot;")80    .replace(/'/g, "&#39;");81}82 83function mentionHTML(u) {84  if (!u || !u.id) return "User";85  return '<a href="tg://user?id=' + u.id + '">' +86    htmlEscape(userDisplayName(u)) + "</a>";87}88 89// ==========================================90// GROUP MESSAGE CHECK91// ==========================================92 93function groupMessageIsForBot() {94  if (!isGroupChatNow()) return true;95  const m = update && update.message;96  if (!m) return false;97  const text = m.text || m.caption || "";98  const username = bot && bot.username;99  if (!username) return false;100 101  const mentioned = text.toLowerCase().includes("@" + username.toLowerCase());102  const replied = !!(103    m.reply_to_message &&104    m.reply_to_message.from &&105    m.reply_to_message.from.is_bot &&106    m.reply_to_message.from.username &&107    m.reply_to_message.from.username.toLowerCase() === username.toLowerCase()108  );109  return mentioned || replied;110}111 112function removeBotMention(text) {113  const username = bot && bot.username;114  if (!username) return String(text || "").trim();115  const escaped = username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");116  return String(text || "")117    .replace(new RegExp("@" + escaped, "ig"), "")118    .trim();119}120 121// ==========================================122// TARGET / DURATION HELPERS123// ==========================================124 125function getTargetAndRest() {126  const reply = update && update.message && update.message.reply_to_message;127  const p = String(params || "").trim();128 129  if (reply && reply.from && reply.from.id) {130    return { id: reply.from.id, user: reply.from, message: reply, rest: p };131  }132 133  if (p) {134    const parts = p.split(/\s+/);135    if (/^\d+$/.test(parts[0])) {136      return {137        id: Number(parts[0]),138        user: null,139        message: null,140        rest: parts.slice(1).join(" ").trim()141      };142    }143  }144  return null;145}146 147function parseDurationSeconds(value) {148  const s = String(value || "").trim().toLowerCase();149  if (!s) return 3600;150  const m = s.match(/^(\d+)\s*(s|m|h|d)$/);151  if (!m) return 3600;152  const n = Number(m[1]);153  if (m[2] === "s") return n;154  if (m[2] === "m") return n * 60;155  if (m[2] === "h") return n * 3600;156  if (m[2] === "d") return n * 86400;157  return 3600;158}159 160// ==========================================161// SEND LONG TEXT162// ==========================================163 164async function sendLongText(text) {165  let s = String(text || "");166  while (s.length > 4000) {167    let cut = s.lastIndexOf("\n", 4000);168    if (cut < 1000) cut = 4000;169    await Api.sendMessage({ text: s.slice(0, cut) });170    s = s.slice(cut).trim();171  }172  if (s) await Api.sendMessage({ text: s });173}174 175// ==========================================176// PARTICIPANT MEMORY FOR /tagall177// ==========================================178 179async function rememberParticipant() {180  if (!isGroupChatNow()) return;181  const m = update && update.message;182  if (!m || !m.from || !m.from.id || m.from.is_bot) return;183  if (isAnonymousAdminMessageNow()) return;184 185  const key = "participants:" + String(chat.id);186  let list = await db.bot.get(key, []);187  if (!Array.isArray(list)) list = [];188 189  const existing = list.find(item => item && item.id === m.from.id);190  if (existing) {191    existing.first_name = m.from.first_name || existing.first_name;192    existing.last_name = m.from.last_name || existing.last_name;193    existing.username = m.from.username || existing.username;194  } else {195    list.push({196      id: m.from.id,197      first_name: m.from.first_name || "",198      last_name: m.from.last_name || "",199      username: m.from.username || ""200    });201  }202 203  if (list.length > 1000) list = list.slice(-1000);204  await db.bot.set(key, list);205}206 207// ==========================================208// AI STYLE PREFERENCES209// ==========================================210 211async function updateUserStyle(text) {212  if (!user || !user.id) return {};213  let profile = await db.user.get("ai_profile", {});214  if (!profile || typeof profile !== "object") profile = {};215  const t = String(text || "").toLowerCase();216 217  if (t.includes("keep it short") || t.includes("be brief") ||218      t.includes("be concise") || t.includes("short answers")) {219    profile.reply_style = "concise";220  }221  if (t.includes("explain in detail") || t.includes("give detailed") ||222      t.includes("more detail") || t.includes("detailed answers")) {223    profile.reply_style = "detailed";224  }225  if (t.includes("reply in hindi")) profile.language = "Hindi";226  if (t.includes("reply in hinglish")) profile.language = "Hinglish";227 228  await db.user.set("ai_profile", profile);229  return profile;230}231 232// ==========================================233// ARCHIVE / HISTORY234// ==========================================235 236async function archiveInteraction(entry) {237  const group = isGroupChatNow();238  const key = group ? "ai_archive:" + String(chat.id) : "ai_archive";239  let archive = group240    ? await db.bot.get(key, [])241    : await db.user.get(key, []);242 243  if (!Array.isArray(archive)) archive = [];244  archive.push(entry);245  if (archive.length > 5000) archive = archive.slice(-5000);246 247  if (group) await db.bot.set(key, archive);248  else await db.user.set(key, archive);249}250 251async function loadAIHistory() {252  if (isGroupChatNow()) {253    const h = await db.bot.get("ai_history:" + String(chat.id), []);254    return Array.isArray(h) ? h : [];255  }256  const h = await db.user.get("ai_history", []);257  return Array.isArray(h) ? h : [];258}259 260async function saveAIHistory(history) {261  if (isGroupChatNow()) {262    await db.bot.set("ai_history:" + String(chat.id), history);263  } else {264    await db.user.set("ai_history", history);265  }266}267 268// ==========================================269// SAFE MATH CALCULATOR270// Supports + - * / ^, parentheses, and postfix percent.271// Example: 15% * 2400 = 360272// ==========================================273 274function calculateExpression(expression) {275  if (typeof expression !== "string" || expression.length > 200) {276    throw new Error("Invalid expression.");277  }278 279  const source = expression.replace(/\s+/g, "");280  const tokens = source.match(/(?:\d+\.?\d*|\.\d+)|[()+\-*/^%]/g);281 282  if (!tokens || tokens.join("") !== source) {283    throw new Error("Unsupported math expression.");284  }285 286  let pos = 0;287  function peek() { return tokens[pos]; }288 289  function primary() {290    const token = tokens[pos++];291    if (token === "(") {292      const value = addSub();293      if (tokens[pos++] !== ")") throw new Error("Missing closing parenthesis.");294      return value;295    }296    if (/^(?:\d+\.?\d*|\.\d+)$/.test(token || "")) return Number(token);297    throw new Error("Expected a number.");298  }299 300  function power() {301    let value = primary();302    if (peek() === "%") {303      pos++;304      value /= 100;305    }306    if (peek() === "^") {307      pos++;308      value = Math.pow(value, unary());309    }310    return value;311  }312 313  function unary() {314    if (peek() === "+") { pos++; return unary(); }315    if (peek() === "-") { pos++; return -unary(); }316    return power();317  }318 319  function mulDiv() {320    let value = unary();321    while (peek() === "*" || peek() === "/") {322      const op = tokens[pos++];323      const right = unary();324      if (op === "/" && right === 0) throw new Error("Cannot divide by zero.");325      value = op === "*" ? value * right : value / right;326    }327    return value;328  }329 330  function addSub() {331    let value = mulDiv();332    while (peek() === "+" || peek() === "-") {333      const op = tokens[pos++];334      const right = mulDiv();335      value = op === "+" ? value + right : value - right;336    }337    return value;338  }339 340  const result = addSub();341  if (pos !== tokens.length || !Number.isFinite(result)) {342    throw new Error("Invalid or out-of-range result.");343  }344  return Number(result.toPrecision(12));345}346 347function runCalculatorTool(args) {348  if (!args || typeof args.expression !== "string") {349    throw new Error("A math expression is required.");350  }351  return {352    expression: args.expression,353    result: calculateExpression(args.expression)354  };355}356 357// ==========================================358// MAIN AI ENGINE WITH AUTOMATIC CALCULATOR359// ==========================================360 361async function runAIConversation(incomingText) {362  const apiKey = process.env.SARVAM_API_KEY;363  if (!apiKey) {364    await Api.sendMessage({ text: "❌ SARVAM_API_KEY was not found." });365    return;366  }367 368  const clean = String(incomingText || "").trim();369  if (!clean) return;370 371  let history = await loadAIHistory();372  if (!Array.isArray(history)) history = [];373 374  const profile = await updateUserStyle(clean);375  const group = isGroupChatNow();376  let speaker = "User";377 378  if (group) {379    speaker = update.message && update.message.from380      ? userDisplayName(update.message.from)381      : "Anonymous admin";382  }383 384  let systemPrompt = `385You are Sarvam, a helpful, accurate, natural conversational AI assistant.386Answer the latest user message. Use conversation history only when relevant.387Resolve short follow-ups such as "why?", "explain that", "it", and "this" from nearby context.388Reply in the same language as the latest user message: English, Hindi, or Hinglish as appropriate.389Use clear grammar, answer directly, and do not invent facts.390For arithmetic, percentages, powers, and numerical calculations, use the calculator tool whenever needed. Do not guess calculation results.391If the user asks who owns you, answer: "${BOT_OWNER_HANDLE} is my owner, the owner of every creation."392Do not claim to be human. You are an AI assistant.393`;394 395  if (profile && profile.reply_style) {396    systemPrompt += "\nThe user prefers " + profile.reply_style + " replies.";397  }398  if (profile && profile.language) {399    systemPrompt += "\nThe user prefers " + profile.language + ".";400  }401 402  const messages = [{ role: "system", content: systemPrompt.trim() }];403  const recent = history.slice(-80);404 405  for (const item of recent) {406    if (item && (item.role === "user" || item.role === "assistant") && item.content) {407      messages.push({ role: item.role, content: String(item.content) });408    }409  }410 411  const modelText = group ? "[" + speaker + "]: " + clean : clean;412  messages.push({ role: "user", content: modelText });413 414  const calculatorTools = [{415    type: "function",416    function: {417      name: "calculator",418      description: "Calculate arithmetic, percentages, powers, and numerical word problems accurately. Use this tool instead of guessing. For example, 15 percent of 2400 is 15% * 2400.",419      parameters: {420        type: "object",421        properties: {422          expression: {423            type: "string",424            description: "A math expression using numbers, +, -, *, /, ^, %, and parentheses."425          }426        },427        required: ["expression"],428        additionalProperties: false429      }430    }431  }];432 433  let answer = "";434  let finished = false;435 436  for (let step = 0; step < 4; step++) {437    let res;438    try {439      res = await HTTP.post("https://api.sarvam.ai/v1/chat/completions", {440        headers: {441          "api-subscription-key": apiKey,442          "Content-Type": "application/json"443        },444        body: {445          model: "sarvam-105b",446          messages: messages,447          tools: calculatorTools,448          tool_choice: "auto",449          max_tokens: 10000,450          temperature: 0.3451        },452        responseType: "json",453        timeout: 70000454      });455    } catch (error) {456      console.log("Sarvam request failed:", String(error));457      await Api.sendMessage({458        text: "⚠️ Sarvam could not answer right now. Please try again."459      });460      return;461    }462 463    if (!res || !res.ok) {464      const detail = res && res.data && res.data.error && res.data.error.message465        ? String(res.data.error.message)466        : "";467      await Api.sendMessage({468        text: "⚠️ Sarvam could not answer right now." + (detail ? "\n\n" + detail : "")469      });470      return;471    }472 473    const message = res.data && res.data.choices &&474      res.data.choices[0] && res.data.choices[0].message;475 476    if (!message) {477      await Api.sendMessage({ text: "⚠️ Sarvam returned an unexpected response." });478      return;479    }480 481    const calls = message.tool_calls || [];482    if (calls.length === 0) {483      answer = String(message.content || "").trim();484      finished = true;485      break;486    }487 488    messages.push(message);489 490    for (const call of calls) {491      let result;492      try {493        if (!call.function || call.function.name !== "calculator") {494          throw new Error("Unknown tool.");495        }496        const args = JSON.parse(call.function.arguments || "{}");497        result = runCalculatorTool(args);498      } catch (error) {499        result = { error: String(error.message || "Calculation failed.") };500      }501 502      messages.push({503        role: "tool",504        tool_call_id: call.id,505        content: JSON.stringify(result)506      });507    }508  }509 510  if (!finished || !answer) {511    await Api.sendMessage({512      text: "⚠️ I couldn't complete the response. Please try again."513    });514    return;515  }516 517  answer = answer.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();518  if (!answer) return;519 520  history.push({ role: "user", content: modelText });521  history.push({ role: "assistant", content: answer });522  if (history.length > 5000) history = history.slice(-5000);523 524  await saveAIHistory(history);525  await archiveInteraction({526    timestamp: Date.now(),527    type: "text",528    user_id: user && user.id ? user.id : null,529    user_name: speaker,530    user_message: clean,531    assistant_message: answer532  });533 534  await sendLongText(answer);535}536 537// ==========================================538// AUTO MODERATION OF ABUSE TOWARD ADMINS539// ==========================================540 541async function autoModerateText(text) {542  if (!isGroupChatNow()) return false;543  if (isAnonymousAdminMessageNow()) return false;544 545  const enabled = await db.bot.get("automod:" + String(chat.id), true);546  if (!enabled) return false;547 548  const m = update && update.message;549  if (!m || !m.reply_to_message) return false;550  if (m.from && m.from.is_bot) return false;551 552  const replied = m.reply_to_message;553 554  if (m.from && await targetIsAdmin(m.from.id)) return false;555 556  let replyingToAdmin = false;557  if (replied.sender_chat && replied.sender_chat.id === chat.id) {558    replyingToAdmin = true;559  } else if (replied.from && replied.from.id) {560    replyingToAdmin = await userIsAdmin(replied.from.id, chat.id);561  }562  if (!replyingToAdmin) return false;563 564  const key = process.env.SARVAM_API_KEY;565  if (!key) return false;566 567  const safeText = String(text || "").slice(0, 1000);568  const prompt = `569You are a conservative Telegram group moderation classifier.570The current user message is:571${safeText}572The user is replying to a group administrator.573Return ONLY valid JSON in this exact format:574{"action":"none","severity":"none","reason":"short reason"}575Allowed values:576- action: "none" or "warn"577- severity: "none", "low", "medium", or "high"578Use "warn" only for clearly abusive, harassing, threatening, or clearly insulting language toward the admin.579Do NOT treat disagreement, criticism, jokes, normal frustration, or a different opinion as abuse.580`;581 582  let r;583  try {584    r = await HTTP.post("https://api.sarvam.ai/v1/chat/completions", {585      headers: {586        "api-subscription-key": key,587        "Content-Type": "application/json"588      },589      body: {590        model: "sarvam-105b",591        messages: [{ role: "user", content: prompt }],592        max_tokens: 180,593        temperature: 0594      },595      responseType: "json",596      timeout: 10000597    });598  } catch (e) {599    return false;600  }601 602  if (!r || !r.ok) return false;603 604  let raw = "";605  try {606    raw = r.data.choices[0].message.content || "";607  } catch (e) {608    return false;609  }610 611  let parsed = null;612  try {613    const match = String(raw).match(/\{[\s\S]*\}/);614    if (match) parsed = JSON.parse(match[0]);615  } catch (e) {616    parsed = null;617  }618 619  if (!parsed || parsed.action !== "warn") return false;620  if (!parsed.severity || parsed.severity === "none") return false;621  if (!m.from || !m.from.id) return false;622 623  return await applyWarning(624    m.from.id,625    m.from,626    parsed.reason || "Clearly inappropriate behaviour toward an admin."627  );628}629 630// ==========================================631// WARNING HANDLER632// NOTE: This sends a warning only. It does not mute or ban.633// ==========================================634 635async function applyWarning(targetId, targetUser, reason) {636  if (!isGroupChatNow() || !targetId) return false;637 638  if (await targetIsAdmin(targetId)) {639    await Api.sendMessage({640      text: "⚠️ I can't moderate a group administrator."641    });642    return false;643  }644 645  const name = targetUser646    ? htmlEscape(userDisplayName(targetUser))647    : "User " + String(targetId);648 649  await Api.sendMessage({650    text: "⚠️ <b>Warning</b>\n\n" +651      "User: " + name + "\n" +652      "Reason: " + htmlEscape(reason || "Inappropriate behaviour."),653    parse_mode: "HTML"654  });655 656  return true;657        }