diff --git a/app/app.py b/app/app.py index 35e856f..ffb1fb4 100644 --- a/app/app.py +++ b/app/app.py @@ -23,6 +23,7 @@ from datetime import datetime from typing import Dict, List, Optional import logging +import httpx from fastapi import FastAPI, UploadFile, WebSocket, WebSocketDisconnect, Request, Form from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse, Response from fastapi.middleware.cors import CORSMiddleware @@ -170,6 +171,68 @@ class SessionHub: hub = SessionHub() +# ---------- Telegram Bot ---------- + +TELEGRAM_API_URL = f"https://api.telegram.org/bot{SETTINGS.telegram_bot_api_key}" +TELEGRAM_OWNER_ID = SETTINGS.telegram_bot_owner_id + +async def send_telegram_message(chat_id: str, text: str): + """Send a message via Telegram bot.""" + if not SETTINGS.telegram_bot_api_key: + return + async with httpx.AsyncClient() as client: + try: + await client.post(f"{TELEGRAM_API_URL}/sendMessage", json={"chat_id": chat_id, "text": text}) + logger.info("Sent Telegram message to %s", chat_id) + except Exception as e: + logger.error("Failed to send Telegram message: %s", e) + +async def handle_telegram_update(update: dict): + """Process a single Telegram update.""" + if "message" not in update: + return + message = update["message"] + chat_id = message.get("chat", {}).get("id") + from_id = message.get("from", {}).get("id") + text = message.get("text", "") + + if str(from_id) != TELEGRAM_OWNER_ID: + logger.warning("Ignoring Telegram message from non-owner: %s", from_id) + return + + if text == "/start": + await send_telegram_message(str(chat_id), "File Drop Bot active.") + +async def poll_telegram_updates(): + """Poll for Telegram updates and process them.""" + if not SETTINGS.telegram_bot_api_key or not TELEGRAM_OWNER_ID: + logger.info("Telegram bot not configured, skipping polling.") + return + + update_offset = 0 + async with httpx.AsyncClient(timeout=35) as client: + while True: + try: + response = await client.get( + f"{TELEGRAM_API_URL}/getUpdates", + params={"offset": update_offset, "timeout": 30} + ) + updates = response.json().get("result", []) + for update in updates: + await handle_telegram_update(update) + update_offset = update["update_id"] + 1 + except Exception as e: + logger.error("Error polling Telegram updates: %s", e) + await asyncio.sleep(10) # wait before retrying on error + +@app.on_event("startup") +async def startup_event(): + """On app startup, send boot message and start polling.""" + if SETTINGS.telegram_bot_api_key and TELEGRAM_OWNER_ID: + await send_telegram_message(TELEGRAM_OWNER_ID, "File Drop Bot booted up.") + asyncio.create_task(poll_telegram_updates()) + + # ---------- Helpers ---------- def sha1_hex(file_bytes: bytes) -> str: diff --git a/app/config.py b/app/config.py index 5c1bc10..68e9396 100644 --- a/app/config.py +++ b/app/config.py @@ -25,6 +25,8 @@ class Settings: chunked_uploads_enabled: bool = False chunk_size_mb: int = 95 timezone: str = "UTC" + telegram_bot_api_key: str = "" + telegram_bot_owner_id: str = "" def _hash_password(pw: str) -> str: """Return PBKDF2-SHA256 hash of a password.""" @@ -74,6 +76,8 @@ def load_settings() -> Settings: except ValueError: chunk_size_mb = 95 timezone = os.getenv("TIMEZONE", "UTC") + telegram_bot_api_key = os.getenv("TELEGRAM_BOT_API_KEY", "") + telegram_bot_owner_id = os.getenv("TELEGRAM_BOT_OWNER_ID", "") return Settings( admin_password=admin_password, max_concurrent=maxc, @@ -85,4 +89,6 @@ def load_settings() -> Settings: chunked_uploads_enabled=chunked_uploads_enabled, chunk_size_mb=chunk_size_mb, timezone=timezone, + telegram_bot_api_key=telegram_bot_api_key, + telegram_bot_owner_id=telegram_bot_owner_id, )