143 lines
4.9 KiB
Python
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()
|