tiktok-live-api 1.4.6 → 1.4.7

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
@@ -185,6 +185,54 @@ client.on('chat', (e) => console.log(e.comment));
185
185
  client.connect();
186
186
  ```
187
187
 
188
+ ## REST API - leaderboards, recruiting, profiles, gifts
189
+
190
+ The WebSocket client streams live events. For everything else - leaderboards,
191
+ gaming ranks, recruiting, live status, gift catalogs, profiles - use the
192
+ `TikTool` REST client. One method per endpoint, fully promise-based.
193
+
194
+ ```typescript
195
+ import { TikTool } from 'tiktok-live-api';
196
+
197
+ const api = new TikTool({ apiKey: 'YOUR_KEY' });
198
+
199
+ // Live status (single + bulk)
200
+ await api.liveStatus('charlidamelio');
201
+ await api.bulkLiveCheck(['user1', 'user2', 'user3']);
202
+
203
+ // Leaderboards + rankings
204
+ await api.leaderboard({ region: 'US+' });
205
+ await api.ranklistGaming('US+');
206
+ await api.regionMovers('US+'); // entered top today, below cutoff now
207
+
208
+ // Recruiting (Global Agency) - find eligible creators
209
+ await api.eligibleCreators({ region: 'US+', limit: 50, min_score: 1000 });
210
+
211
+ // Profiles + gifts
212
+ await api.profileInfo('khaby.lame');
213
+ await api.userProfile('khaby.lame');
214
+ await api.giftsByCountry('US');
215
+ ```
216
+
217
+ Tier gating is enforced server-side: a call above your tier throws a
218
+ `TikToolError` with `status === 403`. Any endpoint not yet wrapped is reachable
219
+ via `api.request('/webcast/...', { query })`.
220
+
221
+ ```typescript
222
+ import { TikTool, TikToolError } from 'tiktok-live-api';
223
+ try {
224
+ await api.eligibleCreators({ region: 'US+' });
225
+ } catch (e) {
226
+ if (e instanceof TikToolError && e.status === 403) {
227
+ console.log('Upgrade required:', e.message);
228
+ }
229
+ }
230
+ ```
231
+
232
+ Method groups: signing/connection, live status, rankings/leaderboards, gifts,
233
+ user/profile, content/feed, live analytics, moderation, CAPTCHA solver, captions.
234
+ See [the full endpoint + tier matrix](https://tik.tools/docs).
235
+
188
236
  ## Events (54 v3 event types)
189
237
 
190
238
  Every event is dispatched by name. `client.on('chat', e => ...)` is fully typed end-to-end. Each event payload extends `BaseEvent` (`type`, `timestamp`, `msgId`, optional `protoVersion: 1 | 2 | 3`).
package/dist/index.d.mts CHANGED
@@ -326,6 +326,110 @@ declare class TikTokLive {
326
326
  private _maybeReconnect;
327
327
  }
328
328
 
329
+ /**
330
+ * TikTool REST client - typed methods for every customer-facing tik.tools
331
+ * REST endpoint. Pairs with the {@link TikTokLive} WebSocket client.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * import { TikTool } from 'tiktok-live-api';
336
+ *
337
+ * const api = new TikTool({ apiKey: 'YOUR_KEY' });
338
+ *
339
+ * const live = await api.isLive('username');
340
+ * const board = await api.leaderboard({ region: 'US+' });
341
+ * const recruits = await api.eligibleCreators({ region: 'US+', limit: 50 });
342
+ * ```
343
+ *
344
+ * Uses the global `fetch` (Node 18+ or any browser).
345
+ */
346
+ interface TikToolOptions {
347
+ apiKey: string;
348
+ /** Override the API base URL. Defaults to https://api.tik.tools */
349
+ baseUrl?: string;
350
+ }
351
+ /** Thrown when an endpoint returns a non-2xx status. `body` holds the parsed error payload. */
352
+ declare class TikToolError extends Error {
353
+ readonly status: number;
354
+ readonly body: unknown;
355
+ constructor(status: number, body: unknown);
356
+ }
357
+ type Query = Record<string, string | number | boolean | undefined | null>;
358
+ interface RequestOpts {
359
+ method?: 'GET' | 'POST';
360
+ query?: Query;
361
+ body?: unknown;
362
+ }
363
+ declare class TikTool {
364
+ private readonly apiKey;
365
+ private readonly baseUrl;
366
+ constructor(options: TikToolOptions);
367
+ /** Low-level request. Use the named methods below; this is exposed for endpoints not yet wrapped. */
368
+ request<T = any>(path: string, opts?: RequestOpts): Promise<T>;
369
+ /**
370
+ * Creator-identifier query params. The API is inconsistent about whether an
371
+ * endpoint reads `unique_id`, `username`, or `uniqueId`, so we send all three;
372
+ * unused ones are ignored server-side.
373
+ */
374
+ private uid;
375
+ signUrl(url: string): Promise<any>;
376
+ signWebsocket(body: Record<string, unknown>): Promise<any>;
377
+ wsCredentials(uniqueId: string): Promise<any>;
378
+ connectionMode(uniqueId: string): Promise<any>;
379
+ checkAlive(uniqueId: string): Promise<any>;
380
+ rateLimits(): Promise<any>;
381
+ wsSessions(): Promise<any>;
382
+ jwt(body?: Record<string, unknown>): Promise<any>;
383
+ isLive(uniqueId: string): Promise<any>;
384
+ liveStatus(uniqueId: string): Promise<any>;
385
+ bulkLiveCheck(usernames: string[]): Promise<any>;
386
+ liveCounts(region?: string): Promise<any>;
387
+ roomId(uniqueId: string): Promise<any>;
388
+ roomInfo(uniqueId: string): Promise<any>;
389
+ roomCover(uniqueId: string): Promise<any>;
390
+ roomVideo(uniqueId: string): Promise<any>;
391
+ rankings(query?: Query): Promise<any>;
392
+ ranklist(query?: Query): Promise<any>;
393
+ ranklistRegional(region: string): Promise<any>;
394
+ ranklistGaming(region: string): Promise<any>;
395
+ gamingMovers(region: string): Promise<any>;
396
+ regionMovers(region: string): Promise<any>;
397
+ leaderboard(query: {
398
+ region: string;
399
+ } & Query): Promise<any>;
400
+ eligibleCreators(query: {
401
+ region?: string;
402
+ page?: number;
403
+ limit?: number;
404
+ min_score?: number;
405
+ } & Query): Promise<any>;
406
+ giftInfo(query?: Query): Promise<any>;
407
+ giftGallery(query?: Query): Promise<any>;
408
+ giftsByCountry(country: string): Promise<any>;
409
+ lookupUsername(uniqueId: string): Promise<any>;
410
+ profileInfo(uniqueId: string): Promise<any>;
411
+ resolveUserIds(usernames: string[]): Promise<any>;
412
+ userProfile(uniqueId: string): Promise<any>;
413
+ userVideos(uniqueId: string, query?: Query): Promise<any>;
414
+ userFollowers(uniqueId: string, query?: Query): Promise<any>;
415
+ userFollowing(uniqueId: string, query?: Query): Promise<any>;
416
+ userLikes(uniqueId: string, query?: Query): Promise<any>;
417
+ userEarnings(uniqueId: string): Promise<any>;
418
+ hashtagList(query?: Query): Promise<any>;
419
+ fetch(body: Record<string, unknown>): Promise<any>;
420
+ feed(query?: Query): Promise<any>;
421
+ chat(uniqueId: string, query?: Query): Promise<any>;
422
+ analyticsVideoList(query?: Query): Promise<any>;
423
+ analyticsVideoDetail(query?: Query): Promise<any>;
424
+ analyticsUserInteractions(query?: Query): Promise<any>;
425
+ moderationMutes(uniqueId: string): Promise<any>;
426
+ moderationBans(uniqueId: string): Promise<any>;
427
+ solvePuzzle(body: Record<string, unknown>): Promise<any>;
428
+ solveRotate(body: Record<string, unknown>): Promise<any>;
429
+ solveShapes(body: Record<string, unknown>): Promise<any>;
430
+ captionsCredits(): Promise<any>;
431
+ }
432
+
329
433
  /**
330
434
  * TikTokCaptions - Real-time AI speech-to-text for TikTok LIVE streams.
331
435
  *
@@ -431,4 +535,4 @@ declare class TikTokCaptions {
431
535
  disconnect(): void;
432
536
  }
433
537
 
434
- export { type BattleEvent, type CaptionEvent, type ChatEvent, type ConnectedEvent, type CreditsEvent, type DisconnectedEvent, type ErrorEvent, type GiftEvent, type LikeEvent, type MemberEvent, type RoomUserSeqEvent, type SocialEvent, TikTokCaptions, type TikTokCaptionsEventMap, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEventMap, type TikTokLiveOptions, type TikTokUser, type TranslationEvent };
538
+ export { type BattleEvent, type CaptionEvent, type ChatEvent, type ConnectedEvent, type CreditsEvent, type DisconnectedEvent, type ErrorEvent, type GiftEvent, type LikeEvent, type MemberEvent, type RoomUserSeqEvent, type SocialEvent, TikTokCaptions, type TikTokCaptionsEventMap, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEventMap, type TikTokLiveOptions, type TikTokUser, TikTool, TikToolError, type TikToolOptions, type TranslationEvent };
package/dist/index.d.ts CHANGED
@@ -326,6 +326,110 @@ declare class TikTokLive {
326
326
  private _maybeReconnect;
327
327
  }
328
328
 
329
+ /**
330
+ * TikTool REST client - typed methods for every customer-facing tik.tools
331
+ * REST endpoint. Pairs with the {@link TikTokLive} WebSocket client.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * import { TikTool } from 'tiktok-live-api';
336
+ *
337
+ * const api = new TikTool({ apiKey: 'YOUR_KEY' });
338
+ *
339
+ * const live = await api.isLive('username');
340
+ * const board = await api.leaderboard({ region: 'US+' });
341
+ * const recruits = await api.eligibleCreators({ region: 'US+', limit: 50 });
342
+ * ```
343
+ *
344
+ * Uses the global `fetch` (Node 18+ or any browser).
345
+ */
346
+ interface TikToolOptions {
347
+ apiKey: string;
348
+ /** Override the API base URL. Defaults to https://api.tik.tools */
349
+ baseUrl?: string;
350
+ }
351
+ /** Thrown when an endpoint returns a non-2xx status. `body` holds the parsed error payload. */
352
+ declare class TikToolError extends Error {
353
+ readonly status: number;
354
+ readonly body: unknown;
355
+ constructor(status: number, body: unknown);
356
+ }
357
+ type Query = Record<string, string | number | boolean | undefined | null>;
358
+ interface RequestOpts {
359
+ method?: 'GET' | 'POST';
360
+ query?: Query;
361
+ body?: unknown;
362
+ }
363
+ declare class TikTool {
364
+ private readonly apiKey;
365
+ private readonly baseUrl;
366
+ constructor(options: TikToolOptions);
367
+ /** Low-level request. Use the named methods below; this is exposed for endpoints not yet wrapped. */
368
+ request<T = any>(path: string, opts?: RequestOpts): Promise<T>;
369
+ /**
370
+ * Creator-identifier query params. The API is inconsistent about whether an
371
+ * endpoint reads `unique_id`, `username`, or `uniqueId`, so we send all three;
372
+ * unused ones are ignored server-side.
373
+ */
374
+ private uid;
375
+ signUrl(url: string): Promise<any>;
376
+ signWebsocket(body: Record<string, unknown>): Promise<any>;
377
+ wsCredentials(uniqueId: string): Promise<any>;
378
+ connectionMode(uniqueId: string): Promise<any>;
379
+ checkAlive(uniqueId: string): Promise<any>;
380
+ rateLimits(): Promise<any>;
381
+ wsSessions(): Promise<any>;
382
+ jwt(body?: Record<string, unknown>): Promise<any>;
383
+ isLive(uniqueId: string): Promise<any>;
384
+ liveStatus(uniqueId: string): Promise<any>;
385
+ bulkLiveCheck(usernames: string[]): Promise<any>;
386
+ liveCounts(region?: string): Promise<any>;
387
+ roomId(uniqueId: string): Promise<any>;
388
+ roomInfo(uniqueId: string): Promise<any>;
389
+ roomCover(uniqueId: string): Promise<any>;
390
+ roomVideo(uniqueId: string): Promise<any>;
391
+ rankings(query?: Query): Promise<any>;
392
+ ranklist(query?: Query): Promise<any>;
393
+ ranklistRegional(region: string): Promise<any>;
394
+ ranklistGaming(region: string): Promise<any>;
395
+ gamingMovers(region: string): Promise<any>;
396
+ regionMovers(region: string): Promise<any>;
397
+ leaderboard(query: {
398
+ region: string;
399
+ } & Query): Promise<any>;
400
+ eligibleCreators(query: {
401
+ region?: string;
402
+ page?: number;
403
+ limit?: number;
404
+ min_score?: number;
405
+ } & Query): Promise<any>;
406
+ giftInfo(query?: Query): Promise<any>;
407
+ giftGallery(query?: Query): Promise<any>;
408
+ giftsByCountry(country: string): Promise<any>;
409
+ lookupUsername(uniqueId: string): Promise<any>;
410
+ profileInfo(uniqueId: string): Promise<any>;
411
+ resolveUserIds(usernames: string[]): Promise<any>;
412
+ userProfile(uniqueId: string): Promise<any>;
413
+ userVideos(uniqueId: string, query?: Query): Promise<any>;
414
+ userFollowers(uniqueId: string, query?: Query): Promise<any>;
415
+ userFollowing(uniqueId: string, query?: Query): Promise<any>;
416
+ userLikes(uniqueId: string, query?: Query): Promise<any>;
417
+ userEarnings(uniqueId: string): Promise<any>;
418
+ hashtagList(query?: Query): Promise<any>;
419
+ fetch(body: Record<string, unknown>): Promise<any>;
420
+ feed(query?: Query): Promise<any>;
421
+ chat(uniqueId: string, query?: Query): Promise<any>;
422
+ analyticsVideoList(query?: Query): Promise<any>;
423
+ analyticsVideoDetail(query?: Query): Promise<any>;
424
+ analyticsUserInteractions(query?: Query): Promise<any>;
425
+ moderationMutes(uniqueId: string): Promise<any>;
426
+ moderationBans(uniqueId: string): Promise<any>;
427
+ solvePuzzle(body: Record<string, unknown>): Promise<any>;
428
+ solveRotate(body: Record<string, unknown>): Promise<any>;
429
+ solveShapes(body: Record<string, unknown>): Promise<any>;
430
+ captionsCredits(): Promise<any>;
431
+ }
432
+
329
433
  /**
330
434
  * TikTokCaptions - Real-time AI speech-to-text for TikTok LIVE streams.
331
435
  *
@@ -431,4 +535,4 @@ declare class TikTokCaptions {
431
535
  disconnect(): void;
432
536
  }
433
537
 
434
- export { type BattleEvent, type CaptionEvent, type ChatEvent, type ConnectedEvent, type CreditsEvent, type DisconnectedEvent, type ErrorEvent, type GiftEvent, type LikeEvent, type MemberEvent, type RoomUserSeqEvent, type SocialEvent, TikTokCaptions, type TikTokCaptionsEventMap, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEventMap, type TikTokLiveOptions, type TikTokUser, type TranslationEvent };
538
+ export { type BattleEvent, type CaptionEvent, type ChatEvent, type ConnectedEvent, type CreditsEvent, type DisconnectedEvent, type ErrorEvent, type GiftEvent, type LikeEvent, type MemberEvent, type RoomUserSeqEvent, type SocialEvent, TikTokCaptions, type TikTokCaptionsEventMap, type TikTokCaptionsOptions, TikTokLive, type TikTokLiveEventMap, type TikTokLiveOptions, type TikTokUser, TikTool, TikToolError, type TikToolOptions, type TranslationEvent };
package/dist/index.js CHANGED
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  TikTokCaptions: () => TikTokCaptions,
34
- TikTokLive: () => TikTokLive
34
+ TikTokLive: () => TikTokLive,
35
+ TikTool: () => TikTool,
36
+ TikToolError: () => TikToolError
35
37
  });
36
38
  module.exports = __toCommonJS(index_exports);
37
39
 
@@ -177,6 +179,216 @@ var TikTokLive = class {
177
179
  }
178
180
  };
179
181
 
182
+ // src/rest.ts
183
+ var REST_BASE = "https://api.tik.tools";
184
+ var TikToolError = class extends Error {
185
+ constructor(status, body) {
186
+ const msg = body && typeof body === "object" && "error" in body ? String(body.error) : `Request failed with status ${status}`;
187
+ super(msg);
188
+ this.name = "TikToolError";
189
+ this.status = status;
190
+ this.body = body;
191
+ }
192
+ };
193
+ var TikTool = class {
194
+ constructor(options) {
195
+ if (!options?.apiKey) throw new Error("TikTool requires an apiKey");
196
+ this.apiKey = options.apiKey;
197
+ this.baseUrl = (options.baseUrl ?? REST_BASE).replace(/\/$/, "");
198
+ }
199
+ /** Low-level request. Use the named methods below; this is exposed for endpoints not yet wrapped. */
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- API boundary
201
+ async request(path, opts = {}) {
202
+ const url = new URL(this.baseUrl + path);
203
+ if (opts.query) {
204
+ for (const [k, v] of Object.entries(opts.query)) {
205
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
206
+ }
207
+ }
208
+ const headers = { "x-api-key": this.apiKey };
209
+ if (opts.body !== void 0) headers["content-type"] = "application/json";
210
+ const res = await fetch(url.toString(), {
211
+ method: opts.method ?? "GET",
212
+ headers,
213
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
214
+ });
215
+ const text = await res.text();
216
+ let data;
217
+ try {
218
+ data = text ? JSON.parse(text) : null;
219
+ } catch {
220
+ data = text;
221
+ }
222
+ if (!res.ok) throw new TikToolError(res.status, data);
223
+ return data;
224
+ }
225
+ /**
226
+ * Creator-identifier query params. The API is inconsistent about whether an
227
+ * endpoint reads `unique_id`, `username`, or `uniqueId`, so we send all three;
228
+ * unused ones are ignored server-side.
229
+ */
230
+ uid(uniqueId) {
231
+ return { unique_id: uniqueId, username: uniqueId, uniqueId };
232
+ }
233
+ // --- Signing / connection ---
234
+ signUrl(url) {
235
+ return this.request("/webcast/sign_url", { method: "POST", body: { url } });
236
+ }
237
+ signWebsocket(body) {
238
+ return this.request("/webcast/sign_websocket", { method: "POST", body });
239
+ }
240
+ wsCredentials(uniqueId) {
241
+ return this.request("/webcast/ws_credentials", { query: this.uid(uniqueId) });
242
+ }
243
+ connectionMode(uniqueId) {
244
+ return this.request("/webcast/connection_mode", { query: this.uid(uniqueId) });
245
+ }
246
+ checkAlive(uniqueId) {
247
+ return this.request("/webcast/check_alive", { query: this.uid(uniqueId) });
248
+ }
249
+ rateLimits() {
250
+ return this.request("/webcast/rate_limits");
251
+ }
252
+ wsSessions() {
253
+ return this.request("/webcast/ws_sessions");
254
+ }
255
+ jwt(body) {
256
+ return this.request("/authentication/jwt", { method: "POST", body: body ?? {} });
257
+ }
258
+ // --- Live status ---
259
+ isLive(uniqueId) {
260
+ return this.request("/webcast/is_live", { query: this.uid(uniqueId) });
261
+ }
262
+ liveStatus(uniqueId) {
263
+ return this.request("/webcast/live_status", { query: this.uid(uniqueId) });
264
+ }
265
+ bulkLiveCheck(usernames) {
266
+ return this.request("/webcast/bulk_live_check", { method: "POST", body: { usernames } });
267
+ }
268
+ liveCounts(region) {
269
+ return this.request("/webcast/live_counts", { query: { region } });
270
+ }
271
+ roomId(uniqueId) {
272
+ return this.request("/webcast/room_id", { query: this.uid(uniqueId) });
273
+ }
274
+ roomInfo(uniqueId) {
275
+ return this.request("/webcast/room_info", { query: this.uid(uniqueId) });
276
+ }
277
+ roomCover(uniqueId) {
278
+ return this.request("/webcast/room_cover", { query: this.uid(uniqueId) });
279
+ }
280
+ roomVideo(uniqueId) {
281
+ return this.request("/webcast/room_video", { query: this.uid(uniqueId) });
282
+ }
283
+ // --- Rankings / leaderboards ---
284
+ rankings(query) {
285
+ return this.request("/webcast/rankings", { query });
286
+ }
287
+ ranklist(query) {
288
+ return this.request("/webcast/ranklist", { query });
289
+ }
290
+ ranklistRegional(region) {
291
+ return this.request("/webcast/ranklist/regional", { query: { region } });
292
+ }
293
+ ranklistGaming(region) {
294
+ return this.request("/webcast/ranklist/gaming", { query: { region } });
295
+ }
296
+ gamingMovers(region) {
297
+ return this.request("/webcast/ranklist/gaming_movers", { query: { region } });
298
+ }
299
+ regionMovers(region) {
300
+ return this.request("/webcast/ranklist/region_movers", { query: { region } });
301
+ }
302
+ leaderboard(query) {
303
+ return this.request("/webcast/leaderboard", { query });
304
+ }
305
+ eligibleCreators(query) {
306
+ return this.request("/webcast/eligible_creators", { query });
307
+ }
308
+ // --- Gifts ---
309
+ giftInfo(query) {
310
+ return this.request("/webcast/gift_info", { query });
311
+ }
312
+ giftGallery(query) {
313
+ return this.request("/webcast/gift_gallery", { query });
314
+ }
315
+ giftsByCountry(country) {
316
+ return this.request("/webcast/gifts_by_country", { query: { country } });
317
+ }
318
+ // --- User / profile ---
319
+ lookupUsername(uniqueId) {
320
+ return this.request("/webcast/lookup_username", { query: this.uid(uniqueId) });
321
+ }
322
+ profileInfo(uniqueId) {
323
+ return this.request("/webcast/profile_info", { query: this.uid(uniqueId) });
324
+ }
325
+ resolveUserIds(usernames) {
326
+ return this.request("/webcast/resolve_user_ids", { method: "POST", body: { usernames } });
327
+ }
328
+ userProfile(uniqueId) {
329
+ return this.request("/webcast/user_profile", { query: this.uid(uniqueId) });
330
+ }
331
+ userVideos(uniqueId, query) {
332
+ return this.request("/webcast/user_videos", { query: { ...this.uid(uniqueId), ...query } });
333
+ }
334
+ userFollowers(uniqueId, query) {
335
+ return this.request("/webcast/user_followers", { query: { ...this.uid(uniqueId), ...query } });
336
+ }
337
+ userFollowing(uniqueId, query) {
338
+ return this.request("/webcast/user_following", { query: { ...this.uid(uniqueId), ...query } });
339
+ }
340
+ userLikes(uniqueId, query) {
341
+ return this.request("/webcast/user_likes", { query: { ...this.uid(uniqueId), ...query } });
342
+ }
343
+ userEarnings(uniqueId) {
344
+ return this.request("/webcast/user_earnings", { query: this.uid(uniqueId) });
345
+ }
346
+ // --- Content / feed ---
347
+ hashtagList(query) {
348
+ return this.request("/webcast/hashtag_list", { query });
349
+ }
350
+ fetch(body) {
351
+ return this.request("/webcast/fetch", { method: "POST", body });
352
+ }
353
+ feed(query) {
354
+ return this.request("/webcast/feed", { query });
355
+ }
356
+ chat(uniqueId, query) {
357
+ return this.request("/webcast/chat", { query: { ...this.uid(uniqueId), ...query } });
358
+ }
359
+ // --- Live analytics ---
360
+ analyticsVideoList(query) {
361
+ return this.request("/webcast/live_analytics/video_list", { query });
362
+ }
363
+ analyticsVideoDetail(query) {
364
+ return this.request("/webcast/live_analytics/video_detail", { query });
365
+ }
366
+ analyticsUserInteractions(query) {
367
+ return this.request("/webcast/live_analytics/user_interactions", { query });
368
+ }
369
+ // --- Moderation ---
370
+ moderationMutes(uniqueId) {
371
+ return this.request("/webcast/moderation/mutes", { query: this.uid(uniqueId) });
372
+ }
373
+ moderationBans(uniqueId) {
374
+ return this.request("/webcast/moderation/bans", { query: this.uid(uniqueId) });
375
+ }
376
+ // --- CAPTCHA solver (Pro+) ---
377
+ solvePuzzle(body) {
378
+ return this.request("/captcha/solve/puzzle", { method: "POST", body });
379
+ }
380
+ solveRotate(body) {
381
+ return this.request("/captcha/solve/rotate", { method: "POST", body });
382
+ }
383
+ solveShapes(body) {
384
+ return this.request("/captcha/solve/shapes", { method: "POST", body });
385
+ }
386
+ // --- Captions ---
387
+ captionsCredits() {
388
+ return this.request("/captions/credits");
389
+ }
390
+ };
391
+
180
392
  // src/captions.ts
