Мультичат на HTML/CSS/JS для агрегации сообщений с нескольких стриминговых платформ в единую ленту с темной темой, гибким шрифтом и поддержкой смайликов. Публичная версия использует PHP-прокси из каталога proxy/ для запросов к Kick, VK Видео Live и YouTube, которые нельзя надёжно выполнить напрямую со статической страницы.
index.html— Главный интерфейс мультичата, шапка со статусами платформ (включая счётчик зрителей Kick#kickViewerCount), модальное окно настроек и динамический cache-busting локальных CSS/JS. CSS запрашивается вheadдо разбораbody, чтобы скрытые элементы не появлялись без стилей.favicon.png— Иконка приложения для вкладки браузера.proxy/index.php— CORS-прокси для разрешённых платформ, который на сервере публикуется какhttps://fra3a.ru/tools/proxy/index.phpи открывается поhttps://fra3a.ru/tools/proxy/. Списки доверенных источников и доменов назначения задаются вALLOWED_ORIGINSиALLOWED_HOST_SUFFIXES; endpoint ограничивает редиректы и размер ответа, пробует IPv4 до IPv6 и требует PHP с расширением cURL. Ошибки записываются вproxy.logрядом со скриптом в каталогеproxy/; заголовокX-Multichat-Request-Idсвязывает браузерную ошибку со строкой[Proxy][request-id]в файле.proxy/settings.example.php— Шаблон необязательной конфигурации купленного HTTP/SOCKS5 upstream-прокси. Копия с именемproxy/settings.phpигнорируется Git и загружается в каталогproxy/рядом с опубликованнымindex.php; через upstream маршрутизируются только запросы к YouTube..github/workflows/deploy.yml.backup— Сохранённый, но не активный шаблон GitHub Actions для деплоя Pages. GitHub не запускает workflow с расширением.backup.css/style.css— Основные стили оформления, темная тема, индикаторы платформ (с тёмно-красным оформлением активного Kick и счётчика зрителей), смайлы, плашки скрытых ответов и скрытие Twitch-бейджей по классу контейнера чата.twitch-user-popup.css— Изолированные стили кликабельных Twitch-ников и popup профиля.
js/utils.js— Вспомогательный модульfetchWithCorsProxyдля обхода браузерных CORS-ограничений Kick, VK Видео Live и YouTube. Для заведомо заблокированных доменов сразу использует собственныйhttps://fra3a.ru/tools/proxy/, затем публичные Corsproxy.io, AllOrigins и Codetabs как аварийный fallback.js/settings.js— Менеджер настроек (сохранение каналов, никнеймов стримера и параметров вlocalStorage). Очищает префиксы@; поддерживаетhideTwitchBadgesдля CSS-скрытия Twitch-бейджиков без удаления из DOM.js/filter.js— Модуль фильтрации: определяющий, является ли сообщение ответом чаттерсов друг другу. Скрывает их в плашку[чаттерсы общаются], если они не адресованы стримеру.js/emotes.js— Парсер и загрузчик нативных Twitch/YouTube, 7TV, BTTV и FFZ смайликов с безопасными URL. Twitch emote ID поддерживают как числовой, так и современный форматemotesv2_*.js/connectors/twitch.js— WebSocket IRC клиент Twitch (wss://irc-ws.chat.twitch.tv:443) с поддержкой IRC tags. Передаёт отдельно отображаемое имяauthorи каноническийloginиз IRC-префикса.kick.js— WebSocket Pusher клиент Kick (wss://ws-us2.pusher.com) и опрос счётчика зрителей через REST API (https://kick.com/api/v2/channels/{channel}).vklive.js— клиент VK Видео Live: сначала подключается к Centrifugo (wss://pubsub.live.vkvideo.ru), а при блокировке WebSocket Origin автоматически переходит на получение последних сообщений через HTTP и собственный CORS-прокси.youtube.js— Поток live-чата YouTube по хэндлу, URL канала или Video ID. Получает initial data из публичной страницы, переключается с Top Chat на полный Live Chat и последовательно обходит GET continuation без API-ключа.
js/twitchUserPopup.js— Twitch-only popup по клику на ник: получает профиль через IVR API с timeout, показывает аватар и ссылки на канал/viewer card. Успешные профили кэшируются, а временные ошибки повторяются при следующем клике.js/app.js— Главный оркестратор приложения, обработка входящих сообщений, рендеринг DOM и интеграция Twitch popup.tests/— Node.js fixture-тесты YouTube initial/continuation payload, VK polling fallback, безопасного HTML-рендеринга, Twitch login/popup, UI-настроек и порядка подключения cache-busted ресурсов.package.json— Командаnpm testдля запуска тестов без дополнительных зависимостей.
- Рабочий прокси размещается отдельно от статики:
proxy/index.phpпубликуется как/tools/proxy/index.php, а необязательный шаблонproxy/settings.example.phpможно разместить рядом для настройки upstream. Используемый frontend endpoint:https://fra3a.ru/tools/proxy/. - Прокси разрешает браузерные запросы с
http://localhost:8088,http://127.0.0.1:8088,https://annacodit.github.io,https://fra3a.ruиhttps://www.fra3a.ru. Для нового адреса фронтенда обновляйте точный Origin вALLOWED_ORIGINS; путь страницы в Origin не входит. fetchWithCorsProxyсначала пробует прямой запрос для CORS-совместимых доменов, затем собственный прокси. Публичные Corsproxy.io, AllOrigins и Codetabs сохранены только как аварийные fallback: бесплатные публичные endpoints не дают гарантий для production, а фактическая проверка показала блокировку Kick и неполный YouTube Live Chat HTML. Не расширяйте цепочку случайными бесплатными прокси; для стабильной замены используйте управляемый платный сервис или собственный allowlist-прокси на edge-платформе вроде Cloudflare Workers.- Чтобы разрешить новую платформу, добавьте её доверенный доменный суффикс в
ALLOWED_HOST_SUFFIXES. CORS не является авторизацией, поэтому для публичного endpoint рекомендуется серверный rate limit. - Kick может отвечать
403на запросы с IP shared-хостинга. Для известных каналовKICK_CHATROOM_IDSвозвращает минимальный совместимый ответ без внешнего запроса; сейчас заданоfra3a => 63014532. Это обеспечивает подключение чата, но не загрузку канальных Kick-смайлов. Для произвольных Kick-каналов нужен доступ хостинга кkick.comлибо переход коннектора на авторизованный официальный Kick API. KickConnectorпериодически опрашивает метаданные канала по адресуhttps://kick.com/api/v2/channels/{channel}каждые 20 секунд (KICK_CONNECTOR_CONFIG.viewerPollIntervalMs = 20000). Когда канал онлайн с активным стримом (data.livestream.viewer_count), количество зрителей отображается в элементе#kickViewerCountвнутри#statusKickтёмно-красным цветом. При завершении стрима или ошибке счётчик очищается.proxy.logсоздаётся автоматически в каталогеproxy/(рядом сindex.php), блокируется на время конкурентной записи и очищается при достижении 5 МиБ.X-Multichat-Log-Status: unavailableозначает, что PHP-процессу не хватает прав записи в каталог. Так как каталог публичный, запретите веб-доступ кproxy.logв панели хостинга или конфигурации Apache.- Контрольная проверка опубликованной версии 6 августа 2026 года: запросы с Origin GitHub Pages, Kick fallback
fra3a, VK polling и YouTube работают. YouTube-страница получена через настроенный upstreamsocks5h; ответ содержитX-Multichat-Proxy: upstream-socks5h. Без рабочего upstream прямой egress shared-хостинга до YouTube ранее завершался таймаутом. - Для маршрутизации YouTube через купленный прокси скопируйте
proxy/settings.example.phpвproxy/settings.php, заполнитеtype,host,port,username,passwordи загрузите в каталог/tools/proxy/рядом сindex.php. Поддерживаютсяhttp,socks5иsocks5h; предпочтителенsocks5h, так как DNS имени назначения также выполняет прокси. Конфигурацию можно вместо файла передать переменными окруженияMULTICHAT_UPSTREAM_PROXY_TYPE,MULTICHAT_UPSTREAM_PROXY_HOST,MULTICHAT_UPSTREAM_PROXY_PORT,MULTICHAT_UPSTREAM_PROXY_USERNAME,MULTICHAT_UPSTREAM_PROXY_PASSWORD. Секретный файл нельзя коммитить или публиковать как текст; при успешном использовании ответ содержитX-Multichat-Proxy: upstream-<type>, а ошибки без учётных данных записываются вproxy.log. - Локальные CSS и JS получают одну версию
Date.now()на загрузку страницы. МассивcssFilesнаходится в раннем loader вhead, аjsFiles— в концеbody; при добавлении ресурса обновляйте соответствующий массив, сохраняя порядок зависимостей. - Клик по нику сообщения Twitch открывает компактный popup с аватаркой из IVR API, ссылкой на канал пользователя и viewer card текущего Twitch-канала. Viewer card открывается через
window.openв отдельном popup-окне; пользователи других платформ клик не обрабатывают. - YouTube-коннектор поддерживает обычные сообщения, Super Chat, Super Sticker, memberships, YouTube emoji, пользовательские бейджи, дедупликацию, request timeout, backoff и отмену устаревших сессий. Если YouTube сообщает об удалении сообщения, локальная строка сохраняется и только помечается CSS-классом
chat-line-deleted. - Минимальный интервал HTML-polling YouTube ограничен пятью секундами: фактический live endpoint может просить опрос каждую секунду и возвращать около 200 КиБ распакованного HTML за запрос, что создаёт избыточный трафик через платный upstream-прокси. Интервал задаётся только в коде константой
YOUTUBE_CONNECTOR_CONFIG.minimumChatPollIntervalMsвjs/connectors/youtube.jsи намеренно не показывается в окне настроек. - VK Видео Live разрешает WebSocket только для доверенных Origin, включая
localhostи собственные домены VK, поэтому GitHub Pages получает403на этапе handshake. В этом случае коннектор опрашиваетv1/blog/<channel>/public_video_stream/chatчерезfetchWithCorsProxy, сортирует сообщения по времени и удаляет повторы по ID. Интервал равен четырём секундам и задаётся только константойVK_LIVE_CONNECTOR_CONFIG.pollIntervalMsвjs/connectors/vklive.js; визуальной настройки для него нет. - Публичный API VK Видео Live возвращает
author.nickColorкак числовой индекс палитры0–15. Коннектор преобразует индекс в тот же HEX-цвет, который использует чат VK, до передачи сообщения в общий рендер;normalizeColorиescapeHTMLдополнительно принимают примитивные значения без ошибки. Полнота чата не зависит от авторизации: polling endpoint возвращает сообщения всех участников. - YouTube-коннектор умеет работать только по сохранённому каналу: открывает
/<channel>/live, извлекает текущий Video ID и при отсутствии эфира повторяет проверку каждые 30 секунд. В опубликованной версии автопоиск и чат проходят через настроенный SOCKS5 upstream; Video ID вручную указывать не обязательно. - Надёжный production-вариант для статического мультичата — официальный YouTube Data API напрямую из браузера. Хэндл преобразуется в channel ID через
channels.list(forHandle=...), текущий эфир ищется черезsearch.list(channelId=...,eventType=live,type=video),videos.list(part=liveStreamingDetails)возвращаетactiveLiveChatId, аliveChatMessages.list(part=id,snippet,authorDetails)— сообщения,nextPageTokenи обязательныйpollingIntervalMillis.www.googleapis.comподдерживает CORS для Origin мультичата; нужен браузерный API-ключ, ограниченный одновременно YouTube Data API и HTTP referrer-ами сайта. OAuth для чтения публичного чата не требуется. Учитывайте действующие квоты: уsearch.listотдельный лимит 100 запросов в сутки, поэтому офлайн-проверка должна выполняться примерно раз в 15 минут, а не каждые 30 секунд; чтение чата следует делать не чаще выданногоpollingIntervalMillisи практически ограничить интервалом не менее 10 секунд. - Получение YouTube-чата без собственного API-ключа технически возможно, но не является равноценным официальному API: внутренний Innertube (
youtubei.js) и HTML/live_chatтребуют прокси при работе в браузере и могут ломаться при изменениях YouTube. Сейчас PHP-прокси направляет YouTube через настроенный SOCKS5 upstream. Альтернативные keyless-варианты: edge-прокси с рабочим egress либо локальный захват открытого чата расширением/desktop-приложением. Обычный YouTube iframe может показать чат, но политика same-origin не позволяет мультичату прочитать и объединить его сообщения. Публичные бесплатные CORS-прокси для production не использовать. - Любой текст чата считается недоверенным:
EmoteManager.parseEmotesсначала заменяет разрешённые HTTPS-изображения на внутренние placeholder-ы, экранирует остальной текст и только затем формирует HTML. - После изменений коннекторов или рендеринга запускать
npm test. - Все настройки хранятся в
localStorageпо ключуmultichat_settings. - Настройка
hideTwitchBadgesпо умолчанию выключена; при включении добавляется классhide-twitch-badgesк общему контейнеру сообщений. CSS скрывает только Twitch-бейджи, включая уже отрисованные, без обхода или повторного рендера DOM. - Сообщения, адресованные стримеру (названия каналов + доп. ники из настроек), никогда не скрываются.
- Плашка
[чаттерсы общаются]является кликабельной и разворачивается для просмотра исходного текста. - Активного GitHub Actions workflow для Pages в репозитории сейчас нет: сохранён только
.github/workflows/deploy.yml.backup. Публикация должна выполняться настройкой Pages из веткиdeployлибо восстановленным workflow с расширением.yml.