@tiktool/live 2.7.1 → 2.8.0

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.
package/dist/index.mjs CHANGED
@@ -396,27 +396,41 @@ function parseWebcastMessage(method, payload) {
396
396
  // Proto: WebcastLinkMicBattle { repeated WebcastLinkMicBattleItems battleUsers=10 }
397
397
  // battleUsers → { battleGroup=2 → { LinkUser user=1 } }
398
398
  case "WebcastLinkMicBattle": {
399
- const battleId = String(getInt(f, 1) || "");
400
- const status = getInt(f, 2) || 1;
399
+ const battleId = String(getInt(f, 4) || getInt(f, 1) || "");
400
+ let status = getInt(f, 7);
401
+ if (!status) {
402
+ const fallback = getInt(f, 2);
403
+ status = fallback > 0 && fallback < 100 ? fallback : 1;
404
+ }
401
405
  const battleDuration = getInt(f, 3);
402
406
  const teams = [];
403
- const battleUserBufs = getAllBytes(f, 10);
404
- for (const bub of battleUserBufs) {
407
+ const hostBufs = getAllBytes(f, 14);
408
+ for (const hb of hostBufs) {
405
409
  try {
406
- const bf = decodeProto(bub);
407
- const groupBuf = getBytes(bf, 2);
408
- if (groupBuf) {
409
- const gf = decodeProto(groupBuf);
410
- const linkUserBuf = getBytes(gf, 1);
411
- if (linkUserBuf) {
412
- const user = parseUser(linkUserBuf);
413
- teams.push({
414
- hostUserId: user.id,
415
- score: 0,
416
- users: [{ user, score: 0 }]
417
- });
410
+ const hf = decodeProto(hb);
411
+ const hostUserId = String(getInt(hf, 1) || "");
412
+ const teamTotalScore = getInt(hf, 3);
413
+ const users = [];
414
+ const containerBuf = getBytes(hf, 4);
415
+ if (containerBuf) {
416
+ const cf = decodeProto(containerBuf);
417
+ const userBufs = getAllBytes(cf, 1);
418
+ for (const ub of userBufs) {
419
+ try {
420
+ const uf = decodeProto(ub);
421
+ users.push({
422
+ user: {
423
+ id: String(getInt(uf, 1) || ""),
424
+ nickname: getStr(uf, 3) || "",
425
+ uniqueId: getStr(uf, 3) || ""
426
+ },
427
+ score: getInt(uf, 2)
428
+ });
429
+ } catch {
430
+ }
418
431
  }
419
432
  }
433
+ teams.push({ hostUserId, score: teamTotalScore, users });
420
434
  } catch {
421
435
  }
422
436
  }
@@ -429,15 +443,42 @@ function parseWebcastMessage(method, payload) {
429
443
  }
430
444
  }
431
445
  }
446
+ if (teams.length === 0) {
447
+ const battleUserBufs = getAllBytes(f, 10);
448
+ for (const bub of battleUserBufs) {
449
+ try {
450
+ const bf = decodeProto(bub);
451
+ const groupBuf = getBytes(bf, 2);
452
+ if (groupBuf) {
453
+ const gf = decodeProto(groupBuf);
454
+ const linkUserBuf = getBytes(gf, 1);
455
+ if (linkUserBuf) {
456
+ const user = parseUser(linkUserBuf);
457
+ teams.push({
458
+ hostUserId: user.id,
459
+ score: 0,
460
+ users: [{ user, score: 0 }]
461
+ });
462
+ }
463
+ }
464
+ } catch {
465
+ }
466
+ }
467
+ }
432
468
  return { ...base, type: "battle", battleId, status, battleDuration, teams };
433
469
  }
434
- // Proto: WebcastLinkMicArmies { msg_header=1, session_id=2, repeated battleItems=3, status=7,
435
- // battle_config=18 { session_id=1, start_time_ms=2, duration_s=3 }, battle_id=17 }
436
- // battleItems → { hostUserId=1, repeated battleGroups=2 }
437
- // battleGroups → { repeated users=1, int32 points=2 }
470
+ // Proto: WebcastLinkMicArmies { msg_header=1, matchId=2, session_id=4, startedAtMs=5,
471
+ // serverTsMs=6, repeated battleItems=3, status=7, sessionTag=17,
472
+ // battle_config=18 { session_id=1, start_time_ms=2, duration_s=3 },
473
+ // repeated hosts=14 { hostUserId=1, teamTotalScore=3, teamIdx=5,
474
+ // userContainer=4 { repeated users=1 { userId=1, score=2, nickname=3 } } }
438
475
  case "WebcastLinkMicArmies": {
439
- const battleId = getStr(f, 17) || String(getInt(f, 2) || "");
476
+ const sessionTag = getStr(f, 17);
477
+ const matchId = getIntStr(f, 2);
478
+ const sessionId = getIntStr(f, 4);
479
+ const battleId = sessionTag || matchId;
440
480
  const battleStatus = getInt(f, 7);
481
+ const serverTsMs = getInt(f, 6);
441
482
  const teams = [];
442
483
  const itemBufs = getAllBytes(f, 3);
443
484
  for (const ib of itemBufs) {
@@ -457,6 +498,10 @@ function parseWebcastMessage(method, payload) {
457
498
  } catch {
458
499
  }
459
500
  }
501
+ let secsRemaining = 0;
502
+ if (battleStartMs > 0 && battleDurationSeconds > 0 && serverTsMs > 0) {
503
+ secsRemaining = Math.max(0, battleDurationSeconds - Math.floor((serverTsMs - battleStartMs) / 1e3));
504
+ }
460
505
  let endTimeMs = 0;
461
506
  let timeLeftSeconds = 0;
462
507
  if (battleStartMs > 0 && battleDurationSeconds > 0) {
@@ -464,6 +509,36 @@ function parseWebcastMessage(method, payload) {
464
509
  const elapsedMs = Date.now() - battleStartMs;
465
510
  timeLeftSeconds = Math.max(0, battleDurationSeconds - Math.floor(elapsedMs / 1e3));
466
511
  }
512
+ const hosts = [];
513
+ const hostBufs = getAllBytes(f, 14);
514
+ for (const hb of hostBufs) {
515
+ try {
516
+ const hf = decodeProto(hb);
517
+ const hostUserId = getIntStr(hf, 1);
518
+ const teamTotalScore = getInt(hf, 3);
519
+ const teamIdx = getInt(hf, 5);
520
+ const contributors = [];
521
+ const containerBuf = getBytes(hf, 4);
522
+ if (containerBuf) {
523
+ const cf = decodeProto(containerBuf);
524
+ const userBufs = getAllBytes(cf, 1);
525
+ for (const ub of userBufs) {
526
+ try {
527
+ const uf = decodeProto(ub);
528
+ contributors.push({
529
+ userId: getIntStr(uf, 1),
530
+ score: getInt(uf, 2),
531
+ nickname: getStr(uf, 3) || ""
532
+ });
533
+ } catch {
534
+ }
535
+ }
536
+ contributors.sort((a, b) => b.score - a.score);
537
+ }
538
+ hosts.push({ hostUserId, teamTotalScore, teamIdx, contributors });
539
+ } catch {
540
+ }
541
+ }
467
542
  return {
468
543
  ...base,
469
544
  type: "battleArmies",
@@ -473,7 +548,162 @@ function parseWebcastMessage(method, payload) {
473
548
  battleStartMs,
474
549
  battleDurationSeconds,
475
550
  timeLeftSeconds,
476
- endTimeMs
551
+ endTimeMs,
552
+ matchId,
553
+ sessionId,
554
+ serverTsMs,
555
+ sessionTag,
556
+ durationSec: battleDurationSeconds,
557
+ secsRemaining,
558
+ hosts
559
+ };
560
+ }
561
+ // Proto: WebcastLinkMicBattleItemCard
562
+ // #2 battleId
563
+ // #3 cardType (2=gloves/crit, 3=mist, 4=match_guide, 10=x2?, 11=x3, ...)
564
+ // #5/#6/#7/#14 inner (location varies with cardType — scan numerically):
565
+ // #1.{
566
+ // #1 effectKey
567
+ // #2 icon block { #1 repeated CDN urls, #2 iconKey, #5 accentColor }
568
+ // #3 activatedAtSec (unix seconds)
569
+ // #4 sender block: #1 sub { #1 userId, #2 nickname, #3 avatar block, #4 uniqueId }
570
+ // #5 durationSec
571
+ // #8 senderUserId (new boosters)
572
+ // #11 endsAtSec
573
+ // #12 senderUserId (old gloves/mist)
574
+ // }
575
+ // #3 comment template msg → #2 commentText
576
+ case "WebcastLinkMicBattleItemCard": {
577
+ const battleId = getIntStr(f, 2);
578
+ const cardType = getInt(f, 3);
579
+ let inner = null;
580
+ for (const fn of [5, 6, 7, 14, 15, 16, 8, 9, 10, 11, 12, 13]) {
581
+ const buf = getBytes(f, fn);
582
+ if (buf) {
583
+ inner = decodeProto(buf);
584
+ break;
585
+ }
586
+ }
587
+ let effectKey = "";
588
+ let effect = "unknown";
589
+ let multiplier = 0;
590
+ let activatedAtSec = 0;
591
+ let durationSec = 0;
592
+ let endsAtSec = 0;
593
+ let senderUserId = "";
594
+ let senderNickname = "";
595
+ let senderUniqueId = "";
596
+ let senderAvatarUrl = "";
597
+ let commentTemplate = "";
598
+ let iconUrl = "";
599
+ let iconKey = "";
600
+ let accentColor = "";
601
+ if (inner) {
602
+ let nameBuf = getBytes(inner, 1);
603
+ let nf = nameBuf ? decodeProto(nameBuf) : [];
604
+ let probedKey = nf.length ? getStr(nf, 1) || "" : "";
605
+ if (!probedKey) {
606
+ const altBuf = getBytes(inner, 3);
607
+ if (altBuf) {
608
+ const altFields = decodeProto(altBuf);
609
+ const altKey = getStr(altFields, 1) || "";
610
+ if (altKey) {
611
+ nameBuf = altBuf;
612
+ nf = altFields;
613
+ probedKey = altKey;
614
+ }
615
+ }
616
+ }
617
+ if (nameBuf && nf.length) {
618
+ effectKey = probedKey;
619
+ if (effectKey === "pm_mt_boost_crit_name") effect = "hammer";
620
+ else if (effectKey === "pm_mt_boost_glove_name" || effectKey === "pm_mt_boost_gloves_name") effect = "gloves";
621
+ else if (effectKey === "pm_mt_boost_mist_name") effect = "mist";
622
+ else if (effectKey === "pm_mt_match_guide_gotPowerUps") effect = "match_guide";
623
+ else if (effectKey.includes("userSentBooster_no2") || effectKey.includes("gotPowerUp_panel_no2")) {
624
+ effect = "booster_x2";
625
+ multiplier = 2;
626
+ } else if (effectKey.includes("userSentBooster_no3") || effectKey.includes("gotPowerUp_panel_no3")) {
627
+ effect = "booster_x3";
628
+ multiplier = 3;
629
+ } else if (effectKey.includes("thunder")) effect = "thunder";
630
+ else if (effectKey.includes("extra_time") || effectKey.includes("extra_round")) effect = "extra_time";
631
+ else if (effectKey) effect = effectKey;
632
+ activatedAtSec = getInt(nf, 3);
633
+ durationSec = getInt(nf, 5);
634
+ endsAtSec = getInt(nf, 11) || getInt(nf, 7);
635
+ senderUserId = getIntStr(nf, 12);
636
+ if (senderUserId === "0") senderUserId = getIntStr(nf, 8);
637
+ if (senderUserId === "0") senderUserId = getStr(nf, 13) || getStr(nf, 9) || "";
638
+ const iconBuf = getBytes(nf, 2);
639
+ if (iconBuf) {
640
+ const icf = decodeProto(iconBuf);
641
+ const urlBufs = getAllBytes(icf, 1);
642
+ for (const ub of urlBufs) {
643
+ const candidate = ub.toString("utf-8");
644
+ if (candidate.includes("://")) {
645
+ iconUrl = candidate;
646
+ break;
647
+ }
648
+ }
649
+ iconKey = getStr(icf, 2) || "";
650
+ accentColor = getStr(icf, 5) || "";
651
+ }
652
+ const senderOuter = getBytes(nf, 4);
653
+ if (senderOuter) {
654
+ const sf = decodeProto(senderOuter);
655
+ const sub = getBytes(sf, 1);
656
+ if (sub) {
657
+ const su = decodeProto(sub);
658
+ const nickBuf = getBytes(su, 2);
659
+ if (nickBuf) {
660
+ try {
661
+ senderNickname = nickBuf.toString("utf-8");
662
+ } catch {
663
+ }
664
+ }
665
+ senderUniqueId = getStr(su, 4) || "";
666
+ if (!senderUserId || senderUserId === "0") senderUserId = getIntStr(su, 1);
667
+ const avBuf = getBytes(su, 3);
668
+ if (avBuf) {
669
+ const af = decodeProto(avBuf);
670
+ const urlBufs = getAllBytes(af, 1);
671
+ for (const ub of urlBufs) {
672
+ const candidate = ub.toString("utf-8");
673
+ if (candidate.includes("://")) {
674
+ senderAvatarUrl = candidate;
675
+ break;
676
+ }
677
+ }
678
+ }
679
+ }
680
+ }
681
+ }
682
+ const commentBuf = getBytes(inner, 3);
683
+ if (commentBuf) {
684
+ const cf = decodeProto(commentBuf);
685
+ commentTemplate = getStr(cf, 2) || "";
686
+ }
687
+ }
688
+ return {
689
+ ...base,
690
+ type: "battleItemCard",
691
+ battleId,
692
+ cardType,
693
+ effect,
694
+ effectKey,
695
+ multiplier,
696
+ senderUserId,
697
+ senderNickname,
698
+ senderUniqueId,
699
+ senderAvatarUrl,
700
+ activatedAtSec,
701
+ durationSec,
702
+ endsAtSec,
703
+ commentTemplate,
704
+ iconUrl,
705
+ iconKey,
706
+ accentColor
477
707
  };
478
708
  }
479
709
  // Proto: WebcastSubNotifyMessage { User user=2, ... subMonth=3 }
@@ -676,13 +906,13 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
676
906
  preSignedCookies = credData.data.cookies || "";
677
907
  preSignedUA = credData.data.user_agent || DEFAULT_UA;
678
908
  ttwid = "__presigned__";
679
- console.log(`[TikTokLive] \u2705 Got pre-signed URL from sign server`);
909
+ console.log(`[TikTokLive] \xE2\u0153\u2026 Got pre-signed URL from sign server`);
680
910
  } else {
681
- console.log(`[TikTokLive] \u26A0\uFE0F ws_credentials bad response: ${JSON.stringify(credData).substring(0, 120)}`);
911
+ console.log(`[TikTokLive] \xE2\u0161\xA0\xEF\xB8\x8F ws_credentials bad response: ${JSON.stringify(credData).substring(0, 120)}`);
682
912
  }
683
913
  }
684
914
  } catch (e) {
685
- console.log(`[TikTokLive] \u274C ws_credentials failed: ${e.message}`);
915
+ console.log(`[TikTokLive] \xE2\x9D\u0152 ws_credentials failed: ${e.message}`);
686
916
  }
687
917
  if (!ttwid) {
688
918
  console.log(`[TikTokLive] Fallback: direct TikTok fetch for ttwid...`);
@@ -697,9 +927,9 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
697
927
  break;
698
928
  }
699
929
  }
