adityajat/SaarvamAI_botPublic · Bot Template
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
commands/@.js
javascript · 657 lines
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, "&")77 .replace(/</g, "<")78 .replace(/>/g, ">")79 .replace(/"/g, """)80 .replace(/'/g, "'");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 }