File size: 12,382 Bytes
10d17d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
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

# check line 93 .. accordingly
# === CONFIG ===
API_ID = os.environ.get("API_ID")                 # Replace with your API ID
API_HASH = os.environ.get("API_HASH")   # Replace with your 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!")

# === SETUP ===
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)

# === MODULE LOADER ===
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()

# === HELPERS ===
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:
    # Try to extract the last noun/phrase as the module name
    # Remove common verbs and stopwords
    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_'  # etc.
    ]
    # Remove punctuation
    desc = re.sub(r'[^a-zA-Z0-9_\s]', '', description.lower())
    # Remove stopwords
    words = [w for w in desc.split() if w not in stopwords]
    if not words:
        return f"mod_{os.urandom(2).hex()}"
    # Join remaining words with underscores
    name = '_'.join(words)
    # Remove leading/trailing underscores and collapse multiple underscores
    name = re.sub(r'_+', '_', name).strip('_')
    # Fallback if name is empty
    if not name:
        return 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)  # Ensure process exits if execl fails

# === WORD TRIGGER ===
@bot.on_message(filters.private & filters.text)
async def jarvis_trigger(client, message):
    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.\n🧠 Determining intent...\nπŸ“˜ Intent: {intent}\nπŸ“ Task: `{description}`", parse_mode=ParseMode.MARKDOWN)

    # --- NEW: Handle EDIT intent for existing files ---
    if intent == "EDIT":
        # Try to extract filename and edit instruction
        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"""\n{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)
                # Try to reload the module if it's in modules/
                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
    # --- END NEW ---

    success = False
    code = ""
    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:
                prompt += f"\n\nNote: Previous attempt failed with error: {str(e)}. 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

            # --- NEW: Use file path from description if present ---
            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 file exists, use edit flow instead of creating a new file
            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"""\n{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())
            # --- END NEW ---

            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}")

            await progress_msg.edit(f"`Attempt {attempt}/5:` Thinking and generating code...\n- Executing prerequisite shell commands...\n- Testing generated code...\n- Tests passed. Writing files...\n- Files written. Initiating restart...")
            await asyncio.sleep(2)
            asyncio.create_task(restart_bot(message.chat.id))
            success = True
            break

        except Exception as e:
            await progress_msg.edit(f"❌ Attempt {attempt} Error: `{str(e)[:100]}...`")

    if not success:
        await progress_msg.edit("❌ All 5 attempts failed. Please try again with a simpler instruction.")

# === /modules ===
@bot.on_message(filters.command("modules") & filters.private)
async def list_modules(client, message):
    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)

# === /delete ===
@bot.on_message(filters.command("delete") & filters.private)
async def delete_module(client, message):
    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`\n♻️ Restart required to take effect.", parse_mode=ParseMode.MARKDOWN)
    else:
        await message.reply(f"❌ Module `{mod_name}.py` not found.", parse_mode=ParseMode.MARKDOWN)

# === /what to do ===
@bot.on_message(filters.regex(r"(?i)what( can)? i do\??") & filters.private)
async def what_to_do(_, message):
    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`\n",
        parse_mode=ParseMode.MARKDOWN
    )

# === START ===
print("πŸš€ Starting Jarvis Bot...")
load_modules()
print("βœ… Bot is ready!")
bot.run()