@tiktool/live 2.6.2 → 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 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;
@@ -567,6 +628,9 @@ declare class TikTokCaptions extends EventEmitter {
567
628
  private readonly _diarization;
568
629
  private readonly _maxDurationMinutes;
569
630
  private _language;
631
+ private streamAbortController;
632
+ private flvExtractor;
633
+ private streamUrl;
570
634
  constructor(options: TikTokCaptionsOptions);
571
635
  /**
572
636
  * Start real-time captions for the configured TikTok user.
@@ -598,6 +662,11 @@ declare class TikTokCaptions extends EventEmitter {
598
662
  private buildWsUrl;
599
663
  private send;
600
664
  private handleMessage;
665
+ /**
666
+ * Connect to the TikTok FLV stream and extract audio.
667
+ * Sends binary audio buffers to the server via WebSocket.
668
+ */
669
+ private connectToStream;
601
670
  }
602
671
 
603
672
  interface GetRanklistOptions {
@@ -755,4 +824,84 @@ interface RegionalRanklistSignedResponse {
755
824
  */
756
825
  declare function getRegionalRanklist(opts: GetRegionalRanklistOptions): Promise<RegionalRanklistSignedResponse>;
757
826
 
758
- 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;
@@ -567,6 +628,9 @@ declare class TikTokCaptions extends EventEmitter {
567
628
  private readonly _diarization;
568
629
  private readonly _maxDurationMinutes;
569
630
  private _language;
631
+ private streamAbortController;
632
+ private flvExtractor;
633
+ private streamUrl;
570
634
  constructor(options: TikTokCaptionsOptions);
571
635
  /**
572
636
  * Start real-time captions for the configured TikTok user.
@@ -598,6 +662,11 @@ declare class TikTokCaptions extends EventEmitter {
598
662
  private buildWsUrl;
599
663
  private send;
600
664
  private handleMessage;
665
+ /**
666
+ * Connect to the TikTok FLV stream and extract audio.
667
+ * Sends binary audio buffers to the server via WebSocket.
668
+ */
669
+ private connectToStream;
601
670
  }
602
671
 
603
672
  interface GetRanklistOptions {
@@ -755,4 +824,84 @@ interface RegionalRanklistSignedResponse {
755
824
  */
756
825
  declare function getRegionalRanklist(opts: GetRegionalRanklistOptions): Promise<RegionalRanklistSignedResponse>;
757
826
 
758
- 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 };