@tiktool/live 2.2.1 → 2.3.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/README.md +95 -2
- package/dist/index.d.mts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +151 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
**Real-time TikTok LIVE events — chat messages, gifts, follows, likes, battles, viewer counts, and 18+ event types — streamed directly to your application via WebSocket.**
|
|
14
14
|
|
|
15
|
-
[Quick Start](#-quick-start) · [Events](#-events) · [REST API](#-rest-api-endpoints) · [Pricing](#-pricing) · [Get API Key](https://tik.tools)
|
|
15
|
+
[Quick Start](#-quick-start) · [Events](#-events) · [API Utilities](#-api-utilities) · [REST API](#-rest-api-endpoints) · [Pricing](#-pricing) · [Get API Key](https://tik.tools)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -122,9 +122,102 @@ live.on('event', (event) => {
|
|
|
122
122
|
|
|
123
123
|
---
|
|
124
124
|
|
|
125
|
+
## 🛠️ API Utilities
|
|
126
|
+
|
|
127
|
+
The SDK includes server-side utilities for calling TikTool REST endpoints. These handle the **sign-and-return** flow automatically — resolving usernames to room IDs, fetching signed URLs, and returning actual TikTok data.
|
|
128
|
+
|
|
129
|
+
### `callApi(options)` — High-Level Helper
|
|
130
|
+
|
|
131
|
+
The easiest way to call any TikTool endpoint. Handles the full flow automatically:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { callApi } from '@tiktool/live';
|
|
135
|
+
|
|
136
|
+
// Get stream video URLs for a user
|
|
137
|
+
const streamData = await callApi({
|
|
138
|
+
apiKey: 'YOUR_API_KEY',
|
|
139
|
+
endpoint: '/webcast/room_video',
|
|
140
|
+
uniqueId: 'streamer_name',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Get room info
|
|
144
|
+
const roomInfo = await callApi({
|
|
145
|
+
apiKey: 'YOUR_API_KEY',
|
|
146
|
+
endpoint: '/webcast/room_info',
|
|
147
|
+
uniqueId: 'streamer_name',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Use a custom server URL
|
|
151
|
+
const data = await callApi({
|
|
152
|
+
serverUrl: 'https://your-server.com',
|
|
153
|
+
apiKey: 'YOUR_API_KEY',
|
|
154
|
+
endpoint: '/webcast/rankings',
|
|
155
|
+
uniqueId: 'streamer_name',
|
|
156
|
+
method: 'GET',
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `CallApiOptions`
|
|
161
|
+
|
|
162
|
+
| Option | Type | Default | Description |
|
|
163
|
+
|--------|------|---------|-------------|
|
|
164
|
+
| `apiKey` | `string` | — | API key from [tik.tools](https://tik.tools) |
|
|
165
|
+
| `endpoint` | `string` | — | API path (e.g. `'/webcast/room_video'`) |
|
|
166
|
+
| `uniqueId` | `string` | — | TikTok username to resolve |
|
|
167
|
+
| `serverUrl` | `string` | `https://api.tik.tools` | Custom API server URL |
|
|
168
|
+
| `method` | `'GET' \| 'POST'` | `'POST'` | HTTP method |
|
|
169
|
+
| `extraBody` | `object` | — | Additional POST body fields |
|
|
170
|
+
|
|
171
|
+
### `resolveRoomId(uniqueId)` — Username to Room ID
|
|
172
|
+
|
|
173
|
+
Scrapes a TikTok live page to extract the `room_id`. Results are cached for 5 minutes. Returns `null` if the user is not live.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { resolveRoomId } from '@tiktool/live';
|
|
177
|
+
|
|
178
|
+
const roomId = await resolveRoomId('streamer_name');
|
|
179
|
+
if (roomId) {
|
|
180
|
+
console.log(`Room ID: ${roomId}`);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `resolveLivePage(uniqueId)` — Full Live Page Resolution
|
|
185
|
+
|
|
186
|
+
Returns all live page metadata including the room ID, session cookie (`ttwid`), and cluster region. Used internally by `TikTokLive.connect()`. Returns `null` if the user is not live.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { resolveLivePage } from '@tiktool/live';
|
|
190
|
+
|
|
191
|
+
const info = await resolveLivePage('streamer_name');
|
|
192
|
+
if (info) {
|
|
193
|
+
console.log(`Room: ${info.roomId}, Region: ${info.clusterRegion}`);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### `LivePageInfo`
|
|
198
|
+
|
|
199
|
+
| Field | Type | Description |
|
|
200
|
+
|-------|------|-------------|
|
|
201
|
+
| `roomId` | `string` | Active room ID for the livestream |
|
|
202
|
+
| `ttwid` | `string` | Session cookie for WebSocket auth |
|
|
203
|
+
| `clusterRegion` | `string` | Server cluster region (e.g. `'us'`, `'eu'`) |
|
|
204
|
+
|
|
205
|
+
### `fetchSignedUrl(response)` — Execute Signed URL Response
|
|
206
|
+
|
|
207
|
+
Takes a sign-and-return API response and fetches the actual TikTok data from the signed URL.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { fetchSignedUrl } from '@tiktool/live';
|
|
211
|
+
|
|
212
|
+
// After calling an API endpoint that returns action: 'fetch_signed_url'
|
|
213
|
+
const tikTokData = await fetchSignedUrl(apiResponse);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
125
218
|
## 🌐 REST API Endpoints
|
|
126
219
|
|
|
127
|
-
The sign server at `api.tik.tools` exposes a full REST API alongside WebSocket signing.
|
|
220
|
+
The sign server at `api.tik.tools` exposes a full REST API alongside WebSocket signing. Endpoints use a **sign-and-return** pattern — when called with a `unique_id`, they return instructions for resolving the room ID; when called with a `room_id`, they return a signed URL to fetch data directly from TikTok. Use `callApi()` to handle this automatically.
|
|
128
221
|
|
|
129
222
|
| Endpoint | Description |
|
|
130
223
|
|----------|-------------|
|
package/dist/index.d.mts
CHANGED
|
@@ -219,4 +219,71 @@ declare class TikTokLive extends TypedEmitter {
|
|
|
219
219
|
private stopHeartbeat;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
/**
|
|
223
|
+
* TikTool API utilities for the sign-and-return API flow.
|
|
224
|
+
*
|
|
225
|
+
* The API server returns signed URLs instead of fetching TikTok data directly.
|
|
226
|
+
* These utilities handle the two-step flow:
|
|
227
|
+
* 1. resolve_required — scrape TikTok HTML to get room_id
|
|
228
|
+
* 2. fetch_signed_url — use the signed URL to get actual TikTok data
|
|
229
|
+
*
|
|
230
|
+
* @module
|
|
231
|
+
*/
|
|
232
|
+
/**
|
|
233
|
+
* Resolved live page metadata from a TikTok live page.
|
|
234
|
+
*/
|
|
235
|
+
interface LivePageInfo {
|
|
236
|
+
/** Active room ID for the livestream */
|
|
237
|
+
roomId: string;
|
|
238
|
+
/** Session cookie required for WebSocket authentication */
|
|
239
|
+
ttwid: string;
|
|
240
|
+
/** Server cluster region (e.g. 'us', 'eu') */
|
|
241
|
+
clusterRegion: string;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Scrape a TikTok live page to extract room metadata.
|
|
245
|
+
* Returns the room ID, session cookie, and cluster region.
|
|
246
|
+
* Results are cached for 5 minutes. Returns `null` if the user is not live.
|
|
247
|
+
*/
|
|
248
|
+
declare function resolveLivePage(uniqueId: string): Promise<LivePageInfo | null>;
|
|
249
|
+
/**
|
|
250
|
+
* Resolve a TikTok username to a room ID.
|
|
251
|
+
* Returns `null` if the user is not currently live.
|
|
252
|
+
* Results are cached for 5 minutes.
|
|
253
|
+
*/
|
|
254
|
+
declare function resolveRoomId(uniqueId: string): Promise<string | null>;
|
|
255
|
+
interface SignedUrlResponse {
|
|
256
|
+
status_code: number;
|
|
257
|
+
action: string;
|
|
258
|
+
signed_url: string;
|
|
259
|
+
headers: Record<string, string>;
|
|
260
|
+
cookies: string;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Execute a signed-URL API response: fetch the signed URL and return the
|
|
264
|
+
* parsed JSON data from TikTok.
|
|
265
|
+
*/
|
|
266
|
+
declare function fetchSignedUrl(response: SignedUrlResponse): Promise<any>;
|
|
267
|
+
interface CallApiOptions {
|
|
268
|
+
/** API server URL (default: https://api.tik.tools) */
|
|
269
|
+
serverUrl?: string;
|
|
270
|
+
/** API key for authentication */
|
|
271
|
+
apiKey: string;
|
|
272
|
+
/** API endpoint path (e.g. '/webcast/room_video') */
|
|
273
|
+
endpoint: string;
|
|
274
|
+
/** TikTok unique_id to resolve */
|
|
275
|
+
uniqueId: string;
|
|
276
|
+
/** HTTP method (default: POST) */
|
|
277
|
+
method?: 'GET' | 'POST';
|
|
278
|
+
/** Additional body fields for POST requests */
|
|
279
|
+
extraBody?: Record<string, any>;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Call a TikTool API endpoint, handling the full
|
|
283
|
+
* resolve_required → room_id → signed_url → fetch flow automatically.
|
|
284
|
+
*
|
|
285
|
+
* Returns the actual TikTok data, or `null` if the user is not live.
|
|
286
|
+
*/
|
|
287
|
+
declare function callApi(opts: CallApiOptions): Promise<any>;
|
|
288
|
+
|
|
289
|
+
export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, type BattleTeam, type BattleTeamUser, type CallApiOptions, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EnvelopeEvent, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type LivePageInfo, type MemberEvent, type QuestionEvent, type RankUpdateEvent, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SignedUrlResponse, type SocialEvent, type SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent, callApi, fetchSignedUrl, resolveLivePage, resolveRoomId };
|
package/dist/index.d.ts
CHANGED
|
@@ -219,4 +219,71 @@ declare class TikTokLive extends TypedEmitter {
|
|
|
219
219
|
private stopHeartbeat;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
/**
|
|
223
|
+
* TikTool API utilities for the sign-and-return API flow.
|
|
224
|
+
*
|
|
225
|
+
* The API server returns signed URLs instead of fetching TikTok data directly.
|
|
226
|
+
* These utilities handle the two-step flow:
|
|
227
|
+
* 1. resolve_required — scrape TikTok HTML to get room_id
|
|
228
|
+
* 2. fetch_signed_url — use the signed URL to get actual TikTok data
|
|
229
|
+
*
|
|
230
|
+
* @module
|
|
231
|
+
*/
|
|
232
|
+
/**
|
|
233
|
+
* Resolved live page metadata from a TikTok live page.
|
|
234
|
+
*/
|
|
235
|
+
interface LivePageInfo {
|
|
236
|
+
/** Active room ID for the livestream */
|
|
237
|
+
roomId: string;
|
|
238
|
+
/** Session cookie required for WebSocket authentication */
|
|
239
|
+
ttwid: string;
|
|
240
|
+
/** Server cluster region (e.g. 'us', 'eu') */
|
|
241
|
+
clusterRegion: string;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Scrape a TikTok live page to extract room metadata.
|
|
245
|
+
* Returns the room ID, session cookie, and cluster region.
|
|
246
|
+
* Results are cached for 5 minutes. Returns `null` if the user is not live.
|
|
247
|
+
*/
|
|
248
|
+
declare function resolveLivePage(uniqueId: string): Promise<LivePageInfo | null>;
|
|
249
|
+
/**
|
|
250
|
+
* Resolve a TikTok username to a room ID.
|
|
251
|
+
* Returns `null` if the user is not currently live.
|
|
252
|
+
* Results are cached for 5 minutes.
|
|
253
|
+
*/
|
|
254
|
+
declare function resolveRoomId(uniqueId: string): Promise<string | null>;
|
|
255
|
+
interface SignedUrlResponse {
|
|
256
|
+
status_code: number;
|
|
257
|
+
action: string;
|
|
258
|
+
signed_url: string;
|
|
259
|
+
headers: Record<string, string>;
|
|
260
|
+
cookies: string;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Execute a signed-URL API response: fetch the signed URL and return the
|
|
264
|
+
* parsed JSON data from TikTok.
|
|
265
|
+
*/
|
|
266
|
+
declare function fetchSignedUrl(response: SignedUrlResponse): Promise<any>;
|
|
267
|
+
interface CallApiOptions {
|
|
268
|
+
/** API server URL (default: https://api.tik.tools) */
|
|
269
|
+
serverUrl?: string;
|
|
270
|
+
/** API key for authentication */
|
|
271
|
+
apiKey: string;
|
|
272
|
+
/** API endpoint path (e.g. '/webcast/room_video') */
|
|
273
|
+
endpoint: string;
|
|
274
|
+
/** TikTok unique_id to resolve */
|
|
275
|
+
uniqueId: string;
|
|
276
|
+
/** HTTP method (default: POST) */
|
|
277
|
+
method?: 'GET' | 'POST';
|
|
278
|
+
/** Additional body fields for POST requests */
|
|
279
|
+
extraBody?: Record<string, any>;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Call a TikTool API endpoint, handling the full
|
|
283
|
+
* resolve_required → room_id → signed_url → fetch flow automatically.
|
|
284
|
+
*
|
|
285
|
+
* Returns the actual TikTok data, or `null` if the user is not live.
|
|
286
|
+
*/
|
|
287
|
+
declare function callApi(opts: CallApiOptions): Promise<any>;
|
|
288
|
+
|
|
289
|
+
export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, type BattleTeam, type BattleTeamUser, type CallApiOptions, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EnvelopeEvent, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type LivePageInfo, type MemberEvent, type QuestionEvent, type RankUpdateEvent, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SignedUrlResponse, type SocialEvent, type SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent, callApi, fetchSignedUrl, resolveLivePage, resolveRoomId };
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
TikTokLive: () => TikTokLive
|
|
33
|
+
TikTokLive: () => TikTokLive,
|
|
34
|
+
callApi: () => callApi,
|
|
35
|
+
fetchSignedUrl: () => fetchSignedUrl,
|
|
36
|
+
resolveLivePage: () => resolveLivePage,
|
|
37
|
+
resolveRoomId: () => resolveRoomId
|
|
34
38
|
});
|
|
35
39
|
module.exports = __toCommonJS(index_exports);
|
|
36
40
|
|
|
@@ -315,7 +319,8 @@ function parseWebcastMessage(method, payload) {
|
|
|
315
319
|
repeatEnd,
|
|
316
320
|
combo: repeatCount > 1 && !repeatEnd,
|
|
317
321
|
giftType,
|
|
318
|
-
groupId
|
|
322
|
+
groupId,
|
|
323
|
+
giftPictureUrl: giftImageUrl
|
|
319
324
|
};
|
|
320
325
|
}
|
|
321
326
|
case "WebcastSocialMessage": {
|
|
@@ -495,9 +500,140 @@ function parseWebcastResponse(payload) {
|
|
|
495
500
|
return events;
|
|
496
501
|
}
|
|
497
502
|
|
|
498
|
-
// src/
|
|
503
|
+
// src/api.ts
|
|
499
504
|
var DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
500
505
|
var DEFAULT_SIGN_SERVER = "https://api.tik.tools";
|
|
506
|
+
var pageCache = /* @__PURE__ */ new Map();
|
|
507
|
+
var PAGE_CACHE_TTL = 5 * 60 * 1e3;
|
|
508
|
+
async function resolveLivePage(uniqueId) {
|
|
509
|
+
const clean = uniqueId.replace(/^@/, "");
|
|
510
|
+
const cached = pageCache.get(clean);
|
|
511
|
+
if (cached && Date.now() - cached.ts < PAGE_CACHE_TTL) {
|
|
512
|
+
return cached.info;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const resp = await fetch(`https://www.tiktok.com/@${clean}/live`, {
|
|
516
|
+
headers: {
|
|
517
|
+
"User-Agent": DEFAULT_UA,
|
|
518
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
519
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
520
|
+
},
|
|
521
|
+
redirect: "follow"
|
|
522
|
+
});
|
|
523
|
+
if (!resp.ok) return null;
|
|
524
|
+
let ttwid = "";
|
|
525
|
+
const setCookies = resp.headers.get("set-cookie") || "";
|
|
526
|
+
for (const part of setCookies.split(",")) {
|
|
527
|
+
const trimmed = part.trim();
|
|
528
|
+
if (trimmed.startsWith("ttwid=")) {
|
|
529
|
+
ttwid = trimmed.split(";")[0].split("=").slice(1).join("=");
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!ttwid && typeof resp.headers.getSetCookie === "function") {
|
|
534
|
+
for (const sc of resp.headers.getSetCookie()) {
|
|
535
|
+
if (typeof sc === "string" && sc.startsWith("ttwid=")) {
|
|
536
|
+
ttwid = sc.split(";")[0].split("=").slice(1).join("=");
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const html = await resp.text();
|
|
542
|
+
let roomId = "";
|
|
543
|
+
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
544
|
+
if (sigiMatch) {
|
|
545
|
+
try {
|
|
546
|
+
const json = JSON.parse(sigiMatch[1]);
|
|
547
|
+
const jsonStr = JSON.stringify(json);
|
|
548
|
+
const m = jsonStr.match(/"roomId"\s*:\s*"(\d+)"/);
|
|
549
|
+
if (m) roomId = m[1];
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!roomId) {
|
|
554
|
+
const patterns = [
|
|
555
|
+
/"roomId"\s*:\s*"(\d+)"/,
|
|
556
|
+
/room_id[=/](\d{10,})/,
|
|
557
|
+
/"idStr"\s*:\s*"(\d{10,})"/
|
|
558
|
+
];
|
|
559
|
+
for (const p of patterns) {
|
|
560
|
+
const m = html.match(p);
|
|
561
|
+
if (m) {
|
|
562
|
+
roomId = m[1];
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (!roomId) return null;
|
|
568
|
+
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
569
|
+
const clusterRegion = crMatch ? crMatch[1] : "";
|
|
570
|
+
const info = { roomId, ttwid, clusterRegion };
|
|
571
|
+
pageCache.set(clean, { info, ts: Date.now() });
|
|
572
|
+
return info;
|
|
573
|
+
} catch {
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
async function resolveRoomId(uniqueId) {
|
|
578
|
+
const info = await resolveLivePage(uniqueId);
|
|
579
|
+
return info?.roomId ?? null;
|
|
580
|
+
}
|
|
581
|
+
async function fetchSignedUrl(response) {
|
|
582
|
+
if (response.action !== "fetch_signed_url" || !response.signed_url) {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
const headers = { ...response.headers || {} };
|
|
586
|
+
if (response.cookies) {
|
|
587
|
+
headers["Cookie"] = response.cookies;
|
|
588
|
+
}
|
|
589
|
+
const resp = await fetch(response.signed_url, { headers, redirect: "follow" });
|
|
590
|
+
const text = await resp.text();
|
|
591
|
+
try {
|
|
592
|
+
return JSON.parse(text);
|
|
593
|
+
} catch {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async function callApi(opts) {
|
|
598
|
+
const serverUrl = (opts.serverUrl || DEFAULT_SIGN_SERVER).replace(/\/$/, "");
|
|
599
|
+
const isGet = opts.method === "GET";
|
|
600
|
+
const ak = encodeURIComponent(opts.apiKey);
|
|
601
|
+
const url1 = isGet ? `${serverUrl}${opts.endpoint}?apiKey=${ak}&unique_id=${encodeURIComponent(opts.uniqueId)}` : `${serverUrl}${opts.endpoint}?apiKey=${ak}`;
|
|
602
|
+
const fetchOpts1 = isGet ? {} : {
|
|
603
|
+
method: "POST",
|
|
604
|
+
headers: { "Content-Type": "application/json" },
|
|
605
|
+
body: JSON.stringify({ unique_id: opts.uniqueId, ...opts.extraBody })
|
|
606
|
+
};
|
|
607
|
+
const resp1 = await fetch(url1, fetchOpts1);
|
|
608
|
+
const data1 = await resp1.json();
|
|
609
|
+
if (data1.status_code === 0 && !["resolve_required", "fetch_signed_url"].includes(data1.action)) {
|
|
610
|
+
return data1;
|
|
611
|
+
}
|
|
612
|
+
if (data1.action === "fetch_signed_url") {
|
|
613
|
+
return fetchSignedUrl(data1);
|
|
614
|
+
}
|
|
615
|
+
if (data1.action === "resolve_required") {
|
|
616
|
+
const roomId = await resolveRoomId(opts.uniqueId);
|
|
617
|
+
if (!roomId) return null;
|
|
618
|
+
const url2 = isGet ? `${serverUrl}${opts.endpoint}?apiKey=${ak}&room_id=${encodeURIComponent(roomId)}` : `${serverUrl}${opts.endpoint}?apiKey=${ak}`;
|
|
619
|
+
const fetchOpts2 = isGet ? {} : {
|
|
620
|
+
method: "POST",
|
|
621
|
+
headers: { "Content-Type": "application/json" },
|
|
622
|
+
body: JSON.stringify({ room_id: roomId, ...opts.extraBody })
|
|
623
|
+
};
|
|
624
|
+
const resp2 = await fetch(url2, fetchOpts2);
|
|
625
|
+
const data2 = await resp2.json();
|
|
626
|
+
if (data2.action === "fetch_signed_url") {
|
|
627
|
+
return fetchSignedUrl(data2);
|
|
628
|
+
}
|
|
629
|
+
return data2;
|
|
630
|
+
}
|
|
631
|
+
return data1;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/client.ts
|
|
635
|
+
var DEFAULT_UA2 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
636
|
+
var DEFAULT_SIGN_SERVER2 = "https://api.tik.tools";
|
|
501
637
|
var TypedEmitter = class {
|
|
502
638
|
_listeners = /* @__PURE__ */ new Map();
|
|
503
639
|
on(event, fn) {
|
|
@@ -582,7 +718,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
582
718
|
constructor(options) {
|
|
583
719
|
super();
|
|
584
720
|
this.uniqueId = options.uniqueId.replace(/^@/, "");
|
|
585
|
-
this.signServerUrl = (options.signServerUrl ||
|
|
721
|
+
this.signServerUrl = (options.signServerUrl || DEFAULT_SIGN_SERVER2).replace(/\/$/, "");
|
|
586
722
|
if (!options.apiKey) throw new Error("apiKey is required. Get a free key at https://tik.tools");
|
|
587
723
|
this.apiKey = options.apiKey;
|
|
588
724
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
@@ -596,48 +732,11 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
596
732
|
if (!this.WS) {
|
|
597
733
|
this.WS = await resolveWebSocket(this.webSocketImpl);
|
|
598
734
|
}
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
"Accept-Language": "en-US,en;q=0.9"
|
|
604
|
-
},
|
|
605
|
-
redirect: "follow"
|
|
606
|
-
});
|
|
607
|
-
let ttwid = "";
|
|
608
|
-
const setCookies = resp.headers.get("set-cookie") || "";
|
|
609
|
-
for (const part of setCookies.split(",")) {
|
|
610
|
-
const trimmed = part.trim();
|
|
611
|
-
if (trimmed.startsWith("ttwid=")) {
|
|
612
|
-
ttwid = trimmed.split(";")[0].split("=").slice(1).join("=");
|
|
613
|
-
break;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
if (!ttwid && typeof resp.headers.getSetCookie === "function") {
|
|
617
|
-
for (const sc of resp.headers.getSetCookie()) {
|
|
618
|
-
if (typeof sc === "string" && sc.startsWith("ttwid=")) {
|
|
619
|
-
ttwid = sc.split(";")[0].split("=").slice(1).join("=");
|
|
620
|
-
break;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
if (!ttwid) throw new Error("Failed to obtain session cookie");
|
|
625
|
-
const html = await resp.text();
|
|
626
|
-
let roomId = "";
|
|
627
|
-
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
628
|
-
if (sigiMatch) {
|
|
629
|
-
try {
|
|
630
|
-
const json = JSON.parse(sigiMatch[1]);
|
|
631
|
-
const jsonStr = JSON.stringify(json);
|
|
632
|
-
const m = jsonStr.match(/"roomId"\s*:\s*"(\d+)"/);
|
|
633
|
-
if (m) roomId = m[1];
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
if (!roomId) throw new Error(`User @${this.uniqueId} is not currently live`);
|
|
735
|
+
const pageInfo = await resolveLivePage(this.uniqueId);
|
|
736
|
+
if (!pageInfo) throw new Error(`User @${this.uniqueId} is not currently live`);
|
|
737
|
+
if (!pageInfo.ttwid) throw new Error("Failed to obtain session cookie");
|
|
738
|
+
const { roomId, ttwid, clusterRegion } = pageInfo;
|
|
638
739
|
this._roomId = roomId;
|
|
639
|
-
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
640
|
-
const clusterRegion = crMatch ? crMatch[1] : "";
|
|
641
740
|
const wsHost = getWsHost(clusterRegion);
|
|
642
741
|
const wsParams = new URLSearchParams({
|
|
643
742
|
version_code: "270000",
|
|
@@ -648,7 +747,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
648
747
|
browser_language: "en-US",
|
|
649
748
|
browser_platform: "Win32",
|
|
650
749
|
browser_name: "Mozilla",
|
|
651
|
-
browser_version:
|
|
750
|
+
browser_version: DEFAULT_UA2.split("Mozilla/")[1] || "5.0",
|
|
652
751
|
browser_online: "true",
|
|
653
752
|
tz_name: "Etc/UTC",
|
|
654
753
|
app_name: "tiktok_web",
|
|
@@ -695,7 +794,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
695
794
|
try {
|
|
696
795
|
this.ws = new this.WS(connUrl, {
|
|
697
796
|
headers: {
|
|
698
|
-
"User-Agent":
|
|
797
|
+
"User-Agent": DEFAULT_UA2,
|
|
699
798
|
"Cookie": `ttwid=${ttwid}`,
|
|
700
799
|
"Origin": "https://www.tiktok.com"
|
|
701
800
|
}
|
|
@@ -874,6 +973,10 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
874
973
|
};
|
|
875
974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
876
975
|
0 && (module.exports = {
|
|
877
|
-
TikTokLive
|
|
976
|
+
TikTokLive,
|
|
977
|
+
callApi,
|
|
978
|
+
fetchSignedUrl,
|
|
979
|
+
resolveLivePage,
|
|
980
|
+
resolveRoomId
|
|
878
981
|
});
|
|
879
982
|
//# sourceMappingURL=index.js.map
|