@tiktool/live 2.6.3 → 2.6.5
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 +80 -20
- package/dist/index.d.mts +142 -1
- package/dist/index.d.ts +142 -1
- package/dist/index.js +47 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,24 +202,15 @@ wss://api.tik.tools/captions?uniqueId=USERNAME&apiKey=YOUR_KEY&translate=en&diar
|
|
|
202
202
|
|
|
203
203
|
### Caption Credits
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
| Tier | Included Credits | Additional Top-Ups |
|
|
208
|
-
|------|-----------------|-------------------|
|
|
209
|
-
| **Free** | 60 min free trial | — |
|
|
210
|
-
| **Pro** | 2,000 min/month | Available |
|
|
211
|
-
| **Ultra** | 10,000 min/month | Available |
|
|
212
|
-
|
|
213
|
-
**Top-Up Packages:**
|
|
205
|
+
Caption credits are **pay-as-you-go add-ons** — no credits are included in the base subscription. Requires Basic tier or higher.
|
|
214
206
|
|
|
215
207
|
| Package | Credits | Price | Per Credit |
|
|
216
208
|
|---------|---------|-------|------------|
|
|
217
|
-
| Starter |
|
|
218
|
-
|
|
|
219
|
-
|
|
|
220
|
-
| Whale | 10,000 min | $149.99 | $0.015/min |
|
|
209
|
+
| **Starter** | 1,000 min | $10 | $0.010/min |
|
|
210
|
+
| **Creator** | 5,000 min | $35 | $0.007/min |
|
|
211
|
+
| **Agency** | 20,000 min | $100 | $0.005/min |
|
|
221
212
|
|
|
222
|
-
> **1 credit = 1 minute** of audio transcribed/translated into one language.
|
|
213
|
+
> **1 credit = 1 minute** of audio transcribed/translated into one language. If translating to 2 languages simultaneously, it burns 2 credits per minute.
|
|
223
214
|
|
|
224
215
|
Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see real-time transcription and translation on actual TikTok LIVE streams.
|
|
225
216
|
|
|
@@ -239,6 +230,10 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
239
230
|
| `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
|
|
240
231
|
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
241
232
|
| `debug` | `boolean` | `false` | Debug logging |
|
|
233
|
+
| `sessionId` | `string` | — | TikTok `sessionid` cookie for authenticated features (ranklist, chat) |
|
|
234
|
+
| `ttTargetIdc` | `string` | — | TikTok target IDC region (e.g. `useast5`). Required with `sessionId` |
|
|
235
|
+
| `roomId` | `string` | — | Pre-known room ID — skips HTML page scrape |
|
|
236
|
+
| `ttwid` | `string` | — | Pre-fetched `ttwid` cookie. With `roomId`, skips all HTTP requests |
|
|
242
237
|
|
|
243
238
|
### Methods
|
|
244
239
|
|
|
@@ -246,9 +241,12 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
246
241
|
|--------|---------|-------------|
|
|
247
242
|
| `connect()` | `Promise<void>` | Connect to livestream |
|
|
248
243
|
| `disconnect()` | `void` | Disconnect |
|
|
244
|
+
| `setSession(sessionId, ttTargetIdc?)` | `void` | Update session at runtime |
|
|
245
|
+
| `buildSessionCookieHeader()` | `string \| undefined` | Build cookie header for auth API requests |
|
|
249
246
|
| `connected` | `boolean` | Connection status |
|
|
250
247
|
| `eventCount` | `number` | Total events received |
|
|
251
248
|
| `roomId` | `string` | Current room ID |
|
|
249
|
+
| `sessionId` | `string \| undefined` | Current session ID |
|
|
252
250
|
|
|
253
251
|
---
|
|
254
252
|
|
|
@@ -256,13 +254,75 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
256
254
|
|
|
257
255
|
All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
|
|
258
256
|
|
|
259
|
-
| Tier | Rate Limit | WS Connections | Bulk Check |
|
|
260
|
-
|
|
261
|
-
| **
|
|
262
|
-
| **
|
|
263
|
-
| **
|
|
257
|
+
| Tier | Requests/Day | Rate Limit | WS Connections | WS Duration | WS Connects | Bulk Check | CAPTCHA | Feed Discovery | Price |
|
|
258
|
+
|------|-------------|-----------|----------------|-------------|-------------|------------|---------|----------------|-------|
|
|
259
|
+
| **Sandbox** | 50 | 5/min | 1 | 60 sec | 10/hr · 30/day | 1 | ✕ | ✕ | Free |
|
|
260
|
+
| **Basic** | 10,000 | 60/min | 3 | 8 hours | 60/hr · 200/day | 10 | ✕ | ✕ | From $7/wk |
|
|
261
|
+
| **Pro** | 75,000 | Unlimited | 50 | 8 hours | Unlimited | 50 | 50/day | 100/day | From $15/wk |
|
|
262
|
+
| **Ultra** | 300,000 | Unlimited | 500 | 8 hours | Unlimited | 500 | 500/day | 2,000/day | From $45/wk |
|
|
263
|
+
|
|
264
|
+
**Caption Credits** are available as pay-as-you-go add-ons (1 credit = 1 min of audio in 1 language):
|
|
265
|
+
- **Starter**: 1,000 credits — $10
|
|
266
|
+
- **Creator**: 5,000 credits — $35
|
|
267
|
+
- **Agency**: 20,000 credits — $100
|
|
268
|
+
|
|
269
|
+
The SDK calls the sign server **once per connection**, then stays connected via WebSocket. Sandbox is for API verification only — use Basic or higher for production.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 🔍 Feed Discovery
|
|
274
|
+
|
|
275
|
+
Discover recommended TikTok LIVE streams. **Requires Pro or Ultra tier.**
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { getLiveFeed, fetchFeed } from '@tiktool/live';
|
|
279
|
+
|
|
280
|
+
// Option 1: Two-step (sign-and-return)
|
|
281
|
+
const signed = await getLiveFeed({
|
|
282
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
283
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
284
|
+
region: 'US',
|
|
285
|
+
count: 10,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const resp = await fetch(signed.signed_url, {
|
|
289
|
+
headers: { ...signed.headers, Cookie: signed.cookies || '' },
|
|
290
|
+
});
|
|
291
|
+
const data = await resp.json();
|
|
292
|
+
|
|
293
|
+
// Option 2: One-step convenience
|
|
294
|
+
const feed = await fetchFeed({
|
|
295
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
296
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
297
|
+
count: 10,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
for (const entry of feed.data || []) {
|
|
301
|
+
const room = entry.data;
|
|
302
|
+
console.log(`🔴 @${room.owner.display_id}: "${room.title}" — ${room.user_count} viewers`);
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Channel Types
|
|
264
307
|
|
|
265
|
-
|
|
308
|
+
| Value | Channel |
|
|
309
|
+
|-------|--------|
|
|
310
|
+
| `"87"` | Recommended (default) |
|
|
311
|
+
| `"86"` | Suggested |
|
|
312
|
+
| `"42"` | Following |
|
|
313
|
+
| `"1111006"` | Gaming |
|
|
314
|
+
|
|
315
|
+
### Pagination
|
|
316
|
+
|
|
317
|
+
Use `maxTime` from the previous response to load more:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const page2 = await getLiveFeed({
|
|
321
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
322
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
323
|
+
maxTime: data.extra?.max_time, // cursor from previous response
|
|
324
|
+
});
|
|
325
|
+
```
|
|
266
326
|
|
|
267
327
|
---
|
|
268
328
|
|
package/dist/index.d.mts
CHANGED
|
@@ -377,6 +377,67 @@ interface EntranceResponse {
|
|
|
377
377
|
}>;
|
|
378
378
|
}
|
|
379
379
|
type RanklistResponse = OnlineAudienceResponse | AnchorRankListResponse | EntranceResponse;
|
|
380
|
+
/** Streamer info in a feed room entry */
|
|
381
|
+
interface FeedRoomOwner {
|
|
382
|
+
/** TikTok user ID */
|
|
383
|
+
id_str: string;
|
|
384
|
+
/** Username (@handle) */
|
|
385
|
+
display_id: string;
|
|
386
|
+
/** Display name */
|
|
387
|
+
nickname: string;
|
|
388
|
+
/** Avatar thumbnail URLs */
|
|
389
|
+
avatar_thumb?: {
|
|
390
|
+
url_list: string[];
|
|
391
|
+
};
|
|
392
|
+
/** Avatar large URLs */
|
|
393
|
+
avatar_large?: {
|
|
394
|
+
url_list: string[];
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** A single live room entry from the feed */
|
|
398
|
+
interface FeedRoom {
|
|
399
|
+
/** Room ID */
|
|
400
|
+
id_str: string;
|
|
401
|
+
/** Stream title */
|
|
402
|
+
title: string;
|
|
403
|
+
/** Current viewer count */
|
|
404
|
+
user_count: number;
|
|
405
|
+
/** Stream cover image URL */
|
|
406
|
+
cover?: {
|
|
407
|
+
url_list: string[];
|
|
408
|
+
};
|
|
409
|
+
/** Streamer info */
|
|
410
|
+
owner: FeedRoomOwner;
|
|
411
|
+
/** Stream status (2 = live) */
|
|
412
|
+
status: number;
|
|
413
|
+
/** Stream start time (unix seconds) */
|
|
414
|
+
create_time?: number;
|
|
415
|
+
/** Like count */
|
|
416
|
+
like_count?: number;
|
|
417
|
+
/** Hashtag IDs */
|
|
418
|
+
hashtag_ids?: string[];
|
|
419
|
+
}
|
|
420
|
+
/** Signed-URL response from GET/POST /webcast/feed */
|
|
421
|
+
interface FeedSignedResponse {
|
|
422
|
+
/** Always 0 on success */
|
|
423
|
+
status_code: number;
|
|
424
|
+
/** The signed TikTok URL to fetch */
|
|
425
|
+
signed_url: string;
|
|
426
|
+
/** Required headers */
|
|
427
|
+
headers: Record<string, string>;
|
|
428
|
+
/** Cookies to include (ttwid, sessionid, etc.) */
|
|
429
|
+
cookies?: string;
|
|
430
|
+
/** Region used */
|
|
431
|
+
region: string;
|
|
432
|
+
/** Channel ID used */
|
|
433
|
+
channel_id: string;
|
|
434
|
+
/** Remaining daily feed calls */
|
|
435
|
+
feed_remaining: number;
|
|
436
|
+
/** Daily feed call limit */
|
|
437
|
+
feed_limit: number;
|
|
438
|
+
/** Human-readable instructions */
|
|
439
|
+
note: string;
|
|
440
|
+
}
|
|
380
441
|
|
|
381
442
|
declare class TikTokLive extends EventEmitter {
|
|
382
443
|
private ws;
|
|
@@ -763,4 +824,84 @@ interface RegionalRanklistSignedResponse {
|
|
|
763
824
|
*/
|
|
764
825
|
declare function getRegionalRanklist(opts: GetRegionalRanklistOptions): Promise<RegionalRanklistSignedResponse>;
|
|
765
826
|
|
|
766
|
-
|
|
827
|
+
interface GetLiveFeedOptions {
|
|
828
|
+
/** API server URL (default: https://api.tik.tools) */
|
|
829
|
+
serverUrl?: string;
|
|
830
|
+
/** API key for authentication (Pro or Ultra tier required) */
|
|
831
|
+
apiKey: string;
|
|
832
|
+
/** Region code (default: 'US') */
|
|
833
|
+
region?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Feed channel:
|
|
836
|
+
* - '87' = Recommended (default)
|
|
837
|
+
* - '86' = Suggested
|
|
838
|
+
* - '42' = Following
|
|
839
|
+
* - '1111006' = Gaming
|
|
840
|
+
*/
|
|
841
|
+
channelId?: string;
|
|
842
|
+
/** Number of rooms to return (max 50, default 20) */
|
|
843
|
+
count?: number;
|
|
844
|
+
/** Pagination cursor from previous response (default: '0') */
|
|
845
|
+
maxTime?: string;
|
|
846
|
+
/** TikTok sessionid cookie — required for populated results */
|
|
847
|
+
sessionId?: string;
|
|
848
|
+
/** TikTok ttwid cookie */
|
|
849
|
+
ttwid?: string;
|
|
850
|
+
/** TikTok msToken cookie */
|
|
851
|
+
msToken?: string;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Get a signed URL for fetching the TikTok LIVE feed.
|
|
855
|
+
*
|
|
856
|
+
* **Two-step pattern**: Returns a signed URL with headers and cookies.
|
|
857
|
+
* Fetch the signed URL from your own IP to get the feed data.
|
|
858
|
+
*
|
|
859
|
+
* Requires **Pro** or **Ultra** API key tier.
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```ts
|
|
863
|
+
* // Step 1: Get signed URL
|
|
864
|
+
* const signed = await getLiveFeed({
|
|
865
|
+
* apiKey: 'your-pro-key',
|
|
866
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
867
|
+
* region: 'US',
|
|
868
|
+
* count: 10,
|
|
869
|
+
* });
|
|
870
|
+
*
|
|
871
|
+
* // Step 2: Fetch from YOUR IP
|
|
872
|
+
* const resp = await fetch(signed.signed_url, {
|
|
873
|
+
* headers: { ...signed.headers, Cookie: signed.cookies || '' },
|
|
874
|
+
* });
|
|
875
|
+
* const data = await resp.json();
|
|
876
|
+
* console.log(`Found ${data.data?.length || 0} live rooms`);
|
|
877
|
+
*
|
|
878
|
+
* // Step 3: Load more (pagination)
|
|
879
|
+
* const nextSigned = await getLiveFeed({
|
|
880
|
+
* apiKey: 'your-pro-key',
|
|
881
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
882
|
+
* maxTime: data.extra?.max_time || '0',
|
|
883
|
+
* });
|
|
884
|
+
* ```
|
|
885
|
+
*/
|
|
886
|
+
declare function getLiveFeed(opts: GetLiveFeedOptions): Promise<FeedSignedResponse>;
|
|
887
|
+
/**
|
|
888
|
+
* Convenience: Get the feed AND fetch the signed URL in one step.
|
|
889
|
+
* Returns the parsed JSON feed data from TikTok.
|
|
890
|
+
*
|
|
891
|
+
* @example
|
|
892
|
+
* ```ts
|
|
893
|
+
* const feed = await fetchFeed({
|
|
894
|
+
* apiKey: 'your-pro-key',
|
|
895
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
896
|
+
* region: 'GR',
|
|
897
|
+
* count: 10,
|
|
898
|
+
* });
|
|
899
|
+
* for (const entry of feed.data || []) {
|
|
900
|
+
* const room = entry.data;
|
|
901
|
+
* console.log(`🔴 @${room.owner.display_id}: "${room.title}" — ${room.user_count} viewers`);
|
|
902
|
+
* }
|
|
903
|
+
* ```
|
|
904
|
+
*/
|
|
905
|
+
declare function fetchFeed(opts: GetLiveFeedOptions): Promise<any>;
|
|
906
|
+
|
|
907
|
+
export { type AnchorRankListEntry, type AnchorRankListResponse, type BarrageEvent, type BaseEvent, type BattleArmiesEvent, type BattleEvent, type BattleTaskEvent, type BattleTeam, type BattleTeamUser, type CaptionCredits, type CaptionData, type CaptionError, type CaptionStatus, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EntranceInfo, type EntranceResponse, type EntranceTab, type EnvelopeEvent, type FeedRoom, type FeedRoomOwner, type FeedSignedResponse, type GetLiveFeedOptions, type GetRanklistOptions, type GetRegionalRanklistOptions, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type MemberEvent, type OnlineAudienceEntry, type OnlineAudienceResponse, type QuestionEvent, type RankUpdateEvent, type RanklistResponse, type RanklistSelfInfo, type RanklistUser, type RegionalRanklistSignedResponse, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SocialEvent, type SubscribeEvent, TikTokCaptions, type TikTokCaptionsEvents, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type TranslationData, type UnknownEvent, fetchFeed, getLiveFeed, getRanklist, getRegionalRanklist };
|
package/dist/index.d.ts
CHANGED
|
@@ -377,6 +377,67 @@ interface EntranceResponse {
|
|
|
377
377
|
}>;
|
|
378
378
|
}
|
|
379
379
|
type RanklistResponse = OnlineAudienceResponse | AnchorRankListResponse | EntranceResponse;
|
|
380
|
+
/** Streamer info in a feed room entry */
|
|
381
|
+
interface FeedRoomOwner {
|
|
382
|
+
/** TikTok user ID */
|
|
383
|
+
id_str: string;
|
|
384
|
+
/** Username (@handle) */
|
|
385
|
+
display_id: string;
|
|
386
|
+
/** Display name */
|
|
387
|
+
nickname: string;
|
|
388
|
+
/** Avatar thumbnail URLs */
|
|
389
|
+
avatar_thumb?: {
|
|
390
|
+
url_list: string[];
|
|
391
|
+
};
|
|
392
|
+
/** Avatar large URLs */
|
|
393
|
+
avatar_large?: {
|
|
394
|
+
url_list: string[];
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** A single live room entry from the feed */
|
|
398
|
+
interface FeedRoom {
|
|
399
|
+
/** Room ID */
|
|
400
|
+
id_str: string;
|
|
401
|
+
/** Stream title */
|
|
402
|
+
title: string;
|
|
403
|
+
/** Current viewer count */
|
|
404
|
+
user_count: number;
|
|
405
|
+
/** Stream cover image URL */
|
|
406
|
+
cover?: {
|
|
407
|
+
url_list: string[];
|
|
408
|
+
};
|
|
409
|
+
/** Streamer info */
|
|
410
|
+
owner: FeedRoomOwner;
|
|
411
|
+
/** Stream status (2 = live) */
|
|
412
|
+
status: number;
|
|
413
|
+
/** Stream start time (unix seconds) */
|
|
414
|
+
create_time?: number;
|
|
415
|
+
/** Like count */
|
|
416
|
+
like_count?: number;
|
|
417
|
+
/** Hashtag IDs */
|
|
418
|
+
hashtag_ids?: string[];
|
|
419
|
+
}
|
|
420
|
+
/** Signed-URL response from GET/POST /webcast/feed */
|
|
421
|
+
interface FeedSignedResponse {
|
|
422
|
+
/** Always 0 on success */
|
|
423
|
+
status_code: number;
|
|
424
|
+
/** The signed TikTok URL to fetch */
|
|
425
|
+
signed_url: string;
|
|
426
|
+
/** Required headers */
|
|
427
|
+
headers: Record<string, string>;
|
|
428
|
+
/** Cookies to include (ttwid, sessionid, etc.) */
|
|
429
|
+
cookies?: string;
|
|
430
|
+
/** Region used */
|
|
431
|
+
region: string;
|
|
432
|
+
/** Channel ID used */
|
|
433
|
+
channel_id: string;
|
|
434
|
+
/** Remaining daily feed calls */
|
|
435
|
+
feed_remaining: number;
|
|
436
|
+
/** Daily feed call limit */
|
|
437
|
+
feed_limit: number;
|
|
438
|
+
/** Human-readable instructions */
|
|
439
|
+
note: string;
|
|
440
|
+
}
|
|
380
441
|
|
|
381
442
|
declare class TikTokLive extends EventEmitter {
|
|
382
443
|
private ws;
|
|
@@ -763,4 +824,84 @@ interface RegionalRanklistSignedResponse {
|
|
|
763
824
|
*/
|
|
764
825
|
declare function getRegionalRanklist(opts: GetRegionalRanklistOptions): Promise<RegionalRanklistSignedResponse>;
|
|
765
826
|
|
|
766
|
-
|
|
827
|
+
interface GetLiveFeedOptions {
|
|
828
|
+
/** API server URL (default: https://api.tik.tools) */
|
|
829
|
+
serverUrl?: string;
|
|
830
|
+
/** API key for authentication (Pro or Ultra tier required) */
|
|
831
|
+
apiKey: string;
|
|
832
|
+
/** Region code (default: 'US') */
|
|
833
|
+
region?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Feed channel:
|
|
836
|
+
* - '87' = Recommended (default)
|
|
837
|
+
* - '86' = Suggested
|
|
838
|
+
* - '42' = Following
|
|
839
|
+
* - '1111006' = Gaming
|
|
840
|
+
*/
|
|
841
|
+
channelId?: string;
|
|
842
|
+
/** Number of rooms to return (max 50, default 20) */
|
|
843
|
+
count?: number;
|
|
844
|
+
/** Pagination cursor from previous response (default: '0') */
|
|
845
|
+
maxTime?: string;
|
|
846
|
+
/** TikTok sessionid cookie — required for populated results */
|
|
847
|
+
sessionId?: string;
|
|
848
|
+
/** TikTok ttwid cookie */
|
|
849
|
+
ttwid?: string;
|
|
850
|
+
/** TikTok msToken cookie */
|
|
851
|
+
msToken?: string;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Get a signed URL for fetching the TikTok LIVE feed.
|
|
855
|
+
*
|
|
856
|
+
* **Two-step pattern**: Returns a signed URL with headers and cookies.
|
|
857
|
+
* Fetch the signed URL from your own IP to get the feed data.
|
|
858
|
+
*
|
|
859
|
+
* Requires **Pro** or **Ultra** API key tier.
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```ts
|
|
863
|
+
* // Step 1: Get signed URL
|
|
864
|
+
* const signed = await getLiveFeed({
|
|
865
|
+
* apiKey: 'your-pro-key',
|
|
866
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
867
|
+
* region: 'US',
|
|
868
|
+
* count: 10,
|
|
869
|
+
* });
|
|
870
|
+
*
|
|
871
|
+
* // Step 2: Fetch from YOUR IP
|
|
872
|
+
* const resp = await fetch(signed.signed_url, {
|
|
873
|
+
* headers: { ...signed.headers, Cookie: signed.cookies || '' },
|
|
874
|
+
* });
|
|
875
|
+
* const data = await resp.json();
|
|
876
|
+
* console.log(`Found ${data.data?.length || 0} live rooms`);
|
|
877
|
+
*
|
|
878
|
+
* // Step 3: Load more (pagination)
|
|
879
|
+
* const nextSigned = await getLiveFeed({
|
|
880
|
+
* apiKey: 'your-pro-key',
|
|
881
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
882
|
+
* maxTime: data.extra?.max_time || '0',
|
|
883
|
+
* });
|
|
884
|
+
* ```
|
|
885
|
+
*/
|
|
886
|
+
declare function getLiveFeed(opts: GetLiveFeedOptions): Promise<FeedSignedResponse>;
|
|
887
|
+
/**
|
|
888
|
+
* Convenience: Get the feed AND fetch the signed URL in one step.
|
|
889
|
+
* Returns the parsed JSON feed data from TikTok.
|
|
890
|
+
*
|
|
891
|
+
* @example
|
|
892
|
+
* ```ts
|
|
893
|
+
* const feed = await fetchFeed({
|
|
894
|
+
* apiKey: 'your-pro-key',
|
|
895
|
+
* sessionId: 'your-tiktok-sessionid',
|
|
896
|
+
* region: 'GR',
|
|
897
|
+
* count: 10,
|
|
898
|
+
* });
|
|
899
|
+
* for (const entry of feed.data || []) {
|
|
900
|
+
* const room = entry.data;
|
|
901
|
+
* console.log(`🔴 @${room.owner.display_id}: "${room.title}" — ${room.user_count} viewers`);
|
|
902
|
+
* }
|
|
903
|
+
* ```
|
|
904
|
+
*/
|
|
905
|
+
declare function fetchFeed(opts: GetLiveFeedOptions): Promise<any>;
|
|
906
|
+
|
|
907
|
+
export { type AnchorRankListEntry, type AnchorRankListResponse, type BarrageEvent, type BaseEvent, type BattleArmiesEvent, type BattleEvent, type BattleTaskEvent, type BattleTeam, type BattleTeamUser, type CaptionCredits, type CaptionData, type CaptionError, type CaptionStatus, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EntranceInfo, type EntranceResponse, type EntranceTab, type EnvelopeEvent, type FeedRoom, type FeedRoomOwner, type FeedSignedResponse, type GetLiveFeedOptions, type GetRanklistOptions, type GetRegionalRanklistOptions, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type MemberEvent, type OnlineAudienceEntry, type OnlineAudienceResponse, type QuestionEvent, type RankUpdateEvent, type RanklistResponse, type RanklistSelfInfo, type RanklistUser, type RegionalRanklistSignedResponse, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SocialEvent, type SubscribeEvent, TikTokCaptions, type TikTokCaptionsEvents, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type TranslationData, type UnknownEvent, fetchFeed, getLiveFeed, getRanklist, getRegionalRanklist };
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
TikTokCaptions: () => TikTokCaptions,
|
|
34
34
|
TikTokLive: () => TikTokLive,
|
|
35
|
+
fetchFeed: () => fetchFeed,
|
|
36
|
+
getLiveFeed: () => getLiveFeed,
|
|
35
37
|
getRanklist: () => getRanklist,
|
|
36
38
|
getRegionalRanklist: () => getRegionalRanklist
|
|
37
39
|
});
|
|
@@ -1493,7 +1495,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1493
1495
|
break;
|
|
1494
1496
|
default:
|
|
1495
1497
|
if (this.debug) {
|
|
1496
|
-
console.log(`[Captions] Unknown message type: ${msg.type}`, msg);
|
|
1498
|
+
if (this.debug) console.log(`[Captions] Unknown message type: ${msg.type}`, msg);
|
|
1497
1499
|
}
|
|
1498
1500
|
}
|
|
1499
1501
|
} catch {
|
|
@@ -1524,10 +1526,10 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1524
1526
|
audioFramesSent++;
|
|
1525
1527
|
audioBytesSent += adtsFrame.length;
|
|
1526
1528
|
if (this.debug && (audioFramesSent <= 3 || audioFramesSent % 100 === 0)) {
|
|
1527
|
-
console.log(`[Captions] Audio frame #${audioFramesSent}: ${adtsFrame.length}b (total: ${audioBytesSent}b)`);
|
|
1529
|
+
if (this.debug) console.log(`[Captions] Audio frame #${audioFramesSent}: ${adtsFrame.length}b (total: ${audioBytesSent}b)`);
|
|
1528
1530
|
}
|
|
1529
1531
|
} else if (this.debug && audioFramesSent === 0) {
|
|
1530
|
-
console.log(`[Captions] WARNING: WS not open (readyState=${this.ws?.readyState}), cannot send audio`);
|
|
1532
|
+
if (this.debug) console.log(`[Captions] WARNING: WS not open (readyState=${this.ws?.readyState}), cannot send audio`);
|
|
1531
1533
|
}
|
|
1532
1534
|
});
|
|
1533
1535
|
try {
|
|
@@ -1560,7 +1562,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1560
1562
|
this.flvExtractor.push(value);
|
|
1561
1563
|
}
|
|
1562
1564
|
if (this.debug && chunks <= 3) {
|
|
1563
|
-
console.log(`[Captions] FLV chunk #${chunks}: ${value?.length || 0}b`);
|
|
1565
|
+
if (this.debug) console.log(`[Captions] FLV chunk #${chunks}: ${value?.length || 0}b`);
|
|
1564
1566
|
}
|
|
1565
1567
|
}
|
|
1566
1568
|
} catch (err) {
|
|
@@ -1568,7 +1570,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1568
1570
|
if (this.debug) console.error("[Captions] FLV stream read error:", err.message);
|
|
1569
1571
|
this.emit("error", { code: "STREAM_READ_ERROR", message: err.message });
|
|
1570
1572
|
} else if (this.debug) {
|
|
1571
|
-
console.log(`[Captions] FLV stream aborted after ${chunks} chunks, ${audioFramesSent} audio frames`);
|
|
1573
|
+
if (this.debug) console.log(`[Captions] FLV stream aborted after ${chunks} chunks, ${audioFramesSent} audio frames`);
|
|
1572
1574
|
}
|
|
1573
1575
|
}
|
|
1574
1576
|
};
|
|
@@ -1585,7 +1587,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1585
1587
|
this.flvExtractor.push(u8);
|
|
1586
1588
|
}
|
|
1587
1589
|
if (this.debug && chunks <= 3) {
|
|
1588
|
-
console.log(`[Captions] FLV chunk #${chunks}: ${u8.length}b`);
|
|
1590
|
+
if (this.debug) console.log(`[Captions] FLV chunk #${chunks}: ${u8.length}b`);
|
|
1589
1591
|
}
|
|
1590
1592
|
}
|
|
1591
1593
|
if (this.debug) console.log(`[Captions] Node stream ended, chunks=${chunks}, audioFrames=${audioFramesSent}`);
|
|
@@ -1594,7 +1596,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1594
1596
|
if (this.debug) console.error("[Captions] FLV stream read error:", err.message);
|
|
1595
1597
|
this.emit("error", { code: "STREAM_READ_ERROR", message: err.message });
|
|
1596
1598
|
} else if (this.debug) {
|
|
1597
|
-
console.log(`[Captions] FLV node stream aborted after ${chunks} chunks, ${audioFramesSent} audio frames`);
|
|
1599
|
+
if (this.debug) console.log(`[Captions] FLV node stream aborted after ${chunks} chunks, ${audioFramesSent} audio frames`);
|
|
1598
1600
|
}
|
|
1599
1601
|
}
|
|
1600
1602
|
};
|
|
@@ -1604,7 +1606,7 @@ var TikTokCaptions = class extends import_events2.EventEmitter {
|
|
|
1604
1606
|
}
|
|
1605
1607
|
} catch (err) {
|
|
1606
1608
|
if (err.name !== "AbortError" && !this.intentionalClose) {
|
|
1607
|
-
console.error("[Captions] FLV stream connect error:", err.message);
|
|
1609
|
+
if (this.debug) console.error("[Captions] FLV stream connect error:", err.message);
|
|
1608
1610
|
this.emit("error", { code: "STREAM_CONNECT_ERROR", message: err.message });
|
|
1609
1611
|
}
|
|
1610
1612
|
}
|
|
@@ -1666,10 +1668,47 @@ async function getRegionalRanklist(opts) {
|
|
|
1666
1668
|
}
|
|
1667
1669
|
return data;
|
|
1668
1670
|
}
|
|
1671
|
+
async function getLiveFeed(opts) {
|
|
1672
|
+
const base = (opts.serverUrl || DEFAULT_SIGN_SERVER2).replace(/\/$/, "");
|
|
1673
|
+
const params = new URLSearchParams();
|
|
1674
|
+
params.set("apiKey", opts.apiKey);
|
|
1675
|
+
if (opts.region) params.set("region", opts.region);
|
|
1676
|
+
if (opts.channelId) params.set("channel_id", opts.channelId);
|
|
1677
|
+
if (opts.count !== void 0) params.set("count", String(Math.min(opts.count, 50)));
|
|
1678
|
+
if (opts.maxTime) params.set("max_time", opts.maxTime);
|
|
1679
|
+
if (opts.sessionId) params.set("session_id", opts.sessionId);
|
|
1680
|
+
if (opts.ttwid) params.set("ttwid", opts.ttwid);
|
|
1681
|
+
if (opts.msToken) params.set("ms_token", opts.msToken);
|
|
1682
|
+
const resp = await fetch(`${base}/webcast/feed?${params.toString()}`);
|
|
1683
|
+
const data = await resp.json();
|
|
1684
|
+
if (resp.status === 429) {
|
|
1685
|
+
throw new Error(data.error || "Feed daily limit reached. Upgrade your plan for more calls.");
|
|
1686
|
+
}
|
|
1687
|
+
if (!resp.ok) {
|
|
1688
|
+
throw new Error(data.error || `Feed request failed (HTTP ${resp.status})`);
|
|
1689
|
+
}
|
|
1690
|
+
return data;
|
|
1691
|
+
}
|
|
1692
|
+
async function fetchFeed(opts) {
|
|
1693
|
+
const signed = await getLiveFeed(opts);
|
|
1694
|
+
const headers = { ...signed.headers || {} };
|
|
1695
|
+
if (signed.cookies) {
|
|
1696
|
+
headers["Cookie"] = signed.cookies;
|
|
1697
|
+
}
|
|
1698
|
+
const resp = await fetch(signed.signed_url, { headers, redirect: "follow" });
|
|
1699
|
+
const text = await resp.text();
|
|
1700
|
+
try {
|
|
1701
|
+
return JSON.parse(text);
|
|
1702
|
+
} catch {
|
|
1703
|
+
return null;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1669
1706
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1670
1707
|
0 && (module.exports = {
|
|
1671
1708
|
TikTokCaptions,
|
|
1672
1709
|
TikTokLive,
|
|
1710
|
+
fetchFeed,
|
|
1711
|
+
getLiveFeed,
|
|
1673
1712
|
getRanklist,
|
|
1674
1713
|
getRegionalRanklist
|
|
1675
1714
|
});
|