@tiktool/live 2.6.3 → 2.6.4
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 +68 -5
- 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 +2 -2
package/README.md
CHANGED
|
@@ -239,6 +239,10 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
239
239
|
| `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
|
|
240
240
|
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
241
241
|
| `debug` | `boolean` | `false` | Debug logging |
|
|
242
|
+
| `sessionId` | `string` | — | TikTok `sessionid` cookie for authenticated features (ranklist, chat) |
|
|
243
|
+
| `ttTargetIdc` | `string` | — | TikTok target IDC region (e.g. `useast5`). Required with `sessionId` |
|
|
244
|
+
| `roomId` | `string` | — | Pre-known room ID — skips HTML page scrape |
|
|
245
|
+
| `ttwid` | `string` | — | Pre-fetched `ttwid` cookie. With `roomId`, skips all HTTP requests |
|
|
242
246
|
|
|
243
247
|
### Methods
|
|
244
248
|
|
|
@@ -246,9 +250,12 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
246
250
|
|--------|---------|-------------|
|
|
247
251
|
| `connect()` | `Promise<void>` | Connect to livestream |
|
|
248
252
|
| `disconnect()` | `void` | Disconnect |
|
|
253
|
+
| `setSession(sessionId, ttTargetIdc?)` | `void` | Update session at runtime |
|
|
254
|
+
| `buildSessionCookieHeader()` | `string \| undefined` | Build cookie header for auth API requests |
|
|
249
255
|
| `connected` | `boolean` | Connection status |
|
|
250
256
|
| `eventCount` | `number` | Total events received |
|
|
251
257
|
| `roomId` | `string` | Current room ID |
|
|
258
|
+
| `sessionId` | `string \| undefined` | Current session ID |
|
|
252
259
|
|
|
253
260
|
---
|
|
254
261
|
|
|
@@ -256,16 +263,72 @@ Try the live demo at [tik.tools/captions](https://tik.tools/captions) — see re
|
|
|
256
263
|
|
|
257
264
|
All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
|
|
258
265
|
|
|
259
|
-
| Tier | Rate Limit | WS Connections | Bulk Check | Caption Credits | Price |
|
|
260
|
-
|
|
261
|
-
| **Free** | 30/min | 3 | 5 | 60 min trial | Free |
|
|
262
|
-
| **Pro** | 120/min | 50 | 50 | 2,000/month | Paid |
|
|
263
|
-
| **Ultra** | Unlimited | 10,000 | 500 | 10,000/month | Paid |
|
|
266
|
+
| Tier | Rate Limit | WS Connections | Bulk Check | Feed Discovery | Caption Credits | Price |
|
|
267
|
+
|------|-----------|----------------|------------|----------------|-----------------|-------|
|
|
268
|
+
| **Free** | 30/min | 3 | 5 | ✕ | 60 min trial | Free |
|
|
269
|
+
| **Pro** | 120/min | 50 | 50 | 100/day | 2,000/month | Paid |
|
|
270
|
+
| **Ultra** | Unlimited | 10,000 | 500 | 2,000/day | 10,000/month | Paid |
|
|
264
271
|
|
|
265
272
|
The SDK calls the sign server **once per connection**, then stays connected via WebSocket. A free key is sufficient for most use cases.
|
|
266
273
|
|
|
267
274
|
---
|
|
268
275
|
|
|
276
|
+
## 🔍 Feed Discovery
|
|
277
|
+
|
|
278
|
+
Discover recommended TikTok LIVE streams. **Requires Pro or Ultra tier.**
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { getLiveFeed, fetchFeed } from '@tiktool/live';
|
|
282
|
+
|
|
283
|
+
// Option 1: Two-step (sign-and-return)
|
|
284
|
+
const signed = await getLiveFeed({
|
|
285
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
286
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
287
|
+
region: 'US',
|
|
288
|
+
count: 10,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const resp = await fetch(signed.signed_url, {
|
|
292
|
+
headers: { ...signed.headers, Cookie: signed.cookies || '' },
|
|
293
|
+
});
|
|
294
|
+
const data = await resp.json();
|
|
295
|
+
|
|
296
|
+
// Option 2: One-step convenience
|
|
297
|
+
const feed = await fetchFeed({
|
|
298
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
299
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
300
|
+
count: 10,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
for (const entry of feed.data || []) {
|
|
304
|
+
const room = entry.data;
|
|
305
|
+
console.log(`🔴 @${room.owner.display_id}: "${room.title}" — ${room.user_count} viewers`);
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Channel Types
|
|
310
|
+
|
|
311
|
+
| Value | Channel |
|
|
312
|
+
|-------|--------|
|
|
313
|
+
| `"87"` | Recommended (default) |
|
|
314
|
+
| `"86"` | Suggested |
|
|
315
|
+
| `"42"` | Following |
|
|
316
|
+
| `"1111006"` | Gaming |
|
|
317
|
+
|
|
318
|
+
### Pagination
|
|
319
|
+
|
|
320
|
+
Use `maxTime` from the previous response to load more:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const page2 = await getLiveFeed({
|
|
324
|
+
apiKey: 'YOUR_PRO_KEY',
|
|
325
|
+
sessionId: 'YOUR_TIKTOK_SESSIONID',
|
|
326
|
+
maxTime: data.extra?.max_time, // cursor from previous response
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
269
332
|
## 🏆 Regional Leaderboard
|
|
270
333
|
|
|
271
334
|
Get daily, hourly, popular, or league LIVE rankings for any streamer. **Requires Pro or Ultra tier.**
|
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
|
});
|