@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 +29 -45
- package/dist/index.d.mts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +186 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +186 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @tiktool/live
|
|
4
4
|
|
|
5
|
-
### Connect to any TikTok LIVE stream in
|
|
5
|
+
### Connect to any TikTok LIVE stream in 4 lines of code.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@tiktool/live)
|
|
8
8
|
[](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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
|
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[
|
|
255
|
+
return { ...base, type: "social", user, action: actionMap[actionInt] || `action_${actionInt}` };
|
|
192
256
|
}
|
|
193
257
|
case "WebcastRoomUserSeqMessage":
|
|
194
|
-
return {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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:
|
|
215
|
-
case "WebcastLiveIntroMessage":
|
|
216
|
-
|
|
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
|
-
|
|
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 =
|
|
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: {
|
|
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();
|