import os import nest_asyncio import logging from flask import Flask from threading import Thread from pymongo import MongoClient from telethon import TelegramClient, events # Enable nested event loops for Flask + Telethon nest_asyncio.apply() logging.basicConfig(level=logging.INFO) # === ENVIRONMENT VARIABLES === API_ID = int(os.getenv("API_ID", "3704772")) API_HASH = os.getenv("API_HASH", "b8e50a035abb851c0dd424e14cac4c06") BOT_TOKEN = os.getenv("TELEGRAM_TOKEN") # Bot token, NOT user session OWNER_ID = int(os.getenv("OWNER_ID")) MONGO_URI = os.getenv("MONGO_URI") PORT = int(os.getenv("PORT", 7860)) # === MongoDB === client = MongoClient(MONGO_URI) db = client.teleg4am_reelssss a_raw = db.raw_links a_reacted = db.reacted_links last_alerts = {"raw": -1, "reacted": -1} # Add indexes to improve performance (run only once on startup) a_raw.create_index([("link", 1)], unique=True) a_raw.create_index([("used", 1)]) a_reacted.create_index([("link", 1)], unique=True) a_reacted.create_index([("used", 1)]) from telethon.sessions import StringSession # === Flask App === app = Flask(__name__) @app.route("/") def home(): return "šŸ¤– Bot is running!" @app.route("/status") def status(): raw = a_raw.count_documents({"used": False}) reacted = a_reacted.count_documents({"used": False}) return {"raw_links_left": raw, "reacted_links_left": reacted}, 200 # === Telethon Bot Setup (Bot Mode) === bot = TelegramClient(StringSession(), API_ID, API_HASH).start(bot_token=BOT_TOKEN) user_context = {} # === Helper: Check if sender is owner === def is_owner(event): return event.sender_id == OWNER_ID # === Command Handlers === @bot.on(events.NewMessage(pattern="/start|/count")) async def count_handler(event): if not is_owner(event): return raw = a_raw.count_documents({"used": False}) reacted = a_reacted.count_documents({"used": False}) await event.reply(f"šŸ“Š Raw: {raw}\nšŸ“Š Reacted: {reacted}") @bot.on(events.NewMessage(pattern="/raw")) async def raw_handler(event): if not is_owner(event): return user_context[event.sender_id] = {"mode": "raw", "links": []} await event.reply("šŸ“„ Send raw links (one per line). Use /done to save.") @bot.on(events.NewMessage(pattern="/reacted")) async def reacted_handler(event): if not is_owner(event): return user_context[event.sender_id] = {"mode": "reacted", "links": []} await event.reply("šŸ“„ Send reacted links (one per line). Use /done to save.") @bot.on(events.NewMessage(pattern="/done")) async def done_handler(event): if not is_owner(event): return ctx = user_context.pop(event.sender_id, None) if not ctx or not ctx["links"]: return await event.reply("āŒ Nothing to save.") col = a_raw if ctx["mode"] == "raw" else a_reacted inserted = 0 for link in ctx["links"]: if col.count_documents({"link": link}) == 0: col.insert_one({"link": link, "used": False}) inserted += 1 await event.reply(f"āœ… Saved {inserted} new {ctx['mode']} link(s).") await check_and_alert(ctx["mode"], col) @bot.on(events.NewMessage(func=lambda e: e.text and e.sender_id in user_context)) async def link_collector(event): if not is_owner(event): return ctx = user_context[event.sender_id] if ctx["mode"] not in ["raw", "reacted"]: return # Skip if it's correction-used or something else links = [l.strip() for l in event.text.split() if l.startswith("http")] ctx["links"].extend(links) await event.reply(f"šŸ”— {len(links)} link(s) added.") # === Alert System === async def check_and_alert(pool: str, col): left = col.count_documents({"used": False}) if 1 <= left <= 5 and last_alerts[pool] != left: last_alerts[pool] = left try: await bot.send_message(OWNER_ID, f"āš ļø Only {left} {pool} link(s) left!") except Exception as e: logging.error(f"āŒ Alert failed: {e}") import os import asyncio import subprocess from telethon import TelegramClient, events from telethon.tl.types import DocumentAttributeFilename @bot.on(events.NewMessage(pattern="/bash")) async def bash(event): cmd = event.message.text.split(None, 1) if len(cmd) < 2: return await event.reply("āŒ Usage: /bash ") process = await asyncio.create_subprocess_shell( cmd[1], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await process.communicate() output = stdout.decode() or "āœ… No output" error = stderr.decode() await event.reply(f"šŸ“„ Code:\n`{cmd[1]}`\n\nšŸ“¤ Output:\n```\n{output.strip()}\n```" + (f"\nāŒ Error:\n```\n{error.strip()}\n```" if error else "")) @bot.on(events.NewMessage(pattern="/dl")) async def dl(event): reply = await event.get_reply_message() if not reply or not reply.media: return await event.reply("āŒ Reply to a media file to download it.") path = await bot.download_media(reply) await event.reply(f"āœ… File downloaded to `{path}`") @bot.on(events.NewMessage(pattern="/ul")) async def ul(event): args = event.raw_text.split(maxsplit=1) if len(args) != 2: return await event.reply("āŒ Usage: /ul ") filepath = args[1].strip() if not os.path.exists(filepath): return await event.reply("āŒ File not found.") await bot.send_file(event.chat_id, filepath, caption=f"šŸ“¤ Uploaded: `{filepath}`") @bot.on(events.NewMessage(pattern=r'^/doc(?:\s+(.+))?')) async def handler_doc(event): reply = await event.get_reply_message() filename = event.pattern_match.group(1) if not reply or not reply.text: await event.reply("āŒ Please reply to a text message to save as a file.") return if not filename: await event.reply("āŒ Usage: `/doc filename.txt`", parse_mode='md') return try: with open(filename, "w", encoding="utf-8") as f: f.write(reply.text) await bot.send_file(event.chat_id, filename, caption=f"āœ… Saved reply to `{filename}`", force_document=True) os.remove(filename) except Exception as e: await event.reply(f"āŒ Error: {e}") user_context = {} # make sure this is defined once globally @bot.on(events.NewMessage(pattern="/cused")) async def correction_used_handler(event): if not is_owner(event): return user_context[event.sender_id] = {"mode": "correct_used", "links": []} await event.reply("šŸ›  Correction mode ON for used links.\nSend all links (one per line). Use /doneused when finished.") @bot.on(events.NewMessage(func=lambda e: e.text and e.sender_id in user_context and not e.raw_text.startswith("/doneused"))) async def collect_links(event): if not is_owner(event): return ctx = user_context.get(event.sender_id) if ctx and ctx.get("mode") == "correct_used": links = [l.strip() for l in event.text.split() if l.startswith("http")] if links: ctx["links"].extend(links) await event.reply(f"šŸ”— Collected {len(links)} link(s). Send more or use /dused.") else: await event.reply("āš ļø No valid links detected. Please send links starting with http.") @bot.on(events.NewMessage(pattern="/dused")) async def process_correction(event): if not is_owner(event): return ctx = user_context.get(event.sender_id) if not ctx or ctx.get("mode") != "correct_used": return await event.reply("āš ļø Not in correction-used mode. Use /cused first.") links = ctx.get("links", []) updated_raw = updated_reacted = skipped = not_found = 0 for link in links: found = False doc_raw = a_raw.find_one({"link": link}) if doc_raw: found = True if doc_raw.get("used") is True: a_raw.update_one({"_id": doc_raw["_id"]}, {"$set": {"used": False}}) updated_raw += 1 else: skipped += 1 doc_reacted = a_reacted.find_one({"link": link}) if doc_reacted: found = True if doc_reacted.get("used") is True: a_reacted.update_one({"_id": doc_reacted["_id"]}, {"$set": {"used": False}}) updated_reacted += 1 else: skipped += 1 if not found: not_found += 1 del user_context[event.sender_id] # Clear mode after done await event.reply( f"āœ… Done processing {len(links)} link(s).\n\n" f"šŸ“¦ Raw updated: {updated_raw}\n" f"šŸŽž Reacted updated: {updated_reacted}\n" f"ā­ Skipped (already unused): {skipped}\n" f"āŒ Not found in both: {not_found}" ) @bot.on(events.NewMessage(pattern="/help")) async def help_handler(event): if not is_owner(event): return await event.reply( "**šŸ¤– Available Commands:**\n\n" "**šŸ“Š Status & Counts**\n" "`/start` — Show total raw/reacted links left\n" "`/count` — Same as /start (alias)\n" "`/status` — (Web endpoint) Get JSON of links left\n\n" "**šŸ“„ Add Links**\n" "`/raw` — Start adding raw links\n" "`/reacted` — Start adding reacted links\n" "`/done` — Save links after sending\n\n" "**šŸ›  Correct Used Flags**\n" "`/cused` — Start correction for used links\n" "`/dused` — Finalize correction and update DB\n\n" "**šŸ’» File & Shell Tools**\n" "`/bash ` — Run a terminal command\n" "`/dl` — Download replied media\n" "`/ul ` — Upload a file from disk\n" "`/doc ` — Save replied text to file and upload\n\n" "**ā„¹ļø Notes:**\n" "- Only you (owner) can use these commands\n" "- All links must start with `http`\n" "- You can send multiple links in one message\n" "- Correction only flips `used: True → False`, not adding\n", parse_mode='md' ) def run_flask(): app.run(host="0.0.0.0", port=PORT) import asyncio async def auto_alert_loop(): while True: try: await check_and_alert("raw", a_raw) await check_and_alert("reacted", a_reacted) except Exception as e: logging.error(f"Auto-alert loop error: {e}") await asyncio.sleep(30) # Check every 30 seconds async def main(): if not all([API_ID, API_HASH, BOT_TOKEN, MONGO_URI]): raise RuntimeError("āŒ Missing ENV vars: API_ID, API_HASH, TELEGRAM_TOKEN, or MONGO_URI") Thread(target=run_flask).start() # Start background alert loop asyncio.create_task(auto_alert_loop()) # Start Flask in a separate thread (or you can replace with FastAPI if needed # Start the bot print("āœ… Starting bot...") await bot.start(bot_token=BOT_TOKEN) await bot.run_until_disconnected() if __name__ == "__main__": import asyncio asyncio.run(main())