671 lines
24 KiB
Python
671 lines
24 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
import time
|
||
from pathlib import Path
|
||
from typing import Any
|
||
from urllib import error, 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": {},
|
||
"auth_flows": {},
|
||
"active_jobs": {},
|
||
"chat_active_jobs": {},
|
||
"chat_queues": {},
|
||
"pending_approvals": {},
|
||
}
|
||
state = json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
||
state.setdefault("sessions", {})
|
||
state.setdefault("auth_flows", {})
|
||
state.setdefault("active_jobs", {})
|
||
state.setdefault("chat_active_jobs", {})
|
||
state.setdefault("chat_queues", {})
|
||
state.setdefault("pending_approvals", {})
|
||
return state
|
||
|
||
|
||
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 send_text_chunks(api: TelegramAPI, chat_id: int, text: str) -> None:
|
||
normalized = text or "Пустой ответ."
|
||
chunk_size = 3800
|
||
for start in range(0, len(normalized), chunk_size):
|
||
api.send_message(chat_id, normalized[start : start + chunk_size])
|
||
|
||
|
||
def summarize_event(event: dict[str, Any]) -> str | None:
|
||
event_type = event.get("type")
|
||
if event_type == "job_status":
|
||
return event.get("message")
|
||
if event_type == "model_request":
|
||
provider = event.get("provider")
|
||
model = event.get("model")
|
||
if provider and model:
|
||
return f"Думаю над ответом через {provider}/{model}"
|
||
if provider:
|
||
return f"Думаю над ответом через {provider}"
|
||
return "Думаю над ответом"
|
||
if event_type == "tool_call":
|
||
return f"Вызываю инструмент: {event.get('name')}"
|
||
if event_type == "tool_result":
|
||
result = event.get("result", {})
|
||
if isinstance(result, dict) and "error" in result:
|
||
return f"Инструмент {event.get('name')} завершился с ошибкой"
|
||
return f"Инструмент {event.get('name')} завершён"
|
||
if event_type == "error":
|
||
return f"Ошибка: {event.get('message')}"
|
||
if event_type == "approval_result":
|
||
status = event.get("status")
|
||
tool_name = event.get("tool_name")
|
||
if status == "approved":
|
||
return f"Подтверждение получено для {tool_name}"
|
||
return f"Подтверждение отклонено для {tool_name}"
|
||
return None
|
||
|
||
|
||
def format_approval_request(event: dict[str, Any]) -> str:
|
||
return (
|
||
"Нужно подтверждение инструмента.\n"
|
||
f"approval_id: {event.get('approval_id')}\n"
|
||
f"tool: {event.get('tool_name')}\n"
|
||
f"args: {json.dumps(event.get('arguments', {}), ensure_ascii=False)}\n\n"
|
||
f"/approve {event.get('approval_id')}\n"
|
||
f"/reject {event.get('approval_id')}"
|
||
)
|
||
|
||
|
||
def get_auth_flow(state: dict[str, Any], chat_id: int) -> dict[str, Any] | None:
|
||
return state.setdefault("auth_flows", {}).get(str(chat_id))
|
||
|
||
|
||
def start_auth_flow(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
*,
|
||
force_new: bool = False,
|
||
) -> dict[str, Any]:
|
||
existing = get_auth_flow(state, chat_id)
|
||
now = time.time()
|
||
if existing and not force_new and existing.get("expires_at", 0) > now:
|
||
send_text_chunks(
|
||
api,
|
||
chat_id,
|
||
"Авторизация Qwen OAuth ещё не завершена.\n"
|
||
f"Открой ссылку:\n{existing['verification_uri_complete']}\n\n"
|
||
"Бот сам проверит завершение и продолжит работу.",
|
||
)
|
||
return existing
|
||
|
||
started = post_json(f"{config.server_url}/api/v1/auth/device/start", {})
|
||
flow = {
|
||
"flow_id": started["flow_id"],
|
||
"user_code": started.get("user_code"),
|
||
"verification_uri": started.get("verification_uri"),
|
||
"verification_uri_complete": started["verification_uri_complete"],
|
||
"expires_at": started["expires_at"],
|
||
"interval_seconds": started.get("interval_seconds", 2),
|
||
"next_poll_at": now + started.get("interval_seconds", 2),
|
||
"pending_messages": existing.get("pending_messages", []) if existing else [],
|
||
}
|
||
state.setdefault("auth_flows", {})[str(chat_id)] = flow
|
||
send_text_chunks(
|
||
api,
|
||
chat_id,
|
||
"Нужна авторизация Qwen OAuth.\n"
|
||
f"Открой ссылку:\n{flow['verification_uri_complete']}\n\n"
|
||
f"user_code: {flow.get('user_code')}\n"
|
||
"После подтверждения бот сам продолжит работу.",
|
||
)
|
||
return flow
|
||
|
||
|
||
def ensure_auth(api: TelegramAPI, config: BotConfig, state: dict[str, Any], chat_id: int) -> bool:
|
||
status = get_json(f"{config.server_url}/api/v1/auth/status")
|
||
if status.get("ready") or status.get("available_providers"):
|
||
return True
|
||
default_provider = status.get("default_provider")
|
||
fallback_providers = status.get("fallback_providers") or []
|
||
if default_provider != "qwen" and "qwen" not in fallback_providers:
|
||
api.send_message(
|
||
chat_id,
|
||
"На сервере нет доступных model provider-ов. "
|
||
f"Текущий default_provider: {default_provider}. "
|
||
"Для GigaChat/YandexGPT нужно настроить серверные credentials.",
|
||
)
|
||
return False
|
||
start_auth_flow(api, config, state, chat_id)
|
||
return False
|
||
|
||
|
||
def enqueue_pending_message(
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
user_id: str,
|
||
session_key: str,
|
||
text: str,
|
||
) -> None:
|
||
flow = get_auth_flow(state, chat_id)
|
||
if not flow:
|
||
return
|
||
pending_messages = flow.setdefault("pending_messages", [])
|
||
pending_messages.append(
|
||
{
|
||
"user_id": user_id,
|
||
"session_key": session_key,
|
||
"text": text,
|
||
"created_at": int(time.time()),
|
||
}
|
||
)
|
||
|
||
|
||
def start_chat_job(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
user_id: str,
|
||
session_key: str,
|
||
text: str,
|
||
*,
|
||
delayed: bool = False,
|
||
) -> None:
|
||
session_id = state.setdefault("sessions", {}).get(session_key)
|
||
prefix = "Обрабатываю отложенный запрос..." if delayed else "Обрабатываю запрос..."
|
||
api.send_message(chat_id, prefix)
|
||
start_result = post_json(
|
||
f"{config.server_url}/api/v1/chat/start",
|
||
{
|
||
"session_id": session_id,
|
||
"user_id": user_id,
|
||
"message": text,
|
||
},
|
||
)
|
||
state["sessions"][session_key] = start_result["session_id"]
|
||
state.setdefault("active_jobs", {})[start_result["job_id"]] = {
|
||
"job_id": start_result["job_id"],
|
||
"chat_id": chat_id,
|
||
"user_id": user_id,
|
||
"session_key": session_key,
|
||
"session_id": start_result["session_id"],
|
||
"seen_seq": 0,
|
||
"sent_statuses": [],
|
||
}
|
||
state.setdefault("chat_active_jobs", {})[str(chat_id)] = start_result["job_id"]
|
||
|
||
|
||
def enqueue_chat_message(
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
user_id: str,
|
||
session_key: str,
|
||
text: str,
|
||
*,
|
||
delayed: bool = False,
|
||
) -> int:
|
||
queue = state.setdefault("chat_queues", {}).setdefault(str(chat_id), [])
|
||
queue.append(
|
||
{
|
||
"user_id": user_id,
|
||
"session_key": session_key,
|
||
"text": text,
|
||
"delayed": delayed,
|
||
"created_at": int(time.time()),
|
||
}
|
||
)
|
||
return len(queue)
|
||
|
||
|
||
def start_or_queue_chat_job(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
user_id: str,
|
||
session_key: str,
|
||
text: str,
|
||
*,
|
||
delayed: bool = False,
|
||
) -> None:
|
||
active_job_id = state.setdefault("chat_active_jobs", {}).get(str(chat_id))
|
||
if active_job_id:
|
||
queue_size = enqueue_chat_message(
|
||
state,
|
||
chat_id,
|
||
user_id,
|
||
session_key,
|
||
text,
|
||
delayed=delayed,
|
||
)
|
||
api.send_message(
|
||
chat_id,
|
||
f"В этом чате уже есть активный запрос. Сообщение поставлено в очередь: {queue_size}.",
|
||
)
|
||
return
|
||
start_chat_job(
|
||
api,
|
||
config,
|
||
state,
|
||
chat_id,
|
||
user_id,
|
||
session_key,
|
||
text,
|
||
delayed=delayed,
|
||
)
|
||
|
||
|
||
def start_next_queued_job(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
) -> None:
|
||
if state.setdefault("chat_active_jobs", {}).get(str(chat_id)):
|
||
return
|
||
queue = state.setdefault("chat_queues", {}).get(str(chat_id)) or []
|
||
if not queue:
|
||
return
|
||
next_item = queue.pop(0)
|
||
if not queue:
|
||
state["chat_queues"].pop(str(chat_id), None)
|
||
start_chat_job(
|
||
api,
|
||
config,
|
||
state,
|
||
chat_id,
|
||
next_item["user_id"],
|
||
next_item["session_key"],
|
||
next_item["text"],
|
||
delayed=bool(next_item.get("delayed")),
|
||
)
|
||
|
||
|
||
def poll_auth_flow(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
*,
|
||
force: bool = False,
|
||
) -> bool:
|
||
flow = get_auth_flow(state, chat_id)
|
||
if not flow:
|
||
return False
|
||
now = time.time()
|
||
if flow.get("expires_at", 0) <= now:
|
||
state["auth_flows"].pop(str(chat_id), None)
|
||
api.send_message(chat_id, "OAuth flow истёк. Запусти /auth ещё раз.")
|
||
return False
|
||
if not force and now < flow.get("next_poll_at", 0):
|
||
return False
|
||
|
||
try:
|
||
result = post_json(
|
||
f"{config.server_url}/api/v1/auth/device/poll",
|
||
{"flow_id": flow["flow_id"]},
|
||
)
|
||
except error.HTTPError as exc:
|
||
body = exc.read().decode("utf-8", errors="replace")
|
||
state["auth_flows"].pop(str(chat_id), None)
|
||
send_text_chunks(
|
||
api,
|
||
chat_id,
|
||
"Не удалось завершить OAuth flow на сервере.\n"
|
||
f"Ответ сервера: {body}\n"
|
||
"Запусти /auth ещё раз.",
|
||
)
|
||
return False
|
||
|
||
if not result.get("done"):
|
||
interval = result.get("interval_seconds", flow.get("interval_seconds", 2))
|
||
flow["interval_seconds"] = interval
|
||
flow["next_poll_at"] = now + interval
|
||
return False
|
||
|
||
state["auth_flows"].pop(str(chat_id), None)
|
||
api.send_message(chat_id, "Qwen OAuth успешно настроен.")
|
||
for item in flow.get("pending_messages", []):
|
||
start_or_queue_chat_job(
|
||
api,
|
||
config,
|
||
state,
|
||
chat_id,
|
||
item["user_id"],
|
||
item["session_key"],
|
||
item["text"],
|
||
delayed=True,
|
||
)
|
||
if not flow.get("pending_messages"):
|
||
api.send_message(chat_id, "Можно отправлять обычные сообщения.")
|
||
return True
|
||
|
||
|
||
def process_auth_flows(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
) -> None:
|
||
for chat_id_raw in list(state.setdefault("auth_flows", {}).keys()):
|
||
try:
|
||
poll_auth_flow(api, config, state, int(chat_id_raw), force=False)
|
||
except Exception as exc:
|
||
print(f"auth flow poll error for chat {chat_id_raw}: {exc}")
|
||
|
||
|
||
def process_active_jobs(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
) -> None:
|
||
active_jobs = state.setdefault("active_jobs", {})
|
||
pending_approvals = state.setdefault("pending_approvals", {})
|
||
for job_id in list(active_jobs.keys()):
|
||
job_state = active_jobs[job_id]
|
||
poll_result = post_json(
|
||
f"{config.server_url}/api/v1/chat/poll",
|
||
{"job_id": job_id, "since_seq": job_state.get("seen_seq", 0)},
|
||
)
|
||
for event in poll_result.get("events", []):
|
||
seq = int(event.get("seq", 0))
|
||
job_state["seen_seq"] = max(job_state.get("seen_seq", 0), seq)
|
||
if event.get("type") == "approval_request":
|
||
pending_approvals[str(job_state["chat_id"])] = {
|
||
"approval_id": event["approval_id"],
|
||
"job_id": job_id,
|
||
}
|
||
send_text_chunks(
|
||
api,
|
||
int(job_state["chat_id"]),
|
||
format_approval_request(event),
|
||
)
|
||
continue
|
||
summary = summarize_event(event)
|
||
sent_statuses = set(job_state.get("sent_statuses", []))
|
||
if summary and summary not in sent_statuses:
|
||
api.send_message(int(job_state["chat_id"]), summary[:4000])
|
||
sent_statuses.add(summary)
|
||
job_state["sent_statuses"] = sorted(sent_statuses)
|
||
|
||
status = poll_result.get("status")
|
||
if status == "completed":
|
||
state["sessions"][job_state["session_key"]] = poll_result["session_id"]
|
||
send_text_chunks(
|
||
api,
|
||
int(job_state["chat_id"]),
|
||
poll_result.get("answer") or "Пустой ответ от модели.",
|
||
)
|
||
active_jobs.pop(job_id, None)
|
||
state.setdefault("chat_active_jobs", {}).pop(str(job_state["chat_id"]), None)
|
||
pending = pending_approvals.get(str(job_state["chat_id"]))
|
||
if pending and pending.get("job_id") == job_id:
|
||
pending_approvals.pop(str(job_state["chat_id"]), None)
|
||
start_next_queued_job(api, config, state, int(job_state["chat_id"]))
|
||
elif status == "failed":
|
||
send_text_chunks(
|
||
api,
|
||
int(job_state["chat_id"]),
|
||
f"Job завершился с ошибкой: {poll_result.get('error')}",
|
||
)
|
||
active_jobs.pop(job_id, None)
|
||
state.setdefault("chat_active_jobs", {}).pop(str(job_state["chat_id"]), None)
|
||
pending = pending_approvals.get(str(job_state["chat_id"]))
|
||
if pending and pending.get("job_id") == job_id:
|
||
pending_approvals.pop(str(job_state["chat_id"]), None)
|
||
start_next_queued_job(api, config, state, int(job_state["chat_id"]))
|
||
elif status == "canceled":
|
||
send_text_chunks(
|
||
api,
|
||
int(job_state["chat_id"]),
|
||
f"Job отменён: {poll_result.get('error') or 'Canceled by operator'}",
|
||
)
|
||
active_jobs.pop(job_id, None)
|
||
state.setdefault("chat_active_jobs", {}).pop(str(job_state["chat_id"]), None)
|
||
pending = pending_approvals.get(str(job_state["chat_id"]))
|
||
if pending and pending.get("job_id") == job_id:
|
||
pending_approvals.pop(str(job_state["chat_id"]), None)
|
||
start_next_queued_job(api, config, state, int(job_state["chat_id"]))
|
||
|
||
|
||
def cancel_chat_work(
|
||
api: TelegramAPI,
|
||
config: BotConfig,
|
||
state: dict[str, Any],
|
||
chat_id: int,
|
||
actor: str,
|
||
*,
|
||
clear_queue: bool,
|
||
) -> bool:
|
||
canceled = False
|
||
active_job_id = state.setdefault("chat_active_jobs", {}).get(str(chat_id))
|
||
if active_job_id:
|
||
post_json(
|
||
f"{config.server_url}/api/v1/chat/cancel",
|
||
{
|
||
"job_id": active_job_id,
|
||
"actor": actor,
|
||
"reason": "Canceled from Telegram bot",
|
||
},
|
||
)
|
||
canceled = True
|
||
if clear_queue:
|
||
queue = state.setdefault("chat_queues", {}).pop(str(chat_id), [])
|
||
canceled = canceled or bool(queue)
|
||
pending = state.setdefault("pending_approvals", {}).get(str(chat_id))
|
||
if pending and pending.get("job_id") == active_job_id:
|
||
state["pending_approvals"].pop(str(chat_id), None)
|
||
return canceled
|
||
|
||
|
||
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)
|
||
state.setdefault("auth_flows", {})
|
||
|
||
if text == "/start":
|
||
api.send_message(
|
||
chat_id,
|
||
"new-qwen bot готов.\nКоманды: /help, /auth, /status, /session, /cancel, /clear, /approve, /reject.",
|
||
)
|
||
return
|
||
|
||
if text == "/help":
|
||
api.send_message(
|
||
chat_id,
|
||
"Команды:\n"
|
||
"/auth - начать Qwen OAuth\n"
|
||
"/auth_check [flow_id] - проверить авторизацию\n"
|
||
"/status - статус OAuth и сервера\n"
|
||
"/session - показать текущую сессию\n"
|
||
"/cancel - отменить активный запрос и очистить очередь\n"
|
||
"/approve [approval_id] - подтвердить инструмент\n"
|
||
"/reject [approval_id] - отклонить инструмент\n"
|
||
"/clear - очистить контекст",
|
||
)
|
||
return
|
||
|
||
if text.startswith("/approve") or text.startswith("/reject"):
|
||
parts = text.split(maxsplit=1)
|
||
approval = state.setdefault("pending_approvals", {}).get(str(chat_id))
|
||
approval_id = parts[1] if len(parts) == 2 else approval.get("approval_id") if approval else None
|
||
if not approval_id:
|
||
api.send_message(chat_id, "Нет pending approval для этого чата.")
|
||
return
|
||
response = post_json(
|
||
f"{config.server_url}/api/v1/approval/respond",
|
||
{
|
||
"approval_id": approval_id,
|
||
"approved": text.startswith("/approve"),
|
||
"actor": user_id,
|
||
},
|
||
)
|
||
if response.get("status") != "pending":
|
||
state["pending_approvals"].pop(str(chat_id), None)
|
||
api.send_message(
|
||
chat_id,
|
||
f"Approval {approval_id}: {response.get('status')}",
|
||
)
|
||
return
|
||
|
||
if text == "/auth":
|
||
start_auth_flow(api, config, state, chat_id, force_new=True)
|
||
return
|
||
|
||
if text.startswith("/auth_check"):
|
||
parts = text.split(maxsplit=1)
|
||
if len(parts) == 2:
|
||
flow = get_auth_flow(state, chat_id)
|
||
if flow:
|
||
flow["flow_id"] = parts[1]
|
||
flow = get_auth_flow(state, chat_id)
|
||
if not flow:
|
||
api.send_message(chat_id, "Нет активного flow_id. Сначала вызови /auth.")
|
||
return
|
||
if not poll_auth_flow(api, config, state, chat_id, force=True):
|
||
api.send_message(chat_id, "Авторизация ещё не завершена. Повторите команду через пару секунд.")
|
||
return
|
||
|
||
if text == "/status":
|
||
status = get_json(f"{config.server_url}/api/v1/auth/status")
|
||
queue_size = len(state.setdefault("chat_queues", {}).get(str(chat_id), []))
|
||
active_job = state.setdefault("chat_active_jobs", {}).get(str(chat_id))
|
||
send_text_chunks(
|
||
api,
|
||
chat_id,
|
||
"Сервер доступен.\n"
|
||
f"OAuth: {'configured' if status.get('authenticated') else 'not configured'}\n"
|
||
f"ready: {status.get('ready')}\n"
|
||
f"available_providers: {', '.join(status.get('available_providers') or []) or '-'}\n"
|
||
f"default_provider: {status.get('default_provider')}\n"
|
||
f"fallback_providers: {', '.join(status.get('fallback_providers') or []) or '-'}\n"
|
||
f"resource_url: {status.get('resource_url')}\n"
|
||
f"expires_at: {status.get('expires_at')}\n"
|
||
f"tool_policy: {status.get('tool_policy')}\n"
|
||
f"pending_approvals: {status.get('pending_approvals')}\n"
|
||
f"active_job: {active_job}\n"
|
||
f"queued_messages: {queue_size}",
|
||
)
|
||
return
|
||
|
||
if text == "/cancel":
|
||
canceled = cancel_chat_work(
|
||
api,
|
||
config,
|
||
state,
|
||
chat_id,
|
||
user_id,
|
||
clear_queue=True,
|
||
)
|
||
if canceled:
|
||
api.send_message(chat_id, "Активный job отменён, очередь чата очищена.")
|
||
else:
|
||
api.send_message(chat_id, "Для этого чата нет активных или queued jobs.")
|
||
return
|
||
|
||
if text == "/session":
|
||
if not session_id:
|
||
api.send_message(chat_id, "У этого чата ещё нет активной сессии.")
|
||
return
|
||
session = post_json(
|
||
f"{config.server_url}/api/v1/session/get",
|
||
{"session_id": session_id},
|
||
)
|
||
send_text_chunks(
|
||
api,
|
||
chat_id,
|
||
"session_id: {session_id}\nmessages: {count}\nlast_answer: {last_answer}".format(
|
||
session_id=session_id,
|
||
count=len(session.get("messages", [])),
|
||
last_answer=session.get("last_answer") or "-",
|
||
),
|
||
)
|
||
return
|
||
|
||
if text == "/clear":
|
||
cancel_chat_work(
|
||
api,
|
||
config,
|
||
state,
|
||
chat_id,
|
||
user_id,
|
||
clear_queue=True,
|
||
)
|
||
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, state, chat_id):
|
||
enqueue_pending_message(state, chat_id, user_id, session_key, text)
|
||
api.send_message(chat_id, "Сообщение поставлено в очередь до завершения авторизации.")
|
||
return
|
||
|
||
start_or_queue_chat_job(api, config, state, chat_id, user_id, session_key, text)
|
||
|
||
|
||
def main() -> None:
|
||
config = BotConfig.load()
|
||
api = TelegramAPI(config.token)
|
||
state = load_state()
|
||
print("new-qwen bot polling started")
|
||
while True:
|
||
try:
|
||
process_auth_flows(api, config, state)
|
||
process_active_jobs(api, config, state)
|
||
timeout = config.poll_timeout
|
||
if state.get("active_jobs"):
|
||
timeout = min(timeout, 3)
|
||
updates = api.get_updates(state.get("offset"), 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)
|
||
save_state(state)
|
||
except Exception as exc:
|
||
print(f"bot loop error: {exc}")
|
||
time.sleep(3)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|