700
- console.log(`[TikTokLive] Direct fetch ttwid: ${ttwid ? "\u2705" : "\u274C none"}`);
930
+ console.log(`[TikTokLive] Direct fetch ttwid: ${ttwid ? "\xE2\u0153\u2026" : "\xE2\x9D\u0152 none"}`);
701
931
  } catch (e) {
702
- console.log(`[TikTokLive] \u274C Direct TikTok fetch failed: ${e.message}`);
932
+ console.log(`[TikTokLive] \xE2\x9D\u0152 Direct TikTok fetch failed: ${e.message}`);
703
933
  }
704
934
  }
705
935
  } else {
@@ -715,13 +945,13 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
715
945
  const roomData = await roomResp.json();
716
946
  if (roomData.status_code === 0 && roomData.data?.room_id) {
717
947
  roomId = roomData.data.room_id;
718
- console.log(`[TikTokLive] \u2705 Got roomId from sign server: ${roomId}`);
948
+ console.log(`[TikTokLive] \xE2\u0153\u2026 Got roomId from sign server: ${roomId}`);
719
949
  } else {
720
- console.log(`[TikTokLive] \u26A0\uFE0F room_id response: ${JSON.stringify(roomData).substring(0, 120)}`);
950
+ console.log(`[TikTokLive] \xE2\u0161\xA0\xEF\xB8\x8F room_id response: ${JSON.stringify(roomData).substring(0, 120)}`);
721
951
  }
722
952
  }
