douyin-danma-listener 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/api.js +18 -0
  2. package/lib/index.js +58 -43
  3. package/package.json +7 -2
package/lib/api.js CHANGED
@@ -1,4 +1,11 @@
1
+ let cookieCache;
1
2
  export const getCookie = async () => {
3
+ const now = new Date().getTime();
4
+ // 缓存1小时:重连时优先复用,避免每次连接都依赖实时网络抓取,
5
+ // 瞬时网络抖动时不会导致 connect() 失败、重试链断裂
6
+ if (cookieCache?.startTimestamp && now - cookieCache.startTimestamp < 60 * 60 * 1000) {
7
+ return cookieCache.cookies;
8
+ }
2
9
  const res = await fetch("https://live.douyin.com/", {
3
10
  method: "GET",
4
11
  });
@@ -11,5 +18,16 @@ export const getCookie = async () => {
11
18
  return cookie.split(";")[0];
12
19
  })
13
20
  .join("; ");
21
+ if (!cookies.includes("ttwid")) {
22
+ // 新cookie不含ttwid,且缓存中有含ttwid的旧cookie时,延长旧缓存继续复用
23
+ if (cookieCache?.cookies) {
24
+ cookieCache.startTimestamp += 60 * 60 * 1000;
25
+ return cookieCache.cookies;
26
+ }
27
+ }
28
+ cookieCache = {
29
+ startTimestamp: now,
30
+ cookies,
31
+ };
14
32
  return cookies;
15
33
  };
package/lib/index.js CHANGED
@@ -1,22 +1,8 @@
1
- import { parse, format } from "node:url";
2
1
  import WebSocket from "ws";
3
2
  import { TypedEmitter } from "tiny-typed-emitter";
4
3
  import { decompressGzip, getXMsStub, getSignature, getUserUniqueId } from "./utils.js";
5
4
  import protobuf from "./proto.js";
6
5
  import { getCookie } from "./api.js";
7
- function buildRequestUrl(url) {
8
- const parsedUrl = parse(url, true);
9
- const existingParams = parsedUrl.query;
10
- existingParams["aid"] = "6383";
11
- existingParams["device_platform"] = "web";
12
- existingParams["browser_language"] = "zh-CN";
13
- existingParams["browser_platform"] = "Win32";
14
- existingParams["browser_name"] = "Mozilla";
15
- existingParams["browser_version"] = "92.0.4515.159";
16
- parsedUrl.search = null;
17
- parsedUrl.query = existingParams;
18
- return format(parsedUrl);
19
- }
20
6
  class DouYinDanmaClient extends TypedEmitter {
21
7
  ws;
22
8
  roomId;
@@ -51,36 +37,46 @@ class DouYinDanmaClient extends TypedEmitter {
51
37
  }
52
38
  }