181
393
  var import_ws2 = __toESM(require("ws"));
182
394
  var CAPTIONS_BASE = "wss://api.tik.tools/captions";
@@ -287,5 +499,7 @@ var TikTokCaptions = class {
287
499
  // Annotate the CommonJS export names for ESM import in node:
288
500
  0 && (module.exports = {
289
501
  TikTokCaptions,
290
- TikTokLive
502
+ TikTokLive,
503
+ TikTool,
504
+ TikToolError
291
505
  });
package/dist/index.mjs CHANGED
@@ -140,6 +140,216 @@ var TikTokLive = class {
140
140
  }
141
141
  };
142
142
 
143
+ // src/rest.ts
144
+ var REST_BASE = "https://api.tik.tools";
145
+ var TikToolError = class extends Error {
146
+ constructor(status, body) {
147
+ const msg = body && typeof body === "object" && "error" in body ? String(body.error) : `Request failed with status ${status}`;
148
+ super(msg);
149
+ this.name = "TikToolError";
150
+ this.status = status;
151
+ this.body = body;
152
+ }
153
+ };
154
+ var TikTool = class {
155
+ constructor(options) {
156
+ if (!options?.apiKey) throw new Error("TikTool requires an apiKey");
157
+ this.apiKey = options.apiKey;
158
+ this.baseUrl = (options.baseUrl ?? REST_BASE).replace(/\/$/, "");
159
+ }
160
+ /** Low-level request. Use the named methods below; this is exposed for endpoints not yet wrapped. */
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- API boundary
162
+ async request(path, opts = {}) {
163
+ const url = new URL(this.baseUrl + path);
164
+ if (opts.query) {
165
+ for (const [k, v] of Object.entries(opts.query)) {
166
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
167
+ }
168
+ }
169
+ const headers = { "x-api-key": this.apiKey };
170
+ if (opts.body !== void 0) headers["content-type"] = "application/json";
171
+ const res = await fetch(url.toString(), {
172
+ method: opts.method ?? "GET",
173
+ headers,
174
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
175
+ });
176
+ const text = await res.text();
177
+ let data;
178
+ try {
179
+ data = text ? JSON.parse(text) : null;
180
+ } catch {
181
+ data = text;
182
+ }
183
+ if (!res.ok) throw new TikToolError(res.status, data);
184
+ return data;
185
+ }
186
+ /**
187
+ * Creator-identifier query params. The API is inconsistent about whether an
188
+ * endpoint reads `unique_id`, `username`, or `uniqueId`, so we send all three;
189
+ * unused ones are ignored server-side.
190
+ */
191
+ uid(uniqueId) {
192
+ return { unique_id: uniqueId, username: uniqueId, uniqueId };
193
+ }
194
+ // --- Signing / connection ---
195
+ signUrl(url) {
196
+ return this.request("/webcast/sign_url", { method: "POST", body: { url } });
197
+ }
198
+ signWebsocket(body) {
199
+ return this.request("/webcast/sign_websocket", { method: "POST", body });
200
+ }
201
+ wsCredentials(uniqueId) {
202
+ return this.request("/webcast/ws_credentials", { query: this.uid(uniqueId) });
203
+ }
204
+ connectionMode(uniqueId) {
205
+ return this.request("/webcast/connection_mode", { query: this.uid(uniqueId) });
206
+ }
207
+ checkAlive(uniqueId) {
208
+ return this.request("/webcast/check_alive", { query: this.uid(uniqueId) });
209
+ }
210
+ rateLimits() {
211
+ return this.request("/webcast/rate_limits");
212
+ }
213
+ wsSessions() {
214
+ return this.request("/webcast/ws_sessions");
215
+ }
216
+ jwt(body) {
217
+ return this.request("/authentication/jwt", { method: "POST", body: body ?? {} });
218
+ }
219
+ // --- Live status ---
220
+ isLive(uniqueId) {
221
+ return this.request("/webcast/is_live", { query: this.uid(uniqueId) });
222
+ }
223
+ liveStatus(uniqueId) {
224
+ return this.request("/webcast/live_status", { query: this.uid(uniqueId) });
225
+ }
226
+ bulkLiveCheck(usernames) {
227
+ return this.request("/webcast/bulk_live_check", { method: "POST", body: { usernames } });
228
+ }
229
+ liveCounts(region) {
230
+ return this.request("/webcast/live_counts", { query: { region } });
231
+ }
232
+ roomId(uniqueId) {
233
+ return this.request("/webcast/room_id", { query: this.uid(uniqueId) });
234
+ }
235
+ roomInfo(uniqueId) {
236
+ return this.request("/webcast/room_info", { query: this.uid(uniqueId) });
237
+ }
238
+ roomCover(uniqueId) {
239
+ return this.request("/webcast/room_cover", { query: this.uid(uniqueId) });
240
+ }
241
+ roomVideo(uniqueId) {
242
+ return this.request("/webcast/room_video", { query: this.uid(uniqueId) });
243
+ }
244
+ // --- Rankings / leaderboards ---
245
+ rankings(query) {
246
+ return this.request("/webcast/rankings", { query });
247
+ }
248
+ ranklist(query) {
249
+ return this.request("/webcast/ranklist", { query });
250
+ }
251
+ ranklistRegional(region) {
252
+ return this.request("/webcast/ranklist/regional", { query: { region } });
253
+ }
254
+ ranklistGaming(region) {
255
+ return this.request("/webcast/ranklist/gaming", { query: { region } });
256
+ }
257
+ gamingMovers(region) {
258
+ return this.request("/webcast/ranklist/gaming_movers", { query: { region } });
259
+ }
260
+ regionMovers(region) {
261
+ return this.request("/webcast/ranklist/region_movers", { query: { region } });
262
+ }
263
+ leaderboard(query) {
264
+ return this.request("/webcast/leaderboard", { query });
265
+ }
266
+ eligibleCreators(query) {
267
+ return this.request("/webcast/eligible_creators", { query });
268
+ }
269
+ // --- Gifts ---
270
+ giftInfo(query) {
271
+ return this.request("/webcast/gift_info", { query });
272
+ }
273
+ giftGallery(query) {
274
+ return this.request("/webcast/gift_gallery", { query });
275
+ }
276
+ giftsByCountry(country) {
277
+ return this.request("/webcast/gifts_by_country", { query: { country } });
278
+ }
279
+ // --- User / profile ---
280
+ lookupUsername(uniqueId) {
281
+ return this.request("/webcast/lookup_username", { query: this.uid(uniqueId) });
282
+ }
283
+ profileInfo(uniqueId) {
284
+ return this.request("/webcast/profile_info", { query: this.uid(uniqueId) });
285
+ }
286
+ resolveUserIds(usernames) {
287
+ return this.request("/webcast/resolve_user_ids", { method: "POST", body: { usernames } });
288
+ }
289
+ userProfile(uniqueId) {
290
+ return this.request("/webcast/user_profile", { query: this.uid(uniqueId) });
291
+ }
292
+ userVideos(uniqueId, query) {
293
+ return this.request("/webcast/user_videos", { query: { ...this.uid(uniqueId), ...query } });
294
+ }
295
+ userFollowers(uniqueId, query) {
296
+ return this.request("/webcast/user_followers", { query: { ...this.uid(uniqueId), ...query } });
297
+ }
298
+ userFollowing(uniqueId, query) {
299
+ return this.request("/webcast/user_following", { query: { ...this.uid(uniqueId), ...query } });
300
+ }
301
+ userLikes(uniqueId, query) {
302
+ return this.request("/webcast/user_likes", { query: { ...this.uid(uniqueId), ...query } });
303
+ }
304
+ userEarnings(uniqueId) {
305
+ return this.request("/webcast/user_earnings", { query: this.uid(uniqueId) });
306
+ }
307
+ // --- Content / feed ---
308
+ hashtagList(query) {
309
+ return this.request("/webcast/hashtag_list", { query });
310
+ }
311
+ fetch(body) {
312
+ return this.request("/webcast/fetch", { method: "POST", body });
313
+ }
314
+ feed(query) {
315
+ return this.request("/webcast/feed", { query });
316
+ }
317
+ chat(uniqueId, query) {
318
+ return this.request("/webcast/chat", { query: { ...this.uid(uniqueId), ...query } });
319
+ }
320
+ // --- Live analytics ---
321
+ analyticsVideoList(query) {
322
+ return this.request("/webcast/live_analytics/video_list", { query });
323
+ }
324
+ analyticsVideoDetail(query) {
325
+ return this.request("/webcast/live_analytics/video_detail", { query });
326
+ }
327
+ analyticsUserInteractions(query) {
328
+ return this.request("/webcast/live_analytics/user_interactions", { query });
329
+ }
330
+ // --- Moderation ---
331
+ moderationMutes(uniqueId) {
332
+ return this.request("/webcast/moderation/mutes", { query: this.uid(uniqueId) });
333
+ }
334
+ moderationBans(uniqueId) {
335
+ return this.request("/webcast/moderation/bans", { query: this.uid(uniqueId) });
336
+ }
337
+ // --- CAPTCHA solver (Pro+) ---
338
+ solvePuzzle(body) {
339
+ return this.request("/captcha/solve/puzzle", { method: "POST", body });
340
+ }
341
+ solveRotate(body) {
342
+ return this.request("/captcha/solve/rotate", { method: "POST", body });
343
+ }
344
+ solveShapes(body) {
345
+ return this.request("/captcha/solve/shapes", { method: "POST", body });
346
+ }
347
+ // --- Captions ---
348
+ captionsCredits() {
349
+ return this.request("/captions/credits");
350
+ }
351
+ };
352
+
143
353
  // src/captions.ts
144
354
  import WebSocket2 from "ws";
145
355
  var CAPTIONS_BASE = "wss://api.tik.tools/captions";
@@ -249,5 +459,7 @@ var TikTokCaptions = class {
249
459
  };
250
460
  export {
251
461
  TikTokCaptions,
252
- TikTokLive
462
+ TikTokLive,
463
+ TikTool,
464
+ TikToolError
253
465
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiktok-live-api",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Unofficial TikTok LIVE API Client - Real-time chat, gifts, viewers, battles, and AI live captions from any TikTok livestream. Managed WebSocket API with 99.9% uptime.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",