| import os |
| import nest_asyncio |
| import logging |
| from flask import Flask |
| from threading import Thread |
| from pymongo import MongoClient |
| from telethon import TelegramClient, events |
|
|
|
|
|
|
| |
| nest_asyncio.apply() |
| logging.basicConfig(level=logging.INFO) |
|
|
| |
| API_ID = int(os.getenv("API_ID", "3704772")) |
| API_HASH = os.getenv("API_HASH", "b8e50a035abb851c0dd424e14cac4c06") |
| BOT_TOKEN = os.getenv("TELEGRAM_TOKEN") |
| OWNER_ID = int(os.getenv("OWNER_ID")) |
| MONGO_URI = os.getenv("MONGO_URI") |
| PORT = int(os.getenv("PORT", 7860)) |
|
|
| |
| client = MongoClient(MONGO_URI) |
| db = client.teleg4am_reelssss |
| a_raw = db.raw_links |
| a_reacted = db.reacted_links |
| last_alerts = {"raw": -1, "reacted": -1} |
| |
| 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 |
| |
| 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 |
|
|
| |
| bot = TelegramClient(StringSession(), API_ID, API_HASH).start(bot_token=BOT_TOKEN) |
| user_context = {} |
|
|
| |
| def is_owner(event): |
| return event.sender_id == OWNER_ID |
|
|
| |
| @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 |
|
|
| 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.") |
|
|
| |
| 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 <command>") |
| 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 <full_filename>") |
|
|
| 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 = {} |
|
|
|
|
| @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] |
|
|
| 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 <command>` β Run a terminal command\n" |
| "`/dl` β Download replied media\n" |
| "`/ul <filename>` β Upload a file from disk\n" |
| "`/doc <filename>` β 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) |
|
|
| 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() |
| |
| asyncio.create_task(auto_alert_loop()) |
|
|
| |
|
|
| |
| print("β
Starting bot...") |
| await bot.start(bot_token=BOT_TOKEN) |
| await bot.run_until_disconnected() |
|
|
| if __name__ == "__main__": |
| import asyncio |
| asyncio.run(main()) |
|
|