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/_handle_callback_query.js
javascript · 929 lines
1/**#command2name: /handle_callback_query3answer: 4keyboard: 5parse_mode: markdown6aliases: 7allow_only_group: false8need_reply: false9is_web: 010#command**/11 12// ========================================13// VOICE CALLBACK HANDLER14// ========================================15 16if (!update.callback_query) {17 return;18}19 20let cq = update.callback_query;21let data = cq.data || "";22 23if (!cq.message) {24 return;25}26 27let chatId = cq.message.chat.id;28let messageId = cq.message.message_id;29 30// Remove Telegram's loading spinner31await Api.answerCallbackQuery({32 callback_query_id: cq.id33});34 35 36// ========================================37// LANGUAGE SELECTED38// ========================================39 40if (data.startsWith("vl_")) {41 42 let language = data.substring(3);43 44 let request = await db.user.get("voice_request");45 46 if (!request || !request.text) {47 await Api.editMessageText({48 chat_id: chatId,49 message_id: messageId,50 text: "❌ Voice request expired. Please use /voice again."51 });52 return;53 }54 55 // Save selected output language56 await db.user.set("voice_language", language);57 58 await Api.editMessageText({59 chat_id: chatId,60 message_id: messageId,61 text: "🎙️ Choose a voice:"62 });63 64 // 37 Bulbul v3 voices65 let keyboard = [66 [67 { text: "👨 Shubh", callback_data: "vs_shubh" },68 { text: "👨 Aditya", callback_data: "vs_aditya" }69 ],70 [71 { text: "👨 Rahul", callback_data: "vs_rahul" },72 { text: "👨 Rohan", callback_data: "vs_rohan" }73 ],74 [75 { text: "👨 Amit", callback_data: "vs_amit" },76 { text: "👨 Dev", callback_data: "vs_dev" }77 ],78 [79 { text: "👨 Ratan", callback_data: "vs_ratan" },80 { text: "👨 Varun", callback_data: "vs_varun" }81 ],82 [83 { text: "👨 Manan", callback_data: "vs_manan" },84 { text: "👨 Sumit", callback_data: "vs_sumit" }85 ],86 [87 { text: "👨 Kabir", callback_data: "vs_kabir" },88 { text: "👨 Aayan", callback_data: "vs_aayan" }89 ],90 [91 { text: "👨 Ashutosh", callback_data: "vs_ashutosh" },92 { text: "👨 Advait", callback_data: "vs_advait" }93 ],94 [95 { text: "👨 Anand", callback_data: "vs_anand" },96 { text: "👨 Tarun", callback_data: "vs_tarun" }97 ],98 [99 { text: "👨 Sunny", callback_data: "vs_sunny" },100 { text: "👨 Mani", callback_data: "vs_mani" }101 ],102 [103 { text: "👨 Gokul", callback_data: "vs_gokul" },104 { text: "👨 Vijay", callback_data: "vs_vijay" }105 ],106 [107 { text: "👨 Mohit", callback_data: "vs_mohit" },108 { text: "👨 Rehan", callback_data: "vs_rehan" }109 ],110 [111 { text: "👨 Soham", callback_data: "vs_soham" },112 { text: "👩 Ritu", callback_data: "vs_ritu" }113 ],114 [115 { text: "👩 Priya", callback_data: "vs_priya" },116 { text: "👩 Neha", callback_data: "vs_neha" }117 ],118 [119 { text: "👩 Pooja", callback_data: "vs_pooja" },120 { text: "👩 Simran", callback_data: "vs_simran" }121 ],122 [123 { text: "👩 Kavya", callback_data: "vs_kavya" },124 { text: "👩 Ishita", callback_data: "vs_ishita" }125 ],126 [127 { text: "👩 Shreya", callback_data: "vs_shreya" },128 { text: "👩 Roopa", callback_data: "vs_roopa" }129 ],130 [131 { text: "👩 Tanya", callback_data: "vs_tanya" },132 { text: "👩 Shruti", callback_data: "vs_shruti" }133 ],134 [135 { text: "👩 Suhani", callback_data: "vs_suhani" },136 { text: "👩 Kavitha", callback_data: "vs_kavitha" }137 ],138 [139 { text: "👩 Rupali", callback_data: "vs_rupali" }140 ],141 [142 { text: "🎧 Voice Demos", callback_data: "vd" }143 ],144 [145 { text: "🌐 Change Language", callback_data: "backlang" }146 ]147 ];148 149 await Api.editMessageReplyMarkup({150 chat_id: chatId,151 message_id: messageId,152 reply_markup: {153 inline_keyboard: keyboard154 }155 });156 157 return;158}159 160 161// ========================================162// CHANGE LANGUAGE163// ========================================164 165if (data === "backlang") {166 167 await Api.editMessageText({168 chat_id: chatId,169 message_id: messageId,170 text: "🌐 Choose the language:"171 });172 173 await Api.editMessageReplyMarkup({174 chat_id: chatId,175 message_id: messageId,176 reply_markup: {177 inline_keyboard: [178 [179 { text: "🇮🇳 Hindi", callback_data: "vl_hi-IN" },180 { text: "🇬🇧 English", callback_data: "vl_en-IN" }181 ],182 [183 { text: "🇧🇩 Bengali", callback_data: "vl_bn-IN" },184 { text: "🇮🇳 Tamil", callback_data: "vl_ta-IN" }185 ],186 [187 { text: "🇮🇳 Telugu", callback_data: "vl_te-IN" },188 { text: "🇮🇳 Gujarati", callback_data: "vl_gu-IN" }189 ],190 [191 { text: "🇮🇳 Kannada", callback_data: "vl_kn-IN" },192 { text: "🇮🇳 Malayalam", callback_data: "vl_ml-IN" }193 ],194 [195 { text: "🇮🇳 Marathi", callback_data: "vl_mr-IN" },196 { text: "🇮🇳 Punjabi", callback_data: "vl_pa-IN" }197 ],198 [199 { text: "🇮🇳 Odia", callback_data: "vl_od-IN" }200 ]201 ]202 }203 });204 205 return;206}207 208 209// ========================================210// VOICE SELECTED211// ========================================212 213if (data.startsWith("vs_")) { 214 215 216const voiceCreditBalance = await db.user.get("voice_credits", 0);217 218if (voiceCreditBalance < 1) {219 await Api.sendMessage({220 text:221 "⚠️ You have no voice credits left.\n\n" +222 "Use /buyvoice to purchase 10 credits, " +223 "or /credits to check your balance."224 });225 return;226}227 228const debitResult = await db.user.set(229 "voice_credits",230 voiceCreditBalance - 1231);232 233if (!debitResult || debitResult.ok === false) {234 await Api.sendMessage({235 text: "Could not reserve a voice credit. Please retry."236 });237 return;238}239 240 241 let voice = data.substring(3);242 243 let request = await db.user.get("voice_request");244 let language = await db.user.get("voice_language");245 246 if (!request || !request.text || !language) {247 await Api.sendMessage({248 chat_id: chatId,249 text: "❌ Voice request expired. Please use /voice again."250 });251 return;252 }253 254 // Sarvam Mayura has a 10000-character limit255 if (request.text.length > 10000) {256 await Api.editMessageText({257 chat_id: chatId,258 message_id: messageId,259 text: "❌ The text is too long.\n\nPlease keep the /voice text within 1000 characters."260 });261 return;262 }263 264 await Api.editMessageText({265 chat_id: chatId,266 message_id: messageId,267 text: "⏳ Processing...\n\n🌐 Translating text..."268 });269 270 271 // ========================================272 // TRANSLATION273 // Automatically detects source language274 // ========================================275 276 let translation = await HTTP.post(277 "https://api.sarvam.ai/translate",278 {279 headers: {280 "api-subscription-key": process.env.SARVAM_API_KEY,281 "Content-Type": "application/json"282 },283 body: {284 input: request.text,285 source_language_code: "auto",286 target_language_code: language,287 model: "mayura:v1",288 mode: "modern-colloquial"289 },290 responseType: "json",291 timeout: 30000292 }293 );294 295 if (296 !translation.ok ||297 !translation.data ||298 !translation.data.translated_text299 ) {300 301 await Api.editMessageText({302 chat_id: chatId,303 message_id: messageId,304 text:305 "❌ Translation failed.\n\n" +306 JSON.stringify(translation.data || translation)307 });308 309 return;310 }311 312 let translatedText = translation.data.translated_text;313 314 315 // ========================================316 // TEXT TO SPEECH317 // ========================================318 319 await Api.editMessageText({320 chat_id: chatId,321 message_id: messageId,322 text:323 "⏳ Generating voice...\n\n" +324 "🌐 Language: " + language + "\n" +325 "🎙️ Voice: " + voice326 });327 328 let tts = await HTTP.post(329 "https://api.sarvam.ai/text-to-speech",330 {331 headers: {332 "api-subscription-key": process.env.SARVAM_API_KEY,333 "Content-Type": "application/json"334 },335 body: {336 text: translatedText,337 language_code: language,338 speaker: voice,339 model: "bulbul:v3",340 output_audio_codec: "mp3"341 },342 responseType: "json",343 timeout: 30000344 }345 );346 347 if (348 !tts.ok ||349 !tts.data ||350 !tts.data.audios ||351 !tts.data.audios[0]352 ) {353 354 await Api.editMessageText({355 chat_id: chatId,356 message_id: messageId,357 text:358 "❌ Sarvam TTS error:\n\n" +359 JSON.stringify(tts.data || tts)360 });361 362 return;363 }364 365 let audioBase64 = tts.data.audios[0];366 367 let audioBuffer = Buffer.from(audioBase64, "base64");368 369 370 // ========================================371 // TELEGRAM MP3 UPLOAD372 // ========================================373 374 let boundary = "----TBLVoice" + Date.now();375 376 let head =377 "--" + boundary + "\r\n" +378 'Content-Disposition: form-data; name="chat_id"\r\n\r\n' +379 chatId + "\r\n" +380 381 "--" + boundary + "\r\n" +382 'Content-Disposition: form-data; name="audio"; filename="voice.mp3"\r\n' +383 "Content-Type: audio/mpeg\r\n\r\n";384 385 let tail =386 "\r\n--" + boundary + "--\r\n";387 388 let body = Buffer.concat([389 Buffer.from(head),390 audioBuffer,391 Buffer.from(tail)392 ]);393 394 let telegram = await HTTP.post(395 "https://api.telegram.org/bot" +396 process.env.BOT_TOKEN +397 "/sendAudio",398 {399 headers: {400 "Content-Type":401 "multipart/form-data; boundary=" + boundary402 },403 body: body,404 timeout: 20000,405 responseType: "json"406 }407 );408 409 410 // ========================================411 // TELEGRAM UPLOAD ERROR412 // ========================================413 414 if (415 !telegram.ok ||416 !telegram.data ||417 !telegram.data.ok418 ) {419 420 await Api.editMessageText({421 chat_id: chatId,422 message_id: messageId,423 text:424 "❌ Telegram could not upload the audio.\n\n" +425 JSON.stringify(telegram.data || telegram)426 });427 428 return;429 }430 431 432 // ========================================433 // SUCCESS434 // ========================================435 436 await Api.deleteMessage({437 chat_id: chatId,438 message_id: messageId439 });440 441 return;442}443 444 445// ========================================446// VOICE DEMOS MENU447// ========================================448 449if (data === "vd") {450 451 let language = await db.user.get("voice_language");452 453 if (!language) {454 await Api.editMessageText({455 chat_id: chatId,456 message_id: messageId,457 text: "❌ Choose a language first."458 });459 return;460 }461 462 await Api.editMessageText({463 chat_id: chatId,464 message_id: messageId,465 text: "🎧 Choose a voice for the demo:"466 });467 468 await Api.editMessageReplyMarkup({469 chat_id: chatId,470 message_id: messageId,471 reply_markup: {472 inline_keyboard: [473 [474 { text: "Shubh", callback_data: "demo_shubh" },475 { text: "Aditya", callback_data: "demo_aditya" }476 ],477 [478 { text: "Ratan", callback_data: "demo_ratan" },479 { text: "Priya", callback_data: "demo_priya" }480 ],481 [482 { text: "Ishita", callback_data: "demo_ishita" },483 { text: "Suhani", callback_data: "demo_suhani" }484 ],485 [486 { text: "Ritu", callback_data: "demo_ritu" },487 { text: "Rupali", callback_data: "demo_rupali" }488 ],489 [490 { text: "⬅️ Back", callback_data: "backvoices" }491 ]492 ]493 }494 });495 496 return;497}498 499 500// ========================================501// BACK TO VOICE LIST502// ========================================503 504if (data === "backvoices") {505 506 await Api.editMessageText({507 chat_id: chatId,508 message_id: messageId,509 text: "🎙️ Choose a voice:"510 });511 512 await Api.editMessageReplyMarkup({513 chat_id: chatId,514 message_id: messageId,515 reply_markup: {516 inline_keyboard: [517 [518 { text: "🎧 Voice Demos", callback_data: "vd" }519 ],520 [521 { text: "🌐 Change Language", callback_data: "backlang" }522 ]523 ]524 }525 });526 527 return;528}529 530 531// ========================================532// DEMO SELECTED533// ========================================534 535if (data.startsWith("demo_")) {536 537 let voice = data.substring(5);538 let language = await db.user.get("voice_language");539 540 if (!language) {541 return;542 }543 544 await Api.editMessageText({545 chat_id: chatId,546 message_id: messageId,547 text: "⏳ Generating " + voice + " demo..."548 });549 550 let demoText = "Hello! This is a voice demonstration.";551 552 // Translate demo text into selected language553 let demoTranslation = await HTTP.post(554 "https://api.sarvam.ai/translate",555 {556 headers: {557 "api-subscription-key": process.env.SARVAM_API_KEY,558 "Content-Type": "application/json"559 },560 body: {561 input: demoText,562 source_language_code: "en-IN",563 target_language_code: language,564 model: "mayura:v1",565 mode: "modern-colloquial"566 },567 responseType: "json",568 timeout: 30000569 }570 );571 572 if (573 !demoTranslation.ok ||574 !demoTranslation.data ||575 !demoTranslation.data.translated_text576 ) {577 578 await Api.editMessageText({579 chat_id: chatId,580 message_id: messageId,581 text: "❌ Demo translation failed."582 });583 584 return;585 }586 587 let translatedDemo =588 demoTranslation.data.translated_text;589 590 591 // Generate MP3 demo592 let tts = await HTTP.post(593 "https://api.sarvam.ai/text-to-speech",594 {595 headers: {596 "api-subscription-key": process.env.SARVAM_API_KEY,597 "Content-Type": "application/json"598 },599 body: {600 text: translatedDemo,601 language_code: language,602 speaker: voice,603 model: "bulbul:v3",604 output_audio_codec: "mp3"605 },606 responseType: "json",607 timeout: 30000608 }609 );610 611 if (612 !tts.ok ||613 !tts.data ||614 !tts.data.audios ||615 !tts.data.audios[0]616 ) {617 618 await Api.editMessageText({619 chat_id: chatId,620 message_id: messageId,621 text: "❌ Demo generation failed."622 });623 624 return;625 }626 627 let audioBuffer =628 Buffer.from(tts.data.audios[0], "base64");629 630 631 // ========================================632 // DEMO MP3 UPLOAD633 // ========================================634 635 let boundary = "----TBLDemo" + Date.now();636 637 let head =638 "--" + boundary + "\r\n" +639 'Content-Disposition: form-data; name="chat_id"\r\n\r\n' +640 chatId + "\r\n" +641 642 "--" + boundary + "\r\n" +643 'Content-Disposition: form-data; name="audio"; filename="demo.mp3"\r\n' +644 "Content-Type: audio/mpeg\r\n\r\n";645 646 let tail =647 "\r\n--" + boundary + "--\r\n";648 649 let body = Buffer.concat([650 Buffer.from(head),651 audioBuffer,652 Buffer.from(tail)653 ]);654 655 let telegram = await HTTP.post(656 "https://api.telegram.org/bot" +657 process.env.BOT_TOKEN +658 "/sendAudio",659 {660 headers: {661 "Content-Type":662 "multipart/form-data; boundary=" + boundary663 },664 body: body,665 timeout: 20000,666 responseType: "json"667 }668 );669 670 if (671 !telegram.ok ||672 !telegram.data ||673 !telegram.data.ok674 ) {675 676 await Api.editMessageText({677 chat_id: chatId,678 message_id: messageId,679 text: "❌ Demo upload failed."680 });681 682 return;683 }684 685 await Api.editMessageText({686 chat_id: chatId,687 message_id: messageId,688 text: "🎧 Demo sent!"689 });690 691 return;692}693 694 695/* ========================================696 TEXT TRANSLATION CALLBACK697======================================== */698 699if (data.startsWith("tr_")) {700 let targetLanguage = data.substring(3);701 let inputText = await db.user.get("translate_request");702 703 if (!inputText || typeof inputText !== "string") {704 await Api.sendMessage({705 chat_id: chatId,706 text: "❌ Translation request expired. Please use /translate again."707 });708 return;709 }710 711 if (!process.env.SARVAM_API_KEY) {712 await Api.sendMessage({713 chat_id: chatId,714 text: "❌ Sarvam API key is missing from the environment."715 });716 return;717 }718 719 await Api.answerCallbackQuery({720 callback_query_id: cq.id,721 text: "Translating..."722 });723 724 try {725 await Api.editMessageText({726 chat_id: chatId,727 message_id: messageId,728 text: "⏳ Translating your text..."729 });730 731 // Detect the source language732 let detected = await HTTP.post(733 "https://api.sarvam.ai/text-lid",734 {735 headers: {736 "api-subscription-key": process.env.SARVAM_API_KEY,737 "Content-Type": "application/json"738 },739 body: {740 input: inputText.substring(0, 1000)741 },742 responseType: "json",743 timeout: 30000744 }745 );746 747 let sourceLanguage =748 detected.data &&749 (detected.data.language_code ||750 detected.data.language);751 752 if (!detected.ok || !sourceLanguage) {753 throw new Error(754 "Language detection failed: " +755 JSON.stringify(detected.data || detected)756 );757 }758 759 // Translate the original text760 let translated = await HTTP.post(761 "https://api.sarvam.ai/translate",762 {763 headers: {764 "api-subscription-key": process.env.SARVAM_API_KEY,765 "Content-Type": "application/json"766 },767 768 769body: {770 input: inputText,771 source_language_code: sourceLanguage,772 target_language_code: targetLanguage,773 model: "sarvam-translate:v1",774 mode: "formal"775},776responseType: "json",777timeout: 30000778}779);780 781 782 if (783 !translated.ok ||784 !translated.data ||785 !translated.data.translated_text786 ) {787 throw new Error(788 "Translation API error: " +789 JSON.stringify(translated.data || translated)790 );791 }792 793 await Api.sendMessage({794 chat_id: chatId,795 text: "🌐 Translation (" + targetLanguage + "):\n\n" +796 translated.data.translated_text797 });798 799 await Api.editMessageText({800 chat_id: chatId,801 message_id: messageId,802 text: "✅ Translation completed."803 });804 805 } catch (error) {806 await Api.editMessageText({807 chat_id: chatId,808 message_id: messageId,809 text: "❌ Translation failed.\n\n" +810 String(error.message || error).substring(0, 2500)811 });812 }813 814 return;815}816 817 818let translatedText = translated.data.translated_text;819 820 821const languageNames = {822 "as-IN": "Assamese",823 "bn-IN": "Bengali",824 "brx-IN": "Bodo",825 "doi-IN": "Dogri",826 "en-IN": "English",827 "gu-IN": "Gujarati",828 "hi-IN": "Hindi",829 "kn-IN": "Kannada",830 "ks-IN": "Kashmiri",831 "kok-IN": "Konkani",832 "mai-IN": "Maithili",833 "ml-IN": "Malayalam",834 "mni-IN": "Manipuri",835 "mr-IN": "Marathi",836 "ne-IN": "Nepali",837 "od-IN": "Odia",838 "pa-IN": "Punjabi",839 "sa-IN": "Sanskrit",840 "sat-IN": "Santali",841 "sd-IN": "Sindhi",842 "ta-IN": "Tamil",843 "te-IN": "Telugu",844 "ur-IN": "Urdu"845};846 847const targetName = languageNames[targetLanguage];848 849if (!targetName) {850 throw new Error("Unsupported target language: " + targetLanguage);851}852 853// Use 105B for these languages; use the dedicated854// translation API for other languages.855const chatLanguages = [856 "bn-IN", "en-IN", "gu-IN", "hi-IN",857 "kn-IN", "ml-IN", "mr-IN", "od-IN",858 "pa-IN", "ta-IN", "te-IN"859];860 861if (chatLanguages.includes(targetLanguage)) {862 const result = await HTTP.post(863 "https://api.sarvam.ai/v1/chat/completions",864 {865 headers: {866 "api-subscription-key": process.env.SARVAM_API_KEY,867 "Content-Type": "application/json"868 },869 body: {870 model: "sarvam-105b",871 messages: [872 {873 role: "system",874 content:875 "Translate the user's entire message into " +876 targetName +877 ". Preserve its meaning, names, numbers, tone, and formatting. " +878 "Do not summarize, omit content, or add commentary. " +879 "Return only the translation."880 },881 {882 role: "user",883 content: inputText884 }885 ],886 temperature: 0.2,887 max_tokens: 4096888 },889 responseType: "json",890 timeout: 60000891 }892 );893 894 translatedText =895 result.data?.choices?.[0]?.message?.content;896 897} else {898 const result = await HTTP.post(899 "https://api.sarvam.ai/translate",900 {901 headers: {902 "api-subscription-key": process.env.SARVAM_API_KEY,903 "Content-Type": "application/json"904 },905 body: {906 input: inputText,907 source_language_code: sourceLanguage,908 target_language_code: targetLanguage,909 model: "sarvam-translate:v1",910 mode: "formal"911 },912 responseType: "json",913 timeout: 30000914 }915 );916 917 translatedText = result.data?.translated_text;918}919 920if (921 typeof translatedText !== "string" ||922 !translatedText.trim()923) {924 throw new Error("Translation returned an empty response.");925}926 927translatedText = translatedText.trim();928 929