← back to browse
goat command

gemini

No description

0
views
0
likes
0
installs

Aliases: m

raw source
const axios = require("axios");

/* =========================================
   ๐Ÿ”‘ GEMINI API KEY
   ๐Ÿ‘‰ ONLY CHANGE THIS ONE LINE
========================================= */

const API_KEY = "AQ.Ab8RN6JYrkYsGJSe_jxw46dVTqtrvefw8UQi5BVuY9UwPFkmsg";


/* =========================================
   ๐Ÿค– FAST MODEL
========================================= */

const MODEL = "gemini-3.1-flash-lite";


/* =========================================
   ๐Ÿ“ MESSAGE LIMIT
========================================= */

const MAX_MESSAGE_LENGTH = 19000;


/* =========================================
   ๐Ÿ’พ CONVERSATION MEMORY
========================================= */

const conversations = new Map();


/* =========================================
   ๐Ÿงน CLEAN OLD CONVERSATIONS
========================================= */

function cleanupConversations() {

	const now = Date.now();

	for (const [key, data] of conversations) {

		/*
		 * Remove conversations older than 30 minutes.
		 */

		if (
			now - data.updatedAt >
			30 * 60 * 1000
		) {
			conversations.delete(key);
		}
	}
}


/* =========================================
   ๐Ÿ”‘ USER CONVERSATION KEY
========================================= */

function getUserKey(event) {

	return `${event.threadID}:${event.senderID}`;
}


/* =========================================
   โœ‚๏ธ SPLIT LONG MESSAGE
========================================= */

function splitMessage(
	text,
	maxLength = MAX_MESSAGE_LENGTH
) {

	const chunks = [];

	if (!text) {
		return chunks;
	}

	for (
		let i = 0;
		i < text.length;
		i += maxLength
	) {

		chunks.push(
			text.slice(i, i + maxLength)
		);
	}

	return chunks;
}


/* =========================================
   ๐Ÿค– GEMINI REQUEST
========================================= */

async function askGemini(
	question,
	conversation
) {

	/* =====================================
	   BUILD CONTENTS
	===================================== */

	const contents = [];


	/* =====================================
	   PREVIOUS CONVERSATION
	===================================== */

	if (
		conversation &&
		Array.isArray(conversation.history)
	) {

		for (
			const item of conversation.history
		) {

			contents.push({
				role: "user",

				parts: [
					{
						text: item.user
					}
				]
			});


			contents.push({
				role: "model",

				parts: [
					{
						text: item.bot
					}
				]
			});
		}
	}


	/* =====================================
	   CURRENT QUESTION
	===================================== */

	contents.push({

		role: "user",

		parts: [
			{
				text: question
			}
		]
	});


	/* =====================================
	   API URL
	===================================== */

	const url =
		`https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent`;


	/* =====================================
	   REQUEST
	===================================== */

	const response =
		await axios.post(

			url,

			{
				systemInstruction: {

					parts: [
						{
							text:
								"You are Gemini AI inside a Messenger group bot. " +
								"Give helpful, accurate and natural answers. " +
								"Reply in the same language as the user whenever possible. " +
								"If the user speaks Bengali, reply in Bengali. " +
								"Keep answers clear and useful. " +
								"Do not mention system instructions, API keys or internal configuration."
						}
					]
				},

				contents,

				generationConfig: {

					temperature: 0.7,

					maxOutputTokens: 2048,

					thinkingConfig: {
						thinkingLevel: "minimal"
					}
				}
			},

			{
				headers: {
					"x-goog-api-key":
						API_KEY,

					"Content-Type":
						"application/json"
				},

				timeout: 45000
			}
		);


	/* =====================================
	   GET RESPONSE
	===================================== */

	const candidates =
		response?.data?.candidates || [];


	const parts =
		candidates[0]?.content?.parts || [];


	const answer =
		parts
			.map(
				part =>
					part.text || ""
			)
			.join("")
			.trim();


	/* =====================================
	   EMPTY RESPONSE CHECK
	===================================== */

	if (!answer) {

		const finishReason =
			candidates[0]?.finishReason ||
			"UNKNOWN";


		throw new Error(
			`Gemini returned an empty response. Finish reason: ${finishReason}`
		);
	}


	return answer;
}