723
953
  } catch (e) {
724
- console.log(`[TikTokLive] \u274C room_id request failed: ${e.message}`);
954
+ console.log(`[TikTokLive] \xE2\x9D\u0152 room_id request failed: ${e.message}`);
725
955
  }
726
956
  if (roomId) {
727
957
  try {
@@ -737,14 +967,14 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
737
967
  const m = credData.data.cookies.match(/ttwid=([^;]+)/);
738
968
  if (m) {
739
969
  ttwid = m[1];
740
- console.log(`[TikTokLive] \u2705 Got ttwid from sign server`);
741
- } else console.log(`[TikTokLive] \u26A0\uFE0F No ttwid in cookies: ${credData.data.cookies.substring(0, 60)}`);
970
+ console.log(`[TikTokLive] \xE2\u0153\u2026 Got ttwid from sign server`);
971
+ } else console.log(`[TikTokLive] \xE2\u0161\xA0\xEF\xB8\x8F No ttwid in cookies: ${credData.data.cookies.substring(0, 60)}`);
742
972
  } else {
743
- console.log(`[TikTokLive] \u26A0\uFE0F No cookies in ws_credentials: ${JSON.stringify(credData).substring(0, 120)}`);
973
+ console.log(`[TikTokLive] \xE2\u0161\xA0\xEF\xB8\x8F No cookies in ws_credentials: ${JSON.stringify(credData).substring(0, 120)}`);
744
974
  }
