Initial commit: Telegram Web Client with bot chat sync
- Backend: FastAPI + Telethon v2 WebSocket server - Frontend: React + TypeScript + Vite + Zustand - Features: Phone auth, 2FA, real-time bot chat - Fix: Use chats= instead of from_users= to sync messages from all devices - Config: BOT_USERNAME=AnvilForgeBot Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
130
backend/main.py
Normal file
130
backend/main.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import uuid
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from config import HOST, PORT
|
||||
from websocket.handler import manager
|
||||
|
||||
# Frontend dist directory
|
||||
FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
logger.info("Starting Telegram Web Client server...")
|
||||
yield
|
||||
logger.info("Shutting down server...")
|
||||
# Cleanup all connections
|
||||
for session_id in list(manager.active_connections.keys()):
|
||||
await manager.disconnect(session_id)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Telegram Web Client",
|
||||
description="WebSocket-based Telegram bot chat client",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
# CORS configuration
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # Configure appropriately for production
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api")
|
||||
async def api_root():
|
||||
return {"status": "ok", "message": "Telegram Web Client API"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""Main WebSocket endpoint for chat"""
|
||||
session_id = str(uuid.uuid4())
|
||||
|
||||
try:
|
||||
await manager.connect(websocket, session_id)
|
||||
|
||||
# Send session ID to client
|
||||
await manager.send_message(session_id, {
|
||||
"type": "connected",
|
||||
"data": {"session_id": session_id}
|
||||
})
|
||||
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
await manager.handle_message(session_id, data)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.info(f"Client disconnected: {session_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket error: {e}")
|
||||
finally:
|
||||
await manager.disconnect(session_id)
|
||||
|
||||
|
||||
@app.websocket("/ws/{session_id}")
|
||||
async def websocket_with_session(websocket: WebSocket, session_id: str):
|
||||
"""WebSocket endpoint with specific session ID (for reconnection)"""
|
||||
try:
|
||||
await manager.connect(websocket, session_id)
|
||||
|
||||
await manager.send_message(session_id, {
|
||||
"type": "connected",
|
||||
"data": {"session_id": session_id}
|
||||
})
|
||||
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
await manager.handle_message(session_id, data)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.info(f"Client disconnected: {session_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket error: {e}")
|
||||
finally:
|
||||
await manager.disconnect(session_id)
|
||||
|
||||
|
||||
# Mount static files if frontend is built
|
||||
if FRONTEND_DIR.exists():
|
||||
app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
|
||||
|
||||
@app.get("/")
|
||||
async def serve_index():
|
||||
return FileResponse(FRONTEND_DIR / "index.html")
|
||||
|
||||
@app.get("/{full_path:path}")
|
||||
async def serve_spa(full_path: str):
|
||||
# Serve static file if exists, otherwise index.html for SPA
|
||||
file_path = FRONTEND_DIR / full_path
|
||||
if file_path.exists() and file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
return FileResponse(FRONTEND_DIR / "index.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host=HOST, port=PORT)
|
||||
Reference in New Issue
Block a user