|
import os |
|
import re |
|
import asyncio |
|
import sys |
|
import importlib.util |
|
from pyrogram import Client, filters |
|
from pyrogram.enums import ParseMode |
|
import google.generativeai as genai |
|
|
|
API_ID = os.environ.get("API_ID") |
|
API_HASH = os.environ.get("API_HASH") |
|
BOT_TOKEN = os.environ.get("BOT_TOKEN") |
|
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") |
|
if not BOT_TOKEN: |
|
raise RuntimeError("BOT_TOKEN environment variable not set!") |
|
if not GEMINI_API_KEY: |
|
raise RuntimeError("GEMINI_API_KEY environment variable not set!") |
|
|
|
genai.configure(api_key=GEMINI_API_KEY) |
|
model = genai.GenerativeModel("gemini-2.5-flash") |
|
bot = Client("JarvisBot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN) |
|
os.makedirs("modules", exist_ok=True) |
|
|
|
def load_modules(folder="modules"): |
|
for file in os.listdir(folder): |
|
if file.endswith(".py"): |
|
path = os.path.join(folder, file) |
|
name = file[:-3] |
|
spec = importlib.util.spec_from_file_location(name, path) |
|
mod = importlib.util.module_from_spec(spec) |
|
spec.loader.exec_module(mod) |
|
|
|
load_modules() |
|
|
|
def extract_module_name(code: str) -> str: |
|
match = re.search(r"def\\s+([a-zA-Z_][a-zA-Z0-9_]*)", code) |
|
return match.group(1).lower() if match else f"mod_{os.urandom(2).hex()}" |
|
|
|
def extract_commands(code: str) -> list: |
|
return re.findall(r'filters\.command\(["\'](\w+)["\']', code) |
|
|
|
def determine_intent(text: str) -> str: |
|
lowered = text.lower() |
|
if any(x in lowered for x in ["create", "make", "build"]): |
|
return "CREATE" |
|
elif any(x in lowered for x in ["edit", "modify"]): |
|
return "EDIT" |
|
elif "recode" in lowered: |
|
return "RECODE" |
|
else: |
|
return "UNSURE" |
|
|
|
def clean_code_blocks(code: str) -> str: |
|
if code.startswith("```python"): |
|
code = code[9:] |
|
if code.startswith("```"): |
|
code = code[3:] |
|
if code.endswith("```"): |
|
code = code[:-3] |
|
return code.strip() |
|
|
|
def extract_module_name_from_description(description: str) -> str: |
|
import re |
|
stopwords = { |
|
'jarvis', 'make', 'create', 'build', 'generate', 'a', 'an', 'the', 'please', 'module', |
|
'for', 'me', 'to', 'with', 'add', 'new', 'of', 'system', 'bot', 'command', 'that', 'and', |
|
'in', 'on', 'by', 'as', 'is', 'it', 'my', 'this', 'do', 'can', 'you', 'i', 'want', 'need', |
|
'from', 'like', 'using', 'feature', 'function', 'implement', 'write', 'code', 'file', |
|
'python', 'telegram', 'pyrogram', 'handler', 'example', 'mod', 'mod_', 'particular', 'user' |
|
} |
|
desc = re.sub(r'[^a-zA-Z0-9_\s]', ' ', description.lower()) |
|
words = desc.split() |
|
filtered_words = [] |
|
for word in words: |
|
if word not in stopwords and len(word) > 2: |
|
filtered_words.append(word) |
|
if filtered_words: |
|
name_words = filtered_words[:3] |
|
name = '_'.join(name_words) |
|
else: |
|
meaningful_words = [w for w in words if len(w) > 3 and w not in stopwords] |
|
if meaningful_words: |
|
name = meaningful_words[0] |
|
else: |
|
clean_desc = re.sub(r'[^a-zA-Z0-9]', '', description.lower()) |
|
name = clean_desc[:10] if clean_desc else f"mod_{os.urandom(2).hex()}" |
|
name = re.sub(r'_+', '_', name).strip('_') |
|
name = name.lower() |
|
if not name: |
|
name = f"mod_{os.urandom(2).hex()}" |
|
return name |
|
|
|
async def restart_bot(chat_id): |
|
await bot.send_message(chat_id, "Restarting to apply new module...") |
|
await bot.stop() |
|
os.execl(sys.executable, sys.executable, *sys.argv) |
|
os._exit(0) |
|
|
|
@bot.on_message(filters.private & filters.text) |
|
async def jarvis_trigger(client, message): |
|
if message.from_user.id != 7361622601: |
|
return await message.reply("Access denied. Only the owner can use this bot.") |
|
|
|
text = message.text.strip() |
|
if not text.lower().startswith("jarvis"): |
|
return |
|
|
|
description = text[6:].strip() |
|
if not description: |
|
return await message.reply("Hello, what would you like me to do?") |
|
|
|
intent = determine_intent(description) |
|
progress_msg = await message.reply(f"Acknowledged.\nDetermining intent...\nIntent: {intent}\nTask: `{description}`", parse_mode=ParseMode.MARKDOWN) |
|
|
|
if intent == "EDIT": |
|
match = re.match(r"edit\s+([\w/\\.]+)\s+(.*)", description, re.IGNORECASE) |
|
if match: |
|
file_path = match.group(1) |
|
edit_instruction = match.group(2) |
|
if not os.path.exists(file_path): |
|
await progress_msg.edit(f"File `{file_path}` not found.") |
|
return |
|
with open(file_path, "r", encoding="utf-8") as f: |
|
current_content = f.read() |
|
edit_prompt = ( |
|
f"You are an expert Python developer. Here is the current content of `{file_path}`:\n" |
|
f"{current_content}\n" |
|
f"Please edit this file to: {edit_instruction}.\n" |
|
f"Output ONLY the new Python code, no explanations or markdown." |
|
) |
|
try: |
|
response = model.generate_content(edit_prompt) |
|
new_code = clean_code_blocks(response.text.strip()) |
|
if not new_code or "def " not in new_code: |
|
await progress_msg.edit(f"Edit failed: No valid code returned.") |
|
return |
|
with open(file_path, "w", encoding="utf-8") as f: |
|
f.write(new_code) |
|
if file_path.startswith("modules/") and file_path.endswith(".py"): |
|
mod_name = os.path.basename(file_path)[:-3] |
|
try: |
|
spec = importlib.util.spec_from_file_location(mod_name, file_path) |
|
mod = importlib.util.module_from_spec(spec) |
|
mod.bot = bot |
|
spec.loader.exec_module(mod) |
|
except Exception as test_error: |
|
await progress_msg.edit(f"Edit applied, but module reload failed: {test_error}") |
|
return |
|
await progress_msg.edit(f"Edit applied to `{file_path}`. Restarting bot...") |
|
await asyncio.sleep(2) |
|
asyncio.create_task(restart_bot(message.chat.id)) |
|
return |
|
except Exception as e: |
|
await progress_msg.edit(f"Edit failed: `{str(e)[:100]}...`") |
|
return |
|
else: |
|
await progress_msg.edit("Could not parse edit command. Use: 'edit <file> <instruction>'") |
|
return |
|
|
|
success = False |
|
code = "" |
|
last_error = None |
|
for attempt in range(1, 6): |
|
await progress_msg.edit(f"Attempt {attempt}/5: Thinking and generating code...\n- Executing prerequisite shell commands...") |
|
|
|
try: |
|
prompt = ( |
|
f"Write a full Pyrogram Telegram bot module that implements:\n" |
|
f"{description}.\n\n" |
|
f"IMPORTANT RULES:\n" |
|
f"1. Use 'bot' variable (not 'Client') - it will be injected\n" |
|
f"2. Include commands using @bot.on_message(filters.command(...))\n" |
|
f"3. Import only what you need from pyrogram\n and use 'from ..t1 import bot'\n 'try: from ..t1 import bot except (ImportError, ValueError): \n from __main__ import bot' \n" |
|
f"4. Don't create a new Client instance\n" |
|
f"5. Make functions async when using bot methods\n\n" |
|
f"Output ONLY Python code, no explanations or markdown." |
|
) |
|
if attempt > 1 and last_error is not None: |
|
prompt += f"\n\nNote: Previous attempt failed with error: {str(last_error)}. Fix that issue this time." |
|
|
|
response = model.generate_content(prompt) |
|
code = clean_code_blocks(response.text.strip()) |
|
|
|
if "def " not in code or "@bot" not in code: |
|
await progress_msg.edit(f"Attempt {attempt}: Invalid function or handler.") |
|
continue |
|
|
|
import re |
|
file_path_match = re.search(r"(modules/[\w\-]+\.py)", description) |
|
if file_path_match: |
|
mod_path = file_path_match.group(1) |
|
mod_name = os.path.basename(mod_path)[:-3] |
|
else: |
|
mod_name = extract_module_name_from_description(description) |
|
mod_path = f"modules/{mod_name}.py" |
|
|
|
if os.path.exists(mod_path): |
|
with open(mod_path, "r", encoding="utf-8") as f: |
|
current_content = f.read() |
|
edit_prompt = ( |
|
f"You are an expert Python developer. Here is the current content of `{mod_path}`:\n" |
|
f"{current_content}\n" |
|
f"Please update this file to: {description}.\n" |
|
f"Output ONLY the new Python code, no explanations or markdown." |
|
) |
|
response = model.generate_content(edit_prompt) |
|
code = clean_code_blocks(response.text.strip()) |
|
|
|
with open(mod_path, "w", encoding="utf-8") as f: |
|
f.write(code) |
|
|
|
await progress_msg.edit(f"Attempt {attempt}/5: Thinking and generating code...\n- Executing prerequisite shell commands...\n- Testing generated code...") |
|
|
|
try: |
|
spec = importlib.util.spec_from_file_location(mod_name, mod_path) |
|
mod = importlib.util.module_from_spec(spec) |
|
mod.bot = bot |
|
spec.loader.exec_module(mod) |
|
except Exception as test_error: |
|
raise RuntimeError(f"Testing error: {test_error}") |
|
|
|
commands = extract_commands(code) |
|
commands_str = ', '.join(f'/{cmd}' for cmd in commands) if commands else 'No commands found.' |
|
|
|
try: |
|
explain_prompt = ( |
|
f"Explain in 2-3 sentences what this Pyrogram Telegram bot module does, given the following code and the user's request.\n" |
|
f"User request: {description}\n" |
|
f"Module code:\n{code}\n" |
|
f"Be concise and clear." |
|
) |
|
explain_response = model.generate_content(explain_prompt) |
|
explanation = explain_response.text.strip() |
|
except Exception as exp_explain: |
|
explanation = f"Could not generate explanation: {exp_explain}" |
|
|
|
await progress_msg.edit( |
|
f"Module created successfully!\n\n" |
|
f"Commands: {commands_str}\n\n" |
|
f"What it does:\n{explanation}\n\n" |
|
f"Restarting bot to load the new module...", |
|
parse_mode=ParseMode.MARKDOWN |
|
) |
|
|
|
await asyncio.sleep(2) |
|
asyncio.create_task(restart_bot(message.chat.id)) |
|
success = True |
|
break |
|
|
|
except Exception as e: |
|
last_error = e |
|
try: |
|
await progress_msg.edit(f"Attempt {attempt} Error: {str(e)[:100]}...") |
|
except: |
|
await message.reply(f"Attempt {attempt} failed. Check logs.") |
|
|
|
if not success: |
|
await progress_msg.edit("All 5 attempts failed. Please try again with a simpler instruction.") |
|
|
|
@bot.on_message(filters.command("modules") & filters.private) |
|
async def list_modules(client, message): |
|
if message.from_user.id != 7361622601: |
|
return await message.reply("Access denied. Only the owner can use this bot.") |
|
modules = [f for f in os.listdir("modules") if f.endswith(".py")] |
|
if modules: |
|
module_list = "\n".join([f"• {m[:-3]}" for m in modules]) |
|
await message.reply(f"Loaded Modules:\n{module_list}", parse_mode=ParseMode.MARKDOWN) |
|
else: |
|
await message.reply("No modules found.", parse_mode=ParseMode.MARKDOWN) |
|
|
|
@bot.on_message(filters.command("delete") & filters.private) |
|
async def delete_module(client, message): |
|
if message.from_user.id != 7361622601: |
|
return await message.reply("Access denied. Only the owner can use this bot.") |
|
if len(message.command) < 2: |
|
return await message.reply("Specify module name.\n\nExample: /delete calculator", parse_mode=ParseMode.MARKDOWN) |
|
mod_name = message.command[1].lower() |
|
mod_path = f"modules/{mod_name}.py" |
|
if os.path.exists(mod_path): |
|
os.remove(mod_path) |
|
await message.reply(f"Deleted {mod_name}.py\nRestart required to take effect.", parse_mode=ParseMode.MARKDOWN) |
|
else: |
|
await message.reply(f"Module {mod_name}.py not found.", parse_mode=ParseMode.MARKDOWN) |
|
|
|
@bot.on_message(filters.regex(r"(?i)what( can)? i do\??") & filters.private) |
|
async def what_to_do(_, message): |
|
if message.from_user.id != 7361622601: |
|
return await message.reply("Access denied. Only the owner can use this bot.") |
|
await message.reply( |
|
"Jarvis Assistant\n\n" |
|
"I can generate custom bot modules for you!\n\n" |
|
"Commands:\n" |
|
"jarvis make a calculator - Natural language trigger\n" |
|
"/modules - List all modules\n" |
|
"/delete <name> - Delete a module\n\n" |
|
"Examples:\n" |
|
"jarvis build a reminder system\n" |
|
"jarvis create a dice game\n" |
|
"jarvis weather checker", |
|
parse_mode=ParseMode.MARKDOWN |
|
) |
|
|
|
print("Starting Jarvis Bot...") |
|
load_modules() |
|
print("Bot is ready!") |
|
bot.run() |
|
|