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:

  • 创建一个新应用,并选择 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 端点 URL,例如 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