From e26763f29d8152188f87e4bd78b18c1b877d07af Mon Sep 17 00:00:00 2001 From: FRA3A Date: Fri, 24 Jul 2026 07:37:19 +0400 Subject: [PATCH 1/3] feat(app): auto-open settings modal when no channel is configured --- js/app.js | 16 ++++++++++++++-- js/settings.js | 13 +++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/js/app.js b/js/app.js index 09a007c..68f518c 100644 --- a/js/app.js +++ b/js/app.js @@ -86,8 +86,7 @@ class MultiChatApp { if (openBtn) { openBtn.addEventListener('click', () => { - this.settings.populateForm(); - modalEl.classList.remove('hidden'); + this.openSettingsModal(); }); } @@ -139,6 +138,19 @@ class MultiChatApp { }); this.applyFontSettings(this.settings.settings.fontSize); + + // Auto-open settings modal if no channel address is specified + if (!this.settings.hasAnyChannelConfigured()) { + this.openSettingsModal(); + } + } + + openSettingsModal() { + const modalEl = document.getElementById('settingsModal'); + if (modalEl) { + this.settings.populateForm(); + modalEl.classList.remove('hidden'); + } } applyFontSettings(sizePx) { diff --git a/js/settings.js b/js/settings.js index 20e975e..d224c36 100644 --- a/js/settings.js +++ b/js/settings.js @@ -45,6 +45,19 @@ class SettingsManager { } } + /** + * Returns true if at least one platform channel is configured. + */ + hasAnyChannelConfigured() { + const s = this.settings; + return !!( + (s.twitchChannel && s.twitchChannel.trim()) || + (s.kickChannel && s.kickChannel.trim()) || + (s.vkChannel && s.vkChannel.trim()) || + (s.youtubeChannel && s.youtubeChannel.trim()) + ); + } + /** * Returns a normalized array of all streamer nicknames * (channel names from all platforms + extra nicknames), stripping leading '@'. From 30b7150c5a918f8ea501c3b9f8efa999a62875f6 Mon Sep 17 00:00:00 2001 From: FRA3A Date: Fri, 24 Jul 2026 07:49:17 +0400 Subject: [PATCH 2/3] chore: remove docs directory from git tracking and add README.md --- .../plans/2026-07-24-auto-open-settings.md | 102 ------------------ .../2026-07-24-auto-open-settings-design.md | 46 -------- readme.md | 48 +++++++++ 3 files changed, 48 insertions(+), 148 deletions(-) delete mode 100644 docs/superpowers/plans/2026-07-24-auto-open-settings.md delete mode 100644 docs/superpowers/specs/2026-07-24-auto-open-settings-design.md create mode 100644 readme.md diff --git a/docs/superpowers/plans/2026-07-24-auto-open-settings.md b/docs/superpowers/plans/2026-07-24-auto-open-settings.md deleted file mode 100644 index ea1c78f..0000000 --- a/docs/superpowers/plans/2026-07-24-auto-open-settings.md +++ /dev/null @@ -1,102 +0,0 @@ -# Auto-open Settings Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Automatically open the settings modal on page load if no channel address (Twitch, Kick, VK Live, YouTube) is configured. - -**Architecture:** Add a `hasAnyChannelConfigured()` method to `SettingsManager` in `js/settings.js`, refactor modal open logic in `MultiChatApp` in `js/app.js`, and invoke it upon initialization if no channel is configured. - -**Tech Stack:** Vanilla JavaScript (ES6+), HTML5, CSS3. - -## Global Constraints -- Do not introduce external dependencies. -- Follow existing patterns in `js/settings.js` and `js/app.js`. - ---- - -### Task 1: Add `hasAnyChannelConfigured()` to `SettingsManager` - -**Files:** -- Modify: `js/settings.js:48-52` - -**Interfaces:** -- Produces: `settingsManager.hasAnyChannelConfigured(): boolean` - -- [ ] **Step 1: Implement `hasAnyChannelConfigured()` method in `SettingsManager`** - -In [js/settings.js](file:///C:/FRA3A/projects/multichat-gemini/js/settings.js), add the method `hasAnyChannelConfigured()` to `SettingsManager` class: - -```javascript - /** - * Returns true if at least one platform channel is configured. - */ - hasAnyChannelConfigured() { - const s = this.settings; - return !!( - (s.twitchChannel && s.twitchChannel.trim()) || - (s.kickChannel && s.kickChannel.trim()) || - (s.vkChannel && s.vkChannel.trim()) || - (s.youtubeChannel && s.youtubeChannel.trim()) - ); - } -``` - -- [ ] **Step 2: Verify method functionality** - -Run in browser console or node evaluation: -`window.settingsManager.hasAnyChannelConfigured()` should return `false` when default empty channels are present, and `true` when at least one channel string is non-empty. - -- [ ] **Step 3: Commit changes** - -```bash -git add js/settings.js -git commit -m "feat(settings): add hasAnyChannelConfigured method" -``` - ---- - -### Task 2: Refactor `openSettingsModal()` and auto-open in `MultiChatApp` - -**Files:** -- Modify: `js/app.js:80-92` - -**Interfaces:** -- Consumes: `window.settingsManager.hasAnyChannelConfigured(): boolean` - -- [ ] **Step 1: Add `openSettingsModal()` helper and auto-open trigger in `MultiChatApp`** - -In [js/app.js](file:///C:/FRA3A/projects/multichat-gemini/js/app.js): - -1. Add `openSettingsModal()` method to `MultiChatApp`: -```javascript - openSettingsModal() { - const modalEl = document.getElementById('settingsModal'); - if (modalEl) { - this.settings.populateForm(); - modalEl.classList.remove('hidden'); - } - } -``` - -2. Update `openBtn` listener in `initUI()` to call `this.openSettingsModal()`. - -3. At the end of `initUI()`, check if `!this.settings.hasAnyChannelConfigured()` and call `this.openSettingsModal()`: -```javascript - if (!this.settings.hasAnyChannelConfigured()) { - this.openSettingsModal(); - } -``` - -- [ ] **Step 2: Verification** - -1. Clear `multichat_settings` from `localStorage` in browser devtools. -2. Refresh page. Verify settings modal pops up automatically on launch. -3. Enter a channel (e.g. `teststream`), click "Сохранить". -4. Refresh page. Verify settings modal does NOT pop up automatically. - -- [ ] **Step 3: Commit changes** - -```bash -git add js/app.js -git commit -m "feat(app): auto-open settings modal when no channel is configured" -``` diff --git a/docs/superpowers/specs/2026-07-24-auto-open-settings-design.md b/docs/superpowers/specs/2026-07-24-auto-open-settings-design.md deleted file mode 100644 index 159ecf8..0000000 --- a/docs/superpowers/specs/2026-07-24-auto-open-settings-design.md +++ /dev/null @@ -1,46 +0,0 @@ -# Design Spec: Auto-open Settings Modal When No Channels Are Configured - -## Overview -When MultiChat is loaded, if no channel address (Twitch, Kick, VK Live, YouTube) is specified in `localStorage` settings, the settings modal dialog should open automatically so the user can immediately configure their channels. - -## Requirements -1. Check configured channels upon application initialization. -2. If all 4 channel fields (`twitchChannel`, `kickChannel`, `vkChannel`, `youtubeChannel`) are empty or contain only whitespace: - - Populate the settings form with existing state. - - Open the settings modal (`#settingsModal`). -3. If at least one channel field is specified, do not open the modal automatically. - -## Proposed Architecture & Changes - -### 1. `SettingsManager` (`js/settings.js`) -Add a helper method `hasAnyChannelConfigured()`: -```javascript -hasAnyChannelConfigured() { - const s = this.settings; - return !!( - (s.twitchChannel && s.twitchChannel.trim()) || - (s.kickChannel && s.kickChannel.trim()) || - (s.vkChannel && s.vkChannel.trim()) || - (s.youtubeChannel && s.youtubeChannel.trim()) - ); -} -``` - -### 2. `MultiChatApp` (`js/app.js`) -1. Refactor modal opening into a helper method `openSettingsModal()`: -```javascript -openSettingsModal() { - const modalEl = document.getElementById('settingsModal'); - if (modalEl) { - this.settings.populateForm(); - modalEl.classList.remove('hidden'); - } -} -``` -2. Call `this.openSettingsModal()` in `initUI()` if `!this.settings.hasAnyChannelConfigured()`. - -## Verification Plan -1. Clear `multichat_settings` in `localStorage` (or test with fresh defaults). -2. Open `index.html` in browser — settings modal should pop up automatically. -3. Enter a channel name (e.g., Twitch: `testchannel`), save settings. -4. Reload page — settings modal should NOT pop up automatically. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1b37772 --- /dev/null +++ b/readme.md @@ -0,0 +1,48 @@ +# MultiChat + +Универсальный и легкий мультичат для стримеров на **HTML / CSS / JavaScript**, агрегирующий сообщения с нескольких стриминговых платформ в единую удобную ленту в реальном времени. + +![MultiChat Overview](index.html) *(Интерфейс мультичата готов к работе в браузере и OBS)* + +--- + +## 🚀 Поддерживаемые платформы + +- **Twitch** — прямое подключение через WebSocket IRC (`wss://irc-ws.chat.twitch.tv:443`) с поддержкой IRC-тегов, бейджей и цветов чаттеров. +- **Kick** — WebSocket-подключение через Pusher (`wss://ws-us2.pusher.com`). +- **VK Play Live / VK Video Live** — WebSocket-подключение через Centrifugo (`wss://pubsub.live.vkvideo.ru`) с автоматическим получением гостевых токенов. +- **YouTube Live** — автоматически находит активную трансляцию по хэндлу канала (`@channel`), URL видео или Video ID и подтягивает Live Chat. + +--- + +## ✨ Ключевые возможности + +- **Единый поток сообщений**: Все чаты объединяются в одну ленту с цветовой индикацией платформ. +- **Интеграция смайлов и бейджей**: + - Поддержка глобальных и канальных смайлов **7TV**, **BetterTTV (BTTV)** и **FrankerFaceZ (FFZ)**. + - Кастомные смайлики Kick и глобальные бейджи Twitch/Kick. +- **Умная фильтрация «Чаттерсы общаются»**: + - Сообщения, адресованные другими зрителями друг другу, могут автоматически сворачиваться в кликабельные плашки `[чаттерсы общаются]`, не отвлекая стримера. + - Упоминания стримера **никогда не скрываются**. +- **Автономность**: + - Не требует сложного бэкенда или баз данных. Настройки сохраняются локально в `localStorage`. + - Содержит встроенные механизмы смены CORS-прокси (`Corsproxy.io`, `AllOrigins`, `Codetabs`) и опциональный собственный `proxy.php`. + +--- + +## 🛠️ Запуск и использование + +### Простой запуск (Браузер) +1. Откройте `index.html` в любом современном веб-браузере. +2. Нажмите на иконку ⚙️ **Настройки** в шапке. +3. Укажите название ваших каналов для нужных платформ (Twitch, Kick, VK Live, YouTube). + +### Использование в качестве док-панели OBS Studio +1. Укажите путь к локальному файлу `index.html` или URL вашего веб-сервера. +2. Разместите панель в удобном месте, заполните адреса каналов. + +--- + +## 📄 Лицензия + +MIT License. Свободно для использования и модификации. From 9fcfb5ecd35721c1ff3408bd4e3d84f9527b3872 Mon Sep 17 00:00:00 2001 From: FRA3A Date: Fri, 24 Jul 2026 08:06:09 +0400 Subject: [PATCH 3/3] inprove --- js/settings.js | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/js/settings.js b/js/settings.js index d224c36..44f2afa 100644 --- a/js/settings.js +++ b/js/settings.js @@ -27,7 +27,21 @@ class SettingsManager { try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { - return { ...defaultSettings, ...JSON.parse(saved) }; + const parsed = JSON.parse(saved); + const settings = { ...defaultSettings, ...parsed }; + const stringFields = [ + 'twitchChannel', + 'kickChannel', + 'vkChannel', + 'youtubeChannel', + 'extraNicknames' + ]; + stringFields.forEach(field => { + if (typeof settings[field] !== 'string') { + settings[field] = defaultSettings[field] || ''; + } + }); + return settings; } } catch (e) { console.error('[MultiChat Settings] Error reading settings from localStorage:', e); @@ -50,12 +64,12 @@ class SettingsManager { */ hasAnyChannelConfigured() { const s = this.settings; - return !!( - (s.twitchChannel && s.twitchChannel.trim()) || - (s.kickChannel && s.kickChannel.trim()) || - (s.vkChannel && s.vkChannel.trim()) || - (s.youtubeChannel && s.youtubeChannel.trim()) - ); + return [ + s.twitchChannel, + s.kickChannel, + s.vkChannel, + s.youtubeChannel + ].some(channel => typeof channel === 'string' && channel.trim().length > 0); } /** @@ -65,21 +79,22 @@ class SettingsManager { getStreamerNicknames() { const names = new Set(); - const cleanNick = (str) => (str || '').toLowerCase().trim().replace(/^@+/, ''); + const cleanNick = (str) => (typeof str === 'string' ? str : '').toLowerCase().trim().replace(/^@+/, ''); + const addIfNotEmpty = (str) => { + const cleaned = cleanNick(str); + if (cleaned) names.add(cleaned); + }; // Add channel names - if (this.settings.twitchChannel) names.add(cleanNick(this.settings.twitchChannel)); - if (this.settings.kickChannel) names.add(cleanNick(this.settings.kickChannel)); - if (this.settings.vkChannel) names.add(cleanNick(this.settings.vkChannel)); - if (this.settings.youtubeChannel) names.add(cleanNick(this.settings.youtubeChannel)); + addIfNotEmpty(this.settings.twitchChannel); + addIfNotEmpty(this.settings.kickChannel); + addIfNotEmpty(this.settings.vkChannel); + addIfNotEmpty(this.settings.youtubeChannel); // Add extra nicknames - if (this.settings.extraNicknames) { + if (typeof this.settings.extraNicknames === 'string' && this.settings.extraNicknames) { const extraList = this.settings.extraNicknames.split(','); - extraList.forEach(n => { - const cleaned = cleanNick(n); - if (cleaned) names.add(cleaned); - }); + extraList.forEach(n => addIfNotEmpty(n)); } return Array.from(names);