Spaces:
Running
Running
Download bot.py from Jitendra55566/Batch_uploder_bot: direct link, hf CLI and curl.
- Browser
- Download file 25.4 kB
-
https://huggingface.co/spaces/Jitendra55566/Batch_uploder_bot/resolve/main/bot.py
- Command line
-
hf download hf://spaces/Jitendra55566/Batch_uploder_bot/bot.py
-
curl -L -o bot.py https://huggingface.co/spaces/Jitendra55566/Batch_uploder_bot/resolve/main/bot.py
25.4 kB
| # --- START OF FILE main.py --- | |
| import os | |
| import time | |
| import uuid | |
| import asyncio | |
| import threading | |
| import logging | |
| import hashlib | |
| import re | |
| import requests | |
| from flask import Flask | |
| from flask_cors import CORS | |
| from pyrogram import Client, filters, idle | |
| from pyrogram.errors import FloodWait # π₯ NEW: Anti-Flood system import | |
| from internetarchive import upload | |
| # --- LOGGING --- | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger("Rajasthan_Bot") | |
| # --- ENV VARIABLES --- | |
| def get_clean_var(name): | |
| val = os.environ.get(name, "") | |
| return val.strip() if val else "" | |
| API_ID = get_clean_var("API_ID") | |
| API_HASH = get_clean_var("API_HASH") | |
| BOT_TOKEN = get_clean_var("BOT_TOKEN") | |
| SESSION_STRING = get_clean_var("SESSION_STRING") | |
| ACCESS_PASSWORD = get_clean_var("PASSWORD") or "Maharaja Jaswant Singh" | |
| IA_ACCESS_KEY = get_clean_var("IA_ACCESS_KEY") | |
| IA_SECRET_KEY = get_clean_var("IA_SECRET_KEY") | |
| IA_BUCKET = get_clean_var("IA_BUCKET") | |
| # π₯ FIREBASE URL FIX π₯ | |
| FIREBASE_URL = get_clean_var("FIREBASE_URL") | |
| if FIREBASE_URL and not FIREBASE_URL.startswith("http"): | |
| FIREBASE_URL = "https://" + FIREBASE_URL | |
| if FIREBASE_URL.endswith("/"): | |
| FIREBASE_URL = FIREBASE_URL[:-1] | |
| # --- FLASK SERVER --- | |
| app = Flask(__name__) | |
| CORS(app) | |
| def home(): | |
| return "Admin Pro - Auto Sync Bot Online π’" | |
| def run_web_server(): | |
| app.run(host="0.0.0.0", port=7860) | |
| # --- GLOBAL VARS & CLIENTS --- | |
| process_lock = asyncio.Lock() | |
| AUTH_USERS = set() | |
| USERBOT_ALIVE = False | |
| STOP_REQUESTED = False | |
| if not API_ID or not BOT_TOKEN: | |
| print("β ERROR: API_ID ya BOT_TOKEN missing hai!") | |
| exit(1) | |
| bot = Client("main_bot", api_id=int(API_ID), api_hash=API_HASH, bot_token=BOT_TOKEN) | |
| userbot = None | |
| if SESSION_STRING: | |
| userbot = Client("user_bot", api_id=int(API_ID), api_hash=API_HASH, session_string=SESSION_STRING) | |
| # --- HELPERS --- | |
| def humanbytes(size): | |
| if not size: return "0 B" | |
| power = 2**10 | |
| n = 0 | |
| power_labels = {0 : '', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'} | |
| while size > power: | |
| size /= power | |
| n += 1 | |
| return f"{size:.2f} {power_labels[n]}" | |
| def get_id(name): | |
| return hashlib.md5(name.strip().lower().encode()).hexdigest()[:12] | |
| def get_base_title(title): | |
| t = str(title).lower() | |
| t = re.sub(r'\(pdf.*?\)', '', t) | |
| t = re.sub(r'\(notes\)', '', t) | |
| t = re.sub(r'pdf\s*\d*', '', t) | |
| t = re.sub(r'part\s*\d+', '', t) | |
| t = re.sub(r'\s*\d+\s*$', '', t) | |
| t = re.sub(r'[^\w]', '', t) | |
| return t.strip() | |
| # π₯ CAPTION PARSER π₯ | |
| def parse_caption(caption, is_pdf=False): | |
| batch = "Unknown Batch" | |
| subject = "General Subject" | |
| chapter = "General Chapter" | |
| title = "Class Lecture" | |
| if not caption: | |
| return batch, subject, chapter, title | |
| try: | |
| # 1. Batch Nikalna | |
| batch_match = re.search(r'Batch:\s*([^\n]+)', caption, re.IGNORECASE) | |
| if batch_match: | |
| batch = batch_match.group(1).strip() | |
| # 2. Subject & Chapter (Agar direct Topic: likha ho) | |
| topic_match = re.search(r'Topic:\s*([^\n]+)', caption, re.IGNORECASE) | |
| if topic_match: | |
| raw_topic = topic_match.group(1).strip() | |
| if 'β' in raw_topic: parts = raw_topic.split('β') | |
| elif '->' in raw_topic: parts = raw_topic.split('->') | |
| elif '-' in raw_topic: parts = raw_topic.split('-') | |
| else: parts = [raw_topic] | |
| if len(parts) >= 2: | |
| subject = parts[0].strip() | |
| chapter = re.sub(r'[β’Β·.]+$', '', parts[1].strip()).strip() | |
| else: | |
| subject = parts[0].strip() | |
| # 3. Title & HINDI CHAPTER Logic | |
| raw_title = "" | |
| title_line_match = re.search(r'Title:\s*([^\n]+)', caption, re.IGNORECASE) | |
| if title_line_match: | |
| raw_title = title_line_match.group(1).strip() | |
| else: | |
| # Agar "Title:" nahi likha hai, to pehli line ko title maan lo | |
| lines = [line.strip() for line in caption.split('\n') if line.strip()] | |
| if lines: raw_title = lines[0] | |
| if raw_title: | |
| base_title = re.sub(r'\.mkv|\.mp4|\.pdf', '', raw_title, flags=re.IGNORECASE).strip() | |
| # π₯ NEW MAGIC: Hindi words ko scan karke Chapter banana π₯ | |
| # \u0900-\u097F ye code computer ko Hindi padhna sikhata hai | |
| hindi_matches = re.findall(r'[\u0900-\u097F]+(?:\s+[\u0900-\u097F]+)*', base_title) | |
| if hindi_matches and chapter == "General Chapter": | |
| # Agar title me hindi word mila (jaise: 'ΰ€Έΰ€ΰ€΅ΰ€Ώΰ€§ΰ€Ύΰ€¨ ΰ€Έΰ€ΰ€Ύ'), to usko Chapter set kardo | |
| chapter = max(hindi_matches, key=len).strip() | |
| if is_pdf: | |
| if "pdf 2" in raw_title.lower(): title = f"{base_title} (PDF 2)" | |
| elif "pdf" in raw_title.lower(): title = f"{base_title} (PDF 1)" | |
| else: title = f"{base_title} (Notes)" | |
| else: | |
| title = base_title | |
| except Exception as e: | |
| logger.error(f"Caption parsing error: {e}") | |
| return batch, subject, chapter, title | |
| # π₯ PROGRESS TRACKER (WITH ANTI-FLOOD FIX) π₯ | |
| async def progress(current, total, message, start_time, header_card): | |
| now = time.time() | |
| diff = now - start_time | |
| # Telegram spam limits ke liye update time ko 5 seconds kar diya gaya hai | |
| if round(diff % 5.0) == 0 or current == total: | |
| percentage = current * 100 / total | |
| speed = current / diff if diff > 0 else 0 | |
| text = ( | |
| f"{header_card}\n" | |
| f"ββββββββββββββββββ\n" | |
| f"β¬οΈ **Status:** Downloading from Telegram...\n" | |
| f"π **Progress:** {percentage:.1f}%\n" | |
| f"πΎ **Done:** {humanbytes(current)} / {humanbytes(total)}\n" | |
| f"β‘ **Speed:** {humanbytes(speed)}/s" | |
| ) | |
| try: | |
| await message.edit(text) | |
| except FloodWait as e: | |
| # Agar telegram rokega, toh bot shaanti se wait karega! | |
| await asyncio.sleep(e.value) | |
| except Exception: | |
| pass | |
| # --- COMMANDS --- | |
| async def start_cmd(c, m): | |
| if m.from_user.id in AUTH_USERS: | |
| await m.reply_text("π **Welcome Back!**\nDirect Video/PDF ki link bhejo (Jaise msg ID 866 waali).") | |
| else: | |
| await m.reply_text("π **Access Denied!** Pehle Password bhejo.") | |
| async def stop_cmd(c, m): | |
| global STOP_REQUESTED | |
| if m.from_user.id not in AUTH_USERS: return | |
| STOP_REQUESTED = True | |
| await m.reply_text("π **Stop Command Received!** Current upload ke baad loop ruk jayega.") | |
| # --- FIREBASE SECURE PATCHER --- | |
| def patch_firebase(url, payload): | |
| res = requests.patch(url, json=payload) | |
| if res.status_code >= 400: | |
| raise Exception(f"Firebase Rejected Request (Status {res.status_code}): {res.text}") | |
| # π₯ DYNAMIC FOLDER RESOLVER (Chronological Sorting for CHAPTERS only) π₯ | |
| global_firebase_cache = {} | |
| def fetch_firebase_keys(parent_url): | |
| if parent_url in global_firebase_cache: | |
| return | |
| global_firebase_cache[parent_url] = {} | |
| if not FIREBASE_URL: return | |
| try: | |
| res = requests.get(parent_url + ".json") | |
| if res.status_code == 200 and res.json(): | |
| for k, v in res.json().items(): | |
| if isinstance(v, dict) and 'title' in v: | |
| global_firebase_cache[parent_url][v['title']] = k | |
| except Exception as e: | |
| pass | |
| def resolve_fb_id(parent_url, title, msg_id): | |
| fetch_firebase_keys(parent_url) | |
| if title in global_firebase_cache[parent_url]: | |
| return global_firebase_cache[parent_url][title] | |
| else: | |
| new_id = f"z_{msg_id:08d}_{get_id(title)[:6]}" | |
| global_firebase_cache[parent_url][title] = new_id | |
| return new_id | |
| # --- MAIN LOGIC --- | |
| async def text_handler(c, m): | |
| global STOP_REQUESTED | |
| if m.from_user.id not in AUTH_USERS: | |
| if m.text == ACCESS_PASSWORD: | |
| AUTH_USERS.add(m.from_user.id) | |
| await m.reply_text("π **Access Granted!** Ab starting video ki link bhejo.") | |
| else: | |
| await m.reply_text("β **Wrong Password!**") | |
| return | |
| if "t.me/" in m.text: | |
| if not userbot or not USERBOT_ALIVE: | |
| return await m.reply_text("β οΈ **Userbot Offline hai.** Naya Session String Check Karein.") | |
| if process_lock.locked(): | |
| return await m.reply_text("β οΈ **Ek extraction already chal raha hai.** Pehle `/stop` bhejo.") | |
| async with process_lock: | |
| STOP_REQUESTED = False | |
| status = await m.reply_text("π **Admin Pro Engine Started...**") | |
| raw_link = m.text.strip().replace("https://", "").replace("http://", "") | |
| if "t.me/" in raw_link: raw_link = raw_link.split("t.me/")[1] | |
| parts = raw_link.split("/") | |
| try: | |
| parts = [p for p in parts if p] | |
| # π₯ MASTER FIX: Link chahe jaisi ho, aakhri part hamesha message ID hota hai | |
| current_msg_id = int(parts[-1].split("?")[0]) | |
| topic_or_post_id = None | |
| if parts[0] == "c": | |
| # Private/Restricted Link ke liye | |
| chat_id = int("-100" + parts[1]) | |
| else: | |
| # Public Link ke liye (yakeenbatchrwa jaise) | |
| chat_id = parts[0] | |
| except Exception: | |
| return await status.edit("β **Invalid Telegram Link Format!**") | |
| no_media_count = 0 | |
| extracted_count = 0 | |
| video_memory_cache = {} | |
| last_fallback_video = None | |
| stop_keywords = ["chapter complete", "stop extract", "subject complete", "topic complete"] | |
| while True: | |
| if STOP_REQUESTED: | |
| await m.reply_text(f"π **Extraction Stopped!**\nUploaded: `{extracted_count}`") | |
| break | |
| try: | |
| msg_ids_to_check = list(range(current_msg_id, current_msg_id + 5)) | |
| messages = await userbot.get_messages(chat_id, msg_ids_to_check) | |
| if messages and messages[0].empty and no_media_count == 0 and len(parts) >= 4 and topic_or_post_id: | |
| await status.edit(f"β οΈ **Message #{current_msg_id} khali hai.**\nMain-Post #{topic_or_post_id} check kar raha hu...") | |
| current_msg_id = topic_or_post_id | |
| msg_ids_to_check = list(range(current_msg_id, current_msg_id + 5)) | |
| messages = await userbot.get_messages(chat_id, msg_ids_to_check) | |
| topic_or_post_id = None | |
| except FloodWait as e: | |
| await asyncio.sleep(e.value) | |
| messages = await userbot.get_messages(chat_id, msg_ids_to_check) | |
| except Exception as e: | |
| err_msg = str(e).lower() | |
| if "peer_id_invalid" in err_msg or "peer id invalid" in err_msg: | |
| await status.edit("π **Peer ID Error Aaya!**\nRuko, Bot apni memory (Chat Cache) auto-refresh kar raha hai...") | |
| try: | |
| # π₯ FIX: Userbot automatically apni chat list fetch karke group ko pehchan lega | |
| async for dialog in userbot.get_dialogs(limit=150): | |
| if dialog.chat.id == chat_id: | |
| break | |
| await status.edit("β **Cache Refresh Ho Gaya!** Ab extraction start ho raha hai...") | |
| continue # Ye wapas upar ja kar same link ko dobara try karega | |
| except Exception as refresh_err: | |
| await m.reply_text(f"π« **Auto-Fix Failed:**\nKya jis account ka SESSION STRING dala hai, wo sach me is group me join hai? Error: {refresh_err}") | |
| break | |
| else: | |
| await m.reply_text(f"π« **Channel Access Error:** `{e}`") | |
| break | |
| if not isinstance(messages, list): messages = [messages] | |
| chunk_has_media = False | |
| for msg in messages: | |
| if STOP_REQUESTED: break | |
| if not msg or msg.empty: continue | |
| if msg.text and any(k in msg.text.lower() for k in stop_keywords): | |
| STOP_REQUESTED = True | |
| await m.reply_text(f"β **'Subject/Chapter Complete' detected!** Loop automatically stopped.") | |
| break | |
| target_msg = None | |
| if getattr(msg, "media", None) and not getattr(msg, "web_page", None): | |
| target_msg = msg | |
| elif getattr(msg, "reply_to_message", None) and getattr(msg.reply_to_message, "media", None): | |
| target_msg = msg.reply_to_message | |
| if not target_msg: continue | |
| if getattr(target_msg, "document", None) or getattr(target_msg, "video", None) or getattr(target_msg, "audio", None) or getattr(target_msg, "photo", None): | |
| chunk_has_media = True | |
| no_media_count = 0 | |
| is_video = False | |
| is_pdf = False | |
| ext = "pdf" | |
| if target_msg.video or getattr(target_msg, "audio", None): | |
| is_video = True | |
| ext = "mp4" if target_msg.video else "mp3" | |
| elif target_msg.document: | |
| fname = getattr(target_msg.document, "file_name", "") or "" | |
| mime = getattr(target_msg.document, "mime_type", "") or "" | |
| try: ext = fname.split(".")[-1].lower() | |
| except: ext = "pdf" | |
| if ext in ['mp4', 'mkv', 'webm', 'ts'] or "video" in mime.lower(): | |
| is_video = True | |
| else: | |
| is_pdf = True | |
| else: | |
| is_pdf = True | |
| file_type_text = "Video π¬" if is_video else "PDF π" | |
| caption = msg.text if msg.text else (target_msg.caption or "") | |
| batch_name, sub_name, chap_name, item_title = parse_caption(caption, is_pdf=is_pdf) | |
| header_card = ( | |
| f"π **CURRENTLY PROCESSING (MSG #{target_msg.id})**\n" | |
| f"ββββββββββββββββββ\n" | |
| f"π· **Batch:** `{batch_name}`\n" | |
| f"π **Subject:** `{sub_name}`\n" | |
| f"π **Chapter:** `{chap_name}`\n" | |
| f"π **Type:** {file_type_text}\n" | |
| f"βΆοΈ **Title:** `{item_title}`" | |
| ) | |
| unique_id = uuid.uuid4().hex[:5] | |
| safe_file_name = f"AdminData_{target_msg.id}_{unique_id}.{ext}" | |
| save_path = f"./{safe_file_name}" | |
| try: | |
| # π₯ AUTO RETRY SYSTEM FOR DOWNLOAD (0 Bytes Fix) π₯ | |
| max_retries = 3 | |
| download_success = False | |
| for attempt in range(max_retries): | |
| try: | |
| start_t = time.time() | |
| await status.edit(f"{header_card}\nββββββββββββββββββ\nβ¬οΈ **Status:** Downloading... (Attempt {attempt+1}/3)") | |
| await userbot.download_media(target_msg, file_name=save_path, progress=progress, progress_args=(status, start_t, header_card)) | |
| if not os.path.exists(save_path) or os.path.getsize(save_path) == 0: | |
| raise Exception("0 bytes corrupted file") | |
| download_success = True | |
| break | |
| except FloodWait as e: | |
| await asyncio.sleep(e.value) | |
| except Exception as e: | |
| if os.path.exists(save_path): os.remove(save_path) | |
| if attempt < max_retries - 1: | |
| await asyncio.sleep(2) # 2 second wait before retry | |
| else: | |
| raise Exception(f"Failed to download after {max_retries} attempts.") | |
| if not download_success: | |
| raise Exception("Download fully failed.") | |
| # π₯ UPLOAD PROCESS π₯ | |
| final_link = "" | |
| try: | |
| await status.edit(f"{header_card}\nββββββββββββββββββ\nβοΈ **Status:** Uploading to Archive.org...") | |
| upload_kwargs = {"access_key": IA_ACCESS_KEY, "secret_key": IA_SECRET_KEY} | |
| await asyncio.to_thread(upload, IA_BUCKET, files={safe_file_name: save_path}, **upload_kwargs) | |
| final_link = f"https://archive.org/download/{IA_BUCKET}/{safe_file_name}" | |
| except Exception as upload_err: | |
| if is_pdf and "unacceptable" in str(upload_err).lower(): | |
| await status.edit(f"{header_card}\nββββββββββββββββββ\nβ οΈ **Archive.org Rejected PDF!**\nπ Uploading to Backup Server...") | |
| def backup_upload(): | |
| with open(save_path, 'rb') as f: | |
| res = requests.post("https://jitendra55566-bot-uplod.hf.space/api/upload", files={'file': f}) | |
| if res.status_code == 200 and 'file_url' in res.json(): | |
| return res.json()['file_url'] | |
| else: | |
| raise Exception(f"Backup Server Failed: {res.text}") | |
| final_link = await asyncio.to_thread(backup_upload) | |
| else: | |
| raise Exception(f"Upload Failed: {upload_err}") | |
| await status.edit(f"{header_card}\nββββββββββββββββββ\nπ **Status:** Updating Firebase Database...") | |
| if not FIREBASE_URL: | |
| raise Exception("FIREBASE_URL Variable Empty hai. HuggingFace Secrets check karein.") | |
| cat_id = get_id(batch_name) | |
| sub_id = get_id(f"{batch_name}_{sub_name}") | |
| chap_url = f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}/subcategories" | |
| chap_id = await asyncio.to_thread(resolve_fb_id, chap_url, chap_name, target_msg.id) | |
| title_hash = get_id(f"{chap_name}_{item_title}")[:6] | |
| item_id = f"z_{target_msg.id:08d}_{title_hash}" | |
| base_match_title = get_base_title(item_title) | |
| memory_key = f"{chap_id}_{base_match_title}" | |
| def push_to_firebase(): | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}.json", {"title": batch_name, "show_on_home": True, "show_on_batches": True}) | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}.json", {"title": sub_name}) | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}/subcategories/{chap_id}.json", {"title": chap_name}) | |
| if is_video: | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}/subcategories/{chap_id}/videos/{item_id}.json", { | |
| "title": item_title, | |
| "url": final_link, | |
| "priority": 0, | |
| "timestamp": target_msg.id | |
| }) | |
| video_memory_cache[memory_key] = item_id | |
| else: | |
| pdf_data = { | |
| "title": item_title, | |
| "url": final_link, | |
| "allow_download": True, | |
| "allow_google": True, | |
| "priority": 0, | |
| "timestamp": target_msg.id | |
| } | |
| matched_video_id = None | |
| if memory_key in video_memory_cache: | |
| matched_video_id = video_memory_cache[memory_key] | |
| elif last_fallback_video and last_fallback_video["chap_id"] == chap_id: | |
| matched_video_id = last_fallback_video["video_id"] | |
| if matched_video_id: | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}/subcategories/{chap_id}/videos/{matched_video_id}/pdfs/{item_id}.json", pdf_data) | |
| else: | |
| patch_firebase(f"{FIREBASE_URL}/categories/{cat_id}/subcategories/{sub_id}/subcategories/{chap_id}/pdfs/{item_id}.json", pdf_data) | |
| await asyncio.to_thread(push_to_firebase) | |
| if is_video: | |
| last_fallback_video = {"chap_id": chap_id, "video_id": item_id} | |
| await m.reply_text(f"β **{file_type_text} UPLOADED!**\nπ¬ **Title:** {item_title}\nπ’ **MSG ID:** #{target_msg.id}") | |
| extracted_count += 1 | |
| except FloodWait as e: | |
| await asyncio.sleep(e.value) | |
| except Exception as e: | |
| await m.reply_text(f"β **Error on MSG #{target_msg.id}:**\n`{e}`") | |
| finally: | |
| if os.path.exists(save_path): os.remove(save_path) | |
| if not chunk_has_media: | |
| no_media_count += 5 | |
| try: | |
| await status.edit(f"β© **Skipping MSG {current_msg_id} to {current_msg_id+4}**\n*(No File Found)*") | |
| except FloodWait as e: | |
| await asyncio.sleep(e.value) | |
| except Exception: | |
| pass | |
| current_msg_id += 5 | |
| if no_media_count > 150: | |
| await status.edit(f"π **Loop Finished.** 150+ khali messages mile.\nUploaded: `{extracted_count}`") | |
| break | |
| await asyncio.sleep(1) | |
| await status.delete() | |
| # --- STARTUP --- | |
| async def main(): | |
| threading.Thread(target=run_web_server, daemon=True).start() | |
| global USERBOT_ALIVE | |
| print("π Starting Main Bot...") | |
| await bot.start() | |
| if userbot: | |
| try: | |
| print("β³ Starting Userbot...") | |
| await userbot.start() | |
| USERBOT_ALIVE = True | |
| print("π Loading chats to fix Peer ID issue...") | |
| # π₯ Bot on hote hi chats memory me save kar lega | |
| async for dialog in userbot.get_dialogs(limit=50): | |
| pass | |
| print("β Userbot Online & Chats Loaded!") | |
| except Exception as e: | |
| print(f"β Userbot Error: {e}") | |
| USERBOT_ALIVE = False | |
| print("β All Systems Ready!") | |
| await idle() | |
| await bot.stop() | |
| if __name__ == "__main__": | |
| loop = asyncio.get_event_loop() | |
| loop.run_until_complete(main()) |