745
975
  }
746
976
  } catch (e) {
747
- console.log(`[TikTokLive] \u274C ws_credentials failed: ${e.message}`);
977
+ console.log(`[TikTokLive] \xE2\x9D\u0152 ws_credentials failed: ${e.message}`);
748
978
  }
749
979
  }
750
980
  if (!roomId || !ttwid) {
@@ -781,7 +1011,7 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
781
1011
  }
782
1012
  console.log(`[TikTokLive] Direct fetch result: roomId=${roomId || "none"}, ttwid=${ttwid ? "yes" : "none"}`);
783
1013
  } catch (e) {
784
- console.log(`[TikTokLive] \u274C Direct TikTok fetch failed: ${e.message}`);
1014
+ console.log(`[TikTokLive] \xE2\x9D\u0152 Direct TikTok fetch failed: ${e.message}`);
785
1015
  }
786
1016
  }
787
1017
  }
@@ -867,18 +1097,21 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
867
1097
  this.handleFrame(Buffer.from(rawData));
868
1098
  });
869
1099
  this.ws.on("close", (code, reason) => {
1100
+ const wasConnected = this._connected;
870
1101
  this._connected = false;
871
1102
  this.stopHeartbeat();
872
1103
  const reasonStr = reason?.toString() || "";
873
1104
  this.emit("disconnected", code, reasonStr);
874
- if (!this.intentionalClose && this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
1105
+ if (wasConnected && !this.intentionalClose && this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
875
1106
  this.reconnectAttempts++;
876
1107
  const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), 3e4);
877
1108
  setTimeout(() => this.connect().catch((e) => this.emit("error", e)), delay);
878
1109
  }
879
1110
  });
880
1111
  this.ws.on("error", (err) => {
881
- this.emit("error", err);
1112
+ if (this._connected) {
1113
+ this.emit("error", err);
1114
+ }
882
1115
  if (!this._connected) reject(err);
883
1116
  });
884
1117
  });
@@ -895,7 +1128,7 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
895
1128
  }
896
1129
  /**
897
1130
  * Fully destroy the client, releasing all resources and listeners.
898
- * After calling destroy(), the instance cannot be reused — create a new one.
1131
+ * After calling destroy(), the instance cannot be reused — create a new one.
899
1132
  */
900
1133
  destroy() {
901
1134
  this.disconnect();
@@ -920,7 +1153,7 @@ var TikTokLive = class _TikTokLive extends EventEmitter {
920
1153
  /**
921
1154
  * Get live stream video URLs for a TikTok user.
922
1155
  * Returns FLV & HLS URLs segmented by quality (FULL_HD1, HD1, SD1, SD2).
923
- * This is a standalone method — no WebSocket connection required.
1156
+ * This is a standalone method — no WebSocket connection required.
924
1157
  *
925
1158
  * @example
926
1159
  * ```ts