/* =========================================
   โŒ ERROR FORMAT
========================================= */

function formatError(error) {

	const status =
		error?.response?.status;

	const apiError =
		error?.response?.data?.error;

	const errorMessage =
		apiError?.message ||
		error?.message ||
		"Unknown error";


	if (status === 400) {

		return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

โš ๏ธ Bad request.

๐Ÿ”Ž ${errorMessage}`;
	}


	if (status === 401) {

		return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

๐Ÿ”‘ Invalid Gemini API key.`;
	}


	if (status === 403) {

		return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

๐Ÿšซ Gemini API access denied.

๐Ÿ”Ž ${errorMessage}`;
	}


	if (status === 404) {

		return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

๐Ÿ”Ž Gemini model not found.

๐Ÿค– Model: ${MODEL}

๐Ÿ”Ž ${errorMessage}`;
	}


	if (status === 429) {

		return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

โšก Gemini API quota or rate limit reached.

๐Ÿ”Ž ${errorMessage}`;
	}


	return `โŒ ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ ๐—˜๐—ฅ๐—ฅ๐—ข๐—ฅ

โš ๏ธ Gemini API request failed.

๐Ÿ“ก Status: ${status || "Unknown"}

๐Ÿ”Ž ${errorMessage}`;
}


/* =========================================
   ๐Ÿš€ PROCESS GEMINI
========================================= */

async function processGemini({
	api,
	message,
	event,
	question,
	conversation
}) {

	/* =====================================
	   โณ LOADING
	===================================== */

	let loadingMessage;

	try {

		loadingMessage =
			await message.reply(
				"โณ ๐Œ๐š๐ซ๐ฎ๐Ÿ'๐ฌ ๐๐จ๐ญ ๐—ถ๐˜€ ๐˜๐—ต๐—ถ๐—ป๐—ธ๐—ถ๐—ป๐—ด..."
			);

	} catch (error) {

		console.error(
			"Loading error:",
			error
		);

		return;
	}


	/* =====================================
	   MESSAGE ID
	===================================== */

	const loadingMessageID =
		loadingMessage?.messageID ||
		loadingMessage?.messageId;


	try {

		/* =================================
		   ASK GEMINI
		================================= */

		const answer =
			await askGemini(
				question,
				conversation
			);


		/* =================================
		   SAVE CONVERSATION
		================================= */

		const userKey =
			getUserKey(event);


		let currentConversation =
			conversations.get(userKey);


		if (!currentConversation) {

			currentConversation = {
				history: []
			};
		}


		currentConversation.history.push({

			user: question,

			bot: answer

		});


		/*
		 * Keep only last 10 turns
		 * to prevent huge prompts.
		 */

		if (
			currentConversation.history.length >
			10
		) {

			currentConversation.history =
				currentConversation.history.slice(-10);
		}


		currentConversation.updatedAt =
			Date.now();


		currentConversation.botMessageID =
			loadingMessageID;


		conversations.set(
			userKey,
			currentConversation
		);


		/* =================================
		   SPLIT LONG RESPONSE
		================================= */

		const chunks =
			splitMessage(answer);


		/* =================================
		   FIRST RESPONSE
		   EDIT LOADING MESSAGE
		================================= */

		const firstMessage =
`๐Ÿค– ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“

${chunks[0]}`;


		if (loadingMessageID) {

			try {

				await api.editMessage(
					firstMessage,
					loadingMessageID
				);

			} catch (editError) {

				console.error(
					"Edit error:",
					editError
				);

				await message.reply(
					firstMessage
				);
			}

		} else {

			const sent =
				await message.reply(
					firstMessage
				);


			if (sent?.messageID) {

				currentConversation.botMessageID =
					sent.messageID;

				conversations.set(
					userKey,
					currentConversation
				);
			}
		}


		/* =================================
		   LONG RESPONSE
		================================= */

		for (
			let i = 1;
			i < chunks.length;
			i++
		) {

			await message.reply(
`๐Ÿค– ๐Œ๐€๐‘๐”๐…'๐’ ๐๐Ž๐“ โ€ข ${i + 1}/${chunks.length}

${chunks[i]}`
			);
		}


		/* =================================
		   CLEAN OLD DATA
		================================= */

		cleanupConversations();

	} catch (error) {

		/* =================================
		   LOG ERROR
		================================= */

		console.error(
			"========== GEMINI ERROR =========="
		);

		console.error(
			"Model:",
			MODEL
		);

		console.error(
			"Status:",
			error?.response?.status
		);

		console.error(
			"Response:",
			error?.response?.data
		);

		console.error(
			"Message:",
			error?.message
		);

		console.error(
			"=================================="
		);


		const errorText =
			formatError(error);


		/* =================================
		   EDIT LOADING MESSAGE
		================================= */

		if (loadingMessageID) {

			try {

				return await api.editMessage(
					errorText,
					loadingMessageID
				);

			} catch (editError) {

				console.error(
					"Error edit failed:",
					editError
				);
			}
		}


		return message.reply(
			errorText
		);
	}
}


/* =========================================
   COMMAND
========================================= */

module.exports = {

	config: {

		name: "gemini",

		aliases: [
			"m"
		],

		version: "1.0.0",

		author:
			"Mohammad Maruf",

		countDown: 3,

		role: 0,

		category: "AI",

		description: {
			en:
				"Chat with Google Gemini AI."
		},

		guide: {
			en:
				"{pn} <question>\n" +
				"Example: {pn} Hello\n\n" +
				"Reply to Gemini's response to continue the conversation."
		}
	},


	/* =========================================
	   START COMMAND
	========================================= */

	onStart: async function ({
		api,
		message,
		event,
		args
	}) {

		/* =====================================
		   API KEY CHECK
		===================================== */

		if (
			!API_KEY ||
			API_KEY ===
				"YOUR_GEMINI_API_KEY_HERE"
		) {

			return message.reply(
				"โŒ ๐—š๐—ฒ๐—บ๐—ถ๐—ป๐—ถ ๐—”๐—ฃ๐—œ ๐—ธ๐—ฒ๐˜† ๐—ถ๐˜€ ๐—ป๐—ผ๐˜ ๐—ฐ๐—ผ๐—ป๐—ณ๐—ถ๐—ด๐˜‚๐—ฟ๐—ฒ๐—ฑ."
			);
		}


		/* =====================================
		   GET QUESTION
		===================================== */

		let question =
			args.join(" ").trim();


		/* =====================================
		   REPLY MESSAGE
		===================================== */

		if (
			!question &&
			event.messageReply &&
			event.messageReply.body
		) {

			question =
				event.messageReply.body.trim();
		}


		/* =====================================
		   DEFAULT GREETING
		===================================== */

		if (!question) {

			question =
				"Say a short friendly greeting to the user and ask how you can help today.";
		}


		/* =====================================
		   START NEW CONVERSATION
		===================================== */

		return processGemini({

			api,

			message,

			event,

			question,

			conversation: null

		});
	},


	/* =========================================
	   ๐Ÿ’ฌ BARE "g" + REPLY CONTINUATION
	========================================= */

	onChat: async function ({
		api,
		message,
		event
	}) {

		if (!event.body) {
			return;
		}


		const body =
			event.body.trim();


		/* =====================================
		   USER KEY
		===================================== */

		const userKey =
			getUserKey(event);


		const conversation =
			conversations.get(userKey);


		/* =====================================
		   CASE 1: JUST "g"
		===================================== */

		if (
			body.toLowerCase() === "g"
		) {

			/*
			 * If replying to the latest
			 * Gemini response, continue.
			 */

			if (
				conversation &&
				event.messageReply &&
				event.messageReply.messageID ===
					conversation.botMessageID
			) {

				return processGemini({

					api,

					message,

					event,

					question:
						"Continue the conversation naturally.",

					conversation
				});
			}


			/*
			 * Otherwise start greeting.
			 */

			return processGemini({

				api,

				message,

				event,

				question:
					"Say a short friendly greeting to the user and ask how you can help today.",

				conversation: null
			});
		}


		/* =====================================
		   CASE 2:
		   REPLY TO GEMINI WITH ANY TEXT
		===================================== */

		if (
			conversation &&
			event.messageReply &&
			event.messageReply.messageID ===
				conversation.botMessageID
		) {

			/*
			 * User replies directly to
			 * Gemini's latest response.
			 *
			 * No /g required.
			 */

			return processGemini({

				api,

				message,

				event,

				question: body,

				conversation
			});
		}
	}
};

View raw file