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 ") 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()