Skip to content

Latest commit

 

History

History
65 lines (61 loc) · 25.1 KB

File metadata and controls

65 lines (61 loc) · 25.1 KB

MultiChat for Streaming Platforms (Twitch, Kick, VK Live, YouTube)

Обзор проекта

Мультичат на 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 и счётчика зрителей), кастомные scrollbar-ы для чата и модальных окон, смайлы, плашки скрытых ответов и скрытие 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 — Менеджер настроек (сохранение каналов, никнеймов стримера, списка избранных пользователей favoriteUsers, списка игнорируемых пользователей/ботов ignoredUsers, ключевых слов для скрытия blockedKeywords и параметров в localStorage). Очищает префиксы @; поддерживает hideTwitchBadges для CSS-скрытия Twitch-бейджиков без удаления из DOM.
  • js/chatterTracker.js — Трекер первого сообщения пользователя за сессию/день и нативных первых сообщений Twitch.
  • js/raidTracker.js — Трекер лидеров рейдов Twitch (RaidTracker): регистрирует входящие рейды и сохраняет их лидеров в активном состоянии на 10 минут.
  • js/streamerTracker.js — Трекер стримеров Twitch (StreamerTracker): запрашивает средний онлайн зрителей через TwitchTracker API, кэширует в localStorage (multichat_streamer_cache) в компактном формате [avgViewers, timestampSec] с TTL 7 дней и обрабатывает очередь проверок с ограничением 600 мс.
  • js/filter.js — Модуль фильтрации: определяет сообщения, подлежащие сворачиванию в спойлер === (ответы чаттеров друг другу, сообщения стримера с упоминанием конкретных зрителей/ответами им, сообщения от пользователей и ботов из ignoredUsers, а также сообщения с ключевыми словами/фразами из blockedKeywords). Любые сообщения, адресованные стримеру (@упоминание или прямой ответ), а также общие сообщения стримера без упоминаний других пользователей никогда не скрываются.
  • js/emotes.js — Парсер и загрузчик нативных Twitch/YouTube, 7TV, BTTV и FFZ смайликов, а также нативных Twitch GIF (gifs tag Tier 2/3) с безопасными HTTPS URL. Twitch emote ID поддерживают как числовой, так и современный формат emotesv2_*.
  • js/connectors/
    • twitch.js — WebSocket IRC клиент Twitch (wss://irc-ws.chat.twitch.tv:443) с поддержкой IRC tags (включая emotes, gifs, badges, color, reply-*) и команды USERNOTICE (msg-id=raid). Передаёт отдельно отображаемое имя 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/USERNOTICE raid, RaidTracker, 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-страница получена через настроенный upstream socks5h; ответ содержит 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.
  • Поддержка нативных GIF от подписчиков Twitch (Tier 2/3): Twitch IRC передаёт тег gifs=<start>-<end>|<gifID>|<gifURL> в PRIVMSG. EmoteManager извлекает диапазон, валидирует HTTPS URL и вставляет медиа-элемент <img class="chat-gif"> с сохранением описания в title. В CSS гифки адаптивно масштабируются (max-width: min(100%, 260px) и height: auto) при сужении экрана или виджета.
  • После изменений коннекторов или рендеринга запускать npm test.
  • Все настройки хранятся в localStorage по ключу multichat_settings.
  • Настройка hideTwitchBadges по умолчанию выключена; при включении добавляется класс hide-twitch-badges к общему контейнеру сообщений. CSS скрывает только Twitch-бейджи, включая уже отрисованные, без обхода или повторного рендера DOM.
  • Сообщения, адресованные стримеру (названия каналов + доп. ники из настроек через @упоминание или прямой ответ), никогда не скрываются.
  • Сообщения стримера с упоминанием/ответом конкретному зрителю сворачиваются под спойлер аналогично ответам чаттеров; общие сообщения стримера без тегов других пользователей остаются открытыми.
  • Сообщения пользователей или ботов из списка ignoredUsers (настраивается в модальном окне через запятую) сворачиваются под спойлер === (.collapsed-reply), если они не адресованы стримеру.
  • Сообщения, содержащие слова или фразы из списка blockedKeywords (настраивается в модальном окне через запятую), сворачиваются под спойлер === (.collapsed-reply), если они не адресованы стримеру.
  • Сообщения пользователей из списка favoriteUsers (настраивается в модальном окне через запятую) выделяются в чате золотисто-янтарной полосой .chat-line-favorite и бейджиком ⭐ Избранный.
  • Сообщения лидера входящего рейда на Twitch выделяются яркой фиолетовой полосой .chat-line-raid-leader и бейджиком ⚔️ Лидер рейда на заданное в настройках время (параметр raidLeaderDurationMinutes, по умолчанию 10 минут). События рейдов определяются автоматически через IRC USERNOTICE (msg-id=raid).
  • Сообщения зрителей Twitch, являющихся активными стримерами со средним онлайном >= X (настраивается в модальном окне через streamerMinViewers, по умолчанию 20 зрителей), выделяются сине-голубой полосой .chat-line-streamer и бейджиком 📺 {avgViewers}. Данные кэшируются в localStorage по ключу multichat_streamer_cache на 7 дней в компактном формате [avgViewers, timestampSec], а фоновые запросы к TwitchTracker API выполняются последовательно с ограничением 600 мс.
  • Плашка спойлера === (.collapsed-reply) является кликабельной и разворачивается для просмотра исходного текста.
  • Активного GitHub Actions workflow для Pages в репозитории сейчас нет: сохранён только .github/workflows/deploy.yml.backup. Публикация должна выполняться настройкой Pages из ветки deploy либо восстановленным workflow с расширением .yml.