@tiktool/live 1.0.0 → 1.2.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @tiktool/live
4
4
 
5
- ### Connect to any TikTok LIVE stream in 3 lines of code.
5
+ ### Connect to any TikTok LIVE stream in 4 lines of code.
6
6
 
7
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
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -11,9 +11,7 @@
11
11
 
12
12
  Real-time chat, gifts, viewers, battles, follows & 18+ event types from any TikTok livestream.
13
13
 
14
- **No API key required** · **Zero config** · **Direct connection**
15
-
16
- [Quick Start](#-quick-start) · [Events](#-events) · [API](#-api-reference) · [Rate Limits](#-rate-limits)
14
+ [Quick Start](#-quick-start) · [Events](#-events) · [API](#-api-reference) · [Rate Limits](#-rate-limits) · [Get API Key](https://tik.tools)
17
15
 
18
16
  </div>
19
17
 
@@ -25,10 +23,15 @@ Real-time chat, gifts, viewers, battles, follows & 18+ event types from any TikT
25
23
  npm install @tiktool/live
26
24
  ```
27
25
 
26
+ Get your free API key at [tik.tools](https://tik.tools)
27
+
28
28
  ```typescript
29
29
  import { TikTokLive } from '@tiktool/live';
30
30
 
31
- const live = new TikTokLive({ uniqueId: 'tv_asahi_news' });
31
+ const live = new TikTokLive({
32
+ uniqueId: 'tv_asahi_news',
33
+ apiKey: 'YOUR_API_KEY',
34
+ });
32
35
 
33
36
  live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
34
37
  live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName} (${e.diamondCount} diamonds)`));
@@ -38,8 +41,6 @@ live.on('roomUserSeq', e => console.log(`Viewers: ${e.viewerCount}`));
38
41
  await live.connect();
39
42
  ```
40
43
 
41
- No API key, no configuration, no server setup. Install and connect.
42
-
43
44
  ---
44
45
 
45
46
  ## How It Works
@@ -58,7 +59,7 @@ No API key, no configuration, no server setup. Install and connect.
58
59
  ```
59
60
 
60
61
  - Your app connects directly to TikTok from your IP address
61
- - The sign server only generates cryptographic signatures
62
+ - The sign server only generates cryptographic signatures (requires API key)
62
63
  - TikTok never sees the sign server
63
64
  - Built-in protobuf parser, no external dependencies
64
65
 
@@ -120,8 +121,8 @@ live.on('event', (event) => {
120
121
  | Option | Type | Default | Description |
121
122
  |--------|------|---------|-------------|
122
123
  | `uniqueId` | `string` | — | TikTok username (without @) |
124
+ | `apiKey` | `string` | — | **Required.** API key from [tik.tools](https://tik.tools) |
123
125
  | `signServerUrl` | `string` | `https://api.tik.tools` | Sign server URL |
124
- | `apiKey` | `string` | — | API key for higher rate limits |
125
126
  | `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
126
127
  | `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
127
128
  | `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
@@ -141,22 +142,15 @@ live.on('event', (event) => {
141
142
 
142
143
  ## Rate Limits
143
144
 
144
- | Tier | Rate Limit | API Key | Price |
145
- |------|-----------|---------|-------|
146
- | Free | 5 signs/min | No | Free |
147
- | Basic | 30 signs/min | Yes | Free (with key) |
148
- | Premium | 120 signs/min | Yes | Coming soon |
145
+ All API requests require an API key. Get yours at [tik.tools](https://tik.tools).
149
146
 
150
- Most users only need the free tier. The SDK calls the sign server once per connection, then stays connected via WebSocket.
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 |
151
152
 
152
- ### Using an API Key
153
-
154
- ```typescript
155
- const live = new TikTokLive({
156
- uniqueId: 'username',
157
- apiKey: 'your-api-key',
158
- });
159
- ```
153
+ The SDK calls the sign server **once per connection**, then stays connected via WebSocket. A free key is sufficient for most use cases.
160
154
 
161
155
  ---
162
156
 
@@ -167,7 +161,10 @@ const live = new TikTokLive({
167
161
  ```typescript
168
162
  import { TikTokLive } from '@tiktool/live';
169
163
 
170
- const live = new TikTokLive({ uniqueId: 'streamer_name' });
164
+ const live = new TikTokLive({
165
+ uniqueId: 'streamer_name',
166
+ apiKey: 'YOUR_API_KEY',
167
+ });
171
168
 
172
169
  live.on('chat', (e) => {
173
170
  if (e.comment.toLowerCase() === '!hello') {
@@ -191,7 +188,10 @@ import { TikTokLive } from '@tiktool/live';
191
188
  import { WebSocketServer } from 'ws';
192
189
 
193
190
  const wss = new WebSocketServer({ port: 8080 });
194
- const live = new TikTokLive({ uniqueId: 'streamer_name' });
191
+ const live = new TikTokLive({
192
+ uniqueId: 'streamer_name',
193
+ apiKey: 'YOUR_API_KEY',
194
+ });
195
195
 
196
196
  live.on('event', (event) => {
197
197
  for (const client of wss.clients) {
@@ -203,25 +203,6 @@ await live.connect();
203
203
  console.log('Forwarding events to ws://localhost:8080');
204
204
  ```
205
205
 
206
- ### Gift Tracker
207
-
208
- ```typescript
209
- import { TikTokLive } from '@tiktool/live';
210
-
211
- const live = new TikTokLive({ uniqueId: 'streamer_name' });
212
- let totalDiamonds = 0;
213
-
214
- live.on('gift', (e) => {
215
- if (e.repeatEnd || !e.combo) {
216
- const diamonds = e.diamondCount * (e.repeatCount || 1);
217
- totalDiamonds += diamonds;
218
- console.log(`${e.user.uniqueId}: ${e.giftName} = ${diamonds} diamonds (Total: ${totalDiamonds})`);
219
- }
220
- });
221
-
222
- await live.connect();
223
- ```
224
-
225
206
  ---
226
207
 
227
208
  ## TypeScript
@@ -231,7 +212,10 @@ Full TypeScript support with type inference:
231
212
  ```typescript
232
213
  import { TikTokLive, ChatEvent, GiftEvent } from '@tiktool/live';
233
214
 
234
- const live = new TikTokLive({ uniqueId: 'username' });
215
+ const live = new TikTokLive({
216
+ uniqueId: 'username',
217
+ apiKey: 'YOUR_API_KEY',
218
+ });
235
219
 
236
220
  live.on('chat', (event: ChatEvent) => {
237
221
  const username: string = event.user.uniqueId;
package/dist/index.d.mts CHANGED
@@ -37,6 +37,8 @@ interface GiftEvent extends BaseEvent {
37
37
  repeatCount: number;
38
38
  repeatEnd: boolean;
39
39
  combo: boolean;
40
+ giftType: number;
41
+ groupId: string;
40
42
  }
41
43
  interface SocialEvent extends BaseEvent {
42
44
  type: 'social';
@@ -48,12 +50,26 @@ interface RoomUserSeqEvent extends BaseEvent {
48
50
  viewerCount: number;
49
51
  totalViewers: number;
50
52
  }
53
+ interface BattleTeamUser {
54
+ user: TikTokUser;
55
+ score: number;
56
+ }
57
+ interface BattleTeam {
58
+ hostUserId: string;
59
+ score: number;
60
+ users: BattleTeamUser[];
61
+ }
51
62
  interface BattleEvent extends BaseEvent {
52
63
  type: 'battle';
64
+ battleId: string;
53
65
  status: number;
66
+ battleDuration: number;
67
+ teams: BattleTeam[];
54
68
  }
55
69
  interface BattleArmiesEvent extends BaseEvent {
56
70
  type: 'battleArmies';
71
+ battleId: string;
72
+ teams: BattleTeam[];
57
73
  }
58
74
  interface SubscribeEvent extends BaseEvent {
59
75
  type: 'subscribe';
@@ -64,9 +80,11 @@ interface EmoteChatEvent extends BaseEvent {
64
80
  type: 'emoteChat';
65
81
  user: TikTokUser;
66
82
  emoteId: string;
83
+ emoteUrl: string;
67
84
  }
68
85
  interface EnvelopeEvent extends BaseEvent {
69
86
  type: 'envelope';
87
+ envelopeId: string;
70
88
  diamondCount: number;
71
89
  }
72
90
  interface QuestionEvent extends BaseEvent {
@@ -80,19 +98,26 @@ interface ControlEvent extends BaseEvent {
80
98
  }
81
99
  interface RoomEvent extends BaseEvent {
82
100
  type: 'room';
83
- status: string;
101
+ status: number;
84
102
  }
85
103
  interface LiveIntroEvent extends BaseEvent {
86
104
  type: 'liveIntro';
105
+ roomId: string;
87
106
  title: string;
88
107
  }
89
108
  interface RankUpdateEvent extends BaseEvent {
90
109
  type: 'rankUpdate';
91
110
  rankType: string;
111
+ rankList: Array<{
112
+ user: TikTokUser;
113
+ rank: number;
114
+ score: number;
115
+ }>;
92
116
  }
93
117
  interface LinkMicEvent extends BaseEvent {
94
118
  type: 'linkMic';
95
- action: number;
119
+ action: string;
120
+ users: TikTokUser[];
96
121
  }
97
122
  interface UnknownEvent extends BaseEvent {
98
123
  type: 'unknown';
@@ -133,7 +158,7 @@ interface RoomInfo {
133
158
  interface TikTokLiveOptions {
134
159
  uniqueId: string;
135
160
  signServerUrl?: string;
136
- apiKey?: string;
161
+ apiKey: string;
137
162
  autoReconnect?: boolean;
138
163
  maxReconnectAttempts?: number;
139
164
  heartbeatInterval?: number;
@@ -150,7 +175,7 @@ declare class TikTokLive extends EventEmitter {
150
175
  private _roomId;
151
176
  private readonly uniqueId;
152
177
  private readonly signServerUrl;
153
- private readonly apiKey?;
178
+ private readonly apiKey;
154
179
  private readonly autoReconnect;
155
180
  private readonly maxReconnectAttempts;
156
181
  private readonly heartbeatInterval;
@@ -170,4 +195,4 @@ declare class TikTokLive extends EventEmitter {
170
195
  private stopHeartbeat;
171
196
  }
172
197
 
173
- export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, 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 SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
198
+ export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, 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 SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
package/dist/index.d.ts CHANGED
@@ -37,6 +37,8 @@ interface GiftEvent extends BaseEvent {
37
37
  repeatCount: number;
38
38
  repeatEnd: boolean;
39
39
  combo: boolean;
40
+ giftType: number;
41
+ groupId: string;
40
42
  }
41
43
  interface SocialEvent extends BaseEvent {
42
44
  type: 'social';
@@ -48,12 +50,26 @@ interface RoomUserSeqEvent extends BaseEvent {
48
50
  viewerCount: number;
49
51
  totalViewers: number;
50
52
  }
53
+ interface BattleTeamUser {
54
+ user: TikTokUser;
55
+ score: number;
56
+ }
57
+ interface BattleTeam {
58
+ hostUserId: string;
59
+ score: number;
60
+ users: BattleTeamUser[];
61
+ }
51
62
  interface BattleEvent extends BaseEvent {
52
63
  type: 'battle';
64
+ battleId: string;
53
65
  status: number;
66
+ battleDuration: number;
67
+ teams: BattleTeam[];
54
68
  }
55
69
  interface BattleArmiesEvent extends BaseEvent {
56
70
  type: 'battleArmies';
71
+ battleId: string;
72
+ teams: BattleTeam[];
57
73
  }
58
74
  interface SubscribeEvent extends BaseEvent {
59
75
  type: 'subscribe';
@@ -64,9 +80,11 @@ interface EmoteChatEvent extends BaseEvent {
64
80
  type: 'emoteChat';
65
81
  user: TikTokUser;
66
82
  emoteId: string;
83
+ emoteUrl: string;
67
84
  }
68
85
  interface EnvelopeEvent extends BaseEvent {
69
86
  type: 'envelope';
87
+ envelopeId: string;
70
88
  diamondCount: number;
71
89
  }
72
90
  interface QuestionEvent extends BaseEvent {
@@ -80,19 +98,26 @@ interface ControlEvent extends BaseEvent {
80
98
  }
81
99
  interface RoomEvent extends BaseEvent {
82
100
  type: 'room';
83
- status: string;
101
+ status: number;
84
102
  }
85
103
  interface LiveIntroEvent extends BaseEvent {
86
104
  type: 'liveIntro';
105
+ roomId: string;
87
106
  title: string;
88
107
  }
89
108
  interface RankUpdateEvent extends BaseEvent {
90
109
  type: 'rankUpdate';
91
110
  rankType: string;
111
+ rankList: Array<{
112
+ user: TikTokUser;
113
+ rank: number;
114
+ score: number;
115
+ }>;
92
116
  }
93
117
  interface LinkMicEvent extends BaseEvent {
94
118
  type: 'linkMic';
95
- action: number;
119
+ action: string;
120
+ users: TikTokUser[];
96
121
  }
97
122
  interface UnknownEvent extends BaseEvent {
98
123
  type: 'unknown';
@@ -133,7 +158,7 @@ interface RoomInfo {
133
158
  interface TikTokLiveOptions {
134
159
  uniqueId: string;
135
160
  signServerUrl?: string;
136
- apiKey?: string;
161
+ apiKey: string;
137
162
  autoReconnect?: boolean;
138
163
  maxReconnectAttempts?: number;
139
164
  heartbeatInterval?: number;
@@ -150,7 +175,7 @@ declare class TikTokLive extends EventEmitter {
150
175
  private _roomId;
151
176
  private readonly uniqueId;
152
177
  private readonly signServerUrl;
153
- private readonly apiKey?;
178
+ private readonly apiKey;
154
179
  private readonly autoReconnect;
155
180
  private readonly maxReconnectAttempts;
156
181
  private readonly heartbeatInterval;
@@ -170,4 +195,4 @@ declare class TikTokLive extends EventEmitter {
170
195
  private stopHeartbeat;
171
196
  }
172
197
 
173
- export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, 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 SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
198
+ export { type BaseEvent, type BattleArmiesEvent, type BattleEvent, 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 SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };
package/dist/index.js CHANGED
@@ -123,6 +123,9 @@ function getInt(fields, fn) {
123
123
  const f = fields.find((x) => x.fn === fn && x.wt === 0);
124
124
  return f ? Number(f.value) : 0;
125
125
  }
126
+ function getAllBytes(fields, fn) {
127
+ return fields.filter((x) => x.fn === fn && x.wt === 2).map((x) => x.value);
128
+ }
126
129
  function buildHeartbeat(roomId) {
127
130
  const payload = encodeField(1, 0, BigInt(roomId));
128
131
  return Buffer.concat([
@@ -158,65 +161,202 @@ function parseUser(data) {
158
161
  const id = String(getInt(f, 1) || getStr(f, 1));
159
162
  const nickname = getStr(f, 3) || getStr(f, 5);
160
163
  const uniqueId = getStr(f, 38) || getStr(f, 4) || getStr(f, 2);
161
- return { id, nickname, uniqueId: uniqueId || nickname || id };
164
+ let profilePicture;
165
+ const avatarBuf = getBytes(f, 9);
166
+ if (avatarBuf) {
167
+ try {
168
+ const avatarFields = decodeProto(avatarBuf);
169
+ const urlBuf = getBytes(avatarFields, 1);
170
+ if (urlBuf) profilePicture = urlBuf.toString("utf-8");
171
+ } catch {
172
+ }
173
+ }
174
+ return { id, nickname, uniqueId: uniqueId || nickname || id, profilePicture };
175
+ }
176
+ function parseBattleTeam(teamBuf) {
177
+ const fields = decodeProto(teamBuf);
178
+ const hostUserId = String(getInt(fields, 1));
179
+ const score = getInt(fields, 2);
180
+ const users = [];
181
+ const userFields = getAllBytes(fields, 3);
182
+ for (const uf of userFields) {
183
+ try {
184
+ const uFields = decodeProto(uf);
185
+ const userBuf = getBytes(uFields, 1);
186
+ const userScore = getInt(uFields, 2);
187
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
188
+ users.push({ user, score: userScore });
189
+ } catch {
190
+ }
191
+ }
192
+ return { hostUserId, score, users };
162
193
  }
163
194
  function parseWebcastMessage(method, payload) {
164
195
  const f = decodeProto(payload);
165
- const userBuf = getBytes(f, 2);
166
- const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
167
196
  const base = { timestamp: Date.now(), msgId: String(getInt(f, 1) || "") };
168
197
  switch (method) {
169
- case "WebcastChatMessage":
198
+ case "WebcastChatMessage": {
199
+ const userBuf = getBytes(f, 2);
200
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
170
201
  return { ...base, type: "chat", user, comment: getStr(f, 3) };
171
- case "WebcastMemberMessage":
172
- return { ...base, type: "member", user, action: getInt(f, 3) };
173
- case "WebcastLikeMessage":
174
- return { ...base, type: "like", user, likeCount: getInt(f, 5), totalLikes: getInt(f, 6) || getInt(f, 7) };
202
+ }
203
+ case "WebcastMemberMessage": {
204
+ const userBuf = getBytes(f, 2);
205
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
206
+ return { ...base, type: "member", user, action: getInt(f, 1) };
207
+ }
208
+ case "WebcastLikeMessage": {
209
+ const userBuf = getBytes(f, 5);
210
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
211
+ return { ...base, type: "like", user, likeCount: getInt(f, 1), totalLikes: getInt(f, 2) || getInt(f, 7) };
212
+ }
175
213
  case "WebcastGiftMessage": {
176
- const giftBuf = getBytes(f, 3);
177
- let giftName = "", giftId = 0, diamondCount = 0;
178
- if (giftBuf) {
179
- const gf = decodeProto(giftBuf);
180
- giftId = getInt(gf, 1);
181
- giftName = getStr(gf, 2);
182
- diamondCount = getInt(gf, 5);
183
- }
214
+ const userBuf = getBytes(f, 7);
215
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
216
+ const giftId = getInt(f, 1);
184
217
  const repeatCount = getInt(f, 5);
185
218
  const repeatEnd = getInt(f, 9) === 1;
186
- return { ...base, type: "gift", user, giftId, giftName, diamondCount, repeatCount, repeatEnd, combo: repeatCount > 1 && !repeatEnd };
219
+ const giftType = getInt(f, 6);
220
+ const groupId = getStr(f, 11);
221
+ let giftName = "", diamondCount = 0;
222
+ const giftInfoBuf = getBytes(f, 15);
223
+ if (giftInfoBuf) {
224
+ const gf = decodeProto(giftInfoBuf);
225
+ giftName = getStr(gf, 1);
226
+ diamondCount = getInt(gf, 5) || getInt(gf, 2);
227
+ }
228
+ if (!giftName) {
229
+ const giftBuf3 = getBytes(f, 3);
230
+ if (giftBuf3) {
231
+ const gf3 = decodeProto(giftBuf3);
232
+ if (!giftName) giftName = getStr(gf3, 2);
233
+ if (!diamondCount) diamondCount = getInt(gf3, 5);
234
+ }
235
+ }
236
+ return {
237
+ ...base,
238
+ type: "gift",
239
+ user,
240
+ giftId,
241
+ giftName,
242
+ diamondCount,
243
+ repeatCount,
244
+ repeatEnd,
245
+ combo: repeatCount > 1 && !repeatEnd,
246
+ giftType,
247
+ groupId
248
+ };
187
249
  }
188
250
  case "WebcastSocialMessage": {
189
- const action = getInt(f, 3);
251
+ const userBuf = getBytes(f, 2);
252
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
253
+ const actionInt = getInt(f, 1);
190
254
  const actionMap = { 1: "follow", 2: "share", 3: "like" };
191
- return { ...base, type: "social", user, action: actionMap[action] || `action_${action}` };
255
+ return { ...base, type: "social", user, action: actionMap[actionInt] || `action_${actionInt}` };
192
256
  }
193
257
  case "WebcastRoomUserSeqMessage":
194
- return { ...base, type: "roomUserSeq", viewerCount: getInt(f, 3), totalViewers: getInt(f, 4) };
195
- case "WebcastLinkMicBattle":
196
- return { ...base, type: "battle", status: getInt(f, 3) };
197
- case "WebcastLinkMicArmies":
198
- return { ...base, type: "battleArmies" };
199
- case "WebcastSubNotifyMessage":
200
- return { ...base, type: "subscribe", user, subMonth: getInt(f, 8) };
201
- case "WebcastEmoteChatMessage":
202
- return { ...base, type: "emoteChat", user, emoteId: getStr(f, 3) };
203
- case "WebcastEnvelopeMessage":
204
- return { ...base, type: "envelope", diamondCount: getInt(f, 3) };
205
- case "WebcastQuestionNewMessage":
206
- return { ...base, type: "question", user, questionText: getStr(f, 3) };
258
+ return {
259
+ ...base,
260
+ type: "roomUserSeq",
261
+ totalViewers: getInt(f, 1) || getInt(f, 3),
262
+ viewerCount: getInt(f, 2) || getInt(f, 4)
263
+ };
264
+ case "WebcastLinkMicBattle": {
265
+ const battleId = String(getInt(f, 1) || getStr(f, 1));
266
+ const status = getInt(f, 2);
267
+ const battleDuration = getInt(f, 3);
268
+ const teams = [];
269
+ const teamBufs = getAllBytes(f, 7);
270
+ for (const tb of teamBufs) {
271
+ try {
272
+ teams.push(parseBattleTeam(tb));
273
+ } catch {
274
+ }
275
+ }
276
+ return { ...base, type: "battle", battleId, status, battleDuration, teams };
277
+ }
278
+ case "WebcastLinkMicArmies": {
279
+ const battleId = String(getInt(f, 1) || getStr(f, 1));
280
+ const teams = [];
281
+ const teamBufs = getAllBytes(f, 3);
282
+ for (const tb of teamBufs) {
283
+ try {
284
+ teams.push(parseBattleTeam(tb));
285
+ } catch {
286
+ }
287
+ }
288
+ return { ...base, type: "battleArmies", battleId, teams };
289
+ }
290
+ case "WebcastSubNotifyMessage": {
291
+ const userBuf = getBytes(f, 2);
292
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
293
+ return { ...base, type: "subscribe", user, subMonth: getInt(f, 3) };
294
+ }
295
+ case "WebcastEmoteChatMessage": {
296
+ const userBuf = getBytes(f, 2);
297
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
298
+ let emoteId = "", emoteUrl = "";
299
+ const emoteBuf = getBytes(f, 3);
300
+ if (emoteBuf) {
301
+ const ef = decodeProto(emoteBuf);
302
+ emoteId = getStr(ef, 1);
303
+ const imageBuf = getBytes(ef, 2);
304
+ if (imageBuf) {
305
+ const imgFields = decodeProto(imageBuf);
306
+ emoteUrl = getStr(imgFields, 1);
307
+ }
308
+ }
309
+ return { ...base, type: "emoteChat", user, emoteId, emoteUrl };
310
+ }
311
+ case "WebcastEnvelopeMessage": {
312
+ const envelopeId = String(getInt(f, 1) || getStr(f, 1));
313
+ return { ...base, type: "envelope", envelopeId, diamondCount: getInt(f, 3) };
314
+ }
315
+ case "WebcastQuestionNewMessage": {
316
+ const userBuf = getBytes(f, 2);
317
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
318
+ return { ...base, type: "question", user, questionText: getStr(f, 3) || getStr(f, 4) };
319
+ }
207
320
  case "WebcastRankUpdateMessage":
208
- case "WebcastHourlyRankMessage":
209
- return { ...base, type: "rankUpdate", rankType: getStr(f, 3) };
321
+ case "WebcastHourlyRankMessage": {
322
+ const rankType = getStr(f, 1) || `rank_${getInt(f, 1)}`;
323
+ const rankList = [];
324
+ const listBufs = getAllBytes(f, 2);
325
+ for (const lb of listBufs) {
326
+ try {
327
+ const rf = decodeProto(lb);
328
+ const userBuf = getBytes(rf, 1);
329
+ const rank = getInt(rf, 2);
330
+ const score = getInt(rf, 3);
331
+ const user = userBuf ? parseUser(userBuf) : { id: "0", nickname: "", uniqueId: "" };
332
+ rankList.push({ user, rank, score });
333
+ } catch {
334
+ }
335
+ }
336
+ return { ...base, type: "rankUpdate", rankType, rankList };
337
+ }
210
338
  case "WebcastControlMessage":
211
- return { ...base, type: "control", action: getInt(f, 2) };
339
+ return { ...base, type: "control", action: getInt(f, 2) || getInt(f, 1) };
212
340
  case "WebcastRoomMessage":
213
341
  case "RoomMessage":
214
- return { ...base, type: "room", status: getStr(f, 3) };
215
- case "WebcastLiveIntroMessage":
216
- return { ...base, type: "liveIntro", title: getStr(f, 3) };
342
+ return { ...base, type: "room", status: getInt(f, 2) };
343
+ case "WebcastLiveIntroMessage": {
344
+ const roomId = String(getInt(f, 1));
345
+ return { ...base, type: "liveIntro", roomId, title: getStr(f, 4) || getStr(f, 2) };
346
+ }
217
347
  case "WebcastLinkMicMethod":
218
- case "WebcastLinkmicBattleTaskMessage":
219
- return { ...base, type: "linkMic", action: getInt(f, 3) };
348
+ case "WebcastLinkmicBattleTaskMessage": {
349
+ const action = getStr(f, 1) || `action_${getInt(f, 1)}`;
350
+ const users = [];
351
+ const userBufs = getAllBytes(f, 2);
352
+ for (const ub of userBufs) {
353
+ try {
354
+ users.push(parseUser(ub));
355
+ } catch {
356
+ }
357
+ }
358
+ return { ...base, type: "linkMic", action, users };
359
+ }
220
360
  default:
221
361
  return { ...base, type: "unknown", method };
222
362
  }
@@ -289,6 +429,7 @@ var TikTokLive = class extends import_events.EventEmitter {
289
429
  super();
290
430
  this.uniqueId = options.uniqueId.replace(/^@/, "");
291
431
  this.signServerUrl = (options.signServerUrl || DEFAULT_SIGN_SERVER).replace(/\/$/, "");
432
+ if (!options.apiKey) throw new Error("apiKey is required. Get a free key at https://tik.tools");
292
433
  this.apiKey = options.apiKey;
293
434
  this.autoReconnect = options.autoReconnect ?? true;
294
435
  this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
@@ -359,12 +500,15 @@ var TikTokLive = class extends import_events.EventEmitter {
359
500
  did_rule: "3"
360
501
  });
361
502
  const rawWsUrl = `https://${wsHost}/webcast/im/ws_proxy/ws_reuse_supplement/?${wsParams}`;
362
- const signUrl = this.apiKey ? `${this.signServerUrl}/webcast/sign_url?apiKey=${this.apiKey}` : `${this.signServerUrl}/webcast/sign_url`;
503
+ const signUrl = `${this.signServerUrl}/webcast/sign_url`;
363
504
  let wsUrl;
364
505
  try {
365
506
  const signResp = await fetch(signUrl, {
366
507
  method: "POST",
367
- headers: { "Content-Type": "application/json" },
508
+ headers: {
509
+ "Content-Type": "application/json",
510
+ "x-api-key": this.apiKey
511
+ },
368
512
  body: JSON.stringify({ url: rawWsUrl })
369
513
  });
370
514
  const signData = await signResp.json();