00:00 / 00:00

LAPLACE Chat

LAPLACE Event Fetcher

直播途中突然斷網?開播後才發現忘了開彈幕機?此功能可在未開啟彈幕機時持續監控直播間事件,並定期同步至本機,真正做到不漏接任何禮物。雲端事件同步僅適用於控制台模式

  • 雲端事件可取得最近 72 小時(可設定)內的所有付費禮物事件
  • 在控制台中會每 20 分鐘自動與雲端同步,用於補回主播可能因網路問題而漏收的禮物
  • 開啟 Bridge Mode 後,可與 LEB SDK 進行聯動,打造屬於您自己的彈幕應用程式

設定此功能需要一定的電腦基礎,如果您是主播,請將本文件轉交給社團/公會內相應的技術人員

前置條件

  • 熟悉容器或 Kubernetes 的基本操作
  • 高中及以上學歷(如果您是國中生,且完全能看懂下方的部署文件,可透過 Discord 與我聯繫)

請確認符合上述條件,否則不建議繼續閱讀

伺服器最低需求

  • linux/amd64
  • CPU:至少 0.25 核
  • 記憶體:至少 256 MB,建議 512 MB 以上。記憶體需求會隨監控的直播間數量線性成長
  • 伺服器能長時間穩定連上嗶哩嗶哩彈幕伺服器,建議選用日本、新加坡或中國(網域需備案)節點的伺服器
  • PostgreSQL
  • Redis(選用),用於快取近期的彈幕訊息

Serverless 需求

目前已測試通過 Koyeb/Vercel + Supabase/Neon/Render 的各種排列組合

容器可用標籤

  • latest:最新穩定版
  • <major>.<minor>.<patch>:指定版本
  • edge:最新開發版
  • sha-<hash>:指定 commit hash 的版本

更多標籤與版本請查看容器介紹頁面

安裝方式

  • 透過 Docker、Docker Compose 或 Kubernetes 進行部署
  • 設定公開網路存取:需支援 HTTPS 存取,可透過 Traefik、Nginx、Caddy 或 serverless 雲端服務進行設定
  • 填入 API:設定完成後,在 LAPLACE Chat 的設定器 - 進階 - 自訂雲端事件 API 中填入您的 API 網址

Docker Compose 設定範例

以下為在 Docker Compose 中部署 LAPLACE Event Fetcher 的設定範例

docker-compose.yml
services:
  lef:
    image: ghcr.io/laplace-live/event-fetcher:latest
    environment:
      DATABASE_URL: postgresql://lef:lef@lef-pg:5432/lef
      ROOMS: 25034104,456117
      TZ: Asia/Shanghai # Recommended, this ensures all cron tasks are executed in CST
    depends_on:
      - lef-pg
      - lef-redis # See below
    # If you want to run the server on a different port, you can uncomment the following lines. Default is 8080.
    # ports:
    #   - 9696:8080
    restart: always

  lef-pg:
    image: postgres:18-alpine # Works with version 16 and above
    environment:
      POSTGRES_DB: lef
      POSTGRES_USER: lef
      POSTGRES_PASSWORD: lef
    volumes:
      # For PostgreSQL 17 and below, you should mount to `/var/lib/postgresql/data`
      - lef-db:/var/lib/postgresql
    restart: always
    healthcheck:
      test: pg_isready -U lef -h 127.0.0.1
      interval: 5s

  # Redis is optional for serving recent chat messages
  lef-redis:
    image: redis:latest
    volumes:
      - lef-redis:/data
    restart: always

volumes:
  lef-db:
  lef-redis:

Kubernetes 設定範例

以下為在 Kubernetes 中部署 LAPLACE Event Fetcher 的設定範例

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: lef-config
data:
  ROOMS: "25034104,456117"
  TZ: "Asia/Shanghai"
secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: lef-secret
type: Opaque
stringData:
  DATABASE_URL: "postgresql://lef:lef@lef-pg:5432/lef"
  POSTGRES_DB: "lef"
  POSTGRES_USER: "lef"
  POSTGRES_PASSWORD: "lef"
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: lef-pg-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
postgres.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lef-pg
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lef-pg
  template:
    metadata:
      labels:
        app: lef-pg
    spec:
      containers:
        - name: postgres
          image: postgres:18-alpine # Works with version 16 and above
          envFrom:
            - secretRef:
                name: lef-secret
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: postgres-storage
              # For PostgreSQL 17 and below, you should mount to `/var/lib/postgresql/data`
              mountPath: /var/lib/postgresql
          livenessProbe:
            exec:
              command:
                - pg_isready
                - -U
                - lef
                - -h
                - 127.0.0.1
            initialDelaySeconds: 30
            periodSeconds: 10
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: lef-pg-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: lef-pg
spec:
  selector:
    app: lef-pg
  ports:
    - port: 5432
event-fetcher.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lef
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lef
  template:
    metadata:
      labels:
        app: lef
    spec:
      containers:
        - name: lef
          image: ghcr.io/laplace-live/event-fetcher:latest
          envFrom:
            - configMapRef:
                name: lef-config
            - secretRef:
                name: lef-secret
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
  name: lef
spec:
  selector:
    app: lef
  ports:
    - port: 80
      targetPort: 8080

Ingress、HTTPS 請依自身的實際需求進行設定

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: lef-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:
    - host: lef.example.tld
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: lef
                port:
                  number: 80

部署:Koyeb + Neon

首先,您需要在 Supabase 建立一個 PostgreSQL 資料庫:

  • 在 Supabase 控制台中建立新的資料庫
  • 將區域改為 AWS US East (N. Virginia)
  • 記住資料庫密碼,並從 Connection Details - Connection string 中記下您的連線字串

接著,我們要在 Koyeb 上部署 event fetcher:

  • 建立新的 app,並選擇 Docker 作為部署方式
  • 使用 ghcr.io/laplace-live/event-fetcher 作為映像檔。留空或填入 edge 可取得最新的測試版映像檔
  • 將區域改為 WAS(靠近您剛建立的資料庫)
  • 選擇 eMicro 執行個體(更大的執行個體可承載更多直播間)
  • 點擊 Advanced 按鈕,並加入以下環境變數:ROOMSDATABASE_URLDATABASE_URL 的值就是上一步取得的連線字串,格式類似 postgresql://postgres:<DB_PASSWORD>@xxxxxxxxxxxxxxxxxxxx.us-east-2.aws.neon.tech/neondb?sslmode=require
  • 點擊 Deploy

已測試的 Serverless 平台

以下組合皆已測試可正常運作:

  • Koyeb + Supabase
  • Koyeb + Neon
  • Render
  • Render + Neon

WebSocket API (Bridge Mode)

Event Fetcher 在根路徑 / 提供 WebSocket 模式,用於即時事件串流,可作為 Event Bridge Server 的替代方案。設定 BRIDGE_MODE=1BRIDGE_MODE=true 即可啟用此功能。啟用後,伺服器會接受來自 LAPLACE Event Bridge SDK 的 WebSocket 連線

身分驗證

若已設定 BRIDGE_TOKEN,用戶端需使用 Sec-WebSocket-Protocol 標頭進行驗證。例如:

const ws = new WebSocket("ws://localhost:8080/", ["client", "auth-token"]);

或者,您也可以透過 token 查詢參數傳入權杖:

const ws = new WebSocket("ws://localhost:8080/?token=auth-token");

直播間篩選

您可以透過 rooms 查詢參數,依特定直播間篩選事件,傳入以逗號分隔的直播間 ID 清單:

// Only receive events from rooms 456117 and 25034104
const ws = new WebSocket("ws://localhost:8080/?rooms=456117,25034104");

// With query-style authentication
const ws = new WebSocket(
  "ws://localhost:8080/?token=auth-token&rooms=456117,25034104",
);

若未提供 rooms 參數,您將收到所有已設定直播間的事件

使用者 UID 篩選

您可以透過 uids 查詢參數,依特定使用者 UID 篩選事件,傳入以逗號分隔的 UID 清單:

