# TTWChatMessageServer
## 簡介
`TTWChatMessageServer` 是一個聊天室訊息服務器,可對接 **TikTok**、**Twitch**、**Kick**、**Odysee** 與 **Youtube** 的直播聊天室訊息,支援以下功能:
- 即時接收聊天室訊息
- 支援禮物、關注、加入、分享等事件
- 可推送到 **Bark** 或 **Socket**
- 可透過簡單 HTTP 接口開關服務
- 可透過簡單 HTTP 接口快速修改BARK/SocketAPI配置
---
## 最近更新
### 贊助廣告定時器重複觸發修正
- **恢復排程前先清空既有 timer**:`resumeAdTimers()` 現在會先執行 `clearAllAdTimers()`,避免啟動流程與 TikTok 重連成功流程都呼叫恢復時,讓同一則廣告被排程多次
- **同一廣告 ID 防重複排程**:恢復每則廣告前會再呼叫 `clearAdTimer(ad.id)`,確保同一個 `adId` 只保留一個有效定時器
- **既有執行中進程注意**:若修正前已經產生重複 timer,需要重啟 `TikTok.js` 一次,之後新的恢復流程才會完全接管
### G#clip 剪輯系統修正
- **指令不再洩漏**:`G#clip` 處理後即 `return`,不再被當一般聊天訊息轉發、送去翻譯或寫入過濾器(原 log 中「G#clip」被當普通訊息送出 + 觸發翻譯 API)
- **剪輯訊息品質**:原本回傳 `null FitPerfectSlothOSsloth-VOD2-62CGySUmMP8`(title 為空顯示 null、只有剪輯 ID),改為組合成可點擊的 `https://clips.twitch.tv/<id>` 連結;未指定標題時顯示預設文案
- **權限限制**:原本任何人輸入 `G#clip` 都能建立剪輯,現在限縮為**主播本人、訂閱者或追隨者**
- **錯誤處理**:新增 `createClip` 失敗的 catch,推送錯誤原因到疊加層與 log
- **順帶修正 G#Ad 訂閱者路徑**:原本訂閱者發 `G#Ad` 時,非同步訂閱檢查讓原始指令文字洩漏進一般聊天,現改為 `await` 檢查並 `return`
### 贊助廣告頭像改進
改進前後差異:
| 情境 | 改進前 | 改進後 |
| ------ | -------- | -------- |
| 訊息文字內貼圖片網址(如 `…/Neuro2.png?raw=true`) | 該網址只當廣告文字,頭像仍是 Twitch 頭貼 | **自動偵測為頭像**,並從文字中移除該網址 |
| 更新廣告時沒帶 `icon=` | `targetAd.iconURL = iconURL` 把頭像**覆寫成空白**,回退成 Twitch 頭貼 | **保留原本頭像**,不會被洗掉 |
| 定時器 / 重啟恢復發送 | 用 `ad.iconURL \|\| ''`,iconURL 為空時**沒頭像**,與即時發送不一致 | 已解析的頭像(含 Twitch fallback)**存入廣告紀錄**,三條路徑用同一張圖 |
| 更新廣告時沒帶 `user=` | 自訂顯示名稱被覆寫回 Twitch 名稱 | **保留原 `overlayUser`** |
### 關鍵字統計集中管理改進
- **統計統一由 Server.js 管理**:`message_stats.json` 現在只有 Server.js 會寫入,TikTok.js 不再直接寫檔,改為退出時把最終統計快照回傳給 Server.js 合併
- **寫檔只增不減(max-merge)**:每次寫入前先讀取現有檔案,與記憶體統計做「取較大值」合併後才寫入,避免 TikTok.js 未運行時舊的快取快照覆蓋掉完整統計
- **手動清空統計**:`/keyword` 頁面新增「🗑️ 清空統計」按鈕(POST `/keyword/clear`),一次清空記憶體、`message_stats.json`,並同步通知 TikTok.js 清空(若在運行)
- **子進程統計併入**:Server.js 收到 TikTok.js 的 `all` 統計快照時會 `mergeStats()` 併入自己的統計 map,TikTok.js 直連收到的訊息也能納入統計
### Socket 重連改進
- 新進階重連邏輯:前 10 次斷線固定 15 秒重連,之後每次 +5 秒,最多拉長到 5 分鐘
- 連線成功或程式關閉時自動重置重連計數與間隔
### TikTok 直播間自動重連
- 直播間斷線(`DISCONNECTED`)或初次連線失敗時**自動重連**,指數退避:15s → 30s → 60s → 120s → 240s(上限 5 分鐘)
- **最多重連 5 次**(`TK_RETRY_MAX`),次數用盡才優雅退出,避免無止盡重試
- **直播真正結束**(`STREAM_END`:使用者主動結束 / 平台封鎖)不會重連,直接標記停止
- 重連成功後自動:恢復觀眾數更新(`viewCache`)、恢復贊助廣告定時器(`resumeAdTimers`)、重設重連計數
### 贊助廣告系統增強
- 支援每人最多 **5 則廣告**,可透過 `id=N` 參數指定修改第幾則
- 管理頁面新增倒數計時器(顯示下次發送剩餘時間),歸零自動刷新
- 管理頁面新增伺服器運行狀態指示燈(🟢 運行中 / 🔴 未運行)
- 管理頁面操作提示改為 Toast 浮動通知,不再使用瀏覽器 alert
- 倒數計時在伺服器未運行時自動隱藏,避免無限 reload
- 支援手動從管理頁面建立贊助廣告
- 支援在管理頁面手動觸發既有廣告立即發送,並重新計算定時廣告的下次發送時間
- 伺服器重啟後自動恢復有效廣告的定時器(`resumeAdTimers`)
### TikTok 表情事件改進
### 新增贊助廣告 (G#Ad) 管理系統
- 新增 `interval=` 參數,訂閱者可設定廣告自動重複間隔(最低 15 分鐘)
- 新增三種審核模式:不需審核(預設)、過濾器自動審核、手動審核
- 新增管理頁面 `/sponsor`,可瀏覽/通過/拒絕/啟用/停用/刪除贊助廣告
- 廣告資料持久化儲存於 `sponsor_ads.json`
- 新增 `/api/sponsor-ads/*` API 端點
### UserScript 改進
- 移除 800ms 發送延遲,訊息凍結解凍後即時送出
- 新增 `visibilitychange` 監聽,分頁回到前景時自動更新觀眾資料
### TikTok 表情事件改進 - CHAT 事件不再跳過僅有表情無文字的訊息
- CHAT 事件不再跳過僅有表情無文字的訊息,改取 `emotes` 陣列內容
- EMOTE 事件新增 Socket 與 Bark 推送
### 管理頁面導航改進
- 主控台、關鍵字、日誌檔案頁面頂部新增快速導航按鈕
- `keyword.html` 改用相對路徑 `EventSource`,不再需要手動修改內網 IP
- `keyword.html` 頁面隱藏時自動關閉 SSE 連線,避免返回上一頁後連線殘留
- Server.js 關鍵字 SSE 端點移除無意義的 `pushLog` 刷版
### 新增 Youtube 直播聊天室支援
- 支援 Youtube 頻道直播聊天室訊息接收
- 透過 YouTube Data API v3 輪詢方式取得即時訊息
- 頻道名稱自動解析(支援頻道名稱或 @handle)
- Youtube 的聊天訊息**有頭像**(API 有提供 `profileImageUrl`)
- 自動遵循 API 的 `pollingIntervalMillis` 決定輪詢頻率
- 需要使用 Google API Key(請在 `.env` 設定 `YOUTUBE_API_KEY`)
#### 支援的事件
| 事件 | 類型 | 說明 |
| ------ | ------ | ------ |
| 💬 一般聊天 | `textMessageEvent` | 即時聊天訊息,可翻譯與過濾 |
| 💰 超級感謝 | `superChatEvent` | Super Chat 付費醒目訊息(含金額) |
| 🖼️ 超級貼圖 | `superStickerEvent` | Super Sticker 付費貼圖(含金額) |
| 🎉 新會員 | `newSponsorEvent` | 新的頻道會員加入 |
| 🎁 贈禮會員 | `giftMembershipReceivedEvent` | 收到贈送的會員資格 |
| ⭐ 會員里程碑 | `memberMilestoneChatEvent` | 會員達到里程碑 |
> **注意**:免費追隨(訂閱按鈕)不會觸發聊天室事件,僅有付費會員相關事件會出現。
### 新增 Odysee 直播聊天室支援
- 支援 Odysee 頻道直播聊天室訊息接收
- 透過 WebSocket 連接 sockety.odysee.tv 取得即時訊息
- 頻道名稱自動解析,不需手動輸入 claim ID
- 目前 Odysee 聊天訊息**不支援頭像顯示**(因 Odysee 的 WebSocket 資料未提供頭像網址)
### 其他更新
新增 Socket 最多重試上限
在`env`文件裡設置 最大重試次數 `SOCKET_RETRY_MAX_COUNT` 預設 最多3次
新增了聊天室訊息翻譯功能
如果語言不是中文會自動使用`env`裡的配置的翻譯API 進行翻譯
暫時還提供設置 控制哪一種語言以外才翻譯
不過你也可以透過更正**TranslateTest.js**
function isChinese() 的判斷條件 來更正那個你的母語
未來版本 會去補充更正此環節 讓他可由手動配置 目前暫時未處理
## 安裝與環境設定
網頁已改用預先產生的本機 Tailwind CSS。修改頁面 class 後執行 `npm run build:css`,
並一同更新 `assets/app.css` 與 `assets/app.css.build.json`;一般啟動會檢查指紋,只在需要時重建。建置方式、升級重啟與各頁排版盤點見
[網頁樣式與版面說明](Docs/WEB_UI.md)。
### 1. 安裝依賴
```bash
npm install
```
> 本專案以 **npm** 為唯一套件管理器(CI 使用 `npm ci`)。請勿使用 yarn/pnpm,
> 避免產生第二份 lock,以及覆蓋 `node_modules` 內以 patch 維護的第三方修改。
執行期資料檔(`gift_map.json`、`gift_list.json`、`message_stats.json`、`autoclip_stats.json`、
`send_messages.json`、`Main_Log.log`、`TikTokRun.log`、`OtherTool/live_engine/config/overlay_settings.json` 等)
不納入版控,repo 內改以對應的 `*.example.json` 提供樣本。首次啟動時,缺少的 `gift_map.json`
會由 `gift_map.example.json` 初始化,`overlay_settings.json` 的預設值也以 `overlay_settings.example.json` 為準(程式預設值的單一來源是 `live_engine/gui/overlay_settings.py` 的 `DEFAULT_OVERLAY_CONFIG`,範本與它一致),其餘檔案會自動建立。
### 2. 建立 .env 檔案
在專案目錄下建立 .env,可參考範例:
主要請以 `Docs/envExample` 裡的為準(或複製 `.env.example` 修改)
```env
# Twitch 設定
CLIENT_ID=你的Twitch Client ID
CLIENT_SECRET=你的Twitch Client Secret
TWITCH_USER_NAME=你的Twitch頻道名稱
# TikTok 設定
TIKTOK_NAME=你的TikTok用戶名
# ─── 三種 TikTok 認證方式,擇一即可 ───
# [方法A] 完整 Cookie(建議,功能最完整)
# 從已登入 TikTok 的瀏覽器 DevTools 取得
TIKTOK_COOKIES=fblo_xxx=yyy; sessionid=xxx; ...
# [方法B] Session ID(舊版相容)
SESSION_ID=你的TikTok sessionid
TT_TARGET_IDC=你的TikTok Target IDC
# [方法C] EulerStream API Key(不用 Cookie,走原始簽名服務)
# SIGN_API_KEY=你的EulerStream API Key
# 推送設定
BARK_API=https://api.day.app/你的BarkKey
NTFY_API=https://ntfy.sh/你的主題
SOCKET_API=http://192.168.0.195:9322
# SOCKET_APIS=其他端點 (選填,多筆以分號分隔,可加 ?filter=類別)
# Kick 設定(OAuth 選填,閱讀公開聊天不需設定)
KICK_CLIENT_ID=你的Kick Client ID
KICK_CLIENT_SECRET=你的Kick Client Secret
KICK_USER_NAME=你的Kick頻道名稱
KICK_CHANNEL_ID=你的Kick頻道ID
# Odysee 設定
ODYSEE_CHANNEL_NAME=你的Odysee頻道名稱
# Youtube 設定
YOUTUBE_API_KEY=你的Google API Key
YOUTUBE_CHANNEL_ID=你的Youtube頻道名稱
# 禮物翻譯設定(可選)
TRANSLATE_API_URL=https://api.mymemory.translated.net/get
TRANSLATE_SOURCE_LANG=en
TRANSLATE_TARGET_LANG=zh-TW
GIFT_TRANSLATE_PREFILL_LIMIT=10
```
**👉 如何取得 TIKTOK_COOKIES(方法A):**
| 方式 | 步驟 |
| ------ | ------ |
| **DevTools Cookie 管理** | 在已登入的 TikTok 頁面按 `F12` → **Application** → 左側 **Cookies** → `tiktok.com` → 全選所有 Cookie 項目 → 複製 → 貼到 `TIKTOK_COOKIES=` |
| **DevTools Network** | 在已登入的 TikTok 頁面按 `F12` → **Network** → 重新整理 → 點任意請求 → 在 **Request Headers** 找到 `Cookie:` 整段複製 |
| **Console 快速複製** | 在已登入的 TikTok 頁面按 `F12` → **Console** → 輸入 `copy(document.cookie)` → 直接貼到 `TIKTOK_COOKIES=` |
> ⚠️ Cookie 有時效性(約數天~數週),過期後需重新取得。系統啟動時會在 log 顯示設定狀態。
**方法B(SESSION_ID)** 只需從 Cookie 中找到 `sessionid` 的值填入即可,相容舊版配置。
**方法C(SIGN_API_KEY)** 適用于不使用 Puppeteer direct-signer 的情境,需有 EulerStream 服務的 API Key。
⚠️CLIENT_ID / CLIENT_SECRET 需從 Twitch 開發者平台取得
禮物翻譯補充:
- 收到禮物時,系統會優先讀取 `gift_map.json` 的對應翻譯。
- 如果禮物名稱尚未建立對應,會先加入 `gift_map.json`,再嘗試呼叫免費翻譯 API 補上翻譯。
- 啟動時 `fetchAvailableGifts()` 也會同步輸出 `gift_list.json`,並依照 `GIFT_TRANSLATE_PREFILL_LIMIT` 預先補一部分未翻譯的禮物名稱。
- 若你想手動修正翻譯結果,直接編輯 `gift_map.json` 即可,之後事件會優先使用你手動設定的內容。
### 3. 建立 tokens.json 範例
用於 Twitch OAuth,初始內容可為:
可參閱Docs/tokens.json
```json
{
"accessToken": "",
"refreshToken": "",
"scope": [
"bits:read",
"channel:read:goals",
"channel:read:redemptions",
"channel:read:subscriptions",
"chat:read",
"clips:edit",
"moderator:read:followers",
"user:read:chat",
"user:read:subscriptions"
],
"expiresIn": 0,
"obtainmentTimestamp": 0
}
```
### 4. Twitch OAuth Scope 自動檢查與重新授權
啟動 Twitch 模式時,系統會自動檢查 `tokens.json` 內的 scope 是否包含 `user:read:subscriptions`(檢查聊天室訂閱者身份)與 `channel:read:subscriptions`(接收訂閱/贈訂 EventSub 事件)。
若缺少 scope,系統會:
1. 印出 Twitch OAuth 授權連結(包含所有必要 scope)
2. 引導你在瀏覽器中授權
3. 請你貼上授權後瀏覽器導向的完整網址
4. 自動交換 authorization code → access token → 更新 `tokens.json`
5. 自動更新 `authProvider`,不需重啟程式
此機制與 Youtube OAuth 的自動刷新類比,確保 token 權限完整。若無需訂閱者檢查功能(`G#Ad` 指令),可忽略 scope 不足的警告。
## 贊助廣告 (G#Ad) 系統
Twitch 訂閱者**或主播本人**可在聊天室輸入 `G#Ad` 指令投放自訂廣告,廣告會透過 Socket 推送至疊加層顯示。
### 存取控制
| 身份 | 能否使用 |
| ------ | --------- |
| 主播本人(`chatterId === tuser`) | ✅ 自動通過 |
| Twitch 訂閱者 | ✅ 透過 `apiClient.subscriptions.checkUserSubscription()` 驗證 |
| 非訂閱者 | ❌ 拒絕,僅在 Server 日誌記錄 |
> 若缺少 `user:read:subscriptions` scope,訂閱者檢查會失敗並提示重新授權,但不影響其他功能。
### 完整指令格式
```txt
G#Ad <訊息> [tts] [icon=<網址>] [user=<名稱>] [interval=<分鐘>]
```
### 參數一覽
| 參數 | 必填 | 範例 | 說明 |
| ------ | ------ | ------ | ------ |
| `G#Ad` | ✅ | `G#Ad` | 指令前綴 |
| `<訊息>` | ✅ | `歡迎來我的頻道` | 廣告文字,支援 emoji shortcode(如 `:heart:`)及圖片網址 |
| `tts` | ❌ | `tts` | 啟用文字轉語音(文字會送至 TTS 引擎) |
| `icon=<網址>` | ❌ | `icon=https://example.com/logo.png` | 自訂圖示 URL,預設使用 Twitch 頭貼 |
| `user=<名稱>` | ❌ | `user=我的商店` | 自訂顯示名稱,預設使用 Twitch 顯示名稱 |
| `interval=<分鐘>` | ❌ | `interval=30` | 自動重複間隔,最低 15 分鐘 |
| `id=<編號>` | ❌ | `id=2` | 指定要修改第幾則廣告(0-4),不指定則修改第 0 則 |
> [!NOTE]
> **頭像(icon)解析規則**
>
> - 有 `icon=<網址>` 時以該網址為頭像。
> - 沒有 `icon=` 時,會**自動偵測訊息文字中結尾為圖片副檔名(`.png/.jpg/.jpeg/.gif/.webp/.avif/.bmp/.svg`)的網址**當作頭像,並從廣告文字中移除該網址。例如 `G#Ad 訂閱主播 哈基米 https://…/Neuro2.png?raw=true` → 頭像為該圖片、文字變為「訂閱主播 哈基米」。
> - 兩者都沒有時使用贊助者的 Twitch 頭貼。
> - **更新時未指定新的 icon/頭像網址,會保留原本的頭像**(不會被覆寫成空白);已解析的頭像會存入廣告紀錄,即時發送、定時器與重啟恢復都會使用同一張圖。
>
### 多廣告管理
每位贊助者最多可建立 **5 則廣告**,透過 `id=N` 參數指定要修改哪一則:
```txt
# 建立第一則廣告(自動)
G#Ad 歡迎訂閱! interval=30
# 修改第一則廣告(id=0,等同不指定)
G#Ad 新優惠訊息! id=0
# 新增第二則廣告
G#Ad 快來追蹤! id=1
# 修改第二則廣告
G#Ad 更新優惠! id=1
```
- 不帶 `id=` 時,預設修改第 **0 則**(第一則)
- 若該使用者還沒有任何廣告,則自動新增
- 超過 5 則時無法新增,需先刪除舊廣告
### 使用範例
```txt
# 最簡單:一次性的廣告
G#Ad 歡迎來我的頻道逛逛!
# 含 TTS
G#Ad 限時優惠中! tts
# 自訂圖示和名稱
G#Ad 今日特價商品 icon=https://i.imgur.com/abc.png user=商店小助手
# 每 30 分鐘自動重複
G#Ad 歡迎訂閱! interval=30
# 混合所有參數
G#Ad 快來參加抽獎! tts icon=https://i.imgur.com/xyz.png user=抽獎活動 interval=60
```
### 間隔行為
| interval 值 | 結果 |
| ------------- | ------ |
| 不設定 或 `interval=0` | **單次發送**,不重複 |
| `interval=30` | 每 30 分鐘自動重複發送一次 |
| `interval=5` | **強制調整為 15 分鐘**,並發送 Bark + 疊加層通知告知贊助者 |
| `interval=15` | 每 15 分鐘重複(最低容許值) |
> 定時器會在程式啟動與 TikTok 重連成功後由 `resumeAdTimers()` 自動恢復;恢復前會先清空既有廣告 timer,避免同一則廣告重複排程造成雙發。
> 在管理頁面手動按下「發送」後,該廣告會立即送到疊加層並更新 `lastSentAt`;若廣告有設定 `interval` 且已啟用,下一次自動發送會從這次手動發送時間重新起算。
### 審核模式
管理頁面 `http://localhost:3332/sponsor` 提供三種審核模式:
| 模式 | 說明 | 適用情境 |
| ------ | ------ | --------- |
| **不需審核** 🟢(預設) | 送出即顯示,立即啟動定時器 | 信任的訂閱者群 |
| **過濾器自動審核** 🟡 | 跑 `processFilter()`,通過過濾器才顯示 | 有廣告帳號騷擾時 |
| **手動審核** 🔴 | 標記為「待審核」,需管理員手動通過或拒絕 | 需要完全掌控廣告內容 |
**過濾器模式流程:**
1. 訂閱者發送 `G#Ad` 指令
2. 系統跑 `processFilter()`(比對使用者名稱、訊息內容)
3. **通過過濾器** → 自動通過,顯示 + 啟動定時器
4. **被過濾器阻擋** → 標記為拒絕,發送 Bark 通知給管理員
**手動模式流程:**
1. 訂閱者發送 `G#Ad` 指令
2. 標記為「待審核」,發送 Bark 通知(點擊可開啟管理頁面)
3. 管理員前往 `/sponsor` 審核
4. 通過 → 立即發送 + 啟動定時器;拒絕 → 停止
### 管理頁面功能 (`/sponsor`)
| 功能 | 說明 |
| ------ | ------ |
| 📋 廣告列表 | 依贊助者分組,顯示狀態、間隔、最後發送時間 |
| ➕ 手動新增 | 直接從管理頁面建立贊助廣告(免透過聊天室指令) |
| 📤 發送 | 手動觸發既有廣告立即送到疊加層;定時廣告會同步更新最後發送時間並重新排程 |
| ✏️ 編輯 | 直接修改既有廣告的訊息內容 / 圖示 URL / 自訂顯示名稱 / TTS / 間隔,儲存後立即更新;即使 TikTok.js 未運行也能寫入 `sponsor_ads.json`(會提示需啟動後才會發送) |
| ✅ 通過 / ❌ 拒絕 | 待審核的廣告可手動通過或拒絕 |
| ▶️ 啟用 / ⏸️ 停用 | 啟用或暫停定時重複(僅對有間隔的廣告顯示) |
| 🗑️ 刪除 | 刪除廣告記錄 |
| ⚙️ 審核模式切換 | 即時切換三種審核模式,立即生效 |
### 管理 API
| API | 方法 | 說明 |
| ----- | ------ | ------ |
| `/api/sponsor-ads` | `GET` | 讀取廣告設定與所有贊助者廣告 |
| `/api/sponsor-ads/create` | `POST` | 建立或更新廣告 |
| `/api/sponsor-ads/trigger` | `POST` | 手動觸發指定 `adId` 立即發送,並更新 `lastSentAt` |
| `/api/sponsor-ads/approve` | `POST` | 通過或拒絕待審核廣告 |
| `/api/sponsor-ads/toggle` | `POST` | 啟用或停用定時廣告 |
| `/api/sponsor-ads/delete` | `POST` | 刪除廣告 |
| `/api/sponsor-ads/settings` | `POST` | 更新審核模式設定 |


### Bark 通知行為
| 事件 | 通知內容 | 附帶 icon |
| ------ | --------- | ----------- |
| 廣告建立(不需審核) | `📢 贊助廣告 (使用者名) — [單次/每N分鐘] 訊息` | 優先取 `:emoji:` shortcode → 訊息內圖片網址 → Twitch 頭貼 |
| 廣告建立(手動審核) | `📋 贊助廣告待審核 (使用者名) — 訊息` + 管理頁面連結 | 同上 |
| 廣告被過濾器阻擋 | `🚫 贊助廣告被阻擋 (使用者名) — 原因` | 同上 |
| 管理頁面手動發送 | `📢 手動發送贊助廣告 (使用者名) — 訊息` + 管理頁面連結 | 同上 |
| 定時器重複發送 | `⏰ 贊助廣告 (使用者名) — 訊息` + 管理頁面連結 | 同上 |
| 間隔低於 15 分調整 | `⚠️ 贊助廣告間隔調整 (使用者名) — 說明` | 同上 |
### 資料儲存
- 檔案位置:`sponsor_ads.json`(專案根目錄)
- 儲存內容:贊助者 ID、顯示名稱、廣告內容、審核狀態、間隔設定、時間戳
- 格式:巢狀 JSON(`{ settings, users }`),啟動時自動載入
- 遷移相容:若偵測到舊版格式(無 `settings` / `users` 欄位),自動轉換
### 環境變數
```env
# 贊助廣告管理頁面網址(Bark 通知點擊後開啟)
# 若管理頁面不在 localhost,需設為實際 IP
SPONSOR_MANAGE_URL=http://localhost:3332/sponsor
```
## 剪輯建立 (G#clip) 系統
Twitch 聊天室中**主播本人、訂閱者或追隨者**輸入 `G#clip` 即可為目前直播建立一段 60 秒的剪輯:
```txt
# 建立剪輯(預設標題)
G#clip
# 建立剪輯並指定標題
G#clip 這波操作太秀了
```
| 項目 | 說明 |
| ------ | ------ |
| 權限 | 主播本人、**訂閱者或追隨者**(非追隨者會被拒絕) |
| 剪輯長度 | 60 秒(`createAfterDelay`) |
| 回饋 | 建立成功後推送 `🎬 剪輯已建立:https://clips.twitch.tv/<id>` 到疊加層與 Bark;失敗則顯示錯誤原因 |
| 指令消耗 | 處理後即 `return`,`G#clip` 不會被當成一般聊天訊息轉發或送去翻譯 |
> 權限檢查順序:主播本人 → 訂閱者(`checkUserSubscription`)→ 追隨者(`getChannelFollowers`,需 `moderator:read:followers` scope,授權範本已含)。任一通過即可建立剪輯。
>
> 剪輯內容是**指令發送時間點往前的一段直播畫面**(Twitch 剪輯機制),成功後由 Twitch 非同步處理,通常幾秒內即可在頻道的 Clips 頁面看到。
### 剪輯歷史頁面 `/clips`
每次成功建立剪輯(`G#clip` 指令或自動剪輯)都會記錄到 `clip_history.json`(保留最近 200 筆)。開啟 `http://localhost:3332/clips`(主控台導覽列的「🎬 剪輯」)可查看:
- 歷史剪輯清單:標題、建立時間、來源(手動/自動)、剪輯連結
- **標題自動帶入**:未指定標題的剪輯會透過 Twitch API 查詢實際標題(Twitch 自動生成的名稱);若 API 查不到則從剪輯 slug 擷取名稱,不會顯示「未命名」
- **▶ 預覽**:在頁面上直接以嵌入播放器觀看剪輯(自動播放,Esc 或點遮罩關閉)
- **複製連結**:複製 `https://clips.twitch.tv/<id>`
- **複製 Embed**:複製 `https://clips.twitch.tv/embed?clip=<id>`
- **複製 iframe**:複製可貼到網站的 `<iframe>` 程式碼(`parent` 已自動帶入目前頁面的主機名)
- **刪除單筆**:每筆紀錄右側有「刪除」按鈕,可個別移除
- **清空歷史**:一鍵清空 `clip_history.json`
- 每 5 秒自動更新清單
> **關於 Embed 的 `parent` 參數**:Twitch 只接受 `localhost`、`127.0.0.1` 或**真實網域**當 parent,**私人 IP(如 192.168.x.x、10.x)無論加不加 port 都會被拒絕**。`/clips` 頁面會自動判斷:目前主機是私人 IP 時改用 `localhost` 並顯示提示。因此:
>
> - 在本機請用 `http://localhost:3332/clips` 開啟,預覽與複製的 iframe 皆可直接使用
> - 透過私人 IP(`192.168.0.102:3332`)開啟時預覽無法播放(Twitch 限制),頁面會顯示改用 localhost 的提示
> - 若要嵌到**其他網站**,需手動把 `parent` 改成該網站的網域,否則 Twitch 會顯示「clips.twitch.tv 拒絕連線」
>
> 標題查詢透過 Twitch 公開的 Clip 資料 API(不需額外 scope);查詢失敗時會退回首段 slug 名稱,不影響剪輯建立。
## 自動剪輯 (AutoClip)
所有平台的有效留言都能提供觸發熱度,Twitch 是實際剪輯畫面的來源。API 與 Userscript 是接收管道,同平台不拆成兩份分數;未知來源仍參與統計,只有明確標記 `isTest: true` 的合成測試資料不參與熱度。
各平台分別計算聊天與觀眾基準,採通過最低留言量及發言人數門檻的平台最高分,不直接相加。預設使用 30 秒窗口、每 5 秒評估、持續約 10 秒,並有暖機、時間有效性、洗頻及冷卻限制。圖表可選取觸發點查看當時保存的留言證據;舊紀錄缺少明細時會明確標示。
`AUTO_CLIP_ENABLED=1` 才啟用,且需啟用 Twitch。`AUTO_CLIP_MODE` 預設為 `shadow`,只記錄觸發;改成 `live` 才會提出正式剪輯請求。Config 儲存後需重啟聊天子程序生效。
完整判斷、時間校準、歷史紀錄與驗證方式見 [留言統計與自動剪輯說明](Docs/KEYWORD_AUTOCLIP_V2.md)。轉接欄位見 [/chat 參數文件](Docs/CHAT_API.md)。
## 自動停止
`AUTO_STOP_ENABLED=1` 時,TikTok 與 Twitch 都明確結束(TikTok `STREAM_END`/Twitch 離線)後,等待 `AUTO_STOP_GRACE_SEC`(預設 60 秒)自動停止聊天子程序;期間任一平台恢復直播會取消倒數。若同時啟用 Kick/Odysee/YouTube,因目前沒有可靠的結束訊號,不會自動停止。設定可從 `/config` 修改,儲存後需重啟子程序。
## 推流端診斷頁 `/pushdiag`
> 需搭配自己的 **SRS / Oryx** 串流伺服器使用。此頁面直接以瀏覽器連到 SRS 的 HTTP-FLV 播放埠(如 `http://192.168.0.102:882`),解析即時 FLV tag 檢查推流端(OBS 等)送出的資料是否正常,是排查「推流端問題」的分析工具。
開啟 `http://localhost:3332/pushdiag`(主控台導覽列「🔧 推流診斷」),填入 SRS 主機與 stream key 後按「開始分析」。
### 版面與排版
- 全頁使用 **Font Awesome 6** 圖示庫(CDN 載入),取代 emoji,各作業系統/瀏覽器顯示一致
- **可收合的連線設定卡**:設定好後可收合只剩一條(標題 + 目前 FLV 網址 + 開始/停止按鈕),開始/停止按鈕**永遠常駐**不需展開即可操作;chevron 指示展開狀態
- **xl(≥1280px)以上兩欄布局**:即時預覽(2/3 寬)與「連線狀態卡 + 健康診斷」(1/3 寬)並排,寬螢幕(如 1700px)下播放器完整可見無需向下捲動;手機版維持單欄堆疊
- 容器最大寬度 `max-w-[1600px]`,充分利用寬螢幕
- 分析期間每 250ms 會重建表格 / 資訊卡 DOM,頁面已停用瀏覽器 scroll anchoring(`overflow-anchor: none`),避免捲軸被自動往上拉、無法停在想看的區塊
### 功能一覽
- **連線狀態卡**:HTTP 狀態、content-type、已收位元組、即時位元率(kbps)、封包計數
- **即時預覽**:用 mpegts.js 直接播放該 FLV 串流,邊看畫面邊對照數據
- 可勾選「分析時自動播放」;「疊加資訊」開關可在影片左上角顯示 HUD(fps / 解析度 / 位元率 / GOP / 封包數 / 影音偏移),偏移過大自動轉紅
- **健康診斷旗標**:自動判別
- 影音齊全 / 只有音訊或只有影片
- GOP(關鍵影格間隔)分析:最大間隔 >5s 黃燈、>10s 紅燈(正常 OBS 預設 2s,過長代表觀看端無法加入直播)
- 位元率偏低(<100 kbps)或過高(>20 Mbps)警告
- 影音時間戳偏移 >3s 警告
- **送率診斷(少送偵測)**:判斷音訊或影片是否少送,運行 30 秒後自動顯示
- **影片送率**:最近 30 秒實際 fps vs 基準(最近 2.5 分鐘量測 fps 的**中位數**,對突發抖動不敏感)。實際 < 90% 亮黃燈「影片疑似少送」
- **音訊送率**:最近 30 秒實際 pkt/s vs 理論值(取樣率 ÷ 1024,AAC-LC 每幀 1024 samples;HE-AAC 為 2048)。實際 < 90% 亮黃燈「音訊疑似少送」,此為最可靠指標(基準由 codec 設定直接決定)
- **影音偏移漂移**:追蹤 `audio ts − video ts` 近 10 秒的漂移量。漂移 > +800ms 亮「影片時間戳落後音訊(影片疑似掉幀)」;< −800ms 亮「音訊疑似掉幀」。此訊號與 fps 量測無關,是「哪一側在掉幀」的決定性交叉驗證
- 為何不能只看封包數:60fps 影片天生每秒 60 個封包、AAC 44.1kHz 只有約 43 個封包(比值 ~1.4:1),**音訊比影片少是正常的**;必須各側跟自己的理論值比才看得出少送
- **元資料卡**:解析 onMetaData(若有送);OBS 通常不送,頁面會提示改以 SPS 為準
- **影片資訊卡**:從 SPS 解出 H.264 Profile / Level / 解析度 / VUI FPS,以及**量測 FPS**(以 FLV **呈現時間戳**而非接收牆鐘時間計算,避免 HTTP 傳輸/緩衝突發造成的高估)、**GOP 最大/平均間隔**(含 <200ms 假標記過濾)
- **音訊資訊卡**:從 AudioSpecificConfig 解出 AAC AOT(LC/HE)、取樣率、頻道
- **圖表**(Chart.js,即時更新)
- 影片 vs 音訊位元率(kbps,每秒聚合,最近 60 秒)
- 影片封包大小(紅色點 = 關鍵影格,可看出 GOP 結構)
- 影音時間戳偏移(audio - video, ms)
- **封包檢查器**:即時 tag 列表(序號 / 型別 / 大小 / 幀型別 / timestamp / 間距),每列有檢視按鈕看原始資料
- **影片 / 音訊塊明細**:最近 50 個區塊,含幀型別、NALU、解析度、AAC 型別、取樣率等
### 原文檢視(封包列檢視按鈕)
點封包列的檢視按鈕開啟彈窗,三種檢視分頁:
- **十六進位**:完整 hex dump(16 bytes/列 + ASCII)
- **UTF-8 文字**:將可讀字串解出(含中文/emoji 多字元,overlong/surrogate 驗證)
- **解析**:直接把 tag 結構解成人話 — 影片(幀型別 / codec / NALU 列表 / SPS 解析度 Profile Level)、音訊(格式 / 取樣率 / AAC 型別 / ASC 解析)、onMetaData(key=value)
> [!NOTE]
> 原始資料僅保留**最近 300 個封包(合計 ~4MB)**,太舊的列點檢視會顯示「已過期」。
### 技術說明
- FLV tag 即為 RTMP 媒體封包(type 8=audio / 9=video / 18=script),瀏覽器直接 `fetch` SRS 的 HTTP-FLV 端點解析,**不需伺服器端轉發**(SRS 需允許 CORS,Oryx 預設 `*`)
- 即時預覽透過 **mpegts.js**(flv.js 維護版)播放 HTTP-FLV,與分析共用同一串流 URL
- 解析用 50ms 時間預算 + 4Hz 批次 DOM 渲染,避免封包解析拖慢 UI
- **長時間運行防凍結**:mpegts.js 預覽以 `enableStashBuffer: false` 直接送 SourceBuffer(避免緩衝/記憶體隨時間累積);內建**停滯守衛**每 250ms 檢查影片 currentTime,若資料持續流入但畫面連續 5 秒未推進即自動重建播放器;每秒聚合桶只保留最近 10 分鐘避免無上限成長
- 圖示庫:`font-awesome@6.7.2`(cdnjs CDN)
- 需 SRS 啟用 HTTP-FLV 播放(如 Oryx 的 `:882/live/<key>.flv`)
## 啟動服務
平常使用 `npm start`;調整樣式與排版使用 `npm run dev`(先建置 CSS,再監看變更)。
開啟 `http://localhost:3332` ,Ctrl+C 結束。兩種模式使用同一設定與連接埠,不要同時啟動。
完整模式差異、CSS 指令與故障排查見 [啟動與開發模式](Docs/STARTUP.md)。
## 服務器預設運行在 Port 3332,提供 HTTP 控制介面
### 1. 查看使用說明
```bash
http://localhost:3332/help
```
### 2. 啟動聊天室訊息服務
```bash
http://localhost:3332/open?user=你的TikTok名&twitchUser=你的Twitch名&kickUser=你的Kick名&isSocket=1&isTwitch=1&isTK=1&isKick=1&isBark=1
```
參數說明:
| 參數 | 說明 |
| -- | -- |
| user | TikTok 用戶名稱(給 isTK 使用),若不設使用 .env 的值 |
| twitchUser | Twitch 用戶名稱(給 isTwitch 使用),若不設使用 .env 的值 |
| kickUser | Kick 頻道名稱(給 isKick 使用),若不設使用 .env 的值 |
| odyseeUser | Odysee 頻道名稱(給 isOdysee 使用),若不設使用 .env 的值 |
| youtubeUser | Youtube 頻道名稱或 ID(給 isYoutube 使用),若不設使用 .env 的值 |
| isTK=1 | 啟用 TikTok 直播聊天室 |
| isTwitch=1 | 啟用 Twitch 直播聊天室 |
| isKick=1 | 啟用 Kick 直播聊天室 |
| isOdysee=1 | 啟用 Odysee 直播聊天室 |
| isYoutube=1 | 啟用 Youtube 直播聊天室 |
| platforms=tiktok,twitch,kick,odysee,youtube | 自由組合平台(逗號分隔),例如 `twitch,kick`、`tiktok,youtube` |
| isBoth=1 | (已棄用,建議改用 `platforms=tiktok,twitch`) |
| isBark=1 | 啟用 Bark 推送通知 |
| isNtfy=1 | 啟用 ntfy 推送通知(設定 NTFY_API,如 `https://ntfy.sh/你的主題`) |
| isSocket=1 | 啟用 Socket 訊息推送 |
> [!WARNING]
> 補丁服務器 重複訊息檢查功能
> 已經直接合併到TikTok.js/Server.js裡
> 以下參數不再使用
>
> - isWeb 啟用備用UserScript監聽服務器
>
> - isDelay 啟用延遲2秒後檢查重複訊息
> - isRepeat 啟用重複訊息檢查
## Socket 連線機制
系統使用 TCP Socket (預設 port 9322) 將各平台的訊息統一推送給用戶端。
### 閒置處理方式
Socket **不會主動因閒置斷線**,連線生命週期完全由 Server 端程式(WebSocket.js / 用戶端)控制:
- 有資料時正常推送
- 無資料時保持連線,不做主動中斷
- Server 端若主動斷線,本系統會在 15 秒後自動重連
- 無需再設定 `SOCKET_IDLE_TIMEOUT`
### 斷線暫存與補發
當 Socket 因任何原因斷線時,發送中的訊息不會被丟棄,而是進入 **`pendingQueue`**(最多暫存 50 筆):
- `sendSocketMessage()` — 聊天、加入、禮物等訊息
- `sendAudienceUpdate()` — 人數更新
重新連線成功後,系統會自動依序**補發**所有暫存訊息,再送出「已連線」通知。
**好處:**
- 減少無意義的斷線重連循環(之前閒置 2 分鐘斷線 → 15 秒重連 → 又閒置 2 分鐘斷線)
- 降低 CPU 和網路消耗
- 用戶端(如 iOS App)不需頻繁處理斷線重連狀態
- 斷線期間產生的訊息不會遺失,重連後自動補上
範例:
```bash
http://localhost:3332/open?user=coffeelatte0709&isTK=1&isBark=1
# 或指定 Twitch:
http://localhost:3332/open?twitchUser=coffeelatte0709&isTwitch=1&isBark=1
# 或指定 Kick:
http://localhost:3332/open?kickUser=你的頻道名&isKick=1&isBark=1
```
### 3. 關閉聊天室訊息服務
```bash
http://localhost:3332/close
```
會嘗試優雅關閉子進程並發送最後一條訊息;若 15 秒未退出則強制終止(SIGKILL),子程序本身也在 8 秒後硬性退出,避免卡在停止中無法重啟。
## Odysee 聊天室整合
### 快速啟動
Odysee 聊天室透過 WebSocket 連接 `sockety.odysee.tv`,不需任何授權即可讀取:
```bash
http://localhost:3332/open?odyseeUser=你的頻道名&isOdysee=1&isSocket=1&isBark=1
```
或透過 `.env` 設定 `ODYSEE_CHANNEL_NAME`:
```bash
http://localhost:3332/open?isOdysee=1&isSocket=1
```
### 注意事項
- Odysee 聊天訊息**沒有頭像**,`img` 欄位會是空字串
- 頻道名稱會自動解析,不需要手動輸入 claim ID
- 如果頻道未開播,程式會自動退出,不會持續輪詢
### Odysee + 其他平台同時運行
```bash
http://localhost:3332/open?user=你的TikTok名&twitchUser=你的Twitch名&kickUser=你的Kick名&odyseeUser=你的Odysee名&isTK=1&isTwitch=1&isKick=1&isOdysee=1
```
或使用 `platforms`:
```bash
http://localhost:3332/open?odyseeUser=你的Odysee名&platforms=tiktok,twitch,kick,odysee
```
## Youtube 聊天室整合
### 準備工作
需要一組 **Google API Key** 才能使用 Youtube Data API v3:
1. 前往 [Google Cloud Console](https://console.cloud.google.com/)
2. 建立或選擇一個專案
3. 啟用 **YouTube Data API v3**
4. 建立 **API Key**,建議限制僅供 YouTube Data API 使用
5. 在 `.env` 加入:
```env
YOUTUBE_API_KEY=你的API金鑰
YOUTUBE_CHANNEL_ID=你的Youtube頻道名稱或ID
```
### 快速啟動 - 示例
```bash
http://localhost:3332/open?youtubeUser=你的頻道名&isYoutube=1&isSocket=1&isBark=1
```
或透過 `.env` 設定:
```bash
http://localhost:3332/open?isYoutube=1&isSocket=1
```
### 支援的事件 - YouTube
| 事件 | 類型 | 說明 |
| ------ | ------ | ------ |
| 💬 一般聊天 | `ChatMessage` | 即時聊天訊息,可翻譯與過濾 |
| 💰 超級感謝 | `SuperChat` | 付費醒目訊息(含金額) |
| 🖼️ 超級貼圖 | `SuperSticker` | 付費貼圖(含金額) |
| 🎉 新會員 | `NewSponsor` | 新頻道會員加入 |
| 🎁 贈禮會員 | `GiftMembership` | 收到贈送的會員 |
| ⭐ 會員里程碑 | `MemberMilestone` | 會員達到里程碑 |
### 注意事項 - YouTube
- 使用 **YouTube Data API v3** 輪詢方式,非 WebSocket
- 免費配額每日 10,000 單位,每次輪詢約花 5 單位
- 程式會自動遵循 API 回傳的 `pollingIntervalMillis` 決定輪詢頻率
- 如果頻道未開播,程式會自動退出,不會持續輪詢
- 可透過 `.env` 的 `YOUTUBE_API_KEY` 設定 API 金鑰
### Youtube + 其他平台同時運行
```bash
http://localhost:3332/open?user=你的TikTok名&youtubeUser=你的Youtube名&isTK=1&isYoutube=1&isSocket=1
```
或使用 `platforms`:
```bash
http://localhost:3332/open?youtubeUser=你的Youtube名&platforms=tiktok,youtube
```
## Kick 聊天室整合
### 快速啟動(不需 OAuth)
Kick 公開聊天室可直接透過 WebSocket 讀取,不需任何授權:
```bash
http://localhost:3332/open?kickUser=你的頻道名&isKick=1&isSocket=1&isBark=1
```
### 支援的事件 - Kick
| 事件 | 說明 |
| ------ | ------ |
| `ChatMessage` | 即時聊天訊息 |
| `Subscription` | 新訂閱 |
| `GiftedSubscriptions` | 贈送訂閱 |
| `UserBanned` | 用戶被封禁 |
| `UserUnbanned` | 用戶解封 |
| `StreamHost` | 主機轉播 |
### OAuth 設定(選填,發送訊息用)
如需發送訊息或存取私有 API,可設定 Kick OAuth:
1. 前往 [Kick Dev Portal](https://dev.kick.com/) 註冊應用程式
2. 設定 Redirect URI 為 `http://localhost:3332/get-kick-token`
3. 在 `.env` 填入 `KICK_CLIENT_ID` 與 `KICK_CLIENT_SECRET`
4. 瀏覽器開啟 Kick 授權 URL(由系統產生),授權完成後自動啟動聊天監聽
Token 會自動儲存至 `kick_tokens.json`,並在過期時自動刷新。
### 從 Browser 啟動
```bash
http://localhost:3332/open?kickUser=你的頻道名&isKick=1&isBark=1&isSocket=1
```
### Kick + Twitch + TikTok + Odysee + Youtube 同時運行
```bash
http://localhost:3332/open?user=你的TikTok名&twitchUser=你的Twitch名&kickUser=你的Kick名&odyseeUser=你的Odysee名&youtubeUser=你的Youtube名&isTK=1&isTwitch=1&isKick=1&isOdysee=1&isYoutube=1
```
### 注意事項 - Kick
- Kick 公開聊天不需 OAuth,直接填入頻道名稱即可
- OAuth 僅用於發送訊息等進階功能
- `kick-wss` 套件負責底層 WebSocket 連接,自動重連
### kick-wss 修正版
原版 `kick-wss` 有兩個問題導致無法正常連接 Kick 聊天室:
1. `getChannelInfo` 缺少必要 HTTP headers(User-Agent、Referer、Origin),被 Cloudflare 阻擋(403)
2. `LEGACY_EVENT_MAPPING` 將 Pusher 事件名 `App\Events\ChatMessageEvent` 錯誤轉換為短名,導致 switch 比對失敗
修正後的完整套件請參閱 `Docs/kick-wss.zip`,解壓後可取代 `node_modules/kick-wss/`。
修改內容:
| 檔案 | 修改 |
| ------ | ------ |
| `dist/WebSocketManager.js` | 支援 `channelId` 選項,省略 API 呼叫 |
| `dist/WebSocketManager.js` | `_channelIdExplicit` 旗標正確判斷是否跳過 API |
| `dist/MessageParser.js` | 移除 `LEGACY_EVENT_MAPPING` 錯誤的正規化 |
## 其他功能
表情對應可於登入後開啟 `/emoji` 管理,支援圖片預覽、增加、修改、啟用/停用與恢復預設;
預設清單在 `emoji_map.json`,本機變更寫入不提交的 `emoji_map_local.json`,約 1 秒後套用至新訊息。
詳見 [表情映射與熱載入](Docs/EMOJI_MAP.md)。
### 分支文件功能說明
- [主服務器的其他功能說明 Service.md](./Service.md)
### 開發測試腳本
開發過程中的測試與分析腳本統一放在 `Test/` 目錄,完整清單與說明見 [Docs/TEST_FILES_REFERENCE.md](./Docs/TEST_FILES_REFERENCE.md)。
```bash
node Test/xxx.mjs
```
### 1. 查看服務狀態
- 一次性狀態查詢
```bash
http://localhost:3332/status
```
- 實時 SSE 狀態
```bash
http://localhost:3332/status/stream
```
預設根目錄就是SSE查詢 展示
```bash
http://localhost:3332/
```
- 訊息次數統計
```bash
http://localhost:3332/keyword
```
- 本地日誌查看
```url
http://localhost:3332/logViewer
```
- 贊助廣告管理
```url
http://localhost:3332/sponsor
```
> [!TIP]
> 用於統計重複訊息 以便屏蔽煩人廣告關鍵字用
>
> 或者做熱門關鍵字統計用
>

目前也加了 **複製按鈕** 方便快速複製添加
可以用此來判斷那些**廣告帳號**老是刷的**關鍵字**
以便後續加入封鎖關鍵字 或**自動禁言規則**裡
> [!NOTE]
> **統計資料由 Server.js 統一管理**,寫入 `message_stats.json` 時會與現有檔案做 **max-merge(只增不減)**,不會被舊快照覆蓋。
> TikTok.js 在運行時其直連收到的訊息統計會同步併入;不在運行時 `/chat` 進來的訊息仍照常累計。
> 頁面右上角「🗑️ 清空統計」可一鍵清空記憶體與檔案(含 TikTok.js 統計),清空後從頭累計。
### 2. 修改環境變數
可快速修改 .env 的 BARK_API、NTFY_API、SOCKET_API 與 SOCKET_APIS:
```bash
http://localhost:3332/config
```
表單提交後會立即更新 process.env,下一次 /open 將生效
現在已添加配置頁存取密碼 對應env的`CONFIG_KEY`進行密碼設置
## Config Editor 認證流程
## 入口
- 使用者訪問 `http://localhost:3332/config`
- 如果尚未登入,系統會自動導向至 `login.html`
## 登入
- 在 `login.html` 輸入密碼並送出
- 後端驗證成功後,會產生一組隨機 **Token**
- Token 透過 **Set-Cookie** 寫入瀏覽器 (`authToken`)
- Token 有效期為 **14 天**
## 使用
- 之後訪問 `/config` 時,瀏覽器會自動帶上 Cookie
- 後端檢查 Cookie 中的 Token 是否有效:
- **有效** → 顯示 `config.html` 並填入環境變數
- **無效或過期** → 導向回 `login.html`
## 登出
- 使用者在 `config.html` 點選「登出」按鈕
- 前端呼叫 `/logout`
- 後端回應 `Set-Cookie: authToken=; Max-Age=0`,清除 Cookie
- 使用者被導回 `login.html`
## Token 有效期
- 每次登入會生成一組新的 Token
- Token 有效期為 **14 天**
- 過期後需要重新登入
- 使用者也可以手動點選「登出」來清除 Cookie
## 日誌與錯誤
所有運行日誌會在瀏覽器根目錄 SSE 頁面即時顯示,也會輸出到控制台
## 注意事項 - TTWChat
1. 修改 .env 後,需要重新 /open 才能讓新設定生效
2. 本服務建議保持內網或私人環境使用
3. TikTok session 過期需重新抓取
## 訊息過濾系統 (MessageFilter)
`MessageFilter.js` 是統一的訊息過濾與統計模組,同時被 `TikTok.js`(原生連線)與 `Server.js`(`/chat` 轉接)引用。支援四種動作:`block`(阻擋)、`replace`(取代)、`delete`(刪除)、`throttle`(**頻率控制**:洗頻與相似重複訊息)。
> **完整說明見 [訊息過濾系統文件](Docs/MESSAGE_FILTER.md)**:所有模式與欄位、範例、預設規則、自訂規則檔(`FilterRules.custom.js`)、匯出/統計 API、`/keyword` 規則產生器與測試、過濾流程。
>
> [!IMPORTANT]
> 規則在**兩個行程各跑一次**(原生在 `TikTok.js`、`/chat` 轉接在 `Server.js`);`throttle` 的狀態因此每行程各一份。修改規則後需**重啟主服務**。
---
## Userscript 轉接入口
瀏覽器腳本透過 `POST http://localhost:3332/chat` 傳送 JSON,由 `Server.js` 交給聊天子程序處理,再送至 Socket 顯示與自動剪輯統計。使用現有主服務即可;舊的 `WebSocket.js` 已退役。
- [/chat 入口與完整參數](Docs/CHAT_API.md):聊天/觀眾 payload、必要欄位、時間、去重、測試標記、回應及 Userscript 發送範例。
- [Userscript 平台與管道](Docs/USERSCRIPT_SOURCES.md):平台歸類、未知來源及腳本版本。
- [Socket 傳輸說明](Docs/SOCKET_TRANSPORT.md):佇列、背壓與重新連線。
`platform` 表示原始平台,`transport` 表示接收管道;此入口固定為 `userscript`。無法辨識平台仍可提供熱度,請使用 `Unknown`。只有合成測試資料才設定 `isTest: true`。
DOM 腳本能取得的訊息、原始 ID 與發送時間取決於平台頁面;缺少資料應保留空值,不要偽造。原始 WebSocket relay 是另一條流程,不等同於 `/chat` 的聊天 JSON。
更新後請重新安裝/更新瀏覽器中的腳本,並重新啟動伺服器與聊天子程序。
## 其他指引
### 提交修改忽略
```shell
git update-index --assume-unchanged <file>
```
### 提交修改忽略回復
```shell
git update-index --no-assume-unchanged <file>
```
### 其他工具 **OtherTool**
這個資料夾是之前做的一些小工具
`Time.html` 是很早以前我用在OBS瀏覽器來源 用來顯示當前時間的附加件
`NetFix.py` 則是平時用來FFMPEG重新編碼壓縮用 的小工具
`live_engine` 用於給Window的聊天疊加層
`Gift.html` TaiwndCSS 商品卡排版設計嘗試
`Mask.py` 一個讓你用來擋不想讓觀眾看到的東西 黑框框可視化視頻編輯器
### live_engine 使用方式
依賴安裝
```shell
pip install PyQt6 PyOpenGL numpy pillow requests
```
疊加層配置的預設值單一來源是 `live_engine/gui/overlay_settings.py` 的 `DEFAULT_OVERLAY_CONFIG`(`config/overlay_settings.example.json` 與它一致);視窗起始寬高仍沿用 `config.py` 的 `WIDTH`/`HEIGHT`。
運行請先進入 live_engine目錄下 在運行 `main.py`
運行會在本地部署一個Socket Server PORT跟ReplyKIT項目是一樣的 在PORT `9322`
## 新更新 部分日誌會採用 `writeLog` 進行本地日誌紀錄
有一些訊息為了方便調試 確認參數 所以特別寫進 `Main_Log.log` `TikTokRun.log`
模組拆分與維護責任請參閱 [模組說明](Docs/MODULES.md)。
專用瀏覽器與持久化登入:執行 `npm start` 後開啟 `/browser`,詳見 [瀏覽器管理](Docs/BROWSER.md)。
- [網頁本地化與語系維護](Docs/I18N.md):首階段繁中/英文頁面、中文回退及翻譯接入方式。