@tiktool/live 2.7.1 → 2.8.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 +426 -235
- package/dist/index.d.mts +75 -4
- package/dist/index.d.ts +75 -4
- package/dist/index.js +272 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +272 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,235 +1,426 @@
|
|
|
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
|
-
## Events
|
|
69
|
-
|
|
70
|
-
### Listening
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
live.on('chat', (event) => {
|
|
74
|
-
event.user.uniqueId // string
|
|
75
|
-
event.comment // string
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
live.on('event', (event) => {
|
|
79
|
-
console.log(event.type, event);
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Reference
|
|
84
|
-
|
|
85
|
-
| Event | Type | Description | Fields |
|
|
86
|
-
|-------|------|-------------|--------|
|
|
87
|
-
| `chat` | `ChatEvent` | Chat message | `user`, `comment` |
|
|
88
|
-
| `member` | `MemberEvent` | User joined | `user`, `action` |
|
|
89
|
-
| `like` | `LikeEvent` | User liked | `user`, `likeCount`, `totalLikes` |
|
|
90
|
-
| `gift` | `GiftEvent` | Gift sent | `user`, `giftName`, `diamondCount`, `repeatCount`, `combo` |
|
|
91
|
-
| `social` | `SocialEvent` | Follow / Share | `user`, `action` |
|
|
92
|
-
| `roomUserSeq` | `RoomUserSeqEvent` | Viewer count | `viewerCount`, `totalViewers` |
|
|
93
|
-
| `battle` | `BattleEvent` |
|
|
94
|
-
| `battleArmies` | `BattleArmiesEvent` |
|
|
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
|
-
Full
|
|
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
|
+
## Events
|
|
69
|
+
|
|
70
|
+
### Listening
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
live.on('chat', (event) => {
|
|
74
|
+
event.user.uniqueId // string
|
|
75
|
+
event.comment // string
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
live.on('event', (event) => {
|
|
79
|
+
console.log(event.type, event);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Reference
|
|
84
|
+
|
|
85
|
+
| Event | Type | Description | Fields |
|
|
86
|
+
|-------|------|-------------|--------|
|
|
87
|
+
| `chat` | `ChatEvent` | Chat message (with inline emotes) | `user`, `comment`, `emotes` |
|
|
88
|
+
| `member` | `MemberEvent` | User joined | `user`, `action` |
|
|
89
|
+
| `like` | `LikeEvent` | User liked | `user`, `likeCount`, `totalLikes` |
|
|
90
|
+
| `gift` | `GiftEvent` | Gift sent | `user`, `giftId`, `giftName`, `diamondCount`, `repeatCount`, `combo`, `groupId` |
|
|
91
|
+
| `social` | `SocialEvent` | Follow / Share | `user`, `action` |
|
|
92
|
+
| `roomUserSeq` | `RoomUserSeqEvent` | Viewer count | `viewerCount`, `totalViewers` |
|
|
93
|
+
| `battle` | `BattleEvent` | PK Battle start / status | `battleId`, `status`, `battleDuration`, `teams` |
|
|
94
|
+
| `battleArmies` | `BattleArmiesEvent` | **PK score update + per-host MVP breakdown** | `battleId`, `teams`, `hosts[]`, `secsRemaining`, `serverTsMs`, `matchId`, `sessionId` |
|
|
95
|
+
| `battleItemCard` | `BattleItemCardEvent` | **x2/x3 boosters, gloves, mist, thunder, extra-time, match-guide** | `effect`, `multiplier`, `senderUserId`, `iconUrl`, `iconKey`, `accentColor`, … |
|
|
96
|
+
| `subscribe` | `SubscribeEvent` | New subscriber | `user`, `subMonth` |
|
|
97
|
+
| `emoteChat` | `EmoteChatEvent` | Emote in chat | `user`, `emoteId`, `emoteUrl` |
|
|
98
|
+
| `envelope` | `EnvelopeEvent` | Treasure chest | `diamondCount` |
|
|
99
|
+
| `question` | `QuestionEvent` | Q&A question | `user`, `questionText` |
|
|
100
|
+
| `control` | `ControlEvent` | Stream control | `action` (3 = ended) |
|
|
101
|
+
| `room` | `RoomEvent` | Room status | `status` |
|
|
102
|
+
| `liveIntro` | `LiveIntroEvent` | Stream intro | `roomId`, `title` |
|
|
103
|
+
| `rankUpdate` | `RankUpdateEvent` | Hourly / weekly rank | `rankType`, `rankList` |
|
|
104
|
+
| `linkMic` | `LinkMicEvent` | Link Mic guest action | `action`, `users` |
|
|
105
|
+
| `unknown` | `UnknownEvent` | Unrecognized | `method` |
|
|
106
|
+
|
|
107
|
+
### Connection Events
|
|
108
|
+
|
|
109
|
+
| Event | Callback | Description |
|
|
110
|
+
|-------|----------|-------------|
|
|
111
|
+
| `connected` | `() => void` | Connected to stream |
|
|
112
|
+
| `disconnected` | `(code, reason) => void` | Disconnected |
|
|
113
|
+
| `roomInfo` | `(info: RoomInfo) => void` | Room info |
|
|
114
|
+
| `error` | `(error: Error) => void` | Error |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🥊 PK Battles
|
|
119
|
+
|
|
120
|
+
This SDK fully parses TikTok PK (Player-vs-Killer) battle protobufs, including
|
|
121
|
+
**multi-guest** matches with up to 4 hosts per side and per-gifter MVP scores.
|
|
122
|
+
|
|
123
|
+
### `battleArmies` — score updates during a PK
|
|
124
|
+
|
|
125
|
+
Emitted every few seconds while a PK is active. The new `hosts[]` array gives
|
|
126
|
+
you the **per-host breakdown** with each gifter's contribution sorted **MVP
|
|
127
|
+
first** (highest score → lowest):
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
live.on('battleArmies', (e) => {
|
|
131
|
+
console.log(`PK ${e.battleId} — ${e.secsRemaining}s remaining`);
|
|
132
|
+
|
|
133
|
+
for (const host of e.hosts ?? []) {
|
|
134
|
+
console.log(` Host ${host.hostUserId} — total ${host.teamTotalScore}`);
|
|
135
|
+
|
|
136
|
+
// contributors[0] = MVP (highest gifter)
|
|
137
|
+
for (const [i, c] of host.contributors.slice(0, 3).entries()) {
|
|
138
|
+
console.log(` #${i + 1} ${c.nickname || c.userId}: ${c.score}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Per-host fields:**
|
|
145
|
+
|
|
146
|
+
| Field | Type | Description |
|
|
147
|
+
|-------|------|-------------|
|
|
148
|
+
| `hostUserId` | `string` | TikTok userId of this side's host |
|
|
149
|
+
| `teamTotalScore` | `number` | Total diamonds for this side |
|
|
150
|
+
| `teamIdx` | `number` | Side index (0 = left, 1 = right, …) |
|
|
151
|
+
| `contributors[]` | `BattleContributor[]` | Per-gifter breakdown, **sorted MVP first** |
|
|
152
|
+
|
|
153
|
+
**Per-contributor fields:**
|
|
154
|
+
|
|
155
|
+
| Field | Type | Description |
|
|
156
|
+
|-------|------|-------------|
|
|
157
|
+
| `userId` | `string` | Gifter's TikTok userId |
|
|
158
|
+
| `score` | `number` | Diamond score contributed |
|
|
159
|
+
| `nickname` | `string` | Display nickname (may be empty) |
|
|
160
|
+
|
|
161
|
+
**Timer & match identity:**
|
|
162
|
+
|
|
163
|
+
| Field | Type | Description |
|
|
164
|
+
|-------|------|-------------|
|
|
165
|
+
| `battleId` | `string` | Battle session tag |
|
|
166
|
+
| `matchId` | `string` | Stable match ID across multi-round PK |
|
|
167
|
+
| `sessionId` | `string` | Per-round session ID |
|
|
168
|
+
| `secsRemaining` | `number` | Seconds left, computed from **TikTok server clock** (no VPS drift) |
|
|
169
|
+
| `serverTsMs` | `number` | TikTok server-side ms epoch at frame emit |
|
|
170
|
+
| `durationSec` | `number` | Total battle duration |
|
|
171
|
+
| `endTimeMs` | `number` | Battle end ms epoch (alias for `battleStartMs + duration*1000`) |
|
|
172
|
+
|
|
173
|
+
### `battleItemCard` — boosters / power-ups during a PK
|
|
174
|
+
|
|
175
|
+
Fired when a gifter activates a special card: x2/x3 multipliers, gloves
|
|
176
|
+
(crit), mist, thunder, extra-time, or match-guide. Includes the **card art**
|
|
177
|
+
URL straight from TikTok's CDN — drop it into an OBS overlay as-is.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
live.on('battleItemCard', (e) => {
|
|
181
|
+
console.log(`${e.senderNickname} activated ${e.effect}`);
|
|
182
|
+
|
|
183
|
+
if (e.multiplier > 0) {
|
|
184
|
+
console.log(` x${e.multiplier} multiplier for ${e.durationSec}s`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Use straight in an <img> tag — TikTok CDN
|
|
188
|
+
console.log(` icon: ${e.iconUrl}`);
|
|
189
|
+
console.log(` accent: ${e.accentColor}`);
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Fields:**
|
|
194
|
+
|
|
195
|
+
| Field | Type | Description |
|
|
196
|
+
|-------|------|-------------|
|
|
197
|
+
| `battleId` | `string` | The PK this card belongs to |
|
|
198
|
+
| `cardType` | `number` | 2=gloves, 3=mist, 4=match_guide, 11=x3, … |
|
|
199
|
+
| `effect` | `string` | `'gloves'` \| `'mist'` \| `'booster_x2'` \| `'booster_x3'` \| `'match_guide'` \| `'thunder'` \| `'extra_time'` \| raw key |
|
|
200
|
+
| `effectKey` | `string` | Raw TikTok resource key |
|
|
201
|
+
| `multiplier` | `number` | 2 or 3 for boosters, else 0 |
|
|
202
|
+
| `senderUserId` | `string` | Gifter who activated it |
|
|
203
|
+
| `senderNickname` | `string` | Display name of the sender |
|
|
204
|
+
| `senderUniqueId` | `string` | Username (`@handle`) of the sender |
|
|
205
|
+
| `senderAvatarUrl` | `string` | Sender's avatar (CDN URL) |
|
|
206
|
+
| `activatedAtSec` | `number` | Unix seconds when activated |
|
|
207
|
+
| `durationSec` | `number` | Active duration |
|
|
208
|
+
| `endsAtSec` | `number` | Unix seconds when it ends |
|
|
209
|
+
| `commentTemplate` | `string` | e.g. `"{0:user} sent 1 magic mist"` |
|
|
210
|
+
| `iconUrl` | `string` | Full TikTok CDN URL for the card art (webp/jpeg) |
|
|
211
|
+
| `iconKey` | `string` | Short id, e.g. `card_mist_v3`, `card_crit_v3`, `top3_buffer` |
|
|
212
|
+
| `accentColor` | `string` | Hex color, e.g. `#BCD9E0` (mist blue), `#E0D4BC` (gloves tan) |
|
|
213
|
+
|
|
214
|
+
### `battle` — PK lifecycle events
|
|
215
|
+
|
|
216
|
+
Fired on start / status change / end. Use for PK ON/OFF banners.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
live.on('battle', (e) => {
|
|
220
|
+
// status: 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING
|
|
221
|
+
if (e.status === 1) console.log('PK started');
|
|
222
|
+
if (e.status === 3) console.log('PK ended');
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## API Reference
|
|
229
|
+
|
|
230
|
+
### `new TikTokLive(options)`
|
|
231
|
+
|
|
232
|
+
| Option | Type | Default | Description |
|
|
233
|
+
|--------|------|---------|-------------|
|
|
234
|
+
| `uniqueId` | `string` | — | TikTok username (without @) |
|
|
235
|
+
| `apiKey` | `string` | — | **Required.** API key from [tik.tools](https://tik.tools) |
|
|
236
|
+
| `signServerUrl` | `string` | `https://api.tik.tools` | Sign server URL |
|
|
237
|
+
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
238
|
+
| `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
|
|
239
|
+
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
240
|
+
| `roomId` | `string` | — | Pre-resolved room ID (skips page fetch when paired with `sessionId`) |
|
|
241
|
+
| `sessionId` | `string` | — | Pre-resolved `ttwid` cookie (skips page fetch when paired with `roomId`) |
|
|
242
|
+
| `debug` | `boolean` | `false` | Debug logging |
|
|
243
|
+
|
|
244
|
+
### Methods
|
|
245
|
+
|
|
246
|
+
| Method | Returns | Description |
|
|
247
|
+
|--------|---------|-------------|
|
|
248
|
+
| `connect()` | `Promise<void>` | Connect to livestream |
|
|
249
|
+
| `disconnect()` | `void` | Disconnect |
|
|
250
|
+
| `getStreamUrl()` | `Promise<StreamInfo>` | Get FLV/HLS playback URLs for the live video |
|
|
251
|
+
| `connected` | `boolean` | Connection status |
|
|
252
|
+
| `eventCount` | `number` | Total events received |
|
|
253
|
+
| `roomId` | `string` | Current room ID |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Rate Limits
|
|
258
|
+
|
|
259
|
+
All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
|
|
260
|
+
|
|
261
|
+
| Tier | Price (USD / week) | WS Connections | Rate limit |
|
|
262
|
+
|------|--------------------|----------------|------------|
|
|
263
|
+
| **Free** | $0 | 1 | 5 req/min |
|
|
264
|
+
| **Basic** | $9 | 25 | 30 req/min |
|
|
265
|
+
| **Pro** | $19 | 100 | 120 req/min |
|
|
266
|
+
| **Ultra** | $58 | 250 | 600 req/min |
|
|
267
|
+
|
|
268
|
+
The SDK calls the sign server **once per connection**, then stays connected
|
|
269
|
+
via WebSocket. A free key is sufficient for most use cases.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Examples
|
|
274
|
+
|
|
275
|
+
### Chat Bot
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { TikTokLive } from '@tiktool/live';
|
|
279
|
+
|
|
280
|
+
const live = new TikTokLive({
|
|
281
|
+
uniqueId: 'streamer_name',
|
|
282
|
+
apiKey: 'YOUR_API_KEY',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
live.on('chat', (e) => {
|
|
286
|
+
if (e.comment.toLowerCase() === '!hello') {
|
|
287
|
+
console.log(`Hello, ${e.user.nickname}!`);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
live.on('gift', (e) => {
|
|
292
|
+
if (e.repeatEnd) {
|
|
293
|
+
console.log(`${e.user.uniqueId} sent ${e.repeatCount}x ${e.giftName} (${e.diamondCount * e.repeatCount} diamonds)`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
await live.connect();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Live PK Battle Dashboard
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { TikTokLive } from '@tiktool/live';
|
|
304
|
+
|
|
305
|
+
const live = new TikTokLive({ uniqueId: 'streamer_name', apiKey: 'YOUR_API_KEY' });
|
|
306
|
+
|
|
307
|
+
live.on('battle', e => {
|
|
308
|
+
if (e.status === 1) console.log('🥊 PK STARTED');
|
|
309
|
+
if (e.status === 3) console.log('🏁 PK ENDED');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
live.on('battleArmies', e => {
|
|
313
|
+
console.log(`\n⏱ ${e.secsRemaining}s remaining`);
|
|
314
|
+
for (const host of e.hosts ?? []) {
|
|
315
|
+
const top = host.contributors[0];
|
|
316
|
+
console.log(` ${host.hostUserId}: ${host.teamTotalScore} ` +
|
|
317
|
+
`(MVP: ${top?.nickname || top?.userId || '—'} ${top?.score ?? 0})`);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
live.on('battleItemCard', e => {
|
|
322
|
+
const tag = e.multiplier > 0 ? `x${e.multiplier} BOOSTER` : e.effect.toUpperCase();
|
|
323
|
+
console.log(`💥 ${e.senderNickname} → ${tag} (${e.durationSec}s)`);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await live.connect();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### OBS Overlay
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { TikTokLive } from '@tiktool/live';
|
|
333
|
+
import { WebSocketServer } from 'ws';
|
|
334
|
+
|
|
335
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
336
|
+
const live = new TikTokLive({
|
|
337
|
+
uniqueId: 'streamer_name',
|
|
338
|
+
apiKey: 'YOUR_API_KEY',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
live.on('event', (event) => {
|
|
342
|
+
for (const client of wss.clients) {
|
|
343
|
+
client.send(JSON.stringify(event));
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
await live.connect();
|
|
348
|
+
console.log('Forwarding events to ws://localhost:8080');
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### HLS / FLV Playback URL
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const info = await live.getStreamUrl();
|
|
355
|
+
console.log('HLS:', info.hlsPullUrl);
|
|
356
|
+
console.log('FLV:', info.flvPullUrl);
|
|
357
|
+
// Or pick a specific quality
|
|
358
|
+
console.log('FULL_HD1 HLS:', info.streamUrls.FULL_HD1?.hls);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## TypeScript
|
|
364
|
+
|
|
365
|
+
Full TypeScript support with type inference:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import {
|
|
369
|
+
TikTokLive,
|
|
370
|
+
ChatEvent,
|
|
371
|
+
GiftEvent,
|
|
372
|
+
BattleArmiesEvent,
|
|
373
|
+
BattleItemCardEvent,
|
|
374
|
+
BattleHost,
|
|
375
|
+
BattleContributor,
|
|
376
|
+
} from '@tiktool/live';
|
|
377
|
+
|
|
378
|
+
const live = new TikTokLive({
|
|
379
|
+
uniqueId: 'username',
|
|
380
|
+
apiKey: 'YOUR_API_KEY',
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
live.on('chat', (event: ChatEvent) => {
|
|
384
|
+
const username: string = event.user.uniqueId;
|
|
385
|
+
const message: string = event.comment;
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
live.on('gift', (event: GiftEvent) => {
|
|
389
|
+
const diamonds: number = event.diamondCount;
|
|
390
|
+
const isCombo: boolean = event.combo;
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
live.on('battleArmies', (event: BattleArmiesEvent) => {
|
|
394
|
+
for (const host of event.hosts ?? []) {
|
|
395
|
+
const mvp: BattleContributor | undefined = host.contributors[0];
|
|
396
|
+
if (mvp) console.log(mvp.nickname, mvp.score);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
live.on('battleItemCard', (event: BattleItemCardEvent) => {
|
|
401
|
+
if (event.multiplier > 0) {
|
|
402
|
+
console.log(`x${event.multiplier} buff active for ${event.durationSec}s`);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Changelog
|
|
410
|
+
|
|
411
|
+
### 2.8.0 (2026-05-19)
|
|
412
|
+
|
|
413
|
+
- **NEW** `battleItemCard` event — x2/x3 boosters, gloves, mist, thunder,
|
|
414
|
+
extra-time, match-guide. Includes `iconUrl`, `iconKey`, `accentColor` for
|
|
415
|
+
drop-in overlay use.
|
|
416
|
+
- **NEW** `BattleArmiesEvent.hosts[]` — multi-guest PK breakdown with per-host
|
|
417
|
+
`contributors[]` sorted **MVP first**.
|
|
418
|
+
- **NEW** `matchId`, `sessionId`, `serverTsMs`, `sessionTag`, `secsRemaining`
|
|
419
|
+
fields on `BattleArmiesEvent`.
|
|
420
|
+
- Verified live against multi-guest battles 2026-05-19.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## License
|
|
425
|
+
|
|
426
|
+
MIT © [tiktool](https://tik.tools)
|