@tiktool/live 2.7.1 → 2.9.0
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 +505 -235
- package/dist/index.d.mts +93 -4
- package/dist/index.d.ts +93 -4
- package/dist/index.js +1541 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1549 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,235 +1,505 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# @tiktool/live
|
|
4
|
-
|
|
5
|
-
### Connect to any TikTok LIVE stream in 4 lines of code.
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/@tiktool/live)
|
|
8
|
-
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
[](https://nodejs.org)
|
|
10
|
-
[](https://www.typescriptlang.org)
|
|
11
|
-
|
|
12
|
-
Real-time chat, gifts, viewers, battles,
|
|
13
|
-
|
|
14
|
-
[Quick Start](#-quick-start) · [Events](#-events) · [API](#-api-reference) · [Rate Limits](#-rate-limits) · [Get API Key](https://tik.tools)
|
|
15
|
-
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## ⚡ Quick Start
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install @tiktool/live
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Get your free API key at [tik.tools](https://tik.tools)
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { TikTokLive } from '@tiktool/live';
|
|
30
|
-
|
|
31
|
-
const live = new TikTokLive({
|
|
32
|
-
uniqueId: 'tv_asahi_news',
|
|
33
|
-
apiKey: 'YOUR_API_KEY',
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
|
|
37
|
-
live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName} (${e.diamondCount} diamonds)`));
|
|
38
|
-
live.on('member', e => console.log(`${e.user.uniqueId} joined`));
|
|
39
|
-
live.on('roomUserSeq', e => console.log(`Viewers: ${e.viewerCount}`));
|
|
40
|
-
|
|
41
|
-
await live.connect();
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## How It Works
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
Your App tik.tools TikTok
|
|
50
|
-
+-----------+ +--------------+ +--------------+
|
|
51
|
-
| -+-- sign_url --> Signs URL | | |
|
|
52
|
-
| Your <-+-- X-Bogus --| with params | | TikTok |
|
|
53
|
-
| Code | | | | WebSocket |
|
|
54
|
-
| -+------------ Connect directly ---------->| Server |
|
|
55
|
-
| <-+------------ Live events (protobuf) <---| |
|
|
56
|
-
+-----------+ +--------------+ +--------------+
|
|
57
|
-
^ Only interaction ^ Direct from
|
|
58
|
-
with our server YOUR IP
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
- Your app connects directly to TikTok from your IP address
|
|
62
|
-
- The sign server only generates cryptographic signatures (requires API key)
|
|
63
|
-
- TikTok never sees the sign server
|
|
64
|
-
- Built-in protobuf parser,
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @tiktool/live
|
|
4
|
+
|
|
5
|
+
### Connect to any TikTok LIVE stream in 4 lines of code.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@tiktool/live)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
|
|
12
|
+
Real-time chat, gifts, viewers, **PK battles with MVP breakdown**, **x2/x3 boosters**, gloves, mist, match-guide & 20+ event types from any TikTok livestream.
|
|
13
|
+
|
|
14
|
+
[Quick Start](#-quick-start) · [Events](#-events) · [PK Battles](#-pk-battles) · [API](#-api-reference) · [Rate Limits](#-rate-limits) · [Get API Key](https://tik.tools)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ⚡ Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @tiktool/live
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Get your free API key at [tik.tools](https://tik.tools)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { TikTokLive } from '@tiktool/live';
|
|
30
|
+
|
|
31
|
+
const live = new TikTokLive({
|
|
32
|
+
uniqueId: 'tv_asahi_news',
|
|
33
|
+
apiKey: 'YOUR_API_KEY',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
|
|
37
|
+
live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName} (${e.diamondCount} diamonds)`));
|
|
38
|
+
live.on('member', e => console.log(`${e.user.uniqueId} joined`));
|
|
39
|
+
live.on('roomUserSeq', e => console.log(`Viewers: ${e.viewerCount}`));
|
|
40
|
+
|
|
41
|
+
await live.connect();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## How It Works
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Your App tik.tools TikTok
|
|
50
|
+
+-----------+ +--------------+ +--------------+
|
|
51
|
+
| -+-- sign_url --> Signs URL | | |
|
|
52
|
+
| Your <-+-- X-Bogus --| with params | | TikTok |
|
|
53
|
+
| Code | | | | WebSocket |
|
|
54
|
+
| -+------------ Connect directly ---------->| Server |
|
|
55
|
+
| <-+------------ Live events (protobuf) <---| |
|
|
56
|
+
+-----------+ +--------------+ +--------------+
|
|
57
|
+
^ Only interaction ^ Direct from
|
|
58
|
+
with our server YOUR IP
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Your app connects directly to TikTok from your IP address
|
|
62
|
+
- The sign server only generates cryptographic signatures (requires API key)
|
|
63
|
+
- TikTok never sees the sign server
|
|
64
|
+
- Built-in protobuf parser, **zero external dependencies** (only `ws` for WebSocket)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Connection Modes
|
|
69
|
+
|
|
70
|
+
There are **two ways** to receive TikTok LIVE events from `tik.tools`. Pick based on scale and IP-exposure tolerance.
|
|
71
|
+
|
|
72
|
+
### Mode 1 — Direct (default, this SDK)
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Your App ──signs URL via──▶ tik.tools ──returns signed URL──▶ Your App
|
|
76
|
+
Your App ───────────────── WS direct to TikTok ───────────────▶ TikTok
|
|
77
|
+
Your App ◀────────────────── live events ────────────────────── TikTok
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **TikTok sees your IP.** Cheapest on our API quota (only the `ws_credentials` call is charged per connection).
|
|
81
|
+
- Good for **up to ~30 concurrent streams** from one container.
|
|
82
|
+
- Above that, TikTok's per-IP rate limit kicks in → frequent `code=1006` closes + reconnect loops.
|
|
83
|
+
- **Solution at scale**: enable the `proxy` option (below) to dial through your own residential pool.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const live = new TikTokLive({
|
|
87
|
+
uniqueId: 'creator_username',
|
|
88
|
+
apiKey: 'YOUR_TIKTOOLS_KEY',
|
|
89
|
+
// Optional: route WS + HTTP through your own proxy (recommended for 30+ streams)
|
|
90
|
+
proxy: 'http://USER:PASS@p.webshare.io:80',
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Install `https-proxy-agent` if using `proxy`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install https-proxy-agent
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Mode 2 — Relayed (via our proxy pool, no SDK install)
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Your App ◀──── wss://api.tik.tools/?uniqueId=X&apiKey=Y ──── tik.tools
|
|
104
|
+
│
|
|
105
|
+
│ WS via
|
|
106
|
+
│ rotated proxies
|
|
107
|
+
▼
|
|
108
|
+
TikTok
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **TikTok sees our IPs** (rotated across hundreds of Webshare + residential proxies).
|
|
112
|
+
- We absorb every per-IP rate-limit hit. Survives any block automatically.
|
|
113
|
+
- Best for **30+ concurrent streams**, low-budget setups, or anyone who doesn't want their own IP fingerprinted by TikTok.
|
|
114
|
+
- Costs slightly more per stream-hour (events stream through us, not signed-once direct).
|
|
115
|
+
- No SDK install — plain `WebSocket`. Events arrive pre-parsed as JSON.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import WebSocket from 'ws';
|
|
119
|
+
|
|
120
|
+
const ws = new WebSocket(
|
|
121
|
+
`wss://api.tik.tools/?uniqueId=creator_username&apiKey=YOUR_KEY`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
ws.on('message', raw => {
|
|
125
|
+
const msg = JSON.parse(raw.toString());
|
|
126
|
+
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, msg.data.diamondCount);
|
|
128
|
+
if (msg.event === 'battleArmies') console.log(msg.data);
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Event payloads are identical to Direct-mode `client.on('chat', …)` etc. — only the wrapping changes (`{event, data}` envelope vs the SDK's separate emit).
|
|
133
|
+
|
|
134
|
+
### Side-by-side
|
|
135
|
+
|
|
136
|
+
| Aspect | Direct (Mode 1) | Relayed (Mode 2) |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| TikTok sees | Your IP | Our rotated proxy pool |
|
|
139
|
+
| Setup | `new TikTokLive(...)` | `new WebSocket(url)` |
|
|
140
|
+
| Scale limit | ~30 streams / container IP | Thousands |
|
|
141
|
+
| Per-event cost | 1× `ws_credentials` per (re)connect | 1× per event |
|
|
142
|
+
| TikTok rate-limit risk | Yes (mitigated with `proxy`) | None — we handle it |
|
|
143
|
+
| Best for | Hobby / single-creator | Production at scale |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Events
|
|
148
|
+
|
|
149
|
+
### Listening
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
live.on('chat', (event) => {
|
|
153
|
+
event.user.uniqueId // string
|
|
154
|
+
event.comment // string
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
live.on('event', (event) => {
|
|
158
|
+
console.log(event.type, event);
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Reference
|
|
163
|
+
|
|
164
|
+
| Event | Type | Description | Fields |
|
|
165
|
+
|-------|------|-------------|--------|
|
|
166
|
+
| `chat` | `ChatEvent` | Chat message (with inline emotes) | `user`, `comment`, `emotes` |
|
|
167
|
+
| `member` | `MemberEvent` | User joined | `user`, `action` |
|
|
168
|
+
| `like` | `LikeEvent` | User liked | `user`, `likeCount`, `totalLikes` |
|
|
169
|
+
| `gift` | `GiftEvent` | Gift sent | `user`, `giftId`, `giftName`, `diamondCount`, `repeatCount`, `combo`, `groupId` |
|
|
170
|
+
| `social` | `SocialEvent` | Follow / Share | `user`, `action` |
|
|
171
|
+
| `roomUserSeq` | `RoomUserSeqEvent` | Viewer count | `viewerCount`, `totalViewers` |
|
|
172
|
+
| `battle` | `BattleEvent` | PK Battle start / status | `battleId`, `status`, `battleDuration`, `teams` |
|
|
173
|
+
| `battleArmies` | `BattleArmiesEvent` | **PK score update + per-host MVP breakdown** | `battleId`, `teams`, `hosts[]`, `secsRemaining`, `serverTsMs`, `matchId`, `sessionId` |
|
|
174
|
+
| `battleItemCard` | `BattleItemCardEvent` | **x2/x3 boosters, gloves, mist, thunder, extra-time, match-guide** | `effect`, `multiplier`, `senderUserId`, `iconUrl`, `iconKey`, `accentColor`, … |
|
|
175
|
+
| `subscribe` | `SubscribeEvent` | New subscriber | `user`, `subMonth` |
|
|
176
|
+
| `emoteChat` | `EmoteChatEvent` | Emote in chat | `user`, `emoteId`, `emoteUrl` |
|
|
177
|
+
| `envelope` | `EnvelopeEvent` | Treasure chest | `diamondCount` |
|
|
178
|
+
| `question` | `QuestionEvent` | Q&A question | `user`, `questionText` |
|
|
179
|
+
| `control` | `ControlEvent` | Stream control | `action` (3 = ended) |
|
|
180
|
+
| `room` | `RoomEvent` | Room status | `status` |
|
|
181
|
+
| `liveIntro` | `LiveIntroEvent` | Stream intro | `roomId`, `title` |
|
|
182
|
+
| `rankUpdate` | `RankUpdateEvent` | Hourly / weekly rank | `rankType`, `rankList` |
|
|
183
|
+
| `linkMic` | `LinkMicEvent` | Link Mic guest action | `action`, `users` |
|
|
184
|
+
| `unknown` | `UnknownEvent` | Unrecognized | `method` |
|
|
185
|
+
|
|
186
|
+
### Connection Events
|
|
187
|
+
|
|
188
|
+
| Event | Callback | Description |
|
|
189
|
+
|-------|----------|-------------|
|
|
190
|
+
| `connected` | `() => void` | Connected to stream |
|
|
191
|
+
| `disconnected` | `(code, reason) => void` | Disconnected |
|
|
192
|
+
| `roomInfo` | `(info: RoomInfo) => void` | Room info |
|
|
193
|
+
| `error` | `(error: Error) => void` | Error |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🥊 PK Battles
|
|
198
|
+
|
|
199
|
+
This SDK fully parses TikTok PK (Player-vs-Killer) battle protobufs, including
|
|
200
|
+
**multi-guest** matches with up to 4 hosts per side and per-gifter MVP scores.
|
|
201
|
+
|
|
202
|
+
### `battleArmies` — score updates during a PK
|
|
203
|
+
|
|
204
|
+
Emitted every few seconds while a PK is active. The new `hosts[]` array gives
|
|
205
|
+
you the **per-host breakdown** with each gifter's contribution sorted **MVP
|
|
206
|
+
first** (highest score → lowest):
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
live.on('battleArmies', (e) => {
|
|
210
|
+
console.log(`PK ${e.battleId} — ${e.secsRemaining}s remaining`);
|
|
211
|
+
|
|
212
|
+
for (const host of e.hosts ?? []) {
|
|
213
|
+
console.log(` Host ${host.hostUserId} — total ${host.teamTotalScore}`);
|
|
214
|
+
|
|
215
|
+
// contributors[0] = MVP (highest gifter)
|
|
216
|
+
for (const [i, c] of host.contributors.slice(0, 3).entries()) {
|
|
217
|
+
console.log(` #${i + 1} ${c.nickname || c.userId}: ${c.score}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Per-host fields:**
|
|
224
|
+
|
|
225
|
+
| Field | Type | Description |
|
|
226
|
+
|-------|------|-------------|
|
|
227
|
+
| `hostUserId` | `string` | TikTok userId of this side's host |
|
|
228
|
+
| `teamTotalScore` | `number` | Total diamonds for this side |
|
|
229
|
+
| `teamIdx` | `number` | Side index (0 = left, 1 = right, …) |
|
|
230
|
+
| `contributors[]` | `BattleContributor[]` | Per-gifter breakdown, **sorted MVP first** |
|
|
231
|
+
|
|
232
|
+
**Per-contributor fields:**
|
|
233
|
+
|
|
234
|
+
| Field | Type | Description |
|
|
235
|
+
|-------|------|-------------|
|
|
236
|
+
| `userId` | `string` | Gifter's TikTok userId |
|
|
237
|
+
| `score` | `number` | Diamond score contributed |
|
|
238
|
+
| `nickname` | `string` | Display nickname (may be empty) |
|
|
239
|
+
|
|
240
|
+
**Timer & match identity:**
|
|
241
|
+
|
|
242
|
+
| Field | Type | Description |
|
|
243
|
+
|-------|------|-------------|
|
|
244
|
+
| `battleId` | `string` | Battle session tag |
|
|
245
|
+
| `matchId` | `string` | Stable match ID across multi-round PK |
|
|
246
|
+
| `sessionId` | `string` | Per-round session ID |
|
|
247
|
+
| `secsRemaining` | `number` | Seconds left, computed from **TikTok server clock** (no VPS drift) |
|
|
248
|
+
| `serverTsMs` | `number` | TikTok server-side ms epoch at frame emit |
|
|
249
|
+
| `durationSec` | `number` | Total battle duration |
|
|
250
|
+
| `endTimeMs` | `number` | Battle end ms epoch (alias for `battleStartMs + duration*1000`) |
|
|
251
|
+
|
|
252
|
+
### `battleItemCard` — boosters / power-ups during a PK
|
|
253
|
+
|
|
254
|
+
Fired when a gifter activates a special card: x2/x3 multipliers, gloves
|
|
255
|
+
(crit), mist, thunder, extra-time, or match-guide. Includes the **card art**
|
|
256
|
+
URL straight from TikTok's CDN — drop it into an OBS overlay as-is.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
live.on('battleItemCard', (e) => {
|
|
260
|
+
console.log(`${e.senderNickname} activated ${e.effect}`);
|
|
261
|
+
|
|
262
|
+
if (e.multiplier > 0) {
|
|
263
|
+
console.log(` x${e.multiplier} multiplier for ${e.durationSec}s`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Use straight in an <img> tag — TikTok CDN
|
|
267
|
+
console.log(` icon: ${e.iconUrl}`);
|
|
268
|
+
console.log(` accent: ${e.accentColor}`);
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Fields:**
|
|
273
|
+
|
|
274
|
+
| Field | Type | Description |
|
|
275
|
+
|-------|------|-------------|
|
|
276
|
+
| `battleId` | `string` | The PK this card belongs to |
|
|
277
|
+
| `cardType` | `number` | 2=gloves, 3=mist, 4=match_guide, 11=x3, … |
|
|
278
|
+
| `effect` | `string` | `'gloves'` \| `'mist'` \| `'booster_x2'` \| `'booster_x3'` \| `'match_guide'` \| `'thunder'` \| `'extra_time'` \| raw key |
|
|
279
|
+
| `effectKey` | `string` | Raw TikTok resource key |
|
|
280
|
+
| `multiplier` | `number` | 2 or 3 for boosters, else 0 |
|
|
281
|
+
| `senderUserId` | `string` | Gifter who activated it |
|
|
282
|
+
| `senderNickname` | `string` | Display name of the sender |
|
|
283
|
+
| `senderUniqueId` | `string` | Username (`@handle`) of the sender |
|
|
284
|
+
| `senderAvatarUrl` | `string` | Sender's avatar (CDN URL) |
|
|
285
|
+
| `activatedAtSec` | `number` | Unix seconds when activated |
|
|
286
|
+
| `durationSec` | `number` | Active duration |
|
|
287
|
+
| `endsAtSec` | `number` | Unix seconds when it ends |
|
|
288
|
+
| `commentTemplate` | `string` | e.g. `"{0:user} sent 1 magic mist"` |
|
|
289
|
+
| `iconUrl` | `string` | Full TikTok CDN URL for the card art (webp/jpeg) |
|
|
290
|
+
| `iconKey` | `string` | Short id, e.g. `card_mist_v3`, `card_crit_v3`, `top3_buffer` |
|
|
291
|
+
| `accentColor` | `string` | Hex color, e.g. `#BCD9E0` (mist blue), `#E0D4BC` (gloves tan) |
|
|
292
|
+
|
|
293
|
+
### `battle` — PK lifecycle events
|
|
294
|
+
|
|
295
|
+
Fired on start / status change / end. Use for PK ON/OFF banners.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
live.on('battle', (e) => {
|
|
299
|
+
// status: 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING
|
|
300
|
+
if (e.status === 1) console.log('PK started');
|
|
301
|
+
if (e.status === 3) console.log('PK ended');
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## API Reference
|
|
308
|
+
|
|
309
|
+
### `new TikTokLive(options)`
|
|
310
|
+
|
|
311
|
+
| Option | Type | Default | Description |
|
|
312
|
+
|--------|------|---------|-------------|
|
|
313
|
+
| `uniqueId` | `string` | — | TikTok username (without @) |
|
|
314
|
+
| `apiKey` | `string` | — | **Required.** API key from [tik.tools](https://tik.tools) |
|
|
315
|
+
| `signServerUrl` | `string` | `https://api.tik.tools` | Sign server URL |
|
|
316
|
+
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
317
|
+
| `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
|
|
318
|
+
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
319
|
+
| `roomId` | `string` | — | Pre-resolved room ID (skips page fetch when paired with `sessionId`) |
|
|
320
|
+
| `sessionId` | `string` | — | Pre-resolved `ttwid` cookie (skips page fetch when paired with `roomId`) |
|
|
321
|
+
| `debug` | `boolean` | `false` | Debug logging |
|
|
322
|
+
|
|
323
|
+
### Methods
|
|
324
|
+
|
|
325
|
+
| Method | Returns | Description |
|
|
326
|
+
|--------|---------|-------------|
|
|
327
|
+
| `connect()` | `Promise<void>` | Connect to livestream |
|
|
328
|
+
| `disconnect()` | `void` | Disconnect |
|
|
329
|
+
| `getStreamUrl()` | `Promise<StreamInfo>` | Get FLV/HLS playback URLs for the live video |
|
|
330
|
+
| `connected` | `boolean` | Connection status |
|
|
331
|
+
| `eventCount` | `number` | Total events received |
|
|
332
|
+
| `roomId` | `string` | Current room ID |
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Rate Limits
|
|
337
|
+
|
|
338
|
+
All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
|
|
339
|
+
|
|
340
|
+
| Tier | Price (USD / week) | WS Connections | Rate limit |
|
|
341
|
+
|------|--------------------|----------------|------------|
|
|
342
|
+
| **Free** | $0 | 1 | 5 req/min |
|
|
343
|
+
| **Basic** | $9 | 25 | 30 req/min |
|
|
344
|
+
| **Pro** | $19 | 100 | 120 req/min |
|
|
345
|
+
| **Ultra** | $58 | 250 | 600 req/min |
|
|
346
|
+
|
|
347
|
+
The SDK calls the sign server **once per connection**, then stays connected
|
|
348
|
+
via WebSocket. A free key is sufficient for most use cases.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Examples
|
|
353
|
+
|
|
354
|
+
### Chat Bot
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { TikTokLive } from '@tiktool/live';
|
|
358
|
+
|
|
359
|
+
const live = new TikTokLive({
|
|
360
|
+
uniqueId: 'streamer_name',
|
|
361
|
+
apiKey: 'YOUR_API_KEY',
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
live.on('chat', (e) => {
|
|
365
|
+
if (e.comment.toLowerCase() === '!hello') {
|
|
366
|
+
console.log(`Hello, ${e.user.nickname}!`);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
live.on('gift', (e) => {
|
|
371
|
+
if (e.repeatEnd) {
|
|
372
|
+
console.log(`${e.user.uniqueId} sent ${e.repeatCount}x ${e.giftName} (${e.diamondCount * e.repeatCount} diamonds)`);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await live.connect();
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Live PK Battle Dashboard
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { TikTokLive } from '@tiktool/live';
|
|
383
|
+
|
|
384
|
+
const live = new TikTokLive({ uniqueId: 'streamer_name', apiKey: 'YOUR_API_KEY' });
|
|
385
|
+
|
|
386
|
+
live.on('battle', e => {
|
|
387
|
+
if (e.status === 1) console.log('🥊 PK STARTED');
|
|
388
|
+
if (e.status === 3) console.log('🏁 PK ENDED');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
live.on('battleArmies', e => {
|
|
392
|
+
console.log(`\n⏱ ${e.secsRemaining}s remaining`);
|
|
393
|
+
for (const host of e.hosts ?? []) {
|
|
394
|
+
const top = host.contributors[0];
|
|
395
|
+
console.log(` ${host.hostUserId}: ${host.teamTotalScore} ` +
|
|
396
|
+
`(MVP: ${top?.nickname || top?.userId || '—'} ${top?.score ?? 0})`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
live.on('battleItemCard', e => {
|
|
401
|
+
const tag = e.multiplier > 0 ? `x${e.multiplier} BOOSTER` : e.effect.toUpperCase();
|
|
402
|
+
console.log(`💥 ${e.senderNickname} → ${tag} (${e.durationSec}s)`);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
await live.connect();
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### OBS Overlay
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { TikTokLive } from '@tiktool/live';
|
|
412
|
+
import { WebSocketServer } from 'ws';
|
|
413
|
+
|
|
414
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
415
|
+
const live = new TikTokLive({
|
|
416
|
+
uniqueId: 'streamer_name',
|
|
417
|
+
apiKey: 'YOUR_API_KEY',
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
live.on('event', (event) => {
|
|
421
|
+
for (const client of wss.clients) {
|
|
422
|
+
client.send(JSON.stringify(event));
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
await live.connect();
|
|
427
|
+
console.log('Forwarding events to ws://localhost:8080');
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### HLS / FLV Playback URL
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
const info = await live.getStreamUrl();
|
|
434
|
+
console.log('HLS:', info.hlsPullUrl);
|
|
435
|
+
console.log('FLV:', info.flvPullUrl);
|
|
436
|
+
// Or pick a specific quality
|
|
437
|
+
console.log('FULL_HD1 HLS:', info.streamUrls.FULL_HD1?.hls);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## TypeScript
|
|
443
|
+
|
|
444
|
+
Full TypeScript support with type inference:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
import {
|
|
448
|
+
TikTokLive,
|
|
449
|
+
ChatEvent,
|
|
450
|
+
GiftEvent,
|
|
451
|
+
BattleArmiesEvent,
|
|
452
|
+
BattleItemCardEvent,
|
|
453
|
+
BattleHost,
|
|
454
|
+
BattleContributor,
|
|
455
|
+
} from '@tiktool/live';
|
|
456
|
+
|
|
457
|
+
const live = new TikTokLive({
|
|
458
|
+
uniqueId: 'username',
|
|
459
|
+
apiKey: 'YOUR_API_KEY',
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
live.on('chat', (event: ChatEvent) => {
|
|
463
|
+
const username: string = event.user.uniqueId;
|
|
464
|
+
const message: string = event.comment;
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
live.on('gift', (event: GiftEvent) => {
|
|
468
|
+
const diamonds: number = event.diamondCount;
|
|
469
|
+
const isCombo: boolean = event.combo;
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
live.on('battleArmies', (event: BattleArmiesEvent) => {
|
|
473
|
+
for (const host of event.hosts ?? []) {
|
|
474
|
+
const mvp: BattleContributor | undefined = host.contributors[0];
|
|
475
|
+
if (mvp) console.log(mvp.nickname, mvp.score);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
live.on('battleItemCard', (event: BattleItemCardEvent) => {
|
|
480
|
+
if (event.multiplier > 0) {
|
|
481
|
+
console.log(`x${event.multiplier} buff active for ${event.durationSec}s`);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Changelog
|
|
489
|
+
|
|
490
|
+
### 2.8.0 (2026-05-19)
|
|
491
|
+
|
|
492
|
+
- **NEW** `battleItemCard` event — x2/x3 boosters, gloves, mist, thunder,
|
|
493
|
+
extra-time, match-guide. Includes `iconUrl`, `iconKey`, `accentColor` for
|
|
494
|
+
drop-in overlay use.
|
|
495
|
+
- **NEW** `BattleArmiesEvent.hosts[]` — multi-guest PK breakdown with per-host
|
|
496
|
+
`contributors[]` sorted **MVP first**.
|
|
497
|
+
- **NEW** `matchId`, `sessionId`, `serverTsMs`, `sessionTag`, `secsRemaining`
|
|
498
|
+
fields on `BattleArmiesEvent`.
|
|
499
|
+
- Verified live against multi-guest battles 2026-05-19.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## License
|
|
504
|
+
|
|
505
|
+
MIT © [tiktool](https://tik.tools)
|