from pydantic import BaseModel import httpx import json from config import TELEGRAM_URL from ai_service import get_ai_response from database import db_manager TELEGRAM_IP = "149.154.167.220" MAX_TELEGRAM_MESSAGE_LENGTH = 4096 def _sanitize_telegram_text(text: str) -> str: if text is None: return "" normalized = str(text).replace("\r\n", "\n").replace("\r", "\n") # Remove control/surrogate chars that Telegram can reject. cleaned = "".join( ch for ch in normalized if (ch in ("\n", "\t") or ord(ch) >= 32) and not (0xD800 <= ord(ch) <= 0xDFFF) ) return cleaned.strip() def _split_telegram_message(text: str, max_length: int = MAX_TELEGRAM_MESSAGE_LENGTH): if len(text) <= max_length: return [text] parts = [] remaining = text while len(remaining) > max_length: split_at = remaining.rfind("\n", 0, max_length) if split_at == -1: split_at = remaining.rfind(" ", 0, max_length) if split_at == -1: split_at = max_length part = remaining[:split_at].strip() if not part: part = remaining[:max_length] split_at = max_length parts.append(part) remaining = remaining[split_at:].strip() if remaining: parts.append(remaining) return parts class ChatInfo(BaseModel): id: int username: str = None first_name: str = None last_name: str = None class Message(BaseModel): chat: ChatInfo text: str = "" class WebhookData(BaseModel): message: Message = None async def telegram_webhook(data: WebhookData): try: if not data.message or not data.message.text: return {"status": "ok"} telegram_id = data.message.chat.id user_text = data.message.text username = data.message.chat.username first_name = data.message.chat.first_name print(f"--- Processing message from {first_name} ---") if db_manager: db_manager.create_or_update_user(telegram_id, username, first_name, data.message.chat.last_name) ai_answer = await get_ai_response(user_text, telegram_id) if TELEGRAM_URL: try: from config import TELEGRAM_TOKEN async with httpx.AsyncClient(timeout=40.0, verify=False, follow_redirects=True) as client: prepared_text = _sanitize_telegram_text( ai_answer or "Sorry, I couldn't generate a response. Please try again." ) if not prepared_text: prepared_text = "Sorry, I couldn't generate a response. Please try again." message_parts = _split_telegram_message(prepared_text, MAX_TELEGRAM_MESSAGE_LENGTH) for idx, part in enumerate(message_parts, start=1): payload = { "chat_id": telegram_id, "text": part, } print( f"--- Sending Telegram message part {idx}/{len(message_parts)} " f"(chars={len(part)}) ---" ) try: # Use form data for maximal compatibility with Telegram endpoint parsers. response = await client.post(TELEGRAM_URL, data=payload) except Exception: print(f"--- DNS Failed. Forcing Direct IP Routing to {TELEGRAM_IP} ---") forced_ip_url = f"https://{TELEGRAM_IP}/bot{TELEGRAM_TOKEN}/sendMessage" json_body = json.dumps(payload, ensure_ascii=False).encode("utf-8") headers = { "Host": "api.telegram.org", "Content-Type": "application/json; charset=utf-8", "Content-Length": str(len(json_body)), } response = await client.post( forced_ip_url, content=json_body, headers=headers, ) if response.status_code != 200: print(f"--- Telegram payload rejected (part {idx}) ---") print(f"--- Payload: {payload} ---") print( f"--- Telegram Rejected Request: " f"{response.status_code} - {response.text} ---" ) break else: print("--- Success: All Telegram message parts delivered ---") except Exception as send_error: print(f"--- Emergency: Network Blockage Detected: {str(send_error)} ---") return {"status": "ok"} except Exception as e: print(f"Error in webhook: {str(e)}") return {"status": "error", "message": str(e)}