new-qwen/bot/app.py

143 lines
4.9 KiB
Python

from __future__ import annotations
import json
import time
from pathlib import Path
from typing import Any
from urllib import request
from config import BotConfig
from telegram_api import TelegramAPI
STATE_FILE = Path(__file__).resolve().parent.parent / ".new-qwen" / "telegram-state.json"
def load_state() -> dict[str, Any]:
if not STATE_FILE.exists():
return {"offset": None, "sessions": {}}
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
def save_state(state: dict[str, Any]) -> None:
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
def post_json(url: str, payload: dict[str, Any]) -> dict[str, Any]:
data = json.dumps(payload).encode("utf-8")
req = request.Request(
url,
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
with request.urlopen(req, timeout=300) as response:
return json.loads(response.read().decode("utf-8"))
def get_json(url: str) -> dict[str, Any]:
with request.urlopen(url, timeout=60) as response:
return json.loads(response.read().decode("utf-8"))
def ensure_auth(api: TelegramAPI, config: BotConfig, chat_id: int) -> bool:
status = get_json(f"{config.server_url}/api/v1/auth/status")
if status.get("authenticated"):
return True
started = post_json(f"{config.server_url}/api/v1/auth/device/start", {})
api.send_message(
chat_id,
"Qwen OAuth не настроен.\n"
f"Откройте ссылку:\n{started['verification_uri_complete']}\n\n"
f"Потом отправьте /auth_check {started['flow_id']}",
)
return False
def handle_message(api: TelegramAPI, config: BotConfig, state: dict[str, Any], message: dict[str, Any]) -> None:
chat_id = message["chat"]["id"]
user_id = str(message.get("from", {}).get("id", chat_id))
text = (message.get("text") or "").strip()
if not text:
api.send_message(chat_id, "Поддерживаются только текстовые сообщения.")
return
session_key = f"{chat_id}:{user_id}"
session_id = state.setdefault("sessions", {}).get(session_key)
if text == "/start":
api.send_message(chat_id, "new-qwen bot готов. Команды: /auth, /clear.")
return
if text == "/auth":
started = post_json(f"{config.server_url}/api/v1/auth/device/start", {})
api.send_message(
chat_id,
"Откройте ссылку для авторизации Qwen OAuth:\n"
f"{started['verification_uri_complete']}\n\n"
f"После подтверждения отправьте /auth_check {started['flow_id']}",
)
return
if text.startswith("/auth_check"):
parts = text.split(maxsplit=1)
if len(parts) != 2:
api.send_message(chat_id, "Использование: /auth_check <flow_id>")
return
result = post_json(
f"{config.server_url}/api/v1/auth/device/poll",
{"flow_id": parts[1]},
)
if result.get("done"):
api.send_message(chat_id, "Qwen OAuth успешно настроен.")
else:
api.send_message(chat_id, "Авторизация ещё не завершена. Повторите команду через пару секунд.")
return
if text == "/clear":
if session_id:
post_json(f"{config.server_url}/api/v1/session/clear", {"session_id": session_id})
state["sessions"].pop(session_key, None)
api.send_message(chat_id, "Контекст сессии очищен.")
return
if not ensure_auth(api, config, chat_id):
return
api.send_message(chat_id, "Обрабатываю запрос...")
result = post_json(
f"{config.server_url}/api/v1/chat",
{
"session_id": session_id,
"user_id": user_id,
"message": text,
},
)
state["sessions"][session_key] = result["session_id"]
answer = result.get("answer") or "Пустой ответ от модели."
api.send_message(chat_id, answer[:4000])
def main() -> None:
config = BotConfig.load()
api = TelegramAPI(config.token)
state = load_state()
print("new-qwen bot polling started")
while True:
try:
updates = api.get_updates(state.get("offset"), config.poll_timeout)
for update in updates:
state["offset"] = update["update_id"] + 1
message = update.get("message")
if message:
handle_message(api, config, state, message)
save_state(state)
except Exception as exc:
print(f"bot loop error: {exc}")
time.sleep(3)
if __name__ == "__main__":
main()