@tiktool/live 2.7.0 → 2.7.1
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.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +107 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -83,8 +83,10 @@ interface BattleArmiesEvent extends BaseEvent {
|
|
|
83
83
|
battleStartMs: number;
|
|
84
84
|
/** Total battle duration in seconds (from f18.f3, typically 300) */
|
|
85
85
|
battleDurationSeconds: number;
|
|
86
|
-
/** Seconds remaining in the battle, computed as max(0, duration - elapsed) */
|
|
86
|
+
/** Seconds remaining in the battle, computed as max(0, duration - elapsed). Note: uses VPS clock, may be off by up to 30s */
|
|
87
87
|
timeLeftSeconds: number;
|
|
88
|
+
/** Battle end timestamp in milliseconds (battleStartMs + duration*1000). Use for clock-independent timer: Math.max(0, (endTimeMs - Date.now()) / 1000) */
|
|
89
|
+
endTimeMs: number;
|
|
88
90
|
}
|
|
89
91
|
interface SubscribeEvent extends BaseEvent {
|
|
90
92
|
type: 'subscribe';
|
package/dist/index.d.ts
CHANGED
|
@@ -83,8 +83,10 @@ interface BattleArmiesEvent extends BaseEvent {
|
|
|
83
83
|
battleStartMs: number;
|
|
84
84
|
/** Total battle duration in seconds (from f18.f3, typically 300) */
|
|
85
85
|
battleDurationSeconds: number;
|
|
86
|
-
/** Seconds remaining in the battle, computed as max(0, duration - elapsed) */
|
|
86
|
+
/** Seconds remaining in the battle, computed as max(0, duration - elapsed). Note: uses VPS clock, may be off by up to 30s */
|
|
87
87
|
timeLeftSeconds: number;
|
|
88
|
+
/** Battle end timestamp in milliseconds (battleStartMs + duration*1000). Use for clock-independent timer: Math.max(0, (endTimeMs - Date.now()) / 1000) */
|
|
89
|
+
endTimeMs: number;
|
|
88
90
|
}
|
|
89
91
|
interface SubscribeEvent extends BaseEvent {
|
|
90
92
|
type: 'subscribe';
|
package/dist/index.js
CHANGED
|
@@ -264,10 +264,11 @@ function parseBattleTeamFromArmies(itemBuf) {
|
|
|
264
264
|
const points = getInt(gf, 2);
|
|
265
265
|
teamScore += points;
|
|
266
266
|
const userBufs = getAllBytes(gf, 1);
|
|
267
|
+
const individualScore = userBufs.length === 1 ? points : -1;
|
|
267
268
|
for (const ub of userBufs) {
|
|
268
269
|
try {
|
|
269
270
|
const user = parseUser(ub);
|
|
270
|
-
users.push({ user, score:
|
|
271
|
+
users.push({ user, score: individualScore });
|
|
271
272
|
} catch {
|
|
272
273
|
}
|
|
273
274
|
}
|
|
@@ -492,8 +493,10 @@ function parseWebcastMessage(method, payload) {
|
|
|
492
493
|
} catch {
|
|
493
494
|
}
|
|
494
495
|
}
|
|
496
|
+
let endTimeMs = 0;
|
|
495
497
|
let timeLeftSeconds = 0;
|
|
496
498
|
if (battleStartMs > 0 && battleDurationSeconds > 0) {
|
|
499
|
+
endTimeMs = battleStartMs + battleDurationSeconds * 1e3;
|
|
497
500
|
const elapsedMs = Date.now() - battleStartMs;
|
|
498
501
|
timeLeftSeconds = Math.max(0, battleDurationSeconds - Math.floor(elapsedMs / 1e3));
|
|
499
502
|
}
|
|
@@ -505,7 +508,8 @@ function parseWebcastMessage(method, payload) {
|
|
|
505
508
|
status: battleStatus,
|
|
506
509
|
battleStartMs,
|
|
507
510
|
battleDurationSeconds,
|
|
508
|
-
timeLeftSeconds
|
|
511
|
+
timeLeftSeconds,
|
|
512
|
+
endTimeMs
|
|
509
513
|
};
|
|
510
514
|
}
|
|
511
515
|
// Proto: WebcastSubNotifyMessage { User user=2, ... subMonth=3 }
|
|
@@ -687,24 +691,37 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
687
691
|
let ttwid = this._presetSessionId;
|
|
688
692
|
let roomId = this._presetRoomId;
|
|
689
693
|
let clusterRegion = "";
|
|
694
|
+
let preSignedUrl = "";
|
|
695
|
+
let preSignedCookies = "";
|
|
696
|
+
let preSignedUA = "";
|
|
690
697
|
if (roomId && ttwid) {
|
|
698
|
+
console.log(`[TikTokLive] Using preset roomId=${roomId} + sessionId (skip TikTok fetch)`);
|
|
691
699
|
} else if (roomId && !ttwid) {
|
|
700
|
+
console.log(`[TikTokLive] Have roomId=${roomId}, fetching ttwid from sign server...`);
|
|
692
701
|
try {
|
|
693
702
|
const credResp = await fetch(`${this.signServerUrl}/webcast/ws_credentials`, {
|
|
694
703
|
method: "POST",
|
|
695
704
|
headers: { "Content-Type": "application/json", "x-api-key": this.apiKey },
|
|
696
705
|
body: JSON.stringify({ unique_id: this.uniqueId, room_id: roomId })
|
|
697
706
|
});
|
|
707
|
+
console.log(`[TikTokLive] ws_credentials response: ${credResp.status}`);
|
|
698
708
|
if (credResp.ok) {
|
|
699
709
|
const credData = await credResp.json();
|
|
700
|
-
if (credData.data?.
|
|
701
|
-
|
|
702
|
-
|
|
710
|
+
if (credData.status_code === 0 && credData.data?.signed_url) {
|
|
711
|
+
preSignedUrl = credData.data.signed_url;
|
|
712
|
+
preSignedCookies = credData.data.cookies || "";
|
|
713
|
+
preSignedUA = credData.data.user_agent || DEFAULT_UA;
|
|
714
|
+
ttwid = "__presigned__";
|
|
715
|
+
console.log(`[TikTokLive] \u2705 Got pre-signed URL from sign server`);
|
|
716
|
+
} else {
|
|
717
|
+
console.log(`[TikTokLive] \u26A0\uFE0F ws_credentials bad response: ${JSON.stringify(credData).substring(0, 120)}`);
|
|
703
718
|
}
|
|
704
719
|
}
|
|
705
|
-
} catch {
|
|
720
|
+
} catch (e) {
|
|
721
|
+
console.log(`[TikTokLive] \u274C ws_credentials failed: ${e.message}`);
|
|
706
722
|
}
|
|
707
723
|
if (!ttwid) {
|
|
724
|
+
console.log(`[TikTokLive] Fallback: direct TikTok fetch for ttwid...`);
|
|
708
725
|
try {
|
|
709
726
|
const resp = await httpGet(`https://www.tiktok.com/@${this.uniqueId}/live`, {
|
|
710
727
|
"User-Agent": DEFAULT_UA,
|
|
@@ -716,35 +733,93 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
716
733
|
break;
|
|
717
734
|
}
|
|
718
735
|
}
|
|
719
|
-
|
|
736
|
+
console.log(`[TikTokLive] Direct fetch ttwid: ${ttwid ? "\u2705" : "\u274C none"}`);
|
|
737
|
+
} catch (e) {
|
|
738
|
+
console.log(`[TikTokLive] \u274C Direct TikTok fetch failed: ${e.message}`);
|
|
720
739
|
}
|
|
721
740
|
}
|
|
722
741
|
} else {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
742
|
+
console.log(`[TikTokLive] No presets for @${this.uniqueId}. Resolving via sign server (${this.signServerUrl})...`);
|
|
743
|
+
try {
|
|
744
|
+
const roomResp = await fetch(`${this.signServerUrl}/webcast/room_id`, {
|
|
745
|
+
method: "POST",
|
|
746
|
+
headers: { "Content-Type": "application/json", "x-api-key": this.apiKey },
|
|
747
|
+
body: JSON.stringify({ unique_id: this.uniqueId })
|
|
748
|
+
});
|
|
749
|
+
console.log(`[TikTokLive] room_id response: ${roomResp.status}`);
|
|
750
|
+
if (roomResp.ok) {
|
|
751
|
+
const roomData = await roomResp.json();
|
|
752
|
+
if (roomData.status_code === 0 && roomData.data?.room_id) {
|
|
753
|
+
roomId = roomData.data.room_id;
|
|
754
|
+
console.log(`[TikTokLive] \u2705 Got roomId from sign server: ${roomId}`);
|
|
755
|
+
} else {
|
|
756
|
+
console.log(`[TikTokLive] \u26A0\uFE0F room_id response: ${JSON.stringify(roomData).substring(0, 120)}`);
|
|
757
|
+
}
|
|
733
758
|
}
|
|
759
|
+
} catch (e) {
|
|
760
|
+
console.log(`[TikTokLive] \u274C room_id request failed: ${e.message}`);
|
|
734
761
|
}
|
|
735
|
-
|
|
736
|
-
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
737
|
-
if (sigiMatch) {
|
|
762
|
+
if (roomId) {
|
|
738
763
|
try {
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
764
|
+
const credResp = await fetch(`${this.signServerUrl}/webcast/ws_credentials`, {
|
|
765
|
+
method: "POST",
|
|
766
|
+
headers: { "Content-Type": "application/json", "x-api-key": this.apiKey },
|
|
767
|
+
body: JSON.stringify({ unique_id: this.uniqueId, room_id: roomId })
|
|
768
|
+
});
|
|
769
|
+
console.log(`[TikTokLive] ws_credentials response: ${credResp.status}`);
|
|
770
|
+
if (credResp.ok) {
|
|
771
|
+
const credData = await credResp.json();
|
|
772
|
+
if (credData.data?.cookies) {
|
|
773
|
+
const m = credData.data.cookies.match(/ttwid=([^;]+)/);
|
|
774
|
+
if (m) {
|
|
775
|
+
ttwid = m[1];
|
|
776
|
+
console.log(`[TikTokLive] \u2705 Got ttwid from sign server`);
|
|
777
|
+
} else console.log(`[TikTokLive] \u26A0\uFE0F No ttwid in cookies: ${credData.data.cookies.substring(0, 60)}`);
|
|
778
|
+
} else {
|
|
779
|
+
console.log(`[TikTokLive] \u26A0\uFE0F No cookies in ws_credentials: ${JSON.stringify(credData).substring(0, 120)}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
} catch (e) {
|
|
783
|
+
console.log(`[TikTokLive] \u274C ws_credentials failed: ${e.message}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (!roomId || !ttwid) {
|
|
787
|
+
console.log(`[TikTokLive] Fallback: direct TikTok fetch (roomId=${roomId || "none"}, ttwid=${ttwid ? "yes" : "none"})...`);
|
|
788
|
+
try {
|
|
789
|
+
const resp = await httpGet(`https://www.tiktok.com/@${this.uniqueId}/live`, {
|
|
790
|
+
"User-Agent": DEFAULT_UA,
|
|
791
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
792
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
793
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
794
|
+
});
|
|
795
|
+
if (!ttwid) {
|
|
796
|
+
for (const sc of [resp.headers["set-cookie"] || []].flat()) {
|
|
797
|
+
if (typeof sc === "string" && sc.startsWith("ttwid=")) {
|
|
798
|
+
ttwid = sc.split(";")[0].split("=").slice(1).join("=");
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (!roomId) {
|
|
804
|
+
const html = resp.body.toString();
|
|
805
|
+
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
806
|
+
if (sigiMatch) {
|
|
807
|
+
try {
|
|
808
|
+
const json = JSON.parse(sigiMatch[1]);
|
|
809
|
+
const jsonStr = JSON.stringify(json);
|
|
810
|
+
const m = jsonStr.match(/"roomId"\s*:\s*"(\d+)"/);
|
|
811
|
+
if (m) roomId = m[1];
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
816
|
+
clusterRegion = crMatch ? crMatch[1] : "";
|
|
817
|
+
}
|
|
818
|
+
console.log(`[TikTokLive] Direct fetch result: roomId=${roomId || "none"}, ttwid=${ttwid ? "yes" : "none"}`);
|
|
819
|
+
} catch (e) {
|
|
820
|
+
console.log(`[TikTokLive] \u274C Direct TikTok fetch failed: ${e.message}`);
|
|
744
821
|
}
|
|
745
822
|
}
|
|
746
|
-
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
747
|
-
clusterRegion = crMatch ? crMatch[1] : "";
|
|
748
823
|
}
|
|
749
824
|
if (!ttwid) throw new Error("Failed to obtain session cookie");
|
|
750
825
|
if (!roomId) throw new Error(`User @${this.uniqueId} is not currently live`);
|
|
@@ -802,12 +877,11 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
802
877
|
wsUrl = rawWsUrl.replace(/^https:\/\//, "wss://");
|
|
803
878
|
}
|
|
804
879
|
return new Promise((resolve, reject) => {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
880
|
+
const finalWsUrl = preSignedUrl ? preSignedUrl.replace(/^https:\/\//, "wss://") : wsUrl;
|
|
881
|
+
const finalHeaders = preSignedUrl ? { "User-Agent": preSignedUA, "Cookie": preSignedCookies, "Origin": "https://www.tiktok.com" } : { "User-Agent": DEFAULT_UA, "Cookie": `ttwid=${ttwid}`, "Origin": "https://www.tiktok.com" };
|
|
882
|
+
console.log(`[TikTokLive] Connecting WS: ${preSignedUrl ? "pre-signed URL" : "self-built URL"}`);
|
|
883
|
+
this.ws = new import_ws.default(finalWsUrl, {
|
|
884
|
+
headers: finalHeaders
|
|
811
885
|
});
|
|
812
886
|
this.ws.on("open", () => {
|
|
813
887
|
this._connected = true;
|