53
39
  async connect() {
54
- const url = await this.getWsInfo(this.roomId);
55
- if (!url) {
56
- this.emit("error", new Error("获取抖音弹幕签名失败"));
57
- return;
40
+ try {
41
+ const url = await this.getWsInfo(this.roomId);
42
+ if (!url) {
43
+ throw new Error("获取抖音弹幕签名失败");
44
+ }
45
+ this.emit("init", url);
46
+ const cookies = this.cookie || (await getCookie());
47
+ this.ws = new WebSocket(url, {
48
+ headers: {
49
+ Cookie: cookies,
50
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
51
+ Origin: "https://live.douyin.com",
52
+ Referer: "https://live.douyin.com/",
53
+ },
54
+ });
55
+ this.ws.on("open", () => {
56
+ this.emit("open");
57
+ this.startHeartbeat();
58
+ this.startTimeoutCheck();
59
+ });
60
+ this.ws.on("message", (data) => {
61
+ this.lastMessageTime = Date.now();
62
+ this.decode(data);
63
+ });
64
+ this.ws.on("close", () => {
65
+ this.emit("close");
66
+ this.reconnect();
67
+ });
68
+ this.ws.on("error", (error) => {
69
+ this.emit("error", error);
70
+ this.reconnect();
71
+ });
58
72
  }
59
- this.emit("init", url);
60
- const cookies = this.cookie || (await getCookie());
61
- this.ws = new WebSocket(url, {
62
- headers: {
63
- Cookie: cookies,
64
- },
65
- });
66
- this.ws.on("open", () => {
67
- this.reconnectAttempts = 0;
68
- this.emit("open");
69
- this.startHeartbeat();
70
- this.startTimeoutCheck();
71
- });
72
- this.ws.on("message", (data) => {
73
- this.lastMessageTime = Date.now();
74
- this.decode(data);
75
- });
76
- this.ws.on("close", () => {
77
- this.emit("close");
78
- this.reconnect();
79
- });
80
- this.ws.on("error", (error) => {
81
- this.emit("error", error);
73
+ catch (err) {
74
+ // getWsInfo/getCookie 等前置步骤失败时,必须让错误可见并继续重试:
75
+ // 否则 WebSocket 不会创建,没有任何事件能触发下一次 reconnect,
76
+ // 弹幕客户端将静默死亡直到录制结束
77
+ this.emit("error", err);
82
78
  this.reconnect();
83
- });
79
+ }
84
80
  }
85
81
  send(data) {
86
82
  if (!this.ws) {
@@ -373,18 +369,37 @@ class DouYinDanmaClient extends TypedEmitter {
373
369
  return;
374
370
  }
375
371
  const webcast5Params = {
372
+ app_name: "douyin_web",
376
373
  room_id: roomId,
377
374
  compress: "gzip",
378
375
  version_code: String(versionCode),
379
376
  webcast_sdk_version: webcastSdkVersion,
377
+ update_version_code: webcastSdkVersion,
380
378
  live_id: "1",
381
379
  did_rule: "3",
382
380
  user_unique_id: userUniqueId,
383
381
  identity: "audience",
384
382
  signature: signature.toString(),
383
+ device_platform: "web",
384
+ cookie_enabled: "true",
385
+ screen_width: "1920",
386
+ screen_height: "1080",
387
+ browser_language: "zh-CN",
388
+ browser_platform: "Win32",
389
+ browser_name: "Mozilla",
390
+ browser_version: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
391
+ browser_online: "true",
392
+ tz_name: "Etc/GMT-8",
393
+ host: "https://live.douyin.com",
394
+ aid: "6383",
395
+ endpoint: "live_pc",
396
+ support_wrds: "1",
397
+ im_path: "/webcast/im/fetch/",
398
+ need_persist_msg_count: "15",
399
+ heartbeatDuration: "0",
385
400
  };
386
401
  const wssUrl = `wss://${this.host}/webcast/im/push/v2/?${new URLSearchParams(webcast5Params).toString()}`;
387
- return buildRequestUrl(wssUrl);
402
+ return wssUrl;
388
403
  }
389
404
  }
390
405
  export default DouYinDanmaClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "douyin-danma-listener",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "douyin danma listener",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./types/index.d.ts",
@@ -28,7 +28,11 @@
28
28
  "弹幕",
29
29
  "抖音"
30
30
  ],
31
- "repository": "https://github.com/renmu123/biliLive-tools/tree/master/packages/DouYinDanma",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/renmu123/biliLive-tools",
34
+ "directory": "packages/DouYinDanma"
35
+ },
32
36
  "author": "renmu123",
33
37
  "license": "GPLV3",
34
38
  "dependencies": {
@@ -38,6 +42,7 @@
38
42
  },
39
43
  "devDependencies": {
40
44
  "@types/node": "*",
45
+ "@types/ws": "^8.5.13",
41
46
  "protobufjs-cli": "^1.1.3"
42
47
  },
43
48
  "scripts": {