// Only receive events triggered by users 11153765 or 1234567
const ws = new WebSocket("ws://localhost:8080/?uids=11153765,1234567");

roomsuids 可以並用;兩者同時設定時,事件必須符合兩項篩選條件才會送出。啟用 UID 篩選期間,沒有 uid(或 uid 為 0)的事件會被排除

接著,伺服器每處理一則事件,您就會即時收到對應的 LaplaceEvent

測試

專案內附了測試用戶端 websocket-test.html,可在瀏覽器中開啟,用來測試 WebSocket 連線並查看即時事件。測試頁面也內建 Mock Event Sender 面板,可透過 /mock 端點送出測試事件

您也可以使用 wscat 測試 WebSocket 連線:

# Via Sec-WebSocket-Protocol header
wscat -c ws://localhost:8080 -s client -s <auth-token>

# Via token query parameter
wscat -c 'ws://localhost:8080/?token=<auth-token>'

# With room filtering
wscat -c 'ws://localhost:8080/?rooms=456117,25034104'

# With uid filtering
wscat -c 'ws://localhost:8080/?uids=11153765,1234567'

# With both query-style authentication and room/uid filtering
wscat -c 'ws://localhost:8080/?token=<auth-token>&rooms=456117,25034104&uids=11153765'

透過 curl 送出模擬事件:

curl -X POST http://localhost:8080/mock \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-admin-key-at-least-12-chars" \
  -d '{"type": "message", "message": "Test message", "username": "TestUser"}'

心跳機制

WebSocket 連線內建心跳機制,用於偵測失效連線並從中恢復:

  • 伺服器 → 用戶端:伺服器每 10 秒送出一則 ping 訊息
  • 用戶端 → 伺服器:用戶端必須回覆 pong 訊息
  • 逾時:若 60 秒內未收到 pong,連線將被關閉
  • 自動重連:相容的用戶端(LAPLACE Event Bridge SDK 1.0.0 以上)會自動重新連線

這能確保長時間連線在遇到網路問題、代理逾時或其他中斷時,仍保持穩定

注意:心跳功能僅在 LAPLACE Event Fetcher 中提供,LAPLACE Event Bridge (LEB) 伺服器並不支援,因為 LEB 的設計是在本機執行,不需考量連線穩定性

OpenTelemetry

本服務內建選用的 OpenTelemetry 支援,可用於分散式追蹤與可觀測性。設定完成後,會自動為 PostgreSQL 操作加上追蹤

Axiom 日誌(選用)

若您使用 Axiom,且除了追蹤之外還想要結構化日誌,當服務在 OTLP 標頭中偵測到 Axiom 憑證時,會自動設定 Pino logger 將日誌送往 Axiom:

  • OTEL_EXPORTER_OTLP_HEADERS 中同時存在 X-Axiom-DatasetAuthorization 時,日誌會自動送出
  • 日誌等級預設為 debug

已納入追蹤的元件

啟用 OpenTelemetry 後,以下元件會自動納入追蹤:

  • PostgreSQL:所有資料庫查詢與操作
  • HTTP 請求:所有傳入的 HTTP 請求與回應

API

