@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 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
- [![npm version](https://img.shields.io/npm/v/@tiktool/live?color=%23ff0050&label=npm&logo=npm)](https://www.npmjs.com/package/@tiktool/live)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green?logo=node.js)](https://nodejs.org)
10
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue?logo=typescript)](https://www.typescriptlang.org)
11
-
12
- Real-time chat, gifts, viewers, battles, follows & 18+ event types from any TikTok livestream.
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, no external dependencies
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` | Link Mic battle | `status` |
94
- | `battleArmies` | `BattleArmiesEvent` | Battle teams | — |
95
- | `subscribe` | `SubscribeEvent` | New subscriber | `user`, `subMonth` |
96
- | `emoteChat` | `EmoteChatEvent` | Emote in chat | `user`, `emoteId` |
97
- | `envelope` | `EnvelopeEvent` | Treasure chest | `diamondCount` |
98
- | `question` | `QuestionEvent` | Q&A question | `user`, `questionText` |
99
- | `control` | `ControlEvent` | Stream control | `action` (3 = ended) |
100
- | `room` | `RoomEvent` | Room status | `status` |
101
- | `liveIntro` | `LiveIntroEvent` | Stream intro | `title` |
102
- | `rankUpdate` | `RankUpdateEvent` | Rank update | `rankType` |
103
- | `linkMic` | `LinkMicEvent` | Link Mic | `action` |
104
- | `unknown` | `UnknownEvent` | Unrecognized | `method` |
105
-
106
- ### Connection Events
107
-
108
- | Event | Callback | Description |
109
- |-------|----------|-------------|
110
- | `connected` | `() => void` | Connected to stream |
111
- | `disconnected` | `(code, reason) => void` | Disconnected |
112
- | `roomInfo` | `(info: RoomInfo) => void` | Room info |
113
- | `error` | `(error: Error) => void` | Error |
114
-
115
- ---
116
-
117
- ## API Reference
118
-
119
- ### `new TikTokLive(options)`
120
-
121
- | Option | Type | Default | Description |
122
- |--------|------|---------|-------------|
123
- | `uniqueId` | `string` | — | TikTok username (without @) |
124
- | `apiKey` | `string` | — | **Required.** API key from [tik.tools](https://tik.tools) |
125
- | `signServerUrl` | `string` | `https://api.tik.tools` | Sign server URL |
126
- | `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
127
- | `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
128
- | `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
129
- | `debug` | `boolean` | `false` | Debug logging |
130
-
131
- ### Methods
132
-
133
- | Method | Returns | Description |
134
- |--------|---------|-------------|
135
- | `connect()` | `Promise<void>` | Connect to livestream |
136
- | `disconnect()` | `void` | Disconnect |
137
- | `connected` | `boolean` | Connection status |
138
- | `eventCount` | `number` | Total events received |
139
- | `roomId` | `string` | Current room ID |
140
-
141
- ---
142
-
143
- ## Rate Limits
144
-
145
- All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
146
-
147
- | Tier | Rate Limit | Endpoints | Price |
148
- |------|-----------|-----------|-------|
149
- | **Free** | 5/min | `sign_url`, `check_alive` | Free |
150
- | **Basic** | 30/min | + `fetch`, `room_info`, `bulk_live_check`, WS relay | Free (with key) |
151
- | **Pro** | 120/min | + `room_video`, all endpoints | Coming soon |
152
-
153
- The SDK calls the sign server **once per connection**, then stays connected via WebSocket. A free key is sufficient for most use cases.
154
-
155
- ---
156
-
157
- ## Examples
158
-
159
- ### Chat Bot
160
-
161
- ```typescript
162
- import { TikTokLive } from '@tiktool/live';
163
-
164
- const live = new TikTokLive({
165
- uniqueId: 'streamer_name',
166
- apiKey: 'YOUR_API_KEY',
167
- });
168
-
169
- live.on('chat', (e) => {
170
- if (e.comment.toLowerCase() === '!hello') {
171
- console.log(`Hello, ${e.user.nickname}!`);
172
- }
173
- });
174
-
175
- live.on('gift', (e) => {
176
- if (e.repeatEnd) {
177
- console.log(`${e.user.uniqueId} sent ${e.repeatCount}x ${e.giftName} (${e.diamondCount * e.repeatCount} diamonds)`);
178
- }
179
- });
180
-
181
- await live.connect();
182
- ```
183
-
184
- ### OBS Overlay
185
-
186
- ```typescript
187
- import { TikTokLive } from '@tiktool/live';
188
- import { WebSocketServer } from 'ws';
189
-
190
- const wss = new WebSocketServer({ port: 8080 });
191
- const live = new TikTokLive({
192
- uniqueId: 'streamer_name',
193
- apiKey: 'YOUR_API_KEY',
194
- });
195
-
196
- live.on('event', (event) => {
197
- for (const client of wss.clients) {
198
- client.send(JSON.stringify(event));
199
- }
200
- });
201
-
202
- await live.connect();
203
- console.log('Forwarding events to ws://localhost:8080');
204
- ```
205
-
206
- ---
207
-
208
- ## TypeScript
209
-
210
- Full TypeScript support with type inference:
211
-
212
- ```typescript
213
- import { TikTokLive, ChatEvent, GiftEvent } from '@tiktool/live';
214
-
215
- const live = new TikTokLive({
216
- uniqueId: 'username',
217
- apiKey: 'YOUR_API_KEY',
218
- });
219
-
220
- live.on('chat', (event: ChatEvent) => {
221
- const username: string = event.user.uniqueId;
222
- const message: string = event.comment;
223
- });
224
-
225
- live.on('gift', (event: GiftEvent) => {
226
- const diamonds: number = event.diamondCount;
227
- const isCombo: boolean = event.combo;
228
- });
229
- ```
230
-
231
- ---
232
-
233
- ## License
234
-
235
- MIT © [tiktool](https://tik.tools)
1
+ <div align="center">
2
+
3
+ # @tiktool/live
4
+
5
+ ### Connect to any TikTok LIVE stream in 4 lines of code.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@tiktool/live?color=%23ff0050&label=npm&logo=npm)](https://www.npmjs.com/package/@tiktool/live)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green?logo=node.js)](https://nodejs.org)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue?logo=typescript)](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)