Мультичат на HTML/CSS/JS для агрегации сообщений с нескольких стриминговых платформ в единую ленту с темной темой, гибким шрифтом и поддержкой смайликов. Публичная версия использует PHP-прокси из каталога proxy/ для запросов к Kick, VK Видео Live и YouTube, которые нельзя надёжно выполнить напрямую со статической страницы.
index.html— Главный интерфейс мультичата, шапка со статусами платформ, модальное окно настроек с однострочным расположением каналов и динамический 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/proxy.settings.example.php— Шаблон необязательной конфигурации купленного HTTP/SOCKS5 upstream-прокси. Копия с именемproxy/proxy.settings.phpигнорируется Git и загружается в каталогproxy/рядом с опубликованнымindex.php; через upstream маршрутизируются запросы к YouTube и Kick..github/workflows/deploy.yml.backup— Сохранённый, но не активный шаблон GitHub Actions для деплоя Pages. GitHub не запускает workflow с расширением.backup.css/style.css— Основные стили оформления, темная тема, индикаторы платформ, кастомные 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/sevenTvTracker.js— Трекер цветов никнеймов и косметических раскрасок (paints) 7TV для зрителей Twitch (SevenTvTracker). При старте загружает глобальные краски через GraphQL (https://7tv.io/v3/gql), парсит линейные/радиальные градиенты, URL-текстуры, фильтры свеченияdrop-shadowи цвета. Для пользователей Twitch запрашивает стиль через GraphQLuserByConnection(или REST/v3/users/twitch/:idс сопоставлением из кэша красок). Кэширует данные вlocalStorage(multichat_7tv_cache) в компактном формате с TTL 7 дней (для найденных) и 1 день (для 404), а очередь запросов троттлит с задержкой 300 мс и лимитом в 100 элементов. При сбоях прямого соединения автоматически используетfetchWithCorsProxy.js/filter.js— Модуль фильтрации: определяет сообщения, подлежащие сворачиванию в спойлер===(ответы чаттеров друг другу, сообщения стримера с упоминанием конкретных зрителей/ответами им, сообщения от пользователей и ботов изignoredUsers, а также сообщения с ключевыми словами/фразами изblockedKeywords). Любые сообщения, адресованные стримеру (@упоминаниеили прямой ответ), а также общие сообщения стримера без упоминаний других пользователей никогда не скрываются.js/emotes.js— Парсер и загрузчик нативных Twitch/YouTube, 7TV, BTTV и FFZ смайликов, а также нативных Twitch GIF (gifstag 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 используется для определения chatroom ID.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/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 получает chatroom ID автоматически через API v2/v1 и тот же upstream-прокси, что YouTube. Жёстко заданных соответствий каналов и комнат нет. Поле Kick принимает имя,
@имяили ссылкуhttps://kick.com/имя; числовой ввод тоже считается именем канала. При сбое разрешения канала коннектор повторяет попытку через 15 секунд, лимит каждого HTTP-запроса вместе с чтением JSON — 60 секунд. Браузер кэширует ID на 7 дней, устаревшие записи прежнего формата обновляются через API. -
PHP кэширует только проверенные JSON-ответы Kick с совпадающим
slugи положительнымchatroom.idна 15 минут. Кэш расположен вsys_get_temp_dir()в файлеmultichat-kick-<sha1 каталога скрипта>.json, ограничен 200 записями/5 МиБ, используетflock; ошибки записи не мешают подключению. Ответ из кэша отмечаетсяX-Multichat-Proxy: kick-cache. -
proxy.logсоздаётся автоматически в каталогеproxy/(рядом сindex.php), блокируется на время конкурентной записи и очищается при достижении 5 МиБ.X-Multichat-Log-Status: unavailableозначает, что PHP-процессу не хватает прав записи в каталог. Так как каталог публичный, запретите веб-доступ кproxy.logв панели хостинга или конфигурации Apache. -
Контрольная проверка опубликованной версии 6 августа 2026 года: запросы с Origin GitHub Pages, VK polling и YouTube работают. YouTube-страница получена через настроенный upstream
socks5h; ответ содержитX-Multichat-Proxy: upstream-socks5h. Без рабочего upstream прямой egress shared-хостинга до YouTube ранее завершался таймаутом. -
Для маршрутизации YouTube и Kick через купленный прокси скопируйте
proxy/proxy.settings.example.phpвproxy/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. Каталог подготовки файлов для ручной загрузки владельцем:C:\FRA3A\www\fra3a.ru\tools\proxy; при обновлении копируется толькоindex.php, файл с паролями сохраняется. Секретный файл нельзя коммитить или публиковать как текст; при успешном использовании ответ содержит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. Если 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 минут). События рейдов определяются автоматически через IRCUSERNOTICE(msg-id=raid). -
Сообщения зрителей Twitch, являющихся активными стримерами со средним онлайном >= X (настраивается в модальном окне через
streamerMinViewers, по умолчанию 20 зрителей), выделяются сине-голубой полосой.chat-line-streamerи бейджиком📺 {avgViewers}. Данные кэшируются вlocalStorageпо ключуmultichat_streamer_cacheна 7 дней в компактном формате[avgViewers, timestampSec], а фоновые запросы к TwitchTracker API выполняются последовательно с ограничением 600 мс. -
Поддержка раскрасок (paints) и цветов никнеймов 7TV (Twitch): Twitch IRC передаёт
user-idв тегахPRIVMSG. Если в настройках включен параметрenableSevenTvColors(по умолчанию включен),MultiChatAppзапрашивает стиль черезSevenTvTracker. При наличии раскраски (градиент или текстура) никнейм получает класс.seventv-paintedсо свойствами-webkit-background-clip: text,-webkit-text-fill-color: transparent, текстурой/градиентом вbackground-imageи фильтромfilter: drop-shadow(...). При отсутствии краски, но наличии кастомного цвета применяется обычный цвет и тень. Если стиля в кэше нет, запрос ставится в фоновую очередь с троттлингом 300 мс. При получении ответа стили динамически обновляются в DOM через событиеonStyleDetectedс плавной CSS-анимацией. В демо-сообщениях чата добавлен пример сообщения отfra3aс краской "Heavy Rain" (радиальный градиент и неоновое свечение). -
Плашка спойлера
===(.collapsed-reply) является кликабельной и разворачивается для просмотра исходного текста. -
Активного GitHub Actions workflow для Pages в репозитории сейчас нет: сохранён только
.github/workflows/deploy.yml.backup. Публикация должна выполняться настройкой Pages из веткиdeployлибо восстановленным workflow с расширением.yml. -
KickConnectorиVkLiveConnectorотменяют запросы настройки при смене/отключении канала и проверяют поколение соединения после асинхронных операций. Обработчики WebSocket привязаны к конкретному сокету: события предыдущей сессии не меняют статус, не публикуют сообщения и не запускают переподключение. -
Таймаут
YoutubeConnector.fetchTextохватывает получение заголовков и полное чтение тела ответа. Отключение канала немедленно завершает ожидание сAbortError; таймаут приводит к обычному повтору polling. -
VK polling получает последние 20 сообщений с паузой 3 секунды после ответа, без дозагрузки истории. При более чем 20 новых сообщениях между ответами возможны пропуски; уменьшение интервала не гарантирует полноту. Собственного таймаута запроса у VK polling пока нет.
-
Регрессионные тесты коннекторов покрывают смену/отключение канала во время загрузки заголовков и тела ответа, запоздалые события WebSocket, отмену и таймаут чтения тела YouTube.
-
Проверка 8 сентября 2026: обновлённый локальный PHP с действующим SOCKS5-прокси получил реальные данные
fra3a,xqc,n3on; опубликованный endpoint также вернулtrainwreckstv. Pusher подтвердил подписку на чатыfra3a,xqc,n3on. Проверка подписки не гарантирует постоянную доступность внутреннего API Kick. После изменений в репозитории требуется ручная загрузка PHP на хостинг и публикация фронтенда. -
Ротация ключа Kick 15 октября 2026: Kick отключил прежний публичный Pusher-ключ 32cbd69e4b950bf97679 (ошибка Pusher 4001). Новый рабочий ключ — 34bf7a0ff419a2a775b9 (кластер us2). KickConnector поддерживает авто-ротацию ключей при 4001, сохраняет рабочий ключ в localStorage (multichat_kick_pusher_key) и отвечает на pusher:ping.