From 360ebcf8a2f2ec787308b18cf47118154ea9e73c Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 28 Jun 2026 01:01:33 +0300 Subject: [PATCH] fix(integrations): MAX endpoint platform-api.max.ru; bot identity from API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit platform-api2.max.ru serves an incomplete cert chain (verify failed) and botapi.max.ru was the wrong host; use https://platform-api.max.ru (valid cert). Bot name/ID is not a form input and not needed for requests (the token identifies the bot). Remove the editable bot-name field; the Проверить action derives bot id/username/name from the provider (MAX /me, Telegram getMe) and stores them read-only, shown in the list and edit form. Co-Authored-By: Claude Opus 4.8 --- .../backend/hub_platform/integrations/checks.py | 17 +++++++++++++---- .../hub_platform/integrations/serializers.py | 2 ++ .../hub_platform/integrations/services.py | 14 +++++++++----- .../features/integrations/IntegrationForm.tsx | 7 +++---- .../features/integrations/IntegrationsPage.tsx | 2 +- .../src/features/integrations/model.ts | 11 +++++------ 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/backend/hub_platform/integrations/checks.py b/apps/backend/hub_platform/integrations/checks.py index 2825334..b73174d 100644 --- a/apps/backend/hub_platform/integrations/checks.py +++ b/apps/backend/hub_platform/integrations/checks.py @@ -21,7 +21,9 @@ import urllib.request from django.conf import settings DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" -DEFAULT_MAX_BASE_URL = "https://platform-api2.max.ru" +# platform-api2.max.ru отдаёт неполную цепочку сертификата (verify failed); +# рабочий и с валидным сертификатом — platform-api.max.ru. +DEFAULT_MAX_BASE_URL = "https://platform-api.max.ru" DEFAULT_TELEGRAM_BASE_URL = "https://api.telegram.org" CheckResult = tuple[bool, str, dict] @@ -72,9 +74,11 @@ def check_max(*, secret: str, base_url: str) -> CheckResult: status, data = _get(f"{base}/me", headers={"Authorization": secret}) if status != 200: return False, f"MAX ответил {status}", {} + bot_id = data.get("user_id") username = data.get("username") or "" name = data.get("name") or username or "бот подключён" - return True, f"MAX: {name}", {"bot_username": username or name} + meta = {"bot_id": str(bot_id) if bot_id else "", "bot_username": username, "bot_name": name} + return True, f"MAX: {name}", meta return _safe(run) @@ -89,7 +93,12 @@ def check_telegram(*, secret: str, base_url: str) -> CheckResult: status, data = _get(f"{base}/bot{secret}/getMe") if status != 200 or not data.get("ok"): return False, f"Telegram ответил {status}", {} - username = (data.get("result") or {}).get("username") or "" - return True, f"Telegram: @{username}" if username else "Telegram: бот подключён", {"bot_username": username} + result = data.get("result") or {} + bot_id = result.get("id") + username = result.get("username") or "" + name = result.get("first_name") or username or "бот подключён" + meta = {"bot_id": str(bot_id) if bot_id else "", "bot_username": username, "bot_name": name} + detail = f"Telegram: @{username}" if username else "Telegram: бот подключён" + return True, detail, meta return _safe(run) diff --git a/apps/backend/hub_platform/integrations/serializers.py b/apps/backend/hub_platform/integrations/serializers.py index 1736309..38acbd3 100644 --- a/apps/backend/hub_platform/integrations/serializers.py +++ b/apps/backend/hub_platform/integrations/serializers.py @@ -12,7 +12,9 @@ def integration_payload(integration: Integration) -> dict[str, object]: "config": { "baseUrl": integration.config.get("base_url", ""), "defaultModel": integration.config.get("default_model", ""), + "botId": integration.config.get("bot_id", ""), "botUsername": integration.config.get("bot_username", ""), + "botName": integration.config.get("bot_name", ""), }, "status": integration.status, "lastCheckedAt": integration.last_checked_at.isoformat() if integration.last_checked_at else None, diff --git a/apps/backend/hub_platform/integrations/services.py b/apps/backend/hub_platform/integrations/services.py index 5b09db0..0aad210 100644 --- a/apps/backend/hub_platform/integrations/services.py +++ b/apps/backend/hub_platform/integrations/services.py @@ -103,10 +103,14 @@ def test_integration(*, integration: Integration) -> Integration: integration.last_error = "" if ok else detail integration.last_checked_at = timezone.now() update_fields = ["status", "last_error", "last_checked_at", "updated_at"] - # Реальное имя бота из ответа API — авторитетный источник, перезаписываем. - bot_username = meta.get("bot_username") if ok else None - if bot_username: - integration.config = {**integration.config, "bot_username": bot_username} - update_fields.append("config") + # Идентичность бота (id/username/имя) — из ответа API, авторитетный источник. + if ok and meta: + config = {**integration.config} + for key in ("bot_id", "bot_username", "bot_name"): + if meta.get(key): + config[key] = meta[key] + if config != integration.config: + integration.config = config + update_fields.append("config") integration.save(update_fields=update_fields) return integration diff --git a/apps/internal-ui/src/features/integrations/IntegrationForm.tsx b/apps/internal-ui/src/features/integrations/IntegrationForm.tsx index a10e328..7bf54cb 100644 --- a/apps/internal-ui/src/features/integrations/IntegrationForm.tsx +++ b/apps/internal-ui/src/features/integrations/IntegrationForm.tsx @@ -15,7 +15,6 @@ export function IntegrationForm({ initial, onClose, onSaved }: { initial: Integr const [provider, setProvider] = useState(initial?.provider ?? "OPENROUTER"); const [name, setName] = useState(initial?.name ?? ""); const [secret, setSecret] = useState(""); - const [botUsername, setBotUsername] = useState(initial?.config.botUsername ?? ""); const [baseUrl, setBaseUrl] = useState(initial?.config.baseUrl ?? ""); const [defaultModel, setDefaultModel] = useState(initial?.config.defaultModel ?? ""); const [submitting, setSubmitting] = useState(false); @@ -28,7 +27,7 @@ export function IntegrationForm({ initial, onClose, onSaved }: { initial: Integr if (!ready) return; setSubmitting(true); setError(null); - const config = { baseUrl: baseUrl.trim(), defaultModel: defaultModel.trim(), botUsername: botUsername.trim() }; + const config = { baseUrl: baseUrl.trim(), defaultModel: defaultModel.trim() }; try { if (isEdit) { await api(`/api/v1/integrations/${initial.id}/`, { @@ -71,8 +70,8 @@ export function IntegrationForm({ initial, onClose, onSaved }: { initial: Integr {meta.hasModel && ( )} - {meta.hasBotName && ( - + {isEdit && initial.config.botUsername && ( + )} {error &&
{error}
}
diff --git a/apps/internal-ui/src/features/integrations/IntegrationsPage.tsx b/apps/internal-ui/src/features/integrations/IntegrationsPage.tsx index 3b4ebe3..3aac133 100644 --- a/apps/internal-ui/src/features/integrations/IntegrationsPage.tsx +++ b/apps/internal-ui/src/features/integrations/IntegrationsPage.tsx @@ -115,7 +115,7 @@ export function IntegrationsPage() {
- {item.name}{meta.label}{item.config.botUsername ? ` · ${item.config.botUsername}` : ""} + {item.name}{meta.label}{item.config.botUsername ? ` · @${item.config.botUsername}` : item.config.botName ? ` · ${item.config.botName}` : ""}
{item.hasSecret ? •••••••• : —} diff --git a/apps/internal-ui/src/features/integrations/model.ts b/apps/internal-ui/src/features/integrations/model.ts index da4afd8..ff3972e 100644 --- a/apps/internal-ui/src/features/integrations/model.ts +++ b/apps/internal-ui/src/features/integrations/model.ts @@ -8,7 +8,7 @@ export type Integration = { provider: IntegrationProvider; name: string; hasSecret: boolean; - config: { baseUrl: string; defaultModel: string; botUsername: string }; + config: { baseUrl: string; defaultModel: string; botId: string; botUsername: string; botName: string }; status: IntegrationStatus; lastCheckedAt: string | null; lastError: string; @@ -22,15 +22,14 @@ type ProviderMeta = { secretLabel: string; defaultBaseUrl: string; hasModel: boolean; - hasBotName: boolean; testable: boolean; }; export const PROVIDERS: Record = { - OPENROUTER: { label: "OpenRouter", kind: "LLM_PROVIDER", secretLabel: "API-ключ", defaultBaseUrl: "https://openrouter.ai/api/v1", hasModel: true, hasBotName: false, testable: true }, - MAX: { label: "MAX", kind: "MESSENGER", secretLabel: "Токен бота", defaultBaseUrl: "https://platform-api2.max.ru", hasModel: false, hasBotName: true, testable: true }, - TELEGRAM: { label: "Telegram", kind: "MESSENGER", secretLabel: "Токен бота", defaultBaseUrl: "https://api.telegram.org", hasModel: false, hasBotName: true, testable: true }, - WEB: { label: "Web-виджет", kind: "MESSENGER", secretLabel: "", defaultBaseUrl: "", hasModel: false, hasBotName: false, testable: false }, + OPENROUTER: { label: "OpenRouter", kind: "LLM_PROVIDER", secretLabel: "API-ключ", defaultBaseUrl: "https://openrouter.ai/api/v1", hasModel: true, testable: true }, + MAX: { label: "MAX", kind: "MESSENGER", secretLabel: "Токен бота", defaultBaseUrl: "https://platform-api.max.ru", hasModel: false, testable: true }, + TELEGRAM: { label: "Telegram", kind: "MESSENGER", secretLabel: "Токен бота", defaultBaseUrl: "https://api.telegram.org", hasModel: false, testable: true }, + WEB: { label: "Web-виджет", kind: "MESSENGER", secretLabel: "", defaultBaseUrl: "", hasModel: false, testable: false }, }; export const STATUS_META: Record = {