@tiktool/live 2.2.1 → 2.3.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/README.md +95 -2
- package/dist/index.d.mts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +149 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -46
- 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
|
|
|
@@ -495,9 +499,140 @@ function parseWebcastResponse(payload) {
|
|
|
495
499
|
return events;
|
|
496
500
|
}
|
|
497
501
|
|
|
498
|
-
// src/
|
|
502
|
+
// src/api.ts
|
|
499
503
|
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
504
|
var DEFAULT_SIGN_SERVER = "https://api.tik.tools";
|
|
505
|
+
var pageCache = /* @__PURE__ */ new Map();
|
|
506
|
+
var PAGE_CACHE_TTL = 5 * 60 * 1e3;
|
|
507
|
+
async function resolveLivePage(uniqueId) {
|
|
508
|
+
const clean = uniqueId.replace(/^@/, "");
|
|
509
|
+
const cached = pageCache.get(clean);
|
|
510
|
+
if (cached && Date.now() - cached.ts < PAGE_CACHE_TTL) {
|
|
511
|
+
return cached.info;
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
const resp = await fetch(`https://www.tiktok.com/@${clean}/live`, {
|
|
515
|
+
headers: {
|
|
516
|
+
"User-Agent": DEFAULT_UA,
|
|
517
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
518
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
519
|
+
},
|
|
520
|
+
redirect: "follow"
|
|
521
|
+
});
|
|
522
|
+
if (!resp.ok) return null;
|
|
523
|
+
let ttwid = "";
|
|
524
|
+
const setCookies = resp.headers.get("set-cookie") || "";
|
|
525
|
+
for (const part of setCookies.split(",")) {
|
|
526
|
+
const trimmed = part.trim();
|
|
527
|
+
if (trimmed.startsWith("ttwid=")) {
|
|
528
|
+
ttwid = trimmed.split(";")[0].split("=").slice(1).join("=");
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!ttwid && typeof resp.headers.getSetCookie === "function") {
|
|
533
|
+
for (const sc of resp.headers.getSetCookie()) {
|
|
534
|
+
if (typeof sc === "string" && sc.startsWith("ttwid=")) {
|
|
535
|
+
ttwid = sc.split(";")[0].split("=").slice(1).join("=");
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const html = await resp.text();
|
|
541
|
+
let roomId = "";
|
|
542
|
+
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
543
|
+
if (sigiMatch) {
|
|
544
|
+
try {
|
|
545
|
+
const json = JSON.parse(sigiMatch[1]);
|
|
546
|
+
const jsonStr = JSON.stringify(json);
|
|
547
|
+
const m = jsonStr.match(/"roomId"\s*:\s*"(\d+)"/);
|
|
548
|
+
if (m) roomId = m[1];
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (!roomId) {
|
|
553
|
+
const patterns = [
|
|
554
|
+
/"roomId"\s*:\s*"(\d+)"/,
|
|
555
|
+
/room_id[=/](\d{10,})/,
|
|
556
|
+
/"idStr"\s*:\s*"(\d{10,})"/
|
|
557
|
+
];
|
|
558
|
+
for (const p of patterns) {
|
|
559
|
+
const m = html.match(p);
|
|
560
|
+
if (m) {
|
|
561
|
+
roomId = m[1];
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!roomId) return null;
|
|
567
|
+
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
568
|
+
const clusterRegion = crMatch ? crMatch[1] : "";
|
|
569
|
+
const info = { roomId, ttwid, clusterRegion };
|
|
570
|
+
pageCache.set(clean, { info, ts: Date.now() });
|
|
571
|
+
return info;
|
|
572
|
+
} catch {
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
async function resolveRoomId(uniqueId) {
|
|
577
|
+
const info = await resolveLivePage(uniqueId);
|
|
578
|
+
return info?.roomId ?? null;
|
|
579
|
+
}
|
|
580
|
+
async function fetchSignedUrl(response) {
|
|
581
|
+
if (response.action !== "fetch_signed_url" || !response.signed_url) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
const headers = { ...response.headers || {} };
|
|
585
|
+
if (response.cookies) {
|
|
586
|
+
headers["Cookie"] = response.cookies;
|
|
587
|
+
}
|
|
588
|
+
const resp = await fetch(response.signed_url, { headers, redirect: "follow" });
|
|
589
|
+
const text = await resp.text();
|
|
590
|
+
try {
|
|
591
|
+
return JSON.parse(text);
|
|
592
|
+
} catch {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function callApi(opts) {
|
|
597
|
+
const serverUrl = (opts.serverUrl || DEFAULT_SIGN_SERVER).replace(/\/$/, "");
|
|
598
|
+
const isGet = opts.method === "GET";
|
|
599
|
+
const ak = encodeURIComponent(opts.apiKey);
|
|
600
|
+
const url1 = isGet ? `${serverUrl}${opts.endpoint}?apiKey=${ak}&unique_id=${encodeURIComponent(opts.uniqueId)}` : `${serverUrl}${opts.endpoint}?apiKey=${ak}`;
|
|
601
|
+
const fetchOpts1 = isGet ? {} : {
|
|
602
|
+
method: "POST",
|
|
603
|
+
headers: { "Content-Type": "application/json" },
|
|
604
|
+
body: JSON.stringify({ unique_id: opts.uniqueId, ...opts.extraBody })
|
|
605
|
+
};
|
|
606
|
+
const resp1 = await fetch(url1, fetchOpts1);
|
|
607
|
+
const data1 = await resp1.json();
|
|
608
|
+
if (data1.status_code === 0 && !["resolve_required", "fetch_signed_url"].includes(data1.action)) {
|
|
609
|
+
return data1;
|
|
610
|
+
}
|
|
611
|
+
if (data1.action === "fetch_signed_url") {
|
|
612
|
+
return fetchSignedUrl(data1);
|
|
613
|
+
}
|
|
614
|
+
if (data1.action === "resolve_required") {
|
|
615
|
+
const roomId = await resolveRoomId(opts.uniqueId);
|
|
616
|
+
if (!roomId) return null;
|
|
617
|
+
const url2 = isGet ? `${serverUrl}${opts.endpoint}?apiKey=${ak}&room_id=${encodeURIComponent(roomId)}` : `${serverUrl}${opts.endpoint}?apiKey=${ak}`;
|
|
618
|
+
const fetchOpts2 = isGet ? {} : {
|
|
619
|
+
method: "POST",
|
|
620
|
+
headers: { "Content-Type": "application/json" },
|
|
621
|
+
body: JSON.stringify({ room_id: roomId, ...opts.extraBody })
|
|
622
|
+
};
|
|
623
|
+
const resp2 = await fetch(url2, fetchOpts2);
|
|
624
|
+
const data2 = await resp2.json();
|
|
625
|
+
if (data2.action === "fetch_signed_url") {
|
|
626
|
+
return fetchSignedUrl(data2);
|
|
627
|
+
}
|
|
628
|
+
return data2;
|
|
629
|
+
}
|
|
630
|
+
return data1;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/client.ts
|
|
634
|
+
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";
|
|
635
|
+
var DEFAULT_SIGN_SERVER2 = "https://api.tik.tools";
|
|
501
636
|
var TypedEmitter = class {
|
|
502
637
|
_listeners = /* @__PURE__ */ new Map();
|
|
503
638
|
on(event, fn) {
|
|
@@ -582,7 +717,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
582
717
|
constructor(options) {
|
|
583
718
|
super();
|
|
584
719
|
this.uniqueId = options.uniqueId.replace(/^@/, "");
|
|
585
|
-
this.signServerUrl = (options.signServerUrl ||
|
|
720
|
+
this.signServerUrl = (options.signServerUrl || DEFAULT_SIGN_SERVER2).replace(/\/$/, "");
|
|
586
721
|
if (!options.apiKey) throw new Error("apiKey is required. Get a free key at https://tik.tools");
|
|
587
722
|
this.apiKey = options.apiKey;
|
|
588
723
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
@@ -596,48 +731,11 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
596
731
|
if (!this.WS) {
|
|
597
732
|
this.WS = await resolveWebSocket(this.webSocketImpl);
|
|
598
733
|
}
|
|
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`);
|
|
734
|
+
const pageInfo = await resolveLivePage(this.uniqueId);
|
|
735
|
+
if (!pageInfo) throw new Error(`User @${this.uniqueId} is not currently live`);
|
|
736
|
+
if (!pageInfo.ttwid) throw new Error("Failed to obtain session cookie");
|
|
737
|
+
const { roomId, ttwid, clusterRegion } = pageInfo;
|
|
638
738
|
this._roomId = roomId;
|
|
639
|
-
const crMatch = html.match(/"clusterRegion"\s*:\s*"([^"]+)"/);
|
|
640
|
-
const clusterRegion = crMatch ? crMatch[1] : "";
|
|
641
739
|
const wsHost = getWsHost(clusterRegion);
|
|
642
740
|
const wsParams = new URLSearchParams({
|
|
643
741
|
version_code: "270000",
|
|
@@ -648,7 +746,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
648
746
|
browser_language: "en-US",
|
|
649
747
|
browser_platform: "Win32",
|
|
650
748
|
browser_name: "Mozilla",
|
|
651
|
-
browser_version:
|
|
749
|
+
browser_version: DEFAULT_UA2.split("Mozilla/")[1] || "5.0",
|
|
652
750
|
browser_online: "true",
|
|
653
751
|
tz_name: "Etc/UTC",
|
|
654
752
|
app_name: "tiktok_web",
|
|
@@ -695,7 +793,7 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
695
793
|
try {
|
|
696
794
|
this.ws = new this.WS(connUrl, {
|
|
697
795
|
headers: {
|
|
698
|
-
"User-Agent":
|
|
796
|
+
"User-Agent": DEFAULT_UA2,
|
|
699
797
|
"Cookie": `ttwid=${ttwid}`,
|
|
700
798
|
"Origin": "https://www.tiktok.com"
|
|
701
799
|
}
|
|
@@ -874,6 +972,10 @@ var TikTokLive = class extends TypedEmitter {
|
|
|
874
972
|
};
|
|
875
973
|
// Annotate the CommonJS export names for ESM import in node:
|
|
876
974
|
0 && (module.exports = {
|
|
877
|
-
TikTokLive
|
|
975
|
+
TikTokLive,
|
|
976
|
+
callApi,
|
|
977
|
+
fetchSignedUrl,
|
|
978
|
+
resolveLivePage,
|
|
979
|
+
resolveRoomId
|
|
878
980
|
});
|
|
879
981
|
//# sourceMappingURL=index.js.map
|