import os import re import asyncio import uvicorn from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from telethon import TelegramClient from contextlib import asynccontextmanager # --- CONFIG --- API_ID = 21934109 API_HASH = 'e7e8c554b9ff88d180983996c33bdf27' # Extracted Peer ID from your web link (-1003794439741) # Telethon requires the -100 prefix dropped for entity lookups via ID integer TARGET_GROUP_ID = -1003794439741 # Dynamically bind to Hugging Face's required routing port PORT = int(os.environ.get("PORT", 7860)) state = {"my_id": None, "group_entity": None} # Pointing explicitly to the absolute path in the HF container client = TelegramClient('/code/num_lookup_session', API_ID, API_HASH) def clean_value(val: str, to_title: bool = False) -> str: if not val: return "N/A" cleaned = val.strip().strip('`').strip() if cleaned.upper() in ["NA", "N/A", "NONE", ""]: return "N/A" return cleaned.title() if to_title else cleaned def parse_multiple_records(text: str) -> list: record_blocks = re.split(r'⭐\s*RECORD\s*#\d+', text, flags=re.IGNORECASE) parsed_records = [] for block in record_blocks[1:]: if not block.strip(): continue name_match = re.search(r'Name:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) father_match = re.search(r'Father\'s Name:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) address_match = re.search(r'Address:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) circle_match = re.search(r'Circle:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) alternate_match = re.search(r'Alternate:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) id_match = re.search(r'\bID:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) email_match = re.search(r'Email:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) truecaller_match = re.search(r'Truecaller:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) mobile_match = re.search(r'Mobile:\s*(.*?)(?:\n|$)', block, re.IGNORECASE) parsed_records.append({ "mobile": clean_value(mobile_match.group(1) if mobile_match else None), "name": clean_value(name_match.group(1) if name_match else None, to_title=True), "father_name": clean_value(father_match.group(1) if father_match else None, to_title=True), "address": clean_value(address_match.group(1) if address_match else None, to_title=True), "circle": clean_value(circle_match.group(1) if circle_match else None), "alternate": clean_value(alternate_match.group(1) if alternate_match else None), "id": clean_value(id_match.group(1) if id_match else None), "email": clean_value(email_match.group(1) if email_match else None), "truecaller": clean_value(truecaller_match.group(1) if truecaller_match else None, to_title=True) }) return parsed_records async def ensure_connected(): if not client.is_connected(): print("[CONNECTION] Client disconnected. Connecting now...") await client.connect() try: await client.get_me() except Exception: print("[CONNECTION] Session link broken. Reconnecting session layer...") await client.disconnect() await client.connect() @asynccontextmanager async def lifespan(app: FastAPI): print("[STARTUP] Connecting directly to Telegram Network...") await client.connect() if not await client.is_user_authorized(): print("!!! NOT LOGGED IN. Please verify your session file !!!") else: print("Telethon Connected and Authorized.") me = await client.get_me() state["my_id"] = me.id try: print(f"[STARTUP] Fetching and caching entity for Group ID: {TARGET_GROUP_ID}") # Group must be in the account's dialog history for this to find it instantly state["group_entity"] = await client.get_input_entity(TARGET_GROUP_ID) print("[STARTUP] Group entity caching completed successfully.") except Exception as e: print(f"[CRITICAL STARTUP ERROR] Could not cache Group Entity: {e}") yield print("[SHUTDOWN] Disconnecting Telegram Client...") await client.disconnect() app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["https://crm.gudmed.in"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/") async def root_index(): return {"status": "healthy", "service": "Telegram Truecaller Group Polling API"} @app.get("/lookup") async def lookup(phone: str): clean_phone = str(phone)[-10:].strip() await ensure_connected() if not state["my_id"] or not state["group_entity"]: raise HTTPException(status_code=503, detail="Telegram client components are uninitialized.") try: # Determine the latest message ID in the group before tracking try: last_messages = await client.get_messages(state["group_entity"], limit=1) start_msg_id = last_messages[0].id if last_messages else 0 except Exception: await ensure_connected() last_messages = await client.get_messages(state["group_entity"], limit=1) start_msg_id = last_messages[0].id if last_messages else 0 command_payload = f"/num {clean_phone}" print(f"[API] Dispatching search request to Group: {command_payload}") await client.send_message(state["group_entity"], command_payload) # Poll up to 50 iterations for group environments due to latency differences for i in range(50): await asyncio.sleep(0.5) try: # Fetch more messages (limit=5) because group chatter can pass quickly messages = await client.get_messages(state["group_entity"], limit=5) except Exception as loop_err: print(f"[API WARNING] Polling read failed on step {i}: {loop_err}. Reconnecting...") await ensure_connected() continue for m in messages: # Target messages that appeared AFTER our request, and are NOT sent by us if m.id > start_msg_id and m.sender_id != state["my_id"]: if not m.text or any(x in m.text for x in ["Fetching", "Please wait", "Searching", "Typing", "/num"]): continue # Validate that this payload matches our tracked query sequence query_match = re.search(r'Query:\s*`?(\d+)`?', m.text, re.IGNORECASE) if query_match and query_match.group(1).endswith(clean_phone): print(f"[API] Targeted response packet found in group on iteration step {i}!") total_records_match = re.search(r'Total Records:\s*(\d+)', m.text, re.IGNORECASE) total_count = int(total_records_match.group(1)) if total_records_match else 0 return { "status": "success", "phone": clean_phone, "total_records": total_count, "records": parse_multiple_records(m.text), "full_text": m.text } raise HTTPException(status_code=504, detail="Timeout: Target bot in group didn't answer with query verification.") except HTTPException: raise except Exception as e: print(f"Lookup Error: {e}") raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=PORT, loop="asyncio")