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:
13
backend/.env.example
Normal file
13
backend/.env.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Telegram API credentials (get from https://my.telegram.org)
|
||||
TELEGRAM_API_ID=12345678
|
||||
TELEGRAM_API_HASH=your_api_hash_here
|
||||
|
||||
# Bot username to chat with (without @)
|
||||
BOT_USERNAME=AnvilForgeBot
|
||||
|
||||
# Session storage directory
|
||||
SESSION_DIR=/opt/telegram-web-client/sessions
|
||||
|
||||
# Server settings
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
1
backend/auth/__init__.py
Normal file
1
backend/auth/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Auth module
|
||||
18
backend/config.py
Normal file
18
backend/config.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Telegram API credentials (get from https://my.telegram.org)
|
||||
API_ID = int(os.getenv("TELEGRAM_API_ID", "0"))
|
||||
API_HASH = os.getenv("TELEGRAM_API_HASH", "")
|
||||
|
||||
# Bot username to chat with
|
||||
BOT_USERNAME = os.getenv("BOT_USERNAME", "telegram_summary_bot")
|
||||
|
||||
# Session storage directory
|
||||
SESSION_DIR = os.getenv("SESSION_DIR", "/opt/telegram-web-client/sessions")
|
||||
|
||||
# Server settings
|
||||
HOST = os.getenv("HOST", "0.0.0.0")
|
||||
PORT = int(os.getenv("PORT", "8000"))
|
||||
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)
|
||||
1
backend/models/__init__.py
Normal file
1
backend/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Models module
|
||||
42
backend/models/schemas.py
Normal file
42
backend/models/schemas.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PhoneAuthRequest(BaseModel):
|
||||
phone: str
|
||||
|
||||
|
||||
class CodeAuthRequest(BaseModel):
|
||||
phone: str
|
||||
code: str
|
||||
phone_code_hash: str
|
||||
|
||||
|
||||
class PasswordAuthRequest(BaseModel):
|
||||
phone: str
|
||||
password: str
|
||||
|
||||
|
||||
class SendMessageRequest(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
id: int
|
||||
text: Optional[str]
|
||||
date: datetime
|
||||
is_outgoing: bool
|
||||
sender_name: Optional[str] = None
|
||||
|
||||
|
||||
class AuthStatus(BaseModel):
|
||||
authenticated: bool
|
||||
phone: Optional[str] = None
|
||||
user_id: Optional[int] = None
|
||||
username: Optional[str] = None
|
||||
|
||||
|
||||
class WebSocketMessage(BaseModel):
|
||||
type: str # "auth_phone", "auth_code", "auth_password", "send_message", "get_history"
|
||||
data: dict
|
||||
6
backend/requirements.txt
Normal file
6
backend/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
telethon>=1.42.0
|
||||
fastapi>=0.128.0
|
||||
uvicorn>=0.40.0
|
||||
websockets>=16.0
|
||||
pydantic>=2.12.0
|
||||
python-dotenv>=1.2.0
|
||||
1
backend/telegram/__init__.py
Normal file
1
backend/telegram/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Telegram module
|
||||
77
backend/telegram/bot_chat.py
Normal file
77
backend/telegram/bot_chat.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from typing import Optional, Callable, List
|
||||
from telethon import events
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from config import BOT_USERNAME
|
||||
from telegram.client import TelegramClientWrapper
|
||||
from models.schemas import MessageResponse
|
||||
|
||||
|
||||
class BotChatManager:
|
||||
def __init__(self, client_wrapper: TelegramClientWrapper):
|
||||
self.client_wrapper = client_wrapper
|
||||
self.bot_entity = None
|
||||
self._message_callback: Optional[Callable] = None
|
||||
|
||||
async def init_bot_chat(self):
|
||||
"""Initialize chat with the bot"""
|
||||
client = self.client_wrapper.client
|
||||
if not client:
|
||||
raise RuntimeError("Client not initialized")
|
||||
|
||||
# Resolve bot entity
|
||||
self.bot_entity = await client.get_entity(BOT_USERNAME)
|
||||
|
||||
# Set up event handler for all messages in bot chat (incoming and outgoing)
|
||||
@client.on(events.NewMessage(chats=self.bot_entity))
|
||||
async def handle_bot_message(event: events.NewMessage):
|
||||
if self._message_callback:
|
||||
msg = event.message
|
||||
response = MessageResponse(
|
||||
id=msg.id,
|
||||
text=msg.text,
|
||||
date=msg.date,
|
||||
is_outgoing=msg.out,
|
||||
sender_name="You" if msg.out else BOT_USERNAME
|
||||
)
|
||||
await self._message_callback(response)
|
||||
|
||||
return self.bot_entity
|
||||
|
||||
def set_message_callback(self, callback: Callable):
|
||||
"""Set callback for incoming messages"""
|
||||
self._message_callback = callback
|
||||
|
||||
async def send_message(self, text: str) -> MessageResponse:
|
||||
"""Send message to the bot"""
|
||||
client = self.client_wrapper.client
|
||||
if not client or not self.bot_entity:
|
||||
raise RuntimeError("Bot chat not initialized")
|
||||
|
||||
msg = await client.send_message(self.bot_entity, text)
|
||||
return MessageResponse(
|
||||
id=msg.id,
|
||||
text=msg.text,
|
||||
date=msg.date,
|
||||
is_outgoing=True,
|
||||
sender_name="You"
|
||||
)
|
||||
|
||||
async def get_history(self, limit: int = 50) -> List[MessageResponse]:
|
||||
"""Get chat history with the bot"""
|
||||
client = self.client_wrapper.client
|
||||
if not client or not self.bot_entity:
|
||||
raise RuntimeError("Bot chat not initialized")
|
||||
|
||||
messages = []
|
||||
async for msg in client.iter_messages(self.bot_entity, limit=limit):
|
||||
messages.append(MessageResponse(
|
||||
id=msg.id,
|
||||
text=msg.text,
|
||||
date=msg.date,
|
||||
is_outgoing=msg.out,
|
||||
sender_name="You" if msg.out else BOT_USERNAME
|
||||
))
|
||||
|
||||
# Return in chronological order
|
||||
return list(reversed(messages))
|
||||
105
backend/telegram/client.py
Normal file
105
backend/telegram/client.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import os
|
||||
import asyncio
|
||||
from typing import Optional, Callable, Any
|
||||
from telethon import TelegramClient
|
||||
from telethon.sessions import StringSession
|
||||
from telethon.errors import SessionPasswordNeededError, PhoneCodeInvalidError
|
||||
from telethon.tl.types import User
|
||||
|
||||
from config import API_ID, API_HASH, SESSION_DIR
|
||||
|
||||
|
||||
class TelegramClientWrapper:
|
||||
def __init__(self, session_id: str):
|
||||
self.session_id = session_id
|
||||
self.session_file = os.path.join(SESSION_DIR, f"{session_id}.session")
|
||||
self.client: Optional[TelegramClient] = None
|
||||
self.phone: Optional[str] = None
|
||||
self.phone_code_hash: Optional[str] = None
|
||||
self._message_callback: Optional[Callable] = None
|
||||
|
||||
async def init_client(self) -> TelegramClient:
|
||||
"""Initialize or restore Telegram client"""
|
||||
os.makedirs(SESSION_DIR, exist_ok=True)
|
||||
|
||||
# Use file-based session for persistence
|
||||
self.client = TelegramClient(
|
||||
self.session_file,
|
||||
API_ID,
|
||||
API_HASH,
|
||||
system_version="4.16.30-vxCUSTOM"
|
||||
)
|
||||
await self.client.connect()
|
||||
return self.client
|
||||
|
||||
async def is_authorized(self) -> bool:
|
||||
"""Check if client is authorized"""
|
||||
if not self.client:
|
||||
await self.init_client()
|
||||
return await self.client.is_user_authorized()
|
||||
|
||||
async def send_code(self, phone: str) -> str:
|
||||
"""Send verification code to phone"""
|
||||
if not self.client:
|
||||
await self.init_client()
|
||||
|
||||
self.phone = phone
|
||||
result = await self.client.send_code_request(phone)
|
||||
self.phone_code_hash = result.phone_code_hash
|
||||
return self.phone_code_hash
|
||||
|
||||
async def sign_in_with_code(self, phone: str, code: str, phone_code_hash: str) -> dict:
|
||||
"""Sign in with verification code"""
|
||||
if not self.client:
|
||||
await self.init_client()
|
||||
|
||||
try:
|
||||
user = await self.client.sign_in(phone, code, phone_code_hash=phone_code_hash)
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"first_name": user.first_name
|
||||
}
|
||||
except SessionPasswordNeededError:
|
||||
return {
|
||||
"success": False,
|
||||
"needs_password": True,
|
||||
"message": "Two-factor authentication required"
|
||||
}
|
||||
except PhoneCodeInvalidError:
|
||||
return {
|
||||
"success": False,
|
||||
"needs_password": False,
|
||||
"message": "Invalid verification code"
|
||||
}
|
||||
|
||||
async def sign_in_with_password(self, password: str) -> dict:
|
||||
"""Sign in with 2FA password"""
|
||||
if not self.client:
|
||||
await self.init_client()
|
||||
|
||||
try:
|
||||
user = await self.client.sign_in(password=password)
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"first_name": user.first_name
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": str(e)
|
||||
}
|
||||
|
||||
async def get_me(self) -> Optional[User]:
|
||||
"""Get current user info"""
|
||||
if not self.client or not await self.is_authorized():
|
||||
return None
|
||||
return await self.client.get_me()
|
||||
|
||||
async def disconnect(self):
|
||||
"""Disconnect client"""
|
||||
if self.client:
|
||||
await self.client.disconnect()
|
||||
1
backend/websocket/__init__.py
Normal file
1
backend/websocket/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# WebSocket module
|
||||
253
backend/websocket/handler.py
Normal file
253
backend/websocket/handler.py
Normal file
@@ -0,0 +1,253 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Dict, Optional
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
import logging
|
||||
|
||||
from telegram.client import TelegramClientWrapper
|
||||
from telegram.bot_chat import BotChatManager
|
||||
from models.schemas import MessageResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: Dict[str, WebSocket] = {}
|
||||
self.clients: Dict[str, TelegramClientWrapper] = {}
|
||||
self.bot_managers: Dict[str, BotChatManager] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, session_id: str):
|
||||
"""Accept WebSocket connection"""
|
||||
await websocket.accept()
|
||||
self.active_connections[session_id] = websocket
|
||||
|
||||
# Initialize Telegram client
|
||||
client_wrapper = TelegramClientWrapper(session_id)
|
||||
await client_wrapper.init_client()
|
||||
self.clients[session_id] = client_wrapper
|
||||
|
||||
logger.info(f"WebSocket connected: {session_id}")
|
||||
|
||||
async def disconnect(self, session_id: str):
|
||||
"""Handle WebSocket disconnection"""
|
||||
if session_id in self.active_connections:
|
||||
del self.active_connections[session_id]
|
||||
|
||||
if session_id in self.clients:
|
||||
await self.clients[session_id].disconnect()
|
||||
del self.clients[session_id]
|
||||
|
||||
if session_id in self.bot_managers:
|
||||
del self.bot_managers[session_id]
|
||||
|
||||
logger.info(f"WebSocket disconnected: {session_id}")
|
||||
|
||||
async def send_message(self, session_id: str, message: dict):
|
||||
"""Send message to specific connection"""
|
||||
if session_id in self.active_connections:
|
||||
await self.active_connections[session_id].send_json(message)
|
||||
|
||||
async def handle_message(self, session_id: str, data: dict):
|
||||
"""Handle incoming WebSocket message"""
|
||||
msg_type = data.get("type")
|
||||
payload = data.get("data", {})
|
||||
|
||||
client = self.clients.get(session_id)
|
||||
if not client:
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": "Client not initialized"}
|
||||
})
|
||||
return
|
||||
|
||||
try:
|
||||
if msg_type == "check_auth":
|
||||
await self._handle_check_auth(session_id, client)
|
||||
|
||||
elif msg_type == "send_code":
|
||||
await self._handle_send_code(session_id, client, payload)
|
||||
|
||||
elif msg_type == "verify_code":
|
||||
await self._handle_verify_code(session_id, client, payload)
|
||||
|
||||
elif msg_type == "verify_password":
|
||||
await self._handle_verify_password(session_id, client, payload)
|
||||
|
||||
elif msg_type == "send_message":
|
||||
await self._handle_send_message(session_id, payload)
|
||||
|
||||
elif msg_type == "get_history":
|
||||
await self._handle_get_history(session_id)
|
||||
|
||||
else:
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": f"Unknown message type: {msg_type}"}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling message: {e}")
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": str(e)}
|
||||
})
|
||||
|
||||
async def _handle_check_auth(self, session_id: str, client: TelegramClientWrapper):
|
||||
"""Check if user is authenticated"""
|
||||
is_auth = await client.is_authorized()
|
||||
|
||||
if is_auth:
|
||||
me = await client.get_me()
|
||||
# Initialize bot chat
|
||||
bot_manager = BotChatManager(client)
|
||||
await bot_manager.init_bot_chat()
|
||||
|
||||
# Set up callback for incoming messages
|
||||
async def on_bot_message(msg: MessageResponse):
|
||||
await self.send_message(session_id, {
|
||||
"type": "new_message",
|
||||
"data": msg.model_dump(mode='json')
|
||||
})
|
||||
|
||||
bot_manager.set_message_callback(on_bot_message)
|
||||
self.bot_managers[session_id] = bot_manager
|
||||
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_status",
|
||||
"data": {
|
||||
"authenticated": True,
|
||||
"user_id": me.id,
|
||||
"username": me.username,
|
||||
"first_name": me.first_name
|
||||
}
|
||||
})
|
||||
else:
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_status",
|
||||
"data": {"authenticated": False}
|
||||
})
|
||||
|
||||
async def _handle_send_code(self, session_id: str, client: TelegramClientWrapper, payload: dict):
|
||||
"""Send verification code"""
|
||||
phone = payload.get("phone")
|
||||
if not phone:
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": "Phone number required"}
|
||||
})
|
||||
return
|
||||
|
||||
phone_code_hash = await client.send_code(phone)
|
||||
await self.send_message(session_id, {
|
||||
"type": "code_sent",
|
||||
"data": {
|
||||
"phone": phone,
|
||||
"phone_code_hash": phone_code_hash
|
||||
}
|
||||
})
|
||||
|
||||
async def _handle_verify_code(self, session_id: str, client: TelegramClientWrapper, payload: dict):
|
||||
"""Verify code and sign in"""
|
||||
phone = payload.get("phone")
|
||||
code = payload.get("code")
|
||||
phone_code_hash = payload.get("phone_code_hash")
|
||||
|
||||
result = await client.sign_in_with_code(phone, code, phone_code_hash)
|
||||
|
||||
if result.get("success"):
|
||||
# Initialize bot chat after successful auth
|
||||
bot_manager = BotChatManager(client)
|
||||
await bot_manager.init_bot_chat()
|
||||
|
||||
async def on_bot_message(msg: MessageResponse):
|
||||
await self.send_message(session_id, {
|
||||
"type": "new_message",
|
||||
"data": msg.model_dump(mode='json')
|
||||
})
|
||||
|
||||
bot_manager.set_message_callback(on_bot_message)
|
||||
self.bot_managers[session_id] = bot_manager
|
||||
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_success",
|
||||
"data": result
|
||||
})
|
||||
elif result.get("needs_password"):
|
||||
await self.send_message(session_id, {
|
||||
"type": "need_password",
|
||||
"data": {"message": result.get("message")}
|
||||
})
|
||||
else:
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_error",
|
||||
"data": {"message": result.get("message")}
|
||||
})
|
||||
|
||||
async def _handle_verify_password(self, session_id: str, client: TelegramClientWrapper, payload: dict):
|
||||
"""Verify 2FA password"""
|
||||
password = payload.get("password")
|
||||
result = await client.sign_in_with_password(password)
|
||||
|
||||
if result.get("success"):
|
||||
# Initialize bot chat after successful auth
|
||||
bot_manager = BotChatManager(client)
|
||||
await bot_manager.init_bot_chat()
|
||||
|
||||
async def on_bot_message(msg: MessageResponse):
|
||||
await self.send_message(session_id, {
|
||||
"type": "new_message",
|
||||
"data": msg.model_dump(mode='json')
|
||||
})
|
||||
|
||||
bot_manager.set_message_callback(on_bot_message)
|
||||
self.bot_managers[session_id] = bot_manager
|
||||
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_success",
|
||||
"data": result
|
||||
})
|
||||
else:
|
||||
await self.send_message(session_id, {
|
||||
"type": "auth_error",
|
||||
"data": {"message": result.get("message")}
|
||||
})
|
||||
|
||||
async def _handle_send_message(self, session_id: str, payload: dict):
|
||||
"""Send message to bot"""
|
||||
bot_manager = self.bot_managers.get(session_id)
|
||||
if not bot_manager:
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": "Not authenticated or bot chat not initialized"}
|
||||
})
|
||||
return
|
||||
|
||||
text = payload.get("text")
|
||||
if not text:
|
||||
return
|
||||
|
||||
msg = await bot_manager.send_message(text)
|
||||
await self.send_message(session_id, {
|
||||
"type": "message_sent",
|
||||
"data": msg.model_dump(mode='json')
|
||||
})
|
||||
|
||||
async def _handle_get_history(self, session_id: str):
|
||||
"""Get chat history"""
|
||||
bot_manager = self.bot_managers.get(session_id)
|
||||
if not bot_manager:
|
||||
await self.send_message(session_id, {
|
||||
"type": "error",
|
||||
"data": {"message": "Not authenticated"}
|
||||
})
|
||||
return
|
||||
|
||||
messages = await bot_manager.get_history()
|
||||
await self.send_message(session_id, {
|
||||
"type": "history",
|
||||
"data": {"messages": [m.model_dump(mode='json') for m in messages]}
|
||||
})
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
Reference in New Issue
Block a user