@tiktool/live 2.12.2 → 2.13.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 +31 -30
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +146 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Real-time chat, gifts, viewers, **PK battles with MVP breakdown**, **x2/x3 boost
|
|
|
23
23
|
npm install @tiktool/live
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Get
|
|
26
|
+
Get an API key at [tik.tools](https://tik.tools/pricing) (7-day free evaluation).
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
29
|
import { TikTokLive } from '@tiktool/live';
|
|
@@ -69,7 +69,7 @@ await live.connect();
|
|
|
69
69
|
|
|
70
70
|
There are **two ways** to receive TikTok LIVE events from `tik.tools`. Pick based on scale and IP-exposure tolerance.
|
|
71
71
|
|
|
72
|
-
### Mode 1
|
|
72
|
+
### Mode 1 - Direct (default, this SDK)
|
|
73
73
|
|
|
74
74
|
```
|
|
75
75
|
Your App ──signs URL via──▶ tik.tools ──returns signed URL──▶ Your App
|
|
@@ -78,7 +78,7 @@ There are **two ways** to receive TikTok LIVE events from `tik.tools`. Pick base
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
- The signed WebSocket is opened from your runtime. TikTok sees the network identity of the host that runs the SDK.
|
|
81
|
-
- Lowest per-stream cost
|
|
81
|
+
- Lowest per-stream cost - only the initial `ws_credentials` call is billed against your API key.
|
|
82
82
|
- Suited to **low- to mid-volume** workloads from a single host.
|
|
83
83
|
- High-volume setups should prefer Mode 2 for predictable, centrally-managed egress.
|
|
84
84
|
|
|
@@ -97,7 +97,7 @@ If you set the optional `proxy` field, install the standard agent:
|
|
|
97
97
|
npm install https-proxy-agent
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
### Mode 2
|
|
100
|
+
### Mode 2 - Relayed (via TikTools managed edge)
|
|
101
101
|
|
|
102
102
|
```
|
|
103
103
|
Your App ◀──── wss://api.tik.tools/?... ──── tik.tools
|
|
@@ -108,10 +108,10 @@ npm install https-proxy-agent
|
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
- The TikTools service handles the upstream TikTok session and forwards decoded events over a single WebSocket to your client.
|
|
111
|
-
- Single, stable egress
|
|
111
|
+
- Single, stable egress - your application connects only to `api.tik.tools`.
|
|
112
112
|
- Recommended for **production scale**, multi-tenant deployments, or any environment that already centralizes outbound traffic.
|
|
113
113
|
|
|
114
|
-
**Easiest
|
|
114
|
+
**Easiest - same SDK, one option:**
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
117
117
|
import { TikTokLive } from '@tiktool/live';
|
|
@@ -131,7 +131,7 @@ await live.connect();
|
|
|
131
131
|
|
|
132
132
|
Event names + payload shapes are **identical to Direct mode**. Switch back and forth by toggling `mode`.
|
|
133
133
|
|
|
134
|
-
**Advanced
|
|
134
|
+
**Advanced - raw WebSocket (no SDK, your own client):**
|
|
135
135
|
|
|
136
136
|
```typescript
|
|
137
137
|
import WebSocket from 'ws';
|
|
@@ -216,7 +216,7 @@ live.on('event', (event) => {
|
|
|
216
216
|
This SDK fully parses TikTok PK (Player-vs-Killer) battle protobufs, including
|
|
217
217
|
**multi-guest** matches with up to 4 hosts per side and per-gifter MVP scores.
|
|
218
218
|
|
|
219
|
-
### `battleArmies`
|
|
219
|
+
### `battleArmies` - score updates during a PK
|
|
220
220
|
|
|
221
221
|
Emitted every few seconds while a PK is active. The new `hosts[]` array gives
|
|
222
222
|
you the **per-host breakdown** with each gifter's contribution sorted **MVP
|
|
@@ -224,10 +224,10 @@ first** (highest score → lowest):
|
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
226
|
live.on('battleArmies', (e) => {
|
|
227
|
-
console.log(`PK ${e.battleId}
|
|
227
|
+
console.log(`PK ${e.battleId} - ${e.secsRemaining}s remaining`);
|
|
228
228
|
|
|
229
229
|
for (const host of e.hosts ?? []) {
|
|
230
|
-
console.log(` Host ${host.hostUserId}
|
|
230
|
+
console.log(` Host ${host.hostUserId} - total ${host.teamTotalScore}`);
|
|
231
231
|
|
|
232
232
|
// contributors[0] = MVP (highest gifter)
|
|
233
233
|
for (const [i, c] of host.contributors.slice(0, 3).entries()) {
|
|
@@ -266,11 +266,11 @@ live.on('battleArmies', (e) => {
|
|
|
266
266
|
| `durationSec` | `number` | Total battle duration |
|
|
267
267
|
| `endTimeMs` | `number` | Battle end ms epoch (alias for `battleStartMs + duration*1000`) |
|
|
268
268
|
|
|
269
|
-
### `battleItemCard`
|
|
269
|
+
### `battleItemCard` - boosters / power-ups during a PK
|
|
270
270
|
|
|
271
271
|
Fired when a gifter activates a special card: x2/x3 multipliers, gloves
|
|
272
272
|
(crit), mist, thunder, extra-time, or match-guide. Includes the **card art**
|
|
273
|
-
URL straight from TikTok's CDN
|
|
273
|
+
URL straight from TikTok's CDN - drop it into an OBS overlay as-is.
|
|
274
274
|
|
|
275
275
|
```typescript
|
|
276
276
|
live.on('battleItemCard', (e) => {
|
|
@@ -280,7 +280,7 @@ live.on('battleItemCard', (e) => {
|
|
|
280
280
|
console.log(` x${e.multiplier} multiplier for ${e.durationSec}s`);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
// Use straight in an <img> tag
|
|
283
|
+
// Use straight in an <img> tag - TikTok CDN
|
|
284
284
|
console.log(` icon: ${e.iconUrl}`);
|
|
285
285
|
console.log(` accent: ${e.accentColor}`);
|
|
286
286
|
});
|
|
@@ -307,7 +307,7 @@ live.on('battleItemCard', (e) => {
|
|
|
307
307
|
| `iconKey` | `string` | Short id, e.g. `card_mist_v3`, `card_crit_v3`, `top3_buffer` |
|
|
308
308
|
| `accentColor` | `string` | Hex color, e.g. `#BCD9E0` (mist blue), `#E0D4BC` (gloves tan) |
|
|
309
309
|
|
|
310
|
-
### `battle`
|
|
310
|
+
### `battle` - PK lifecycle events
|
|
311
311
|
|
|
312
312
|
Fired on start / status change / end. Use for PK ON/OFF banners.
|
|
313
313
|
|
|
@@ -327,15 +327,15 @@ live.on('battle', (e) => {
|
|
|
327
327
|
|
|
328
328
|
| Option | Type | Default | Description |
|
|
329
329
|
|--------|------|---------|-------------|
|
|
330
|
-
| `uniqueId` | `string` |
|
|
331
|
-
| `apiKey` | `string` |
|
|
330
|
+
| `uniqueId` | `string` | - | TikTok username (without @) |
|
|
331
|
+
| `apiKey` | `string` | - | **Required.** API key from [tik.tools](https://tik.tools) |
|
|
332
332
|
| `signServerUrl` | `string` | `https://api.tik.tools` | Sign server URL |
|
|
333
333
|
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
334
334
|
| `maxReconnectAttempts` | `number` | `5` | Max reconnect attempts |
|
|
335
335
|
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval (ms) |
|
|
336
|
-
| `roomId` | `string` |
|
|
337
|
-
| `sessionId` | `string` |
|
|
338
|
-
| `proxy` | `string` |
|
|
336
|
+
| `roomId` | `string` | - | Pre-resolved room ID (skips page fetch when paired with `sessionId`) |
|
|
337
|
+
| `sessionId` | `string` | - | Pre-resolved `ttwid` cookie (skips page fetch when paired with `roomId`) |
|
|
338
|
+
| `proxy` | `string` | - | HTTP(S) proxy URL for Direct mode (e.g. `http://USER:PASS@host:port`). Requires `https-proxy-agent`. |
|
|
339
339
|
| `mode` | `'direct' \| 'relayed'` | `'direct'` | Connection mode. See [Connection Modes](#connection-modes). |
|
|
340
340
|
| `debug` | `boolean` | `false` | Debug logging |
|
|
341
341
|
|
|
@@ -393,7 +393,7 @@ curl -H "X-Api-Key: YOUR_KEY" \
|
|
|
393
393
|
"https://api.tik.tools/webcast/user_profile?unique_id=anyuser"
|
|
394
394
|
```
|
|
395
395
|
|
|
396
|
-
Returns full profile JSON. Pro tier and above. Cached server-side for 24h
|
|
396
|
+
Returns full profile JSON. Pro tier and above. Cached server-side for 24h - repeated lookups are free and instant.
|
|
397
397
|
|
|
398
398
|
---
|
|
399
399
|
|
|
@@ -403,16 +403,17 @@ All API requests require an API key. Get yours at [tik.tools](https://tik.tools)
|
|
|
403
403
|
|
|
404
404
|
| Tier | Price | Requests/day | WS Connections | Notes |
|
|
405
405
|
|------|-------|--------------|----------------|-------|
|
|
406
|
-
| **
|
|
407
|
-
| **
|
|
408
|
-
| **
|
|
409
|
-
| **
|
|
406
|
+
| **Sandbox** | Free, 7-day evaluation | 5,000 | 3 (2h per WS) | Masked leaderboards. No datacenter proxies - calls run from your own IP. Refused after 7 days from account creation (WS close 4401, REST 403). |
|
|
407
|
+
| **Basic** | $7/wk or $19/mo | 10,000 | 20 (8h per WS) | Datacenter proxies, bulk live check |
|
|
408
|
+
| **Pro** | $15/wk or $49/mo | 75,000 | 50 (12h per WS) | Unmasked leaderboards · CAPTCHA Solver · Feed Discovery · priority routing |
|
|
409
|
+
| **Ultra** | $45/wk or $149/mo | 300,000 | 250 (24h per WS) | Unmasked leagues · priority chat support |
|
|
410
|
+
| **Agency** | $119/wk or $399/mo | 1,000,000 | 500 (24h per WS) + Firehose | Everything in Ultra + **Live Gifter Firehose WS** (region/league/global + min-diamond filters) + VIP Telegram alerts + VIP Web Vault |
|
|
410
411
|
|
|
411
412
|
The SDK calls the sign server **once per connection**, then stays connected
|
|
412
|
-
via WebSocket.
|
|
413
|
-
|
|
413
|
+
via WebSocket. Sandbox, the free 7-day evaluation, caps each WebSocket at 2 hours;
|
|
414
|
+
paid plans allow 8 to 24 hours per connection.
|
|
414
415
|
|
|
415
|
-
### Live Gifter Firehose
|
|
416
|
+
### Live Gifter Firehose - Agency
|
|
416
417
|
|
|
417
418
|
Real-time gift event stream from Dragonfly fan-out. Filter by region, league,
|
|
418
419
|
or globally; cap by minimum diamond threshold. Mid-stream filter updates
|
|
@@ -474,7 +475,7 @@ live.on('battleArmies', e => {
|
|
|
474
475
|
for (const host of e.hosts ?? []) {
|
|
475
476
|
const top = host.contributors[0];
|
|
476
477
|
console.log(` ${host.hostUserId}: ${host.teamTotalScore} ` +
|
|
477
|
-
`(MVP: ${top?.nickname || top?.userId || '
|
|
478
|
+
`(MVP: ${top?.nickname || top?.userId || '-'} ${top?.score ?? 0})`);
|
|
478
479
|
}
|
|
479
480
|
});
|
|
480
481
|
|
|
@@ -570,10 +571,10 @@ live.on('battleItemCard', (event: BattleItemCardEvent) => {
|
|
|
570
571
|
|
|
571
572
|
### 2.8.0 (2026-05-19)
|
|
572
573
|
|
|
573
|
-
- **NEW** `battleItemCard` event
|
|
574
|
+
- **NEW** `battleItemCard` event - x2/x3 boosters, gloves, mist, thunder,
|
|
574
575
|
extra-time, match-guide. Includes `iconUrl`, `iconKey`, `accentColor` for
|
|
575
576
|
drop-in overlay use.
|
|
576
|
-
- **NEW** `BattleArmiesEvent.hosts[]`
|
|
577
|
+
- **NEW** `BattleArmiesEvent.hosts[]` - multi-guest PK breakdown with per-host
|
|
577
578
|
`contributors[]` sorted **MVP first**.
|
|
578
579
|
- **NEW** `matchId`, `sessionId`, `serverTsMs`, `sessionTag`, `secsRemaining`
|
|
579
580
|
fields on `BattleArmiesEvent`.
|
package/dist/index.d.mts
CHANGED
|
@@ -45,7 +45,18 @@ interface GiftEvent extends BaseEvent {
|
|
|
45
45
|
repeatEnd: boolean;
|
|
46
46
|
combo: boolean;
|
|
47
47
|
giftType: number;
|
|
48
|
+
/**
|
|
49
|
+
* TikTok's combo group id: the same on every frame of one combo. Follow a
|
|
50
|
+
* combo with (user.id, giftId, groupId) and take its highest repeatCount.
|
|
51
|
+
*/
|
|
48
52
|
groupId: string;
|
|
53
|
+
/**
|
|
54
|
+
* Who received the gift, as a full-precision string: the host, or the
|
|
55
|
+
* guest in a multi-guest LIVE. '' when the frame did not state it.
|
|
56
|
+
*/
|
|
57
|
+
toUserId: string;
|
|
58
|
+
/** Same as `toUserId`; matches the field name used by the relay events. */
|
|
59
|
+
receiverUserId: string;
|
|
49
60
|
}
|
|
50
61
|
interface SocialEvent extends BaseEvent {
|
|
51
62
|
type: 'social';
|
|
@@ -55,6 +66,7 @@ interface SocialEvent extends BaseEvent {
|
|
|
55
66
|
interface RoomUserSeqEvent extends BaseEvent {
|
|
56
67
|
type: 'roomUserSeq';
|
|
57
68
|
viewerCount: number;
|
|
69
|
+
/** TikTok's running viewer total for this LIVE; restarts with a new LIVE. */
|
|
58
70
|
totalViewers: number;
|
|
59
71
|
}
|
|
60
72
|
interface BattleTeamUser {
|
|
@@ -230,6 +242,12 @@ interface TikTokLiveEvents {
|
|
|
230
242
|
envelope: (event: EnvelopeEvent) => void;
|
|
231
243
|
question: (event: QuestionEvent) => void;
|
|
232
244
|
control: (event: ControlEvent) => void;
|
|
245
|
+
/** The host ended the LIVE (TikTok control action 3, or the relay's confirmed end). */
|
|
246
|
+
streamEnd: (event: {
|
|
247
|
+
type: 'streamEnd';
|
|
248
|
+
uniqueId: string;
|
|
249
|
+
reason: string;
|
|
250
|
+
}) => void;
|
|
233
251
|
room: (event: RoomEvent) => void;
|
|
234
252
|
liveIntro: (event: LiveIntroEvent) => void;
|
|
235
253
|
rankUpdate: (event: RankUpdateEvent) => void;
|
|
@@ -444,6 +462,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
444
462
|
* Resolve unknown host user IDs via the sign server API.
|
|
445
463
|
* Caches results and re-emits the battleArmies event with enriched hostUser data.
|
|
446
464
|
*/
|
|
465
|
+
private _hostResolveUnavailable;
|
|
447
466
|
private resolveHostUsers;
|
|
448
467
|
private startHeartbeat;
|
|
449
468
|
private stopHeartbeat;
|
package/dist/index.d.ts
CHANGED
|
@@ -45,7 +45,18 @@ interface GiftEvent extends BaseEvent {
|
|
|
45
45
|
repeatEnd: boolean;
|
|
46
46
|
combo: boolean;
|
|
47
47
|
giftType: number;
|
|
48
|
+
/**
|
|
49
|
+
* TikTok's combo group id: the same on every frame of one combo. Follow a
|
|
50
|
+
* combo with (user.id, giftId, groupId) and take its highest repeatCount.
|
|
51
|
+
*/
|
|
48
52
|
groupId: string;
|
|
53
|
+
/**
|
|
54
|
+
* Who received the gift, as a full-precision string: the host, or the
|
|
55
|
+
* guest in a multi-guest LIVE. '' when the frame did not state it.
|
|
56
|
+
*/
|
|
57
|
+
toUserId: string;
|
|
58
|
+
/** Same as `toUserId`; matches the field name used by the relay events. */
|
|
59
|
+
receiverUserId: string;
|
|
49
60
|
}
|
|
50
61
|
interface SocialEvent extends BaseEvent {
|
|
51
62
|
type: 'social';
|
|
@@ -55,6 +66,7 @@ interface SocialEvent extends BaseEvent {
|
|
|
55
66
|
interface RoomUserSeqEvent extends BaseEvent {
|
|
56
67
|
type: 'roomUserSeq';
|
|
57
68
|
viewerCount: number;
|
|
69
|
+
/** TikTok's running viewer total for this LIVE; restarts with a new LIVE. */
|
|
58
70
|
totalViewers: number;
|
|
59
71
|
}
|
|
60
72
|
interface BattleTeamUser {
|
|
@@ -230,6 +242,12 @@ interface TikTokLiveEvents {
|
|
|
230
242
|
envelope: (event: EnvelopeEvent) => void;
|
|
231
243
|
question: (event: QuestionEvent) => void;
|
|
232
244
|
control: (event: ControlEvent) => void;
|
|
245
|
+
/** The host ended the LIVE (TikTok control action 3, or the relay's confirmed end). */
|
|
246
|
+
streamEnd: (event: {
|
|
247
|
+
type: 'streamEnd';
|
|
248
|
+
uniqueId: string;
|
|
249
|
+
reason: string;
|
|
250
|
+
}) => void;
|
|
233
251
|
room: (event: RoomEvent) => void;
|
|
234
252
|
liveIntro: (event: LiveIntroEvent) => void;
|
|
235
253
|
rankUpdate: (event: RankUpdateEvent) => void;
|
|
@@ -444,6 +462,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
444
462
|
* Resolve unknown host user IDs via the sign server API.
|
|
445
463
|
* Caches results and re-emits the battleArmies event with enriched hostUser data.
|
|
446
464
|
*/
|
|
465
|
+
private _hostResolveUnavailable;
|
|
447
466
|
private resolveHostUsers;
|
|
448
467
|
private startHeartbeat;
|
|
449
468
|
private stopHeartbeat;
|
package/dist/index.js
CHANGED
|
@@ -496,6 +496,121 @@ var require_browser = __commonJS({
|
|
|
496
496
|
}
|
|
497
497
|
});
|
|
498
498
|
|
|
499
|
+
// ../../node_modules/has-flag/index.js
|
|
500
|
+
var require_has_flag = __commonJS({
|
|
501
|
+
"../../node_modules/has-flag/index.js"(exports2, module2) {
|
|
502
|
+
"use strict";
|
|
503
|
+
module2.exports = (flag, argv = process.argv) => {
|
|
504
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
505
|
+
const position = argv.indexOf(prefix + flag);
|
|
506
|
+
const terminatorPosition = argv.indexOf("--");
|
|
507
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ../../node_modules/supports-color/index.js
|
|
513
|
+
var require_supports_color = __commonJS({
|
|
514
|
+
"../../node_modules/supports-color/index.js"(exports2, module2) {
|
|
515
|
+
"use strict";
|
|
516
|
+
var os = require("os");
|
|
517
|
+
var tty = require("tty");
|
|
518
|
+
var hasFlag = require_has_flag();
|
|
519
|
+
var { env } = process;
|
|
520
|
+
var forceColor;
|
|
521
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
522
|
+
forceColor = 0;
|
|
523
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
524
|
+
forceColor = 1;
|
|
525
|
+
}
|
|
526
|
+
if ("FORCE_COLOR" in env) {
|
|
527
|
+
if (env.FORCE_COLOR === "true") {
|
|
528
|
+
forceColor = 1;
|
|
529
|
+
} else if (env.FORCE_COLOR === "false") {
|
|
530
|
+
forceColor = 0;
|
|
531
|
+
} else {
|
|
532
|
+
forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function translateLevel(level) {
|
|
536
|
+
if (level === 0) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
level,
|
|
541
|
+
hasBasic: true,
|
|
542
|
+
has256: level >= 2,
|
|
543
|
+
has16m: level >= 3
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function supportsColor(haveStream, streamIsTTY) {
|
|
547
|
+
if (forceColor === 0) {
|
|
548
|
+
return 0;
|
|
549
|
+
}
|
|
550
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
551
|
+
return 3;
|
|
552
|
+
}
|
|
553
|
+
if (hasFlag("color=256")) {
|
|
554
|
+
return 2;
|
|
555
|
+
}
|
|
556
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
const min = forceColor || 0;
|
|
560
|
+
if (env.TERM === "dumb") {
|
|
561
|
+
return min;
|
|
562
|
+
}
|
|
563
|
+
if (process.platform === "win32") {
|
|
564
|
+
const osRelease = os.release().split(".");
|
|
565
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
566
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
567
|
+
}
|
|
568
|
+
return 1;
|
|
569
|
+
}
|
|
570
|
+
if ("CI" in env) {
|
|
571
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
572
|
+
return 1;
|
|
573
|
+
}
|
|
574
|
+
return min;
|
|
575
|
+
}
|
|
576
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
577
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
578
|
+
}
|
|
579
|
+
if (env.COLORTERM === "truecolor") {
|
|
580
|
+
return 3;
|
|
581
|
+
}
|
|
582
|
+
if ("TERM_PROGRAM" in env) {
|
|
583
|
+
const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
584
|
+
switch (env.TERM_PROGRAM) {
|
|
585
|
+
case "iTerm.app":
|
|
586
|
+
return version >= 3 ? 3 : 2;
|
|
587
|
+
case "Apple_Terminal":
|
|
588
|
+
return 2;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
592
|
+
return 2;
|
|
593
|
+
}
|
|
594
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
595
|
+
return 1;
|
|
596
|
+
}
|
|
597
|
+
if ("COLORTERM" in env) {
|
|
598
|
+
return 1;
|
|
599
|
+
}
|
|
600
|
+
return min;
|
|
601
|
+
}
|
|
602
|
+
function getSupportLevel(stream) {
|
|
603
|
+
const level = supportsColor(stream, stream && stream.isTTY);
|
|
604
|
+
return translateLevel(level);
|
|
605
|
+
}
|
|
606
|
+
module2.exports = {
|
|
607
|
+
supportsColor: getSupportLevel,
|
|
608
|
+
stdout: translateLevel(supportsColor(true, tty.isatty(1))),
|
|
609
|
+
stderr: translateLevel(supportsColor(true, tty.isatty(2)))
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
499
614
|
// node_modules/debug/src/node.js
|
|
500
615
|
var require_node = __commonJS({
|
|
501
616
|
"node_modules/debug/src/node.js"(exports2, module2) {
|
|
@@ -515,7 +630,7 @@ var require_node = __commonJS({
|
|
|
515
630
|
);
|
|
516
631
|
exports2.colors = [6, 2, 3, 4, 5, 1];
|
|
517
632
|
try {
|
|
518
|
-
const supportsColor =
|
|
633
|
+
const supportsColor = require_supports_color();
|
|
519
634
|
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
520
635
|
exports2.colors = [
|
|
521
636
|
20,
|
|
@@ -1511,12 +1626,18 @@ function parseWebcastMessage(method, payload) {
|
|
|
1511
1626
|
}
|
|
1512
1627
|
}
|
|
1513
1628
|
let toUserId = "";
|
|
1629
|
+
const receiverBuf = getBytes(f, 8);
|
|
1630
|
+
if (receiverBuf) {
|
|
1631
|
+
const r = parseUser(receiverBuf);
|
|
1632
|
+
if (r.id && r.id !== "0") toUserId = r.id;
|
|
1633
|
+
}
|
|
1514
1634
|
const extraBuf = getBytes(f, 23);
|
|
1515
|
-
if (extraBuf) {
|
|
1516
|
-
const
|
|
1517
|
-
toUserId =
|
|
1635
|
+
if (!toUserId && extraBuf) {
|
|
1636
|
+
const id = getIntStr(decodeProto(extraBuf), 8);
|
|
1637
|
+
if (id !== "0") toUserId = id;
|
|
1518
1638
|
}
|
|
1519
|
-
const
|
|
1639
|
+
const rawGroup = getIntStr(f, 11);
|
|
1640
|
+
const groupId = getStr(f, 11) || (rawGroup !== "0" ? rawGroup : "");
|
|
1520
1641
|
return {
|
|
1521
1642
|
...base,
|
|
1522
1643
|
type: "gift",
|
|
@@ -1528,7 +1649,9 @@ function parseWebcastMessage(method, payload) {
|
|
|
1528
1649
|
repeatEnd,
|
|
1529
1650
|
combo: repeatCount > 1 && !repeatEnd,
|
|
1530
1651
|
giftType,
|
|
1531
|
-
groupId
|
|
1652
|
+
groupId,
|
|
1653
|
+
toUserId,
|
|
1654
|
+
receiverUserId: toUserId
|
|
1532
1655
|
};
|
|
1533
1656
|
}
|
|
1534
1657
|
// Proto: WebcastSocialMessage { User user=2, WebcastMessageEvent event=1 }
|
|
@@ -1554,7 +1677,7 @@ function parseWebcastMessage(method, payload) {
|
|
|
1554
1677
|
// Proto: WebcastRoomUserSeqMessage { int32 viewerCount=3 }
|
|
1555
1678
|
case "WebcastRoomUserSeqMessage": {
|
|
1556
1679
|
const viewerCount = getInt(f, 3) || getInt(f, 2);
|
|
1557
|
-
const totalViewers = getInt(f,
|
|
1680
|
+
const totalViewers = getInt(f, 7) || viewerCount;
|
|
1558
1681
|
return { ...base, type: "roomUserSeq", totalViewers, viewerCount };
|
|
1559
1682
|
}
|
|
1560
1683
|
// Proto: WebcastLinkMicBattle { repeated WebcastLinkMicBattleItems battleUsers=10 }
|
|
@@ -2068,6 +2191,11 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2068
2191
|
if (this._destroyed) throw new Error("Client has been destroyed. Create a new instance.");
|
|
2069
2192
|
this.intentionalClose = false;
|
|
2070
2193
|
if (this.mode === "relayed") {
|
|
2194
|
+
if (this._presetRoomId) {
|
|
2195
|
+
console.warn(
|
|
2196
|
+
`[TikTokLive] roomId=${this._presetRoomId} is ignored in mode:'relayed' - the managed relay resolves the room itself. Use mode:'direct' with roomId to pin an exact room (resolve it first via /webcast/bulk_live_check).`
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2071
2199
|
return this._connectRelayed();
|
|
2072
2200
|
}
|
|
2073
2201
|
let ttwid = this._presetSessionId;
|
|
@@ -2503,6 +2631,9 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2503
2631
|
}
|
|
2504
2632
|
this.emit("event", evt);
|
|
2505
2633
|
this.emit(evt.type, evt);
|
|
2634
|
+
if (evt.type === "control" && evt.action === 3) {
|
|
2635
|
+
this.emit("streamEnd", { type: "streamEnd", uniqueId: this.uniqueId, reason: "creator_offline" });
|
|
2636
|
+
}
|
|
2506
2637
|
}
|
|
2507
2638
|
}
|
|
2508
2639
|
} catch {
|
|
@@ -2512,8 +2643,12 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2512
2643
|
* Resolve unknown host user IDs via the sign server API.
|
|
2513
2644
|
* Caches results and re-emits the battleArmies event with enriched hostUser data.
|
|
2514
2645
|
*/
|
|
2646
|
+
// Set once the sign server refuses host resolution for this key (tier below
|
|
2647
|
+
// Basic answers 403). Before this, every battle frame retried and filled the
|
|
2648
|
+
// customer's API log with 403s they never asked for.
|
|
2649
|
+
_hostResolveUnavailable = false;
|
|
2515
2650
|
async resolveHostUsers(userIds, originalEvt) {
|
|
2516
|
-
const toResolve = userIds.filter((id) => !this._pendingHostResolves.has(id));
|
|
2651
|
+
const toResolve = this._hostResolveUnavailable ? [] : userIds.filter((id) => !this._pendingHostResolves.has(id));
|
|
2517
2652
|
if (toResolve.length === 0) {
|
|
2518
2653
|
this.emit("event", originalEvt);
|
|
2519
2654
|
this.emit(originalEvt.type, originalEvt);
|
|
@@ -2531,7 +2666,9 @@ var TikTokLive = class _TikTokLive extends import_events.EventEmitter {
|
|
|
2531
2666
|
body: JSON.stringify({ user_ids: toResolve }),
|
|
2532
2667
|
signal: AbortSignal.timeout(5e3)
|
|
2533
2668
|
});
|
|
2534
|
-
if (res.
|
|
2669
|
+
if (res.status === 401 || res.status === 403) {
|
|
2670
|
+
this._hostResolveUnavailable = true;
|
|
2671
|
+
} else if (res.ok) {
|
|
2535
2672
|
const json = await res.json();
|
|
2536
2673
|
if (json.data) {
|
|
2537
2674
|
for (const [uid, info] of Object.entries(json.data)) {
|