Files
chatballs/apps/internal-ui/src/shared/providers.ts
T
AndreyandClaude Opus 5 31985fc35c ✨ feat(vk): канал ВКонтакте — сообщество как точка входа
Сообщения сообщества ВКонтакте принимаются через Bots Long Poll: тот же
способ, что у Telegram и MAX, поэтому подключению не нужен ни публичный
адрес, ни доступ извне — установка за NAT работает наравне с остальными.

Позиция потока живёт в poll_marker, адрес сервера и ключ — в памяти
процесса: их выдают на несколько часов, и колонка под них означала бы
запись в базу на каждом цикле опроса. Ответы failed 1-3 восстанавливаются
в том же цикле, иначе подключение висело бы с протухшим ключом до
перезапуска воркера.

Идентификатор сообщества владелец не вводит: его называет сам ключ
доступа, и проверка подключения кладёт его в конфигурацию — как имя бота
у Telegram и MAX. Проверка заодно смотрит настройки сообщества: без Long
Poll и события о входящем сообщении приём невозможен, и об этом честнее
сказать сразу, а не молчать зелёным статусом. Настройки чужого сообщества
при этом не меняются. Нехватка прав у ключа объясняется словами: ВКонтакте
отвечает на неё английским «no access», из которого не видно, что включить.

Имя, логин и фото отправителя ВКонтакте в апдейте не присылает — их
забирает один users.get на пачку сообщений, а не на каждое: на оживлённом
сообществе запрос на реплику упёрся бы в частоту обращений.

Телефона и кнопки «поделиться контактом» у ВКонтакте нет, поэтому просьба
уходит обычным сообщением, как и почтой. Голосовые принимаются, но не
отправляются: провайдер ждёт ogg/opus, а композер пишет webm.

Токен уходит строкой запроса — заголовка авторизации у ВКонтакте нет.
Поэтому адрес больше нигде не печатается как есть: и журнал, и поле
последней ошибки подключения, которое видно в интерфейсе, проходят через
маскирование секретных параметров.

Проверено на живом сообществе: проверка подключения, опрос Long Poll,
приём текста, голосового и фото с подстановкой имени, логина и аватара.
Тесты транспорта и проверки подключения, каталоги переводов, ruff из
корня, tsc и vitest по internal-ui.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:02:20 +03:00

50 lines
2.6 KiB
TypeScript

import { t } from "../i18n";
/* Провайдеры подключений: MAX / Telegram / ВКонтакте / Web Chat / Email.
Общий словарь для всего приложения — раньше он лежал в демо-данных диалогов,
и продуктовые экраны каналов зависели от файла с примерами.
Провайдеры различаются точкой и коротким лейблом на своих токенах
(ADR-HUB-0013), а не бренд-логотипами. */
export type ProviderKey = "EMAIL" | "MAX" | "TG" | "VK" | "WEB";
export const providerMeta: Record<ProviderKey, { label: string; short: string; color: string; bg: string }> = {
EMAIL: { label: "Email", short: "Email", color: "#d48806", bg: "#fff7e6" },
MAX: { label: "MAX", short: "MAX", color: "#6b5be0", bg: "#f2f0ff" },
TG: { label: "Telegram", short: "TG", color: "#2f8fd0", bg: "#eaf6fd" },
VK: { label: "ВКонтакте", short: "VK", color: "#0077ff", bg: "#e8f2ff" },
WEB: { label: "Web Chat", short: "Web", color: "#0f9b8e", bg: "#e8f7f4" },
};
/** Код провайдера из API → ключ словаря. */
const BY_CODE: Record<string, ProviderKey> = {
EMAIL: "EMAIL",
MAX: "MAX",
TELEGRAM: "TG",
TG: "TG",
VK: "VK",
WEB: "WEB",
WEBCHAT: "WEB",
};
export function providerKey(code: string): ProviderKey | null {
return BY_CODE[code] ?? null;
}
/* Плитка подключения (дизайн-базлайн v2, кадры K1/K5 «Контакты», G1/G3 «Агенты»):
марка в 14% на подложке карточки, глиф — цветом марки. Одна таблица на всё
приложение: список контактов, карточка контакта и раздел «Агенты». */
export const channelMap = {
EMAIL: { full: "Email", color: "#d48806", bg: "color-mix(in srgb, #fa8c16 14%, var(--surface-card))" },
MAX: { full: "MAX", color: "#5b5bd6", bg: "color-mix(in srgb, #5b5bd6 14%, var(--surface-card))" },
TG: { full: "Telegram", color: "#229ed9", bg: "color-mix(in srgb, #229ed9 14%, var(--surface-card))" },
VK: { full: "ВКонтакте", color: "#0077ff", bg: "color-mix(in srgb, #0077ff 14%, var(--surface-card))" },
WEB: { full: t("common.web_widget"), color: "#0f9b8e", bg: "color-mix(in srgb, #0f9b8e 14%, var(--surface-card))" },
} satisfies Record<ProviderKey, { full: string; color: string; bg: string }>;
export function providerLabel(code: string): string {
const key = providerKey(code);
return key ? providerMeta[key].label : code;
}