您可以在自己執行個體的 /openapi 路徑查閱 API 文件

  • GET /events/<room_id>:依直播間 ID 從資料庫取得事件
    • 查詢參數:
      • ?full=1:取得所有事件
      • ?uids=<uid1>,<uid2>:依一個或多個使用者 UID 篩選事件(以逗號分隔)。沒有相符 uid 的事件會被排除
  • POST /upload:上傳事件並存入資料庫
    • 標頭:
      • AuthorizationBearer <ADMIN_TOKEN>(必填,權杖長度至少 12 個字元)
    • 內容:
      • 原始 JSON LaplaceEvent[] 或 LAPLACE Chat Archive (.lca)
  • POST /mock:模擬一則 LaplaceEvent,並廣播給所有已連線的 WebSocket 用戶端(測試時很實用)。範例請見 @laplace.live/event-types
    • 標頭:
      • AuthorizationBearer <ADMIN_TOKEN>(必填,權杖長度至少 12 個字元)
    • 內容:
      • 至少包含 type 欄位的 JSON 物件(例如 {"type": "message", "message": "Test message"}
      • 事件中的所有欄位都會被廣播,並額外附上 mock: true 旗標與目前的時間戳記
  • GET /ping:檢查伺服器是否正常運作。此端點會連線至資料庫,並以 JSON 格式回傳 pong 與 200 狀態碼,失敗時則回傳 500 狀態碼
  • GET /info:取得目前執行個體的資訊,包含已連線的直播間與主播資料
    • 標頭:
      • Authorization<AUTH_TOKEN>Bearer <AUTH_TOKEN>(設定 AUTH_TOKEN 時為必填)

環境變數

  • PORT(選用):伺服器監聽的連接埠。預設:8080
  • ROOMS:要抓取的直播間,多個直播間可用逗號分隔。預設:456117。建議每個節點加入的直播間少於 10 個,否則嗶哩嗶哩會阻擋您取得事件
  • DATABASE_URL:要連線的資料庫,例如 postgresql://username:password@pg:5432/lef
  • REDIS_URL(選用):要連線的 Redis,例如 redis://username:password@redis:6379
  • EVENTS_KEEP(選用):超過此保留時長的事件將被捨棄。預設:72(小時)
  • REDIS_EVENT_LIMIT(選用):每個直播間在 Redis 中最多保存的事件數。預設:100
  • RESTART_WAIT(選用):重新建立連線前的等待時間。預設:2000(毫秒)
  • RESTART_INTERVAL(選用):以 cron 格式表示的連線重啟間隔。預設:0 6,18 * * *(每天早上 6:00 與下午 6:00)
  • LOGIN_SYNC_TOKEN(選用):來自 LAPLACE Login Sync 擴充功能的權杖。預設:undefined。多組金鑰可用逗號分隔
  • AUTH_TOKEN(選用):一組長隨機字串,作為存取 REST API 的憑證。適合只想將 API 開放給已授權使用者的情況。預設:undefined。多組權杖可用逗號分隔(原名 AUTH_KEY,仍可使用,但會顯示淘汰警告)
  • ADMIN_TOKEN(選用):一組長隨機字串,作為上傳事件至雲端與送出模擬事件的憑證,長度至少 12 個字元。預設:undefined。若要上傳事件或使用 mock 端點,就必須設定此變數。可使用 openssl rand -hex 32 產生隨機字串(原名 ADMIN_KEY,仍可使用,但會顯示淘汰警告)
  • BRIDGE_MODE(選用):啟用 WebSocket bridge 模式,以進行即時事件串流。設為 false0 可停用。預設:undefined(停用)(原名 WEBSOCKET_BRIDGE,仍可使用,但會顯示淘汰警告)
  • BRIDGE_TOKEN(選用):WebSocket 驗證用的密碼。設定後,用戶端必須提供此密碼才能連線至 WebSocket 端點。預設:undefined(原名 WEBSOCKET_BRIDGE_AUTH,仍可使用,但會顯示淘汰警告)
  • OTEL_EXPORTER_OTLP_ENDPOINT(選用):OpenTelemetry 端點網址,例如 https://api.example.com。未設定時,OpenTelemetry 將停用
  • OTEL_EXPORTER_OTLP_HEADERS(選用):隨 OpenTelemetry 請求送出的標頭,格式為以逗號分隔的 key=value,例如 Authorization=Bearer <token>,X-Dataset=my-dataset
  • OTEL_TRACE_SAMPLE_RATE(選用):OpenTelemetry 追蹤的取樣率。預設:0.1(10%)

開發

bun run dev # session 1
bunx drizzle-kit studio # session 2

# init db and create migrations
bunx drizzle-kit migrate

# Update schemas
bunx drizzle-kit generate

FAQ

為什麼沒有 Docker Hub?

Docker Hub 很爛

為什麼不開源?

  • 我不認為其他使用者會為這個專案做出貢獻
  • 我不需要靠開源專案來幫我找工作
  • 我不希望其他使用者濫用這個專案

最後更新於 2026年9月19日

Tech otakus destroy the world