@tiktool/live 2.9.0 → 2.10.1
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 +59 -11
- package/dist/index.d.mts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,22 +97,42 @@ Install `https-proxy-agent` if using `proxy`:
|
|
|
97
97
|
npm install https-proxy-agent
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
### Mode 2 — Relayed (via our proxy pool
|
|
100
|
+
### Mode 2 — Relayed (via our proxy pool)
|
|
101
101
|
|
|
102
102
|
```
|
|
103
|
-
Your App ◀──── wss://api.tik.tools
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
Your App ◀──── wss://api.tik.tools/?... ──── tik.tools
|
|
104
|
+
│
|
|
105
|
+
│ WS via
|
|
106
|
+
│ rotated proxies
|
|
107
|
+
▼
|
|
108
|
+
TikTok
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
- **TikTok sees our IPs** (rotated across hundreds of Webshare + residential proxies).
|
|
112
112
|
- We absorb every per-IP rate-limit hit. Survives any block automatically.
|
|
113
113
|
- Best for **30+ concurrent streams**, low-budget setups, or anyone who doesn't want their own IP fingerprinted by TikTok.
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
|
|
115
|
+
**Easiest — same SDK, one option:**
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { TikTokLive } from '@tiktool/live';
|
|
119
|
+
|
|
120
|
+
const live = new TikTokLive({
|
|
121
|
+
uniqueId: 'creator_username',
|
|
122
|
+
apiKey: 'YOUR_KEY',
|
|
123
|
+
mode: 'relayed', // ← that's it
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
|
|
127
|
+
live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName}`));
|
|
128
|
+
live.on('battleArmies', e => console.log(e));
|
|
129
|
+
|
|
130
|
+
await live.connect();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Event names + payload shapes are **identical to Direct mode**. Switch back and forth by toggling `mode`.
|
|
134
|
+
|
|
135
|
+
**Advanced — raw WebSocket (no SDK, your own client):**
|
|
116
136
|
|
|
117
137
|
```typescript
|
|
118
138
|
import WebSocket from 'ws';
|
|
@@ -124,12 +144,12 @@ const ws = new WebSocket(
|
|
|
124
144
|
ws.on('message', raw => {
|
|
125
145
|
const msg = JSON.parse(raw.toString());
|
|
126
146
|
if (msg.event === 'chat') console.log(msg.data.user.uniqueId, msg.data.comment);
|
|
127
|
-
if (msg.event === 'gift') console.log(msg.data.user.uniqueId, msg.data.giftName
|
|
147
|
+
if (msg.event === 'gift') console.log(msg.data.user.uniqueId, msg.data.giftName);
|
|
128
148
|
if (msg.event === 'battleArmies') console.log(msg.data);
|
|
129
149
|
});
|
|
130
150
|
```
|
|
131
151
|
|
|
132
|
-
|
|
152
|
+
Use this when integrating from a non-Node runtime (Python, Go, Bun, browser) or when you want absolute control over the connection.
|
|
133
153
|
|
|
134
154
|
### Side-by-side
|
|
135
155
|
|
|
@@ -318,6 +338,8 @@ live.on('battle', (e) => {
|
|
|
318
338
|
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
319
339
|
| `roomId` | `string` | — | Pre-resolved room ID (skips page fetch when paired with `sessionId`) |
|
|
320
340
|
| `sessionId` | `string` | — | Pre-resolved `ttwid` cookie (skips page fetch when paired with `roomId`) |
|
|
341
|
+
| `proxy` | `string` | — | HTTP(S) proxy URL for Direct mode (e.g. `http://USER:PASS@host:port`). Requires `https-proxy-agent`. |
|
|
342
|
+
| `mode` | `'direct' \| 'relayed'` | `'direct'` | Connection mode. See [Connection Modes](#connection-modes). |
|
|
321
343
|
| `debug` | `boolean` | `false` | Debug logging |
|
|
322
344
|
|
|
323
345
|
### Methods
|
|
@@ -331,6 +353,32 @@ live.on('battle', (e) => {
|
|
|
331
353
|
| `eventCount` | `number` | Total events received |
|
|
332
354
|
| `roomId` | `string` | Current room ID |
|
|
333
355
|
|
|
356
|
+
### REST endpoints (companion to the WS SDK)
|
|
357
|
+
|
|
358
|
+
For lookups outside the live event stream, hit the sign server directly with your API key:
|
|
359
|
+
|
|
360
|
+
| Endpoint | Method | Tier | Use case |
|
|
361
|
+
|----------|--------|------|----------|
|
|
362
|
+
| `/webcast/room_id` | POST | sandbox+ | `unique_id` → `room_id` |
|
|
363
|
+
| `/webcast/room_info` | POST | sandbox+ | `unique_id` → `room_id` + `alive` + `title` |
|
|
364
|
+
| `/webcast/check_alive` | GET/POST | sandbox+ | Is `room_id` currently live? |
|
|
365
|
+
| `/webcast/bulk_live_check` | POST | basic+ | Batch check up to 500 users in one call |
|
|
366
|
+
| `/webcast/live_status` | GET | sandbox+ | `unique_id` → live snapshot incl. viewer count |
|
|
367
|
+
| `/webcast/user_profile` | GET | **pro+** | **`unique_id` → numeric `id`, `secUid`, nickname, bio, avatars, follower stats** |
|
|
368
|
+
| `/webcast/resolve_user_ids` | POST | sandbox+ | Batch numeric `userId` → `unique_id` (reverse of `user_profile`) |
|
|
369
|
+
| `/webcast/rankings` | GET | sandbox+ | Top gifters / hourly rank for a room |
|
|
370
|
+
| `/webcast/room_video` | POST | basic+ | Get HLS / FLV stream URLs |
|
|
371
|
+
| `/webcast/ws_credentials` | POST | sandbox+ | Get signed WS URL + ttwid (used by Direct mode under the hood) |
|
|
372
|
+
|
|
373
|
+
**Need the streamer's numeric TikTok ID for a username?**
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
curl -H "X-Api-Key: YOUR_KEY" \
|
|
377
|
+
"https://api.tik.tools/webcast/user_profile?unique_id=anyuser"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Returns full profile JSON. Pro tier and above. Cached server-side for 24h — repeated lookups are free and instant.
|
|
381
|
+
|
|
334
382
|
---
|
|
335
383
|
|
|
336
384
|
## Rate Limits
|
package/dist/index.d.mts
CHANGED
|
@@ -291,6 +291,20 @@ interface TikTokLiveOptions {
|
|
|
291
291
|
* proxy: 'http://USER:PASS@p.webshare.io:80'
|
|
292
292
|
*/
|
|
293
293
|
proxy?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Connection mode.
|
|
296
|
+
* - `'direct'` (default): SDK opens WebSocket directly to TikTok. TikTok
|
|
297
|
+
* sees your IP. Cheapest on API quota. Limited to ~30 concurrent
|
|
298
|
+
* streams per container before TikTok rate-limits.
|
|
299
|
+
* - `'relayed'`: SDK connects to TikTools' managed relay
|
|
300
|
+
* (`wss://api.tik.tools/?...`). We connect to TikTok on your behalf
|
|
301
|
+
* from our rotated proxy pool. TikTok never sees your IP. No rate-
|
|
302
|
+
* limit risk. Scales to thousands of concurrent streams.
|
|
303
|
+
*
|
|
304
|
+
* The decoded events emitted are identical in both modes — code that
|
|
305
|
+
* uses `client.on('chat', e => ...)` works without changes.
|
|
306
|
+
*/
|
|
307
|
+
mode?: 'direct' | 'relayed';
|
|
294
308
|
}
|
|
295
309
|
|
|
296
310
|
declare class TikTokLive extends EventEmitter {
|
|
@@ -315,6 +329,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
315
329
|
private readonly _presetRoomId;
|
|
316
330
|
private readonly _presetSessionId;
|
|
317
331
|
private readonly proxyUrl;
|
|
332
|
+
private readonly mode;
|
|
318
333
|
constructor(options: TikTokLiveOptions);
|
|
319
334
|
/**
|
|
320
335
|
* Build an HttpsProxyAgent when `proxy` option is set, else undefined.
|
|
@@ -367,6 +382,13 @@ declare class TikTokLive extends EventEmitter {
|
|
|
367
382
|
private resolveHostUsers;
|
|
368
383
|
private startHeartbeat;
|
|
369
384
|
private stopHeartbeat;
|
|
385
|
+
/**
|
|
386
|
+
* Relayed-mode connect. Opens a plain WebSocket to TikTools' managed relay
|
|
387
|
+
* endpoint. Events arrive pre-decoded as `{event, data}` JSON envelopes.
|
|
388
|
+
* The SDK re-emits them on the same event names as Direct mode so user
|
|
389
|
+
* code is identical regardless of mode.
|
|
390
|
+
*/
|
|
391
|
+
private _connectRelayed;
|
|
370
392
|
}
|
|
371
393
|
|
|
372
394
|
export { type BaseEvent, type BattleArmiesEvent, type BattleContributor, type BattleEvent, type BattleHost, type BattleItemCardEvent, type BattleTeam, type BattleTeamUser, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EnvelopeEvent, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type MemberEvent, type QuestionEvent, type RankUpdateEvent, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SocialEvent, type StreamInfo, type StreamQuality, type StreamUrls, type SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
|
package/dist/index.d.ts
CHANGED
|
@@ -291,6 +291,20 @@ interface TikTokLiveOptions {
|
|
|
291
291
|
* proxy: 'http://USER:PASS@p.webshare.io:80'
|
|
292
292
|
*/
|
|
293
293
|
proxy?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Connection mode.
|
|
296
|
+
* - `'direct'` (default): SDK opens WebSocket directly to TikTok. TikTok
|
|
297
|
+
* sees your IP. Cheapest on API quota. Limited to ~30 concurrent
|
|
298
|
+
* streams per container before TikTok rate-limits.
|
|
299
|
+
* - `'relayed'`: SDK connects to TikTools' managed relay
|
|
300
|
+
* (`wss://api.tik.tools/?...`). We connect to TikTok on your behalf
|
|
301
|
+
* from our rotated proxy pool. TikTok never sees your IP. No rate-
|
|
302
|
+
* limit risk. Scales to thousands of concurrent streams.
|
|
303
|
+
*
|
|
304
|
+
* The decoded events emitted are identical in both modes — code that
|
|
305
|
+
* uses `client.on('chat', e => ...)` works without changes.
|
|
306
|
+
*/
|
|
307
|
+
mode?: 'direct' | 'relayed';
|
|
294
308
|
}
|
|
295
309
|
|
|
296
310
|
declare class TikTokLive extends EventEmitter {
|
|
@@ -315,6 +329,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
315
329
|
private readonly _presetRoomId;
|
|
316
330
|
private readonly _presetSessionId;
|
|
317
331
|
private readonly proxyUrl;
|
|
332
|
+
private readonly mode;
|
|
318
333
|
constructor(options: TikTokLiveOptions);
|
|
319
334
|
/**
|
|
320
335
|
* Build an HttpsProxyAgent when `proxy` option is set, else undefined.
|
|
@@ -367,6 +382,13 @@ declare class TikTokLive extends EventEmitter {
|
|
|
367
382
|
private resolveHostUsers;
|
|
368
383
|
private startHeartbeat;
|
|
369
384
|
private stopHeartbeat;
|
|
385
|
+
/**
|
|
386
|
+
* Relayed-mode connect. Opens a plain WebSocket to TikTools' managed relay
|
|
387
|
+
* endpoint. Events arrive pre-decoded as `{event, data}` JSON envelopes.
|
|
388
|
+
* The SDK re-emits them on the same event names as Direct mode so user
|
|
389
|
+
* code is identical regardless of mode.
|
|
390
|
+
*/
|
|
391
|
+
private _connectRelayed;
|
|
370
392
|
}
|
|
371
393
|
|
|
372
394
|
export { type BaseEvent, type BattleArmiesEvent, type BattleContributor, type BattleEvent, type BattleHost, type BattleItemCardEvent, type BattleTeam, type BattleTeamUser, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EnvelopeEvent, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type MemberEvent, type QuestionEvent, type RankUpdateEvent, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SocialEvent, type StreamInfo, type StreamQuality, type StreamUrls, type SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
|
package/dist/index.js
CHANGED
|
@@ -2147,6 +2147,7 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2147
2147
|
_presetRoomId;
|
|
2148
2148
|
_presetSessionId;
|
|
2149
2149
|
proxyUrl;
|
|
2150
|
+
mode;
|
|
2150
2151
|
constructor(options) {
|
|
2151
2152
|
super();
|
|
2152
2153
|
this.setMaxListeners(20);
|
|
@@ -2161,6 +2162,7 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2161
2162
|
this._presetRoomId = options.roomId || "";
|
|
2162
2163
|
this._presetSessionId = options.sessionId || "";
|
|
2163
2164
|
this.proxyUrl = options.proxy || "";
|
|
2165
|
+
this.mode = options.mode || "direct";
|
|
2164
2166
|
}
|
|
2165
2167
|
/**
|
|
2166
2168
|
* Build an HttpsProxyAgent when `proxy` option is set, else undefined.
|
|
@@ -2180,6 +2182,9 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2180
2182
|
async connect() {
|
|
2181
2183
|
if (this._destroyed) throw new Error("Client has been destroyed. Create a new instance.");
|
|
2182
2184
|
this.intentionalClose = false;
|
|
2185
|
+
if (this.mode === "relayed") {
|
|
2186
|
+
return this._connectRelayed();
|
|
2187
|
+
}
|
|
2183
2188
|
let ttwid = this._presetSessionId;
|
|
2184
2189
|
let roomId = this._presetRoomId;
|
|
2185
2190
|
let clusterRegion = "";
|
|
@@ -2635,6 +2640,73 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2635
2640
|
this.heartbeatTimer = null;
|
|
2636
2641
|
}
|
|
2637
2642
|
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Relayed-mode connect. Opens a plain WebSocket to TikTools' managed relay
|
|
2645
|
+
* endpoint. Events arrive pre-decoded as `{event, data}` JSON envelopes.
|
|
2646
|
+
* The SDK re-emits them on the same event names as Direct mode so user
|
|
2647
|
+
* code is identical regardless of mode.
|
|
2648
|
+
*/
|
|
2649
|
+
async _connectRelayed() {
|
|
2650
|
+
const host = this.signServerUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
|
|
2651
|
+
const wsUrl = `${host}/?uniqueId=${encodeURIComponent(this.uniqueId)}&apiKey=${encodeURIComponent(this.apiKey)}`;
|
|
2652
|
+
if (this.debug) console.log(`[TikTokLive] Relayed mode: ${wsUrl.replace(this.apiKey, "***")}`);
|
|
2653
|
+
return new Promise((resolve, reject) => {
|
|
2654
|
+
this.ws = new import_ws.default(wsUrl);
|
|
2655
|
+
let firstOpen = true;
|
|
2656
|
+
this.ws.on("open", () => {
|
|
2657
|
+
this._connected = true;
|
|
2658
|
+
this.reconnectAttempts = 0;
|
|
2659
|
+
if (firstOpen) {
|
|
2660
|
+
firstOpen = false;
|
|
2661
|
+
resolve();
|
|
2662
|
+
}
|
|
2663
|
+
this.emit("connected");
|
|
2664
|
+
});
|
|
2665
|
+
this.ws.on("message", (raw) => {
|
|
2666
|
+
try {
|
|
2667
|
+
const msg = JSON.parse(raw.toString());
|
|
2668
|
+
if (typeof msg !== "object" || !msg) return;
|
|
2669
|
+
const evName = msg.event;
|
|
2670
|
+
const evData = msg.data ?? msg;
|
|
2671
|
+
if (!evName) return;
|
|
2672
|
+
if (evName === "connected" || evName === "_journal" || evName === "ping" || evName === "pong") return;
|
|
2673
|
+
if (evName === "roomInfo") {
|
|
2674
|
+
const ri = {
|
|
2675
|
+
roomId: evData.roomId || "",
|
|
2676
|
+
wsHost: evData.wsHost || "",
|
|
2677
|
+
clusterRegion: evData.clusterRegion || "",
|
|
2678
|
+
connectedAt: evData.connectedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
2679
|
+
};
|
|
2680
|
+
this.emit("roomInfo", ri);
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
this.emit(evName, evData);
|
|
2684
|
+
this.emit("event", evData);
|
|
2685
|
+
} catch {
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
this.ws.on("close", (code, reason) => {
|
|
2689
|
+
this._connected = false;
|
|
2690
|
+
const reasonStr = reason?.toString() || "";
|
|
2691
|
+
this.emit("disconnected", code, reasonStr);
|
|
2692
|
+
if (!this.intentionalClose && this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
2693
|
+
this.reconnectAttempts++;
|
|
2694
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
2695
|
+
if (this.debug) console.log(`[TikTokLive] Relayed reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
2696
|
+
setTimeout(() => {
|
|
2697
|
+
this._connectRelayed().catch(() => {
|
|
2698
|
+
});
|
|
2699
|
+
}, delay);
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
this.ws.on("error", (err) => {
|
|
2703
|
+
if (firstOpen) {
|
|
2704
|
+
firstOpen = false;
|
|
2705
|
+
reject(err);
|
|
2706
|
+
} else this.emit("error", err);
|
|
2707
|
+
});
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2638
2710
|
};
|
|
2639
2711
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2640
2712
|
0 && (module.exports = {
|