@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 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
- Every API key includes a **60-minute free trial** for live captions (speech-to-text with auto-detect to any language).
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 | 150 min | $4.99 | $0.033/min |
218
- | Growth | 600 min | $14.99 | $0.025/min |
219
- | Scale | 2,000 min | $39.99 | $0.020/min |
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 | 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 |
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
- The SDK calls the sign server **once per connection**, then stays connected via WebSocket. A free key is sufficient for most use cases.
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
- 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 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, getRanklist, getRegionalRanklist };
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
- 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 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, getRanklist, getRegionalRanklist };
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
  });