CoolFace
Apppublic

Streamixph05/Rstream

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
app.py373 linesDownload Raw Back to root
1# app.py (THE REAL, FINAL, CLEAN, EASY-TO-READ FULL CODE)2 3import os4import asyncio5import secrets6import traceback7import uvicorn8import re9import logging10from contextlib import asynccontextmanager11 12from pyrogram import Client, filters, enums13from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, ChatMemberUpdated14from pyrogram.errors import FloodWait, UserNotParticipant15from fastapi import FastAPI, Request, HTTPException16from fastapi.middleware.cors import CORSMiddleware17from fastapi.responses import JSONResponse, StreamingResponse18from pyrogram.file_id import FileId19from pyrogram import raw20from pyrogram.session import Session, Auth21from fastapi.responses import HTMLResponse22from fastapi.templating import Jinja2Templates23import math24 25# Project ki dusri files se important cheezein import karo26from config import Config27from database import db28 29# =====================================================================================30# --- SETUP: BOT, WEB SERVER, AUR LOGGING ---31# =====================================================================================32 33@asynccontextmanager34async def lifespan(app: FastAPI):35    """36    Yeh function bot ko web server ke saath start aur stop karta hai.37    """38    print("--- Lifespan: Server chalu ho raha hai... ---")39    40    await db.connect()41    42    try:43        print("Starting main Pyrogram bot...")44        await bot.start()45        46        me = await bot.get_me()47        Config.BOT_USERNAME = me.username48        print(f"✅ Main Bot [@{Config.BOT_USERNAME}] safaltapoorvak start ho gaya.")49 50        # --- MULTI-CLIENT STARTUP ---51        multi_clients[0] = bot52        work_loads[0] = 053        await initialize_clients()54        55        print(f"Verifying storage channel ({Config.STORAGE_CHANNEL})...")56        await bot.get_chat(Config.STORAGE_CHANNEL)57        print("✅ Storage channel accessible hai.")58 59        if Config.FORCE_SUB_CHANNEL:60            try:61                print(f"Verifying force sub channel ({Config.FORCE_SUB_CHANNEL})...")62                await bot.get_chat(Config.FORCE_SUB_CHANNEL)63                print("✅ Force Sub channel accessible hai.")64            except Exception as e:65                print(f"!!! WARNING: Bot, Force Sub channel mein admin nahi hai. Error: {e}")66        67        try:68            await cleanup_channel(bot)69        except Exception as e:70            print(f"Warning: Channel cleanup fail ho gaya. Error: {e}")71 72        print("--- Lifespan: Startup safaltapoorvak poora hua. ---")73    74    except Exception as e:75        print(f"!!! FATAL ERROR: Bot startup ke dauraan error aa gaya: {traceback.format_exc()}")76    77    yield78    79    print("--- Lifespan: Server band ho raha hai... ---")80    if bot.is_initialized:81        await bot.stop()82    print("--- Lifespan: Shutdown poora hua. ---")83 84app = FastAPI(lifespan=lifespan)85templates = Jinja2Templates(directory="templates")86app.add_middleware(87    CORSMiddleware,88    allow_origins=["*"],89    allow_credentials=True,90    allow_methods=["*"],91    allow_headers=["*"],92)93 94# --- LOG FILTER: YEH SIRF /dl/ WALE LOGS KO CHUPAYEGA ---95class HideDLFilter(logging.Filter):96    def filter(self, record: logging.LogRecord) -> bool:97        # Agar log message mein "GET /dl/" hai, toh usse mat dikhao98        return "GET /dl/" not in record.getMessage()99 100# Uvicorn ke 'access' logger par filter lagao101logging.getLogger("uvicorn.access").addFilter(HideDLFilter())102# --- FIX KHATAM ---103 104bot = Client("SimpleStreamBot", api_id=Config.API_ID, api_hash=Config.API_HASH, bot_token=Config.BOT_TOKEN, in_memory=True)105multi_clients = {}; work_loads = {}; class_cache = {}106 107# =====================================================================================108# --- MULTI-CLIENT LOGIC ---109# =====================================================================================110 111class TokenParser:112    """ Environment variables se MULTI_TOKENs ko parse karta hai. """113    @staticmethod114    def parse_from_env():115        return {116            c + 1: t117            for c, (_, t) in enumerate(118                filter(lambda n: n[0].startswith("MULTI_TOKEN"), sorted(os.environ.items()))119            )120        }121 122async def start_client(client_id, bot_token):123    """ Ek naye client bot ko start karta hai. """124    try:125        print(f"Attempting to start Client: {client_id}")126        client = await Client(127            name=str(client_id), 128            api_id=Config.API_ID, 129            api_hash=Config.API_HASH,130            bot_token=bot_token, 131            no_updates=True, 132            in_memory=True133        ).start()134        work_loads[client_id] = 0135        multi_clients[client_id] = client136        print(f"✅ Client {client_id} started successfully.")137    except Exception as e:138        print(f"!!! CRITICAL ERROR: Failed to start Client {client_id} - Error: {e}")139 140async def initialize_clients():141    """ Saare additional clients ko initialize karta hai. """142    all_tokens = TokenParser.parse_from_env()143    if not all_tokens:144        print("No additional clients found. Using default bot only.")145        return146    147    print(f"Found {len(all_tokens)} extra clients. Starting them...")148    tasks = [start_client(i, token) for i, token in all_tokens.items()]149    await asyncio.gather(*tasks)150 151    if len(multi_clients) > 1:152        print(f"✅ Multi-Client Mode Enabled. Total Clients: {len(multi_clients)}")153 154# =====================================================================================155# --- HELPER FUNCTIONS ---156# =====================================================================================157 158def get_readable_file_size(size_in_bytes):159    if not size_in_bytes:160        return '0B'161    power = 1024162    n = 0163    power_labels = {0: 'B', 1: 'KB', 2: 'MB', 3: 'GB'}164    while size_in_bytes >= power and n < len(power_labels) - 1:165        size_in_bytes /= power166        n += 1167    return f"{size_in_bytes:.2f} {power_labels[n]}"168 169def mask_filename(name: str):170    if not name:171        return "Protected File"172    base, ext = os.path.splitext(name)173    metadata_pattern = re.compile(174        r'((19|20)\d{2}|4k|2160p|1080p|720p|480p|360p|HEVC|x265|BluRay|WEB-DL|HDRip)',175        re.IGNORECASE176    )177    match = metadata_pattern.search(base)178    if match:179        title_part = base[:match.start()].strip(' .-_')180        metadata_part = base[match.start():]181    else:182        title_part = base183        metadata_part = ""184    masked_title = ''.join(c if (i % 3 == 0 and c.isalnum()) else ('*' if c.isalnum() else c) for i, c in enumerate(title_part))185    return f"{masked_title} {metadata_part}{ext}".strip()186 187# =====================================================================================188# --- PYROGRAM BOT HANDLERS ---189# =====================================================================================190 191@bot.on_message(filters.command("start") & filters.private)192async def start_command(client: Client, message: Message):193    user_id = message.from_user.id194    user_name = message.from_user.first_name195    196    if len(message.command) > 1 and message.command[1].startswith("verify_"):197        unique_id = message.command[1].split("_", 1)[1]198        199        if Config.FORCE_SUB_CHANNEL:200            try:201                await client.get_chat_member(Config.FORCE_SUB_CHANNEL, user_id)202            except UserNotParticipant:203                channel_username = str(Config.FORCE_SUB_CHANNEL).replace('@', '')204                channel_link = f"https://t.me/{channel_username}"205                join_button = InlineKeyboardButton("📢 Join Channel", url=channel_link)206                retry_button = InlineKeyboardButton("✅ Joined", url=f"https://t.me/{Config.BOT_USERNAME}?start={message.command[1]}")207                keyboard = InlineKeyboardMarkup([[join_button], [retry_button]])208                await message.reply_text(209                    "**You Must Join Our Channel To Get The Link!**\n\n"210                    "__Join Channel & Click '✅ Joined'.__",211                    reply_markup=keyboard, quote=True212                )213                return214 215        final_link = f"{Config.BASE_URL}/show/{unique_id}"216        reply_text = f"__✅ Verification Successful!\n\nCopy Link:__ `{final_link}`"217        button = InlineKeyboardMarkup([[InlineKeyboardButton("Open Link", url=final_link)]])218        await message.reply_text(reply_text, reply_markup=button, quote=True, disable_web_page_preview=True)219 220    else:221        reply_text = f"""222👋 **Hello, {user_name}!**223 224__Welcome To Sharing Box Bot. I Can Help You Create Permanent, Shareable Links For Your Files.__225 226**How To Use Me:**227 228__Just Send Or Forward Any File To Me And I will instantly give you a special link that you can share with anyone!__229"""230        await message.reply_text(reply_text)231 232async def handle_file_upload(message: Message, user_id: int):233    try:234        sent_message = await message.copy(chat_id=Config.STORAGE_CHANNEL)235        unique_id = secrets.token_urlsafe(8)236        await db.save_link(unique_id, sent_message.id)237        238        verify_link = f"https://t.me/{Config.BOT_USERNAME}?start=verify_{unique_id}"239        button = InlineKeyboardMarkup([[InlineKeyboardButton("Get Link Now", url=verify_link)]])240        241        await message.reply_text("__✅ File Uploaded!__", reply_markup=button, quote=True)242    except Exception as e:243        print(f"!!! ERROR: {traceback.format_exc()}"); await message.reply_text("Sorry, something went wrong.")244 245@bot.on_message(filters.private & (filters.document | filters.video | filters.audio))246async def file_handler(_, message: Message):247    await handle_file_upload(message, message.from_user.id)248 249@bot.on_chat_member_updated(filters.chat(Config.STORAGE_CHANNEL))250async def simple_gatekeeper(c: Client, m_update: ChatMemberUpdated):251    try:252        if(m_update.new_chat_member and m_update.new_chat_member.status==enums.ChatMemberStatus.MEMBER):253            u=m_update.new_chat_member.user254            if u.id==Config.OWNER_ID or u.is_self: return255            print(f"Gatekeeper: Kicking {u.id}"); await c.ban_chat_member(Config.STORAGE_CHANNEL,u.id); await c.unban_chat_member(Config.STORAGE_CHANNEL,u.id)256    except Exception as e: print(f"Gatekeeper Error: {e}")257 258async def cleanup_channel(c: Client):259    print("Gatekeeper: Running cleanup..."); allowed={Config.OWNER_ID,c.me.id}260    try:261        async for m in c.get_chat_members(Config.STORAGE_CHANNEL):262            if m.user.id in allowed: continue263            if m.status in [enums.ChatMemberStatus.ADMINISTRATOR,enums.ChatMemberStatus.OWNER]: continue264            try: print(f"Cleanup: Kicking {m.user.id}"); await c.ban_chat_member(Config.STORAGE_CHANNEL,m.user.id); await asyncio.sleep(1)265            except FloodWait as e: await asyncio.sleep(e.value)266            except Exception as e: print(f"Cleanup Error: {e}")267    except Exception as e: print(f"Cleanup Error: {e}")268 269# =====================================================================================270# --- FASTAPI WEB SERVER ---271# =====================================================================================272 273@app.get("/")274async def health_check():275    """276    This route provides a 200 OK response for uptime monitors.277    """278    return {"status": "ok", "message": "Server is healthy and running!"}279 280@app.get("/show/{unique_id}", response_class=HTMLResponse)281async def show_page(request: Request, unique_id: str):282    return templates.TemplateResponse(283        "show.html",284        {"request": request}285    )286 287@app.get("/api/file/{unique_id}", response_class=JSONResponse)288async def get_file_details_api(request: Request, unique_id: str):289    message_id = await db.get_link(unique_id)290    if not message_id:291        raise HTTPException(status_code=404, detail="Link expired or invalid.")292    main_bot = multi_clients.get(0)293    if not main_bot:294        raise HTTPException(status_code=503, detail="Bot is not ready.")295    try:296        message = await main_bot.get_messages(Config.STORAGE_CHANNEL, message_id)297    except Exception:298        raise HTTPException(status_code=404, detail="File not found on Telegram.")299    media = message.document or message.video or message.audio300    if not media:301        raise HTTPException(status_code=404, detail="Media not found in the message.")302    file_name = media.file_name or "file"303    safe_file_name = "".join(c for c in file_name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()304    mime_type = media.mime_type or "application/octet-stream"305    response_data = {306        "file_name": mask_filename(file_name),307        "file_size": get_readable_file_size(media.file_size),308        "is_media": mime_type.startswith(("video", "audio")),309        "direct_dl_link": f"{Config.BASE_URL}/dl/{message_id}/{safe_file_name}",310        "mx_player_link": f"intent:{Config.BASE_URL}/dl/{message_id}/{safe_file_name}#Intent;action=android.intent.action.VIEW;type={mime_type};end",311        "vlc_player_link": f"intent:{Config.BASE_URL}/dl/{message_id}/{safe_file_name}#Intent;action=android.intent.action.VIEW;type={mime_type};package=org.videolan.vlc;end"312    }313    return response_data314 315class ByteStreamer:316    def __init__(self,c:Client):self.client=c317    @staticmethod318    async def get_location(f:FileId): return raw.types.InputDocumentFileLocation(id=f.media_id,access_hash=f.access_hash,file_reference=f.file_reference,thumb_size=f.thumbnail_size)319    async def yield_file(self,f:FileId,i:int,o:int,fc:int,lc:int,pc:int,cs:int):320        c=self.client;work_loads[i]+=1;ms=c.media_sessions.get(f.dc_id)321        if ms is None:322            if f.dc_id!=await c.storage.dc_id():323                ak=await Auth(c,f.dc_id,await c.storage.test_mode()).create();ms=Session(c,f.dc_id,ak,await c.storage.test_mode(),is_media=True);await ms.start();ea=await c.invoke(raw.functions.auth.ExportAuthorization(dc_id=f.dc_id));await ms.invoke(raw.functions.auth.ImportAuthorization(id=ea.id,bytes=ea.bytes))324            else:ms=c.session325            c.media_sessions[f.dc_id]=ms326        loc=await self.get_location(f);cp=1327        try:328            while cp<=pc:329                r=await ms.invoke(raw.functions.upload.GetFile(location=loc,offset=o,limit=cs),retries=0)330                if isinstance(r,raw.types.upload.File):331                    chk=r.bytes332                    if not chk:break333                    if pc==1:yield chk[fc:lc]334                    elif cp==1:yield chk[fc:]335                    elif cp==pc:yield chk[:lc]336                    else:yield chk337                    cp+=1;o+=cs338                else:break339        finally:work_loads[i]-=1340 341@app.get("/dl/{mid}/{fname}")342async def stream_media(r:Request,mid:int,fname:str):343    if not work_loads: raise HTTPException(503)344    client_id = min(work_loads, key=work_loads.get)345    c = multi_clients.get(client_id)346    if not c: raise HTTPException(503)347    348    tc=class_cache.get(c) or ByteStreamer(c);class_cache[c]=tc349    try:350        msg=await c.get_messages(Config.STORAGE_CHANNEL,mid);m=msg.document or msg.video or msg.audio351        if not m or msg.empty:raise FileNotFoundError352        fid=FileId.decode(m.file_id);fsize=m.file_size;rh=r.headers.get("Range","");fb,ub=0,fsize-1353        if rh:354            rps=rh.replace("bytes=","").split("-");fb=int(rps[0])355            if len(rps)>1 and rps[1]:ub=int(rps[1])356        if(ub>=fsize)or(fb<0):raise HTTPException(416)357        rl=ub-fb+1;cs=1024*1024;off=(fb//cs)*cs;fc=fb-off;lc=(ub%cs)+1;pc=math.ceil(rl/cs)358        body=tc.yield_file(fid,client_id,off,fc,lc,pc,cs);sc=206 if rh else 200359        hdrs={"Content-Type":m.mime_type or "application/octet-stream","Accept-Ranges":"bytes","Content-Disposition":f'inline; filename="{m.file_name}"',"Content-Length":str(rl)}360        if rh:hdrs["Content-Range"]=f"bytes {fb}-{ub}/{fsize}"361        return StreamingResponse(body,status_code=sc,headers=hdrs)362    except FileNotFoundError:raise HTTPException(404)363    except Exception:print(traceback.format_exc());raise HTTPException(500)364 365# =====================================================================================366# --- MAIN EXECUTION BLOCK ---367# =====================================================================================368 369if __name__ == "__main__":370    port = int(os.environ.get("PORT", 8000))371    # Log level ko "info" rakho taaki hamara filter kaam kar sake372    uvicorn.run("app:app", host="0.0.0.0", port=port, log_level="info")373