tiktok-live-api 1.2.19 โ 1.4.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 +59 -77
- package/dist/index.d.mts +61 -1
- package/dist/index.d.ts +61 -1
- package/package.json +84 -88
- package/bin/demo.mjs +0 -689
package/README.md
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="banner.png" alt="tiktok-live-api" width="100%" />
|
|
2
|
+
<img src="https://raw.githubusercontent.com/tiktool/tiktok-live-api/main/banner.png" alt="tiktok-live-api" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# tiktok-live-api
|
|
6
6
|
|
|
7
|
+
> ๐ **Full documentation, guides, and dashboard โ [tik.tools](https://tik.tools)** | ๐ **Python SDK โ [tik.tools/guides/python-tiktok-live](https://tik.tools/guides/python-tiktok-live)** | ๐ **WebSocket API โ [tik.tools/websocket](https://tik.tools/websocket)**
|
|
8
|
+
|
|
7
9
|
**Unofficial TikTok LIVE API Client for Node.js & TypeScript** โ Connect to any TikTok LIVE stream and receive real-time chat messages, gifts, likes, follows, viewer counts, battles, and more. Includes AI-powered live captions (speech-to-text). Powered by the [TikTool](https://tik.tools) managed API.
|
|
8
10
|
|
|
9
11
|
[](https://www.npmjs.com/package/tiktok-live-api)
|
|
10
12
|
[](https://www.npmjs.com/package/tiktok-live-api)
|
|
11
13
|
[](https://www.typescriptlang.org/)
|
|
12
14
|
[](https://github.com/tiktool/tiktok-live-api/blob/main/LICENSE)
|
|
13
|
-
[](https://discord.gg/y8TwuFBAmD)
|
|
14
15
|
|
|
15
16
|
<p align="center">
|
|
16
17
|
<img src="https://raw.githubusercontent.com/tiktool/tiktok-live-api/main/tiktok-live-api.gif" alt="TikTok Live API Demo โ real-time chat, gifts, and viewer events" width="700">
|
|
@@ -18,17 +19,6 @@
|
|
|
18
19
|
|
|
19
20
|
> This package is **not affiliated with or endorsed by TikTok**. It connects to the [TikTool Live](https://tik.tools) managed API service โ 99.9% uptime, no reverse engineering, no maintenance required. Also available for [Python](https://pypi.org/project/tiktok-live-api/) and [any language via WebSocket](https://tik.tools/docs).
|
|
20
21
|
|
|
21
|
-
## ๐ One-Command Quick Start
|
|
22
|
-
|
|
23
|
-
Instantly connect to a live TikTok stream and print real-time events to your terminal. No signup, no install:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npx tiktok-live-api
|
|
27
|
-
```
|
|
28
|
-
*Or connect to a specific stream:* `npx tiktok-live-api @username`
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
22
|
## Install
|
|
33
23
|
|
|
34
24
|
```bash
|
|
@@ -200,7 +190,9 @@ client.connect();
|
|
|
200
190
|
| `member` | Viewer joined | `user` |
|
|
201
191
|
| `subscribe` | New subscriber | `user` |
|
|
202
192
|
| `roomUserSeq` | Viewer count | `viewerCount`, `topViewers` |
|
|
203
|
-
| `battle` |
|
|
193
|
+
| `battle` | PK start / end / status change | `battleId`, `status` (1=ACTIVE / 2=STARTING / 3=ENDED / 4=PREPARING), `battleDuration`, `teams` |
|
|
194
|
+
| `battleArmies` | Live PK score update | `battleId`, `status`, `matchId`, `sessionId`, `durationSec`, `secsRemaining`, `hosts[]` โ each host has `teamTotalScore` + `contributors[]` (MVP first) |
|
|
195
|
+
| `battleItemCard` | Booster multipliers, gloves, mist, match-guide, thunder, extra-time | `effect` (`'gloves'` / `'mist'` / `'booster_x2'` / `'booster_x3'` / `'match_guide'` / ...), `multiplier` (2 or 3), `senderUserId`, `senderNickname`, `activatedAtSec`, `durationSec`, `endsAtSec`, `commentTemplate` |
|
|
204
196
|
| `roomPin` | Pinned/starred message | `user`, `comment`, `action`, `durationSeconds` |
|
|
205
197
|
| `envelope` | Treasure chest | `diamonds`, `user` |
|
|
206
198
|
| `streamEnd` | Stream ended | `reason` |
|
|
@@ -211,80 +203,44 @@ client.connect();
|
|
|
211
203
|
|
|
212
204
|
All events are fully typed with TypeScript interfaces. Your IDE will show autocompletion for every field.
|
|
213
205
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
Transcribe and translate any TikTok LIVE stream in real-time. **This feature is unique to TikTool Live โ no other TikTok library offers it.**
|
|
206
|
+
### Battle / PK example
|
|
217
207
|
|
|
218
208
|
```typescript
|
|
219
|
-
import {
|
|
209
|
+
import { TikTokLive } from 'tiktok-live-api';
|
|
220
210
|
|
|
221
|
-
|
|
222
|
-
live.on('event', (event) => {
|
|
223
|
-
console.log(event.type, event);
|
|
224
|
-
});
|
|
225
|
-
```
|
|
211
|
+
const client = new TikTokLive({ uniqueId: 'creator_username', apiKey: 'tk_...' });
|
|
226
212
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
| Event | Type | Description | Fields |
|
|
230
|
-
|-------|------|-------------|--------|
|
|
231
|
-
| `chat` | `ChatEvent` | Chat message | `user`, `comment`, `starred?` |
|
|
232
|
-
| `member` | `MemberEvent` | User joined | `user`, `action` |
|
|
233
|
-
| `like` | `LikeEvent` | User liked | `user`, `likeCount`, `totalLikes` |
|
|
234
|
-
| `gift` | `GiftEvent` | Gift sent | `user`, `giftName`, `diamondCount`, `repeatCount`, `combo` |
|
|
235
|
-
| `social` | `SocialEvent` | Follow / Share | `user`, `action` |
|
|
236
|
-
| `roomUserSeq` | `RoomUserSeqEvent` | Viewer count | `viewerCount`, `totalViewers` |
|
|
237
|
-
| `battle` | `BattleEvent` | Link Mic battle | `status` |
|
|
238
|
-
| `battleArmies` | `BattleArmiesEvent` | Battle teams | โ |
|
|
239
|
-
| `subscribe` | `SubscribeEvent` | New subscriber | `user`, `subMonth` |
|
|
240
|
-
| `emoteChat` | `EmoteChatEvent` | Emote in chat | `user`, `emoteId` |
|
|
241
|
-
| `envelope` | `EnvelopeEvent` | Treasure chest | `diamondCount` |
|
|
242
|
-
| `question` | `QuestionEvent` | Q&A question | `user`, `questionText` |
|
|
243
|
-
| `control` | `ControlEvent` | Stream control | `action` (3 = ended) |
|
|
244
|
-
| `room` | `RoomEvent` | Room status | `status` |
|
|
245
|
-
| `liveIntro` | `LiveIntroEvent` | Stream intro | `title` |
|
|
246
|
-
| `rankUpdate` | `RankUpdateEvent` | Rank update | `rankType` |
|
|
247
|
-
| `linkMic` | `LinkMicEvent` | Link Mic | `action` |
|
|
248
|
-
| `roomPin` | `RoomPinEvent` | Pinned/starred message | `user`, `comment`, `action`, `durationSeconds` |
|
|
249
|
-
| `unknown` | `UnknownEvent` | Unrecognized | `method` |
|
|
250
|
-
|
|
251
|
-
### Connection Events
|
|
252
|
-
|
|
253
|
-
| Event | Callback | Description |
|
|
254
|
-
|-------|----------|-------------|
|
|
255
|
-
| `connected` | `() => void` | Connected to stream |
|
|
256
|
-
| `disconnected` | `(code, reason) => void` | Disconnected |
|
|
257
|
-
| `roomInfo` | `(info: RoomInfo) => void` | Room info |
|
|
258
|
-
| `error` | `(error: Error) => void` | Error |
|
|
213
|
+
client.on('battle', e => console.log('PK', e.status, e.battleId, 'duration=', e.battleDuration));
|
|
259
214
|
|
|
260
|
-
|
|
215
|
+
client.on('battleArmies', e => {
|
|
216
|
+
console.log('Countdown:', e.secsRemaining, 's');
|
|
217
|
+
for (const host of e.hosts ?? []) {
|
|
218
|
+
console.log(`@${host.hostUserId} team total=${host.teamTotalScore}`);
|
|
219
|
+
const mvp = host.contributors[0];
|
|
220
|
+
if (mvp) console.log(` MVP @${mvp.nickname} score=${mvp.score}`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
client.on('battleItemCard', e => {
|
|
225
|
+
if (e.multiplier > 0) console.log(`x${e.multiplier} booster from @${e.senderUniqueId}`);
|
|
226
|
+
else console.log(`Effect ${e.effect} from @${e.senderUniqueId} (${e.durationSec}s)`);
|
|
227
|
+
});
|
|
261
228
|
|
|
262
|
-
|
|
229
|
+
await client.connect();
|
|
230
|
+
```
|
|
263
231
|
|
|
264
|
-
AI-powered speech-to-text transcription and translation for TikTok LIVE streams. Features include:
|
|
265
232
|
|
|
266
|
-
|
|
267
|
-
- **Speaker diarization** โ Identifies individual speakers in multi-person streams
|
|
268
|
-
- **Real-time translation** โ Translate to any supported language with sub-second latency
|
|
269
|
-
- **Partial + final results** โ Get streaming partial transcripts and confirmed final text
|
|
270
|
-
- **Credit-based billing** โ 1 credit = 1 minute of transcription/translation
|
|
233
|
+
## Live Captions (Speech-to-Text)
|
|
271
234
|
|
|
272
|
-
|
|
235
|
+
Transcribe and translate any TikTok LIVE stream in real-time. **This feature is unique to TikTool Live โ no other TikTok library offers it.**
|
|
273
236
|
|
|
274
237
|
```typescript
|
|
275
|
-
import { TikTokCaptions } from '
|
|
276
|
-
|
|
277
|
-
const captions = new TikTokCaptions({
|
|
278
|
-
uniqueId: 'streamer_name',
|
|
279
|
-
apiKey: 'YOUR_API_KEY',
|
|
280
|
-
translate: 'en',
|
|
281
|
-
diarization: true,
|
|
282
|
-
=======
|
|
238
|
+
import { TikTokCaptions } from 'tiktok-live-api';
|
|
239
|
+
|
|
283
240
|
const captions = new TikTokCaptions('streamer_username', {
|
|
284
241
|
apiKey: 'YOUR_API_KEY',
|
|
285
242
|
translate: 'en', // translate to English
|
|
286
243
|
diarization: true, // identify who is speaking
|
|
287
|
-
>>>>>>> ac7990ac0be77c4206d9d8fa0bccbe1c85a2bbe6
|
|
288
244
|
});
|
|
289
245
|
|
|
290
246
|
captions.on('caption', (event) => {
|
|
@@ -423,10 +379,36 @@ client.on('gift', (event: GiftEvent) => {
|
|
|
423
379
|
|
|
424
380
|
| Tier | Requests/Day | WebSocket Connections | Price |
|
|
425
381
|
|------|-------------|----------------------|-------|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
382
|
+
| Community | 2,500 | 15 (no time limit) | Free forever |
|
|
383
|
+
| Pro | 75,000 | 50 (8h) | from $59/mo +tax |
|
|
384
|
+
| Ultra | 300,000 | 250 (8h) | from $219/mo +tax |
|
|
385
|
+
| **Global Agency** | 300,000 | 500 (8h) + Firehose | $549/mo +tax |
|
|
386
|
+
|
|
387
|
+
Full plan details at [tik.tools/pricing](https://tik.tools/pricing). Highlights:
|
|
388
|
+
|
|
389
|
+
- **Community** ($0 forever): 2,500 req/day ยท 15 WS ยท no time limit ยท masked leaderboards. Designed for devs building apps โ upgrade when you need real usernames. No datacenter proxies; calls must come from your own IP.
|
|
390
|
+
- **Pro** (monthly): 75K req/day ยท 50 WS ยท unmasked leaderboards ยท CAPTCHA Solver ยท Feed Discovery ยท 5 AI caption streams ยท priority routing ยท chat support
|
|
391
|
+
- **Ultra** (monthly): 300K req/day ยท 250 WS ยท 20 AI caption streams ยท **League Rankings API** unmasked ยท 99.5% uptime SLA ยท priority chat support
|
|
392
|
+
- **Global Agency** ($549/mo): Everything in Ultra + **Live Gifter Firehose WS** (region/league/global filters, min-diamond threshold) + VIP Telegram alerts + VIP Web Vault (unmasked historical visual access)
|
|
393
|
+
|
|
394
|
+
### Live Gifter Firehose โ Global Agency
|
|
395
|
+
|
|
396
|
+
Real-time gift event stream from our Dragonfly fan-out. Filter by region, league, or globally; cap by minimum diamond threshold. Mid-stream filter updates supported via `update_filter` frame, no reconnect needed.
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
const ws = new WebSocket(
|
|
400
|
+
`wss://api.tik.tools/firehose/gifters?apiKey=${KEY}&mode=region®ion=US%2B&min_diamonds=1000`
|
|
401
|
+
)
|
|
402
|
+
ws.on('message', (raw) => {
|
|
403
|
+
const evt = JSON.parse(raw)
|
|
404
|
+
// evt: { type:'gifter_alert', ts, gifter:{username,displayName,isAnonymous},
|
|
405
|
+
// creator:{uniqueId}, gift:{name,totalDiamonds}, region }
|
|
406
|
+
})
|
|
407
|
+
// Update filter without reconnect
|
|
408
|
+
ws.send(JSON.stringify({ type: 'update_filter', mode: 'global', min_diamonds: 5000 }))
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Modes: `global` (all regions), `region` (single region code), `league` (region + league class, e.g. `B2`).
|
|
430
412
|
|
|
431
413
|
## Also Available
|
|
432
414
|
|
package/dist/index.d.mts
CHANGED
|
@@ -59,12 +59,70 @@ interface RoomUserSeqEvent {
|
|
|
59
59
|
viewerCount: number;
|
|
60
60
|
topViewers: TikTokUser[];
|
|
61
61
|
}
|
|
62
|
-
/** Payload for `battle` events. */
|
|
62
|
+
/** Payload for `battle` events (PK start / end / countdown). */
|
|
63
63
|
interface BattleEvent {
|
|
64
64
|
type: string;
|
|
65
|
+
battleId: string;
|
|
66
|
+
/** 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING */
|
|
67
|
+
status: number;
|
|
68
|
+
battleDuration: number;
|
|
65
69
|
teams: Array<Record<string, unknown>>;
|
|
66
70
|
scores: number[];
|
|
67
71
|
}
|
|
72
|
+
/** One host on a battle side โ present per-host in multi-guest PK. */
|
|
73
|
+
interface BattleHost {
|
|
74
|
+
hostUserId: string;
|
|
75
|
+
teamTotalScore: number;
|
|
76
|
+
teamIdx: number;
|
|
77
|
+
/** Sorted MVP first (highest score โ lowest). */
|
|
78
|
+
contributors: BattleContributor[];
|
|
79
|
+
}
|
|
80
|
+
interface BattleContributor {
|
|
81
|
+
userId: string;
|
|
82
|
+
score: number;
|
|
83
|
+
nickname: string;
|
|
84
|
+
}
|
|
85
|
+
/** Payload for `battleArmies` events โ score updates during PK. */
|
|
86
|
+
interface BattleArmiesEvent {
|
|
87
|
+
battleId: string;
|
|
88
|
+
/** 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING */
|
|
89
|
+
status: number;
|
|
90
|
+
teams: Array<Record<string, unknown>>;
|
|
91
|
+
matchId?: string;
|
|
92
|
+
sessionId?: string;
|
|
93
|
+
startedAtMs?: number;
|
|
94
|
+
serverTsMs?: number;
|
|
95
|
+
sessionTag?: string;
|
|
96
|
+
durationSec?: number;
|
|
97
|
+
secsRemaining?: number;
|
|
98
|
+
/** Per-host breakdown with MVP contributors. */
|
|
99
|
+
hosts?: BattleHost[];
|
|
100
|
+
}
|
|
101
|
+
/** Payload for `battleItemCard` events โ multipliers (x2/x3), gloves, mist, etc. */
|
|
102
|
+
interface BattleItemCardEvent {
|
|
103
|
+
battleId: string;
|
|
104
|
+
cardType: number;
|
|
105
|
+
/** 'gloves' | 'mist' | 'booster_x2' | 'booster_x3' | 'match_guide' |
|
|
106
|
+
* 'thunder' | 'extra_time' | raw resource key. */
|
|
107
|
+
effect: string;
|
|
108
|
+
effectKey: string;
|
|
109
|
+
/** 2 or 3 for booster_x2/x3, otherwise 0. */
|
|
110
|
+
multiplier: number;
|
|
111
|
+
senderUserId: string;
|
|
112
|
+
senderNickname: string;
|
|
113
|
+
senderUniqueId: string;
|
|
114
|
+
senderAvatarUrl: string;
|
|
115
|
+
activatedAtSec: number;
|
|
116
|
+
durationSec: number;
|
|
117
|
+
endsAtSec: number;
|
|
118
|
+
commentTemplate: string;
|
|
119
|
+
/** Full TikTok CDN URL for the card art (webp/jpeg). */
|
|
120
|
+
iconUrl: string;
|
|
121
|
+
/** Short identifier e.g. 'card_mist_v3' / 'card_crit_v3' / 'top3_buffer'. */
|
|
122
|
+
iconKey: string;
|
|
123
|
+
/** Hex e.g. '#BCD9E0' (mist blue), '#E0D4BC' (gloves tan). */
|
|
124
|
+
accentColor: string;
|
|
125
|
+
}
|
|
68
126
|
/** Payload for `roomPin` (starred/pinned message) events. */
|
|
69
127
|
interface RoomPinEvent {
|
|
70
128
|
/** User who wrote the pinned message. */
|
|
@@ -128,6 +186,8 @@ interface TikTokLiveEventMap {
|
|
|
128
186
|
subscribe: SocialEvent;
|
|
129
187
|
roomUserSeq: RoomUserSeqEvent;
|
|
130
188
|
battle: BattleEvent;
|
|
189
|
+
battleArmies: BattleArmiesEvent;
|
|
190
|
+
battleItemCard: BattleItemCardEvent;
|
|
131
191
|
roomPin: RoomPinEvent;
|
|
132
192
|
envelope: Record<string, unknown>;
|
|
133
193
|
streamEnd: Record<string, unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -59,12 +59,70 @@ interface RoomUserSeqEvent {
|
|
|
59
59
|
viewerCount: number;
|
|
60
60
|
topViewers: TikTokUser[];
|
|
61
61
|
}
|
|
62
|
-
/** Payload for `battle` events. */
|
|
62
|
+
/** Payload for `battle` events (PK start / end / countdown). */
|
|
63
63
|
interface BattleEvent {
|
|
64
64
|
type: string;
|
|
65
|
+
battleId: string;
|
|
66
|
+
/** 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING */
|
|
67
|
+
status: number;
|
|
68
|
+
battleDuration: number;
|
|
65
69
|
teams: Array<Record<string, unknown>>;
|
|
66
70
|
scores: number[];
|
|
67
71
|
}
|
|
72
|
+
/** One host on a battle side โ present per-host in multi-guest PK. */
|
|
73
|
+
interface BattleHost {
|
|
74
|
+
hostUserId: string;
|
|
75
|
+
teamTotalScore: number;
|
|
76
|
+
teamIdx: number;
|
|
77
|
+
/** Sorted MVP first (highest score โ lowest). */
|
|
78
|
+
contributors: BattleContributor[];
|
|
79
|
+
}
|
|
80
|
+
interface BattleContributor {
|
|
81
|
+
userId: string;
|
|
82
|
+
score: number;
|
|
83
|
+
nickname: string;
|
|
84
|
+
}
|
|
85
|
+
/** Payload for `battleArmies` events โ score updates during PK. */
|
|
86
|
+
interface BattleArmiesEvent {
|
|
87
|
+
battleId: string;
|
|
88
|
+
/** 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING */
|
|
89
|
+
status: number;
|
|
90
|
+
teams: Array<Record<string, unknown>>;
|
|
91
|
+
matchId?: string;
|
|
92
|
+
sessionId?: string;
|
|
93
|
+
startedAtMs?: number;
|
|
94
|
+
serverTsMs?: number;
|
|
95
|
+
sessionTag?: string;
|
|
96
|
+
durationSec?: number;
|
|
97
|
+
secsRemaining?: number;
|
|
98
|
+
/** Per-host breakdown with MVP contributors. */
|
|
99
|
+
hosts?: BattleHost[];
|
|
100
|
+
}
|
|
101
|
+
/** Payload for `battleItemCard` events โ multipliers (x2/x3), gloves, mist, etc. */
|
|
102
|
+
interface BattleItemCardEvent {
|
|
103
|
+
battleId: string;
|
|
104
|
+
cardType: number;
|
|
105
|
+
/** 'gloves' | 'mist' | 'booster_x2' | 'booster_x3' | 'match_guide' |
|
|
106
|
+
* 'thunder' | 'extra_time' | raw resource key. */
|
|
107
|
+
effect: string;
|
|
108
|
+
effectKey: string;
|
|
109
|
+
/** 2 or 3 for booster_x2/x3, otherwise 0. */
|
|
110
|
+
multiplier: number;
|
|
111
|
+
senderUserId: string;
|
|
112
|
+
senderNickname: string;
|
|
113
|
+
senderUniqueId: string;
|
|
114
|
+
senderAvatarUrl: string;
|
|
115
|
+
activatedAtSec: number;
|
|
116
|
+
durationSec: number;
|
|
117
|
+
endsAtSec: number;
|
|
118
|
+
commentTemplate: string;
|
|
119
|
+
/** Full TikTok CDN URL for the card art (webp/jpeg). */
|
|
120
|
+
iconUrl: string;
|
|
121
|
+
/** Short identifier e.g. 'card_mist_v3' / 'card_crit_v3' / 'top3_buffer'. */
|
|
122
|
+
iconKey: string;
|
|
123
|
+
/** Hex e.g. '#BCD9E0' (mist blue), '#E0D4BC' (gloves tan). */
|
|
124
|
+
accentColor: string;
|
|
125
|
+
}
|
|
68
126
|
/** Payload for `roomPin` (starred/pinned message) events. */
|
|
69
127
|
interface RoomPinEvent {
|
|
70
128
|
/** User who wrote the pinned message. */
|
|
@@ -128,6 +186,8 @@ interface TikTokLiveEventMap {
|
|
|
128
186
|
subscribe: SocialEvent;
|
|
129
187
|
roomUserSeq: RoomUserSeqEvent;
|
|
130
188
|
battle: BattleEvent;
|
|
189
|
+
battleArmies: BattleArmiesEvent;
|
|
190
|
+
battleItemCard: BattleItemCardEvent;
|
|
131
191
|
roomPin: RoomPinEvent;
|
|
132
192
|
envelope: Record<string, unknown>;
|
|
133
193
|
streamEnd: Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,88 +1,84 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "tiktok-live-api",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Unofficial TikTok LIVE API Client โ Real-time chat, gifts, viewers, battles, and AI live captions from any TikTok livestream. Managed WebSocket API with 99.9% uptime.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"tiktok",
|
|
30
|
-
"tiktok-live",
|
|
31
|
-
"tiktok-
|
|
32
|
-
"tiktok-
|
|
33
|
-
"tiktok-
|
|
34
|
-
"tiktok-
|
|
35
|
-
"tiktok-
|
|
36
|
-
"tiktok-
|
|
37
|
-
"tiktok-
|
|
38
|
-
"tiktok-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"tiktok-
|
|
53
|
-
"tiktok-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"tiktok-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
},
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"engines": {
|
|
86
|
-
"node": ">=16.0.0"
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "tiktok-live-api",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Unofficial TikTok LIVE API Client โ Real-time chat, gifts, viewers, battles, and AI live captions from any TikTok livestream. Managed WebSocket API with 99.9% uptime.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"tiktok",
|
|
26
|
+
"tiktok-live",
|
|
27
|
+
"tiktok-api",
|
|
28
|
+
"tiktok-live-api",
|
|
29
|
+
"tiktok-live-connector",
|
|
30
|
+
"tiktok-live-sdk",
|
|
31
|
+
"tiktok-websocket",
|
|
32
|
+
"tiktok-chat",
|
|
33
|
+
"tiktok-gifts",
|
|
34
|
+
"tiktok-bot",
|
|
35
|
+
"tiktok-data",
|
|
36
|
+
"tiktok-stream",
|
|
37
|
+
"tiktok-events",
|
|
38
|
+
"tiktok-viewer",
|
|
39
|
+
"live-streaming",
|
|
40
|
+
"live-chat",
|
|
41
|
+
"webcast",
|
|
42
|
+
"websocket",
|
|
43
|
+
"real-time",
|
|
44
|
+
"speech-to-text",
|
|
45
|
+
"captions",
|
|
46
|
+
"transcription",
|
|
47
|
+
"translation",
|
|
48
|
+
"tiktok-live-python",
|
|
49
|
+
"tiktok-tools",
|
|
50
|
+
"euler-stream",
|
|
51
|
+
"tiktoklive",
|
|
52
|
+
"tiktok-connector",
|
|
53
|
+
"tiktok-live-node",
|
|
54
|
+
"tiktok-live-js",
|
|
55
|
+
"tiktok-scraper",
|
|
56
|
+
"tiktok-monitoring",
|
|
57
|
+
"livestream-api"
|
|
58
|
+
],
|
|
59
|
+
"author": {
|
|
60
|
+
"name": "TikTool",
|
|
61
|
+
"email": "support@tik.tools",
|
|
62
|
+
"url": "https://tik.tools"
|
|
63
|
+
},
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "https://github.com/tiktool/tiktok-live-api"
|
|
68
|
+
},
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://github.com/tiktool/tiktok-live-api/issues"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://tik.tools",
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"ws": "^8.16.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@types/ws": "^8.5.10",
|
|
78
|
+
"tsup": "^8.0.0",
|
|
79
|
+
"typescript": "^5.3.0"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=16.0.0"
|
|
83
|
+
}
|
|
84
|
+
}
|
package/bin/demo.mjs
DELETED
|
@@ -1,689 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import WebSocket from 'ws';
|
|
4
|
-
import readline from 'readline';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
|
|
9
|
-
// โโ Boilerplate Components โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
10
|
-
|
|
11
|
-
const REACT_TEMPLATE = [
|
|
12
|
-
'"use client";',
|
|
13
|
-
"import React, { useEffect, useState, useRef } from 'react';",
|
|
14
|
-
"",
|
|
15
|
-
"export default function TikTokLiveComponent() {",
|
|
16
|
-
" const [events, setEvents] = useState([]);",
|
|
17
|
-
" const [inputUsername, setInputUsername] = useState('gbnews');",
|
|
18
|
-
" const [apiKey, setApiKey] = useState('demo_tiktokliveapi_public_2026');",
|
|
19
|
-
" const [connected, setConnected] = useState(false);",
|
|
20
|
-
" const wsRef = useRef(null);",
|
|
21
|
-
"",
|
|
22
|
-
" function addEvent(str) {",
|
|
23
|
-
" setEvents(prev => [...prev.slice(-50), str]);",
|
|
24
|
-
" }",
|
|
25
|
-
"",
|
|
26
|
-
" function connectStream(e) {",
|
|
27
|
-
" if (e) e.preventDefault();",
|
|
28
|
-
" if (wsRef.current) { wsRef.current.close(); wsRef.current = null; }",
|
|
29
|
-
" setEvents([]);",
|
|
30
|
-
" setConnected(false);",
|
|
31
|
-
" const user = (inputUsername || '').replace(/^@/, '');",
|
|
32
|
-
" if (!user) return;",
|
|
33
|
-
" try {",
|
|
34
|
-
" const ws = new WebSocket('wss://api.tik.tools?uniqueId=' + user + '&apiKey=' + apiKey);",
|
|
35
|
-
" wsRef.current = ws;",
|
|
36
|
-
" ws.onopen = () => { setConnected(true); addEvent('Connected to @' + user); };",
|
|
37
|
-
" ws.onclose = () => { setConnected(false); wsRef.current = null; };",
|
|
38
|
-
" ws.onerror = () => { addEvent('WebSocket error'); };",
|
|
39
|
-
" ws.onmessage = (evt) => {",
|
|
40
|
-
" try {",
|
|
41
|
-
" const msg = JSON.parse(evt.data);",
|
|
42
|
-
" const t = msg.event || '';",
|
|
43
|
-
" const d = msg.data || {};",
|
|
44
|
-
" const u = d.user ? d.user.uniqueId : '';",
|
|
45
|
-
" if (t === 'chat') addEvent('[chat] ' + u + ': ' + d.comment);",
|
|
46
|
-
" else if (t === 'gift') addEvent('[gift] ' + u + ' sent ' + (d.giftName || 'a gift'));",
|
|
47
|
-
" else if (t === 'like') addEvent('[like] ' + u + ' liked');",
|
|
48
|
-
" else if (t === 'member') addEvent('[join] ' + u + ' joined');",
|
|
49
|
-
" else if (t === 'roomUserSeq') addEvent('[viewers] ' + (d.viewerCount || 0));",
|
|
50
|
-
" } catch (err) {}",
|
|
51
|
-
" };",
|
|
52
|
-
" } catch (err) { addEvent('Connection failed: ' + err.message); }",
|
|
53
|
-
" }",
|
|
54
|
-
"",
|
|
55
|
-
" useEffect(() => { return () => { if (wsRef.current) wsRef.current.close(); }; }, []);",
|
|
56
|
-
"",
|
|
57
|
-
" return (",
|
|
58
|
-
" <div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 600, margin: '40px auto', border: '1px solid #e5e7eb', borderRadius: '12px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', background: '#fff' }}>",
|
|
59
|
-
" <div style={{ padding: '24px', borderBottom: '1px solid #e5e7eb' }}>",
|
|
60
|
-
" <h2 style={{ margin: '0 0 8px 0', fontSize: '1.5rem', fontWeight: 700, color: '#111827' }}>TikTok Live SDK</h2>",
|
|
61
|
-
" <p style={{ margin: 0, fontSize: '0.875rem', color: '#4b5563' }}>",
|
|
62
|
-
' Get your own API key at <a href="https://tik.tools" target="_blank" style={{ color: \'#3b82f6\', textDecoration: \'none\' }}>tik.tools</a>',
|
|
63
|
-
" </p>",
|
|
64
|
-
" </div>",
|
|
65
|
-
" <div style={{ padding: '24px' }}>",
|
|
66
|
-
" <form onSubmit={connectStream} style={{ display: 'flex', flexDirection: 'column', gap: '16px', marginBottom: '24px' }}>",
|
|
67
|
-
" <div>",
|
|
68
|
-
" <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '6px', color: '#111827' }}>TikTok Username</label>",
|
|
69
|
-
' <input type="text" value={inputUsername} onChange={(e) => setInputUsername(e.target.value)} style={{ width: \'100%\', padding: \'8px 12px\', borderRadius: \'6px\', border: \'1px solid #d1d5db\', boxSizing: \'border-box\', color: \'#111827\' }} placeholder="e.g. gbnews" />',
|
|
70
|
-
" </div>",
|
|
71
|
-
" <div>",
|
|
72
|
-
" <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '6px', color: '#111827' }}>API Key</label>",
|
|
73
|
-
' <input type="text" value={apiKey} onChange={(e) => setApiKey(e.target.value)} style={{ width: \'100%\', padding: \'8px 12px\', borderRadius: \'6px\', border: \'1px solid #d1d5db\', boxSizing: \'border-box\', fontFamily: \'monospace\', color: \'#111827\' }} />',
|
|
74
|
-
" <p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#6b7280' }}>The demo key is rate-limited. Replace with your own key for production.</p>",
|
|
75
|
-
" </div>",
|
|
76
|
-
' <button type="submit" style={{ background: \'#111827\', color: \'#fff\', padding: \'10px 16px\', borderRadius: \'6px\', border: \'none\', fontWeight: 600, cursor: \'pointer\', fontSize: \'0.875rem\' }}>Connect Stream</button>',
|
|
77
|
-
" </form>",
|
|
78
|
-
" <div style={{ marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>",
|
|
79
|
-
" <div style={{ width: 10, height: 10, borderRadius: '50%', background: connected ? '#10b981' : '#ef4444', transition: 'background 0.3s' }}></div>",
|
|
80
|
-
" <span style={{ fontSize: '0.875rem', color: '#111827', fontWeight: 600 }}>{connected ? 'Connected' : 'Disconnected'}</span>",
|
|
81
|
-
" </div>",
|
|
82
|
-
" <div style={{ height: 350, overflowY: 'auto', background: '#f9fafb', padding: '12px', borderRadius: '8px', border: '1px solid #e5e7eb', fontSize: '0.875rem', display: 'flex', flexDirection: 'column' }}>",
|
|
83
|
-
" {events.map((e, i) => <div key={i} style={{ padding: '6px 0', borderBottom: '1px solid #f3f4f6', wordBreak: 'break-word', color: '#374151' }}>{e}</div>)}",
|
|
84
|
-
" {events.length === 0 && <div style={{ color: '#9ca3af', textAlign: 'center', margin: 'auto 0' }}>{connected ? 'Waiting for events...' : 'Connect to see events'}</div>}",
|
|
85
|
-
" </div>",
|
|
86
|
-
" </div>",
|
|
87
|
-
" </div>",
|
|
88
|
-
" );",
|
|
89
|
-
"}",
|
|
90
|
-
].join('\n') + "\n";
|
|
91
|
-
|
|
92
|
-
const VUE_TEMPLATE = [
|
|
93
|
-
"<template>",
|
|
94
|
-
' <div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 40px auto; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); background: #fff">',
|
|
95
|
-
' <div style="padding: 24px; border-bottom: 1px solid #e5e7eb">',
|
|
96
|
-
' <h2 style="margin: 0 0 8px 0; font-size: 1.5rem; font-weight: 600">TikTok Live SDK</h2>',
|
|
97
|
-
' <p style="margin: 0; font-size: 0.875rem; color: #6b7280">',
|
|
98
|
-
' Get your own API key and unlock unlimited connections at <a href="https://tik.tools" target="_blank" style="color: #3b82f6; text-decoration: none">tik.tools</a>',
|
|
99
|
-
" </p>",
|
|
100
|
-
" </div>",
|
|
101
|
-
' <div style="padding: 24px">',
|
|
102
|
-
' <form @submit.prevent="handleConnect" style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px">',
|
|
103
|
-
" <div>",
|
|
104
|
-
' <label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 6px">TikTok Username</label>',
|
|
105
|
-
' <input type="text" v-model="inputUsername" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #d1d5db; box-sizing: border-box" placeholder="e.g. gbnews" />',
|
|
106
|
-
" </div>",
|
|
107
|
-
" <div>",
|
|
108
|
-
' <label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 6px">API Key</label>',
|
|
109
|
-
' <input type="text" v-model="apiKey" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #d1d5db; box-sizing: border-box; font-family: monospace" />',
|
|
110
|
-
' <p style="margin: 4px 0 0 0; font-size: 0.75rem; color: #9ca3af">The demo key is rate-limited. Replace with your own key for production.</p>',
|
|
111
|
-
" </div>",
|
|
112
|
-
' <button type="submit" style="background: #000; color: #fff; padding: 10px 16px; border-radius: 6px; border: none; font-weight: 500; cursor: pointer">Connect Stream</button>',
|
|
113
|
-
" </form>",
|
|
114
|
-
' <div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px">',
|
|
115
|
-
" <div :style=\"{ width: '10px', height: '10px', borderRadius: '50%', background: connected ? '#10b981' : '#ef4444', transition: 'background 0.3s' }\"></div>",
|
|
116
|
-
" <span style=\"font-size: 0.875rem; color: '#374151'; font-weight: 500\">{{ connected ? 'Connected to @' + activeUsername : 'Disconnected' }}</span>",
|
|
117
|
-
" </div>",
|
|
118
|
-
' <div style="height: 350px; overflow-y: auto; background: #f9fafb; padding: 12px; border-radius: 8px; border: 1px solid #e5e7eb; font-size: 0.875rem; display: flex; flex-direction: column">',
|
|
119
|
-
' <div v-for="(e, i) in events" :key="i" style="padding: 6px 0; border-bottom: 1px solid #f3f4f6; word-break: break-word">',
|
|
120
|
-
" {{ e }}",
|
|
121
|
-
" </div>",
|
|
122
|
-
' <div v-if="events.length === 0" style="color: #9ca3af; text-align: center; margin: auto 0">{{ connected ? "Waiting for events..." : "Connect to a valid stream to see events" }}</div>',
|
|
123
|
-
" </div>",
|
|
124
|
-
" </div>",
|
|
125
|
-
" </div>",
|
|
126
|
-
"</template>",
|
|
127
|
-
"",
|
|
128
|
-
"<script setup>",
|
|
129
|
-
"import { ref, onMounted, onUnmounted } from 'vue'",
|
|
130
|
-
"",
|
|
131
|
-
"const inputUsername = ref('gbnews')",
|
|
132
|
-
"const activeUsername = ref('gbnews')",
|
|
133
|
-
"const apiKey = ref('demo_tiktokliveapi_public_2026')",
|
|
134
|
-
"const connected = ref(false)",
|
|
135
|
-
"const events = ref([])",
|
|
136
|
-
"let ws = null",
|
|
137
|
-
"",
|
|
138
|
-
"function addEvent(str) {",
|
|
139
|
-
" events.value.push(str)",
|
|
140
|
-
" if (events.value.length > 50) events.value.shift()",
|
|
141
|
-
"}",
|
|
142
|
-
"",
|
|
143
|
-
"function connectStream() {",
|
|
144
|
-
" if (ws) { ws.close(); ws = null }",
|
|
145
|
-
" events.value = []",
|
|
146
|
-
" connected.value = false",
|
|
147
|
-
" activeUsername.value = inputUsername.value",
|
|
148
|
-
" const user = (activeUsername.value || '').replace(/^@/, '')",
|
|
149
|
-
" if (!user) return",
|
|
150
|
-
" const key = apiKey.value || ''",
|
|
151
|
-
" try {",
|
|
152
|
-
" ws = new WebSocket('wss://api.tik.tools?uniqueId=' + user + '&apiKey=' + key)",
|
|
153
|
-
" } catch (e) { addEvent('โ ๏ธ Connection failed: ' + e.message); return }",
|
|
154
|
-
" ws.onopen = () => { connected.value = true; addEvent('โ
Connected to @' + user) }",
|
|
155
|
-
" ws.onclose = () => { connected.value = false; ws = null; addEvent('โ Disconnected') }",
|
|
156
|
-
" ws.onerror = () => { addEvent('โ ๏ธ WebSocket error') }",
|
|
157
|
-
" ws.onmessage = (evt) => {",
|
|
158
|
-
" try {",
|
|
159
|
-
" const msg = JSON.parse(evt.data)",
|
|
160
|
-
" const t = msg.event || ''",
|
|
161
|
-
" const d = msg.data || {}",
|
|
162
|
-
" const u = d.user ? d.user.uniqueId : ''",
|
|
163
|
-
" if (t === 'chat') addEvent('๐ฌ ' + u + ': ' + d.comment)",
|
|
164
|
-
" else if (t === 'gift') addEvent('๐ ' + u + ' sent ' + (d.giftName || 'a gift'))",
|
|
165
|
-
" else if (t === 'like') addEvent('โค๏ธ ' + u + ' liked')",
|
|
166
|
-
" else if (t === 'member') addEvent('๐ ' + u + ' joined')",
|
|
167
|
-
" } catch (e) {}",
|
|
168
|
-
" }",
|
|
169
|
-
"}",
|
|
170
|
-
"",
|
|
171
|
-
"const handleConnect = () => { connectStream() }",
|
|
172
|
-
"",
|
|
173
|
-
"onMounted(() => { connectStream() })",
|
|
174
|
-
"onUnmounted(() => { if (ws) ws.close() })",
|
|
175
|
-
"</script>"
|
|
176
|
-
].join('\n') + "\n";
|
|
177
|
-
|
|
178
|
-
const E = String.fromCharCode(27);
|
|
179
|
-
const R = E + "[0m";
|
|
180
|
-
const B = E + "[1m";
|
|
181
|
-
const D = E + "[2m";
|
|
182
|
-
const C = {
|
|
183
|
-
cyan: E + "[38;5;80m", green: E + "[38;5;114m", yellow: E + "[38;5;222m",
|
|
184
|
-
mag: E + "[38;5;176m", blue: E + "[38;5;111m", red: E + "[38;5;203m",
|
|
185
|
-
gray: E + "[38;5;242m", white: E + "[38;5;252m", pink: E + "[38;5;218m",
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// โโ Interactive Prompt โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
189
|
-
|
|
190
|
-
const args = process.argv.slice(2);
|
|
191
|
-
const isInteractive = args.length === 0;
|
|
192
|
-
|
|
193
|
-
if (!isInteractive) {
|
|
194
|
-
runDemo();
|
|
195
|
-
} else {
|
|
196
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
197
|
-
|
|
198
|
-
console.log("\n " + B + "tiktok-live-api" + R + "\n Unofficial TikTok LIVE SDK by TikTool\n");
|
|
199
|
-
console.log(" What would you like to do?");
|
|
200
|
-
console.log(" 1) " + C.cyan + "Watch Live Terminal Demo" + R);
|
|
201
|
-
console.log(" 2) " + C.green + "Scaffold a Nuxt 3 (Vue) App" + R);
|
|
202
|
-
console.log(" 3) " + C.blue + "Scaffold a Next.js (React) App" + R);
|
|
203
|
-
console.log(" 4) " + C.yellow + "Scaffold a Vite (React) App" + R);
|
|
204
|
-
console.log(" 5) " + C.mag + "Scaffold a Plain Node.js Project" + R);
|
|
205
|
-
console.log(" 6) " + C.pink + "Scaffold a Python Project" + R + "\n");
|
|
206
|
-
|
|
207
|
-
function promptAction() {
|
|
208
|
-
rl.question(" Choose [1-6]: ", (answer) => {
|
|
209
|
-
handleMenu(answer.trim());
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function handleMenu(choice) {
|
|
214
|
-
if (choice === '1') {
|
|
215
|
-
rl.close();
|
|
216
|
-
return runDemo();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (!['2', '3', '4', '5', '6'].includes(choice)) {
|
|
220
|
-
console.log(" " + C.red + "Invalid choice." + R);
|
|
221
|
-
return promptAction();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
rl.question("\n Target directory (e.g. ./my-app) [./tiktok-live-project]: ", (dir) => {
|
|
225
|
-
dir = dir.trim() || './tiktok-live-project';
|
|
226
|
-
|
|
227
|
-
if (choice === '6') {
|
|
228
|
-
rl.close();
|
|
229
|
-
return runScaffold(choice, dir, 'pip');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
console.log("\n Select Package Manager:");
|
|
233
|
-
console.log(" 1) npm " + D + "(Default)" + R);
|
|
234
|
-
console.log(" 2) yarn");
|
|
235
|
-
console.log(" 3) pnpm");
|
|
236
|
-
console.log(" 4) bun");
|
|
237
|
-
|
|
238
|
-
rl.question("\n Choose [1-4]: ", (pmAnswer) => {
|
|
239
|
-
const pmChoice = pmAnswer.trim() || '1';
|
|
240
|
-
const pmMap = { '1': 'npm', '2': 'yarn', '3': 'pnpm', '4': 'bun' };
|
|
241
|
-
const pm = pmMap[pmChoice] || 'npm';
|
|
242
|
-
|
|
243
|
-
rl.close();
|
|
244
|
-
runScaffold(choice, dir, pm);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
promptAction();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// โโ Scaffolding Logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
253
|
-
|
|
254
|
-
function runScaffold(choice, targetDir, pm) {
|
|
255
|
-
const isNuxt = choice === '2';
|
|
256
|
-
const isNext = choice === '3';
|
|
257
|
-
const isVite = choice === '4';
|
|
258
|
-
const isNode = choice === '5';
|
|
259
|
-
const isPython = choice === '6';
|
|
260
|
-
const fullPath = path.resolve(process.cwd(), targetDir);
|
|
261
|
-
|
|
262
|
-
const npxCmd = pm === 'bun' ? 'bunx' : (pm === 'pnpm' ? 'pnpm dlx' : 'npx');
|
|
263
|
-
const installCmd = pm === 'npm' ? 'npm install tiktok-live-api' : pm === 'yarn' ? 'yarn add tiktok-live-api' : pm === 'pnpm' ? 'pnpm add tiktok-live-api' : 'bun add tiktok-live-api';
|
|
264
|
-
const devCmd = pm === 'npm' ? 'npm run dev' : pm === 'yarn' ? 'yarn dev' : pm === 'pnpm' ? 'pnpm dev' : 'bun run dev';
|
|
265
|
-
|
|
266
|
-
console.log("\n " + B + "๐ Scaffolding project into " + targetDir + " using " + pm + "..." + R + "\n");
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
if (isNode) {
|
|
270
|
-
if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
|
|
271
|
-
} else if (isPython) {
|
|
272
|
-
if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
|
|
273
|
-
} else if (isNuxt) {
|
|
274
|
-
execSync(npxCmd + ' nuxi@latest init "' + targetDir + '" --packageManager ' + pm, { stdio: 'inherit' });
|
|
275
|
-
} else if (isNext) {
|
|
276
|
-
execSync(npxCmd + ' create-next-app@latest "' + targetDir + '" --js --eslint --tailwind --app --src-dir --import-alias "@/*" --use-' + pm, { stdio: 'inherit' });
|
|
277
|
-
} else if (isVite) {
|
|
278
|
-
execSync(npxCmd + ' create-vite@latest "' + targetDir + '" --no-interactive --template react', { stdio: 'inherit' });
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (isNode) {
|
|
282
|
-
// โโ Plain Node.js Starter Kit โโ
|
|
283
|
-
const pkgJson = {
|
|
284
|
-
name: path.basename(fullPath),
|
|
285
|
-
version: '1.0.0',
|
|
286
|
-
type: 'module',
|
|
287
|
-
scripts: { start: 'node index.mjs', dev: 'node --watch index.mjs' },
|
|
288
|
-
dependencies: {}
|
|
289
|
-
};
|
|
290
|
-
fs.writeFileSync(path.join(fullPath, 'package.json'), JSON.stringify(pkgJson, null, 2) + '\n');
|
|
291
|
-
|
|
292
|
-
console.log("\n " + B + "๐ฆ Installing " + C.cyan + "tiktok-live-api" + B + " SDK..." + R + "\n");
|
|
293
|
-
execSync(installCmd, { cwd: fullPath, stdio: 'inherit' });
|
|
294
|
-
|
|
295
|
-
fs.writeFileSync(path.join(fullPath, 'index.mjs'), [
|
|
296
|
-
"import { TikTokLive } from 'tiktok-live-api';",
|
|
297
|
-
"",
|
|
298
|
-
"// โโ Configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
299
|
-
"const USERNAME = 'gbnews'; // TikTok @username",
|
|
300
|
-
"const API_KEY = 'demo_tiktokliveapi_public_2026'; // Get yours at https://tik.tools",
|
|
301
|
-
"",
|
|
302
|
-
"// โโ Connect โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
303
|
-
"const client = new TikTokLive(USERNAME, { apiKey: API_KEY });",
|
|
304
|
-
"",
|
|
305
|
-
"client.on('connected', () => {",
|
|
306
|
-
" console.log('\\nโ
Connected to @' + USERNAME + '\\n');",
|
|
307
|
-
"});",
|
|
308
|
-
"",
|
|
309
|
-
"client.on('chat', (data) => {",
|
|
310
|
-
" console.log(`๐ฌ ${data.user.uniqueId}: ${data.comment}`);",
|
|
311
|
-
"});",
|
|
312
|
-
"",
|
|
313
|
-
"client.on('gift', (data) => {",
|
|
314
|
-
" console.log(`๐ ${data.user.uniqueId} sent ${data.giftName} x${data.repeatCount}`);",
|
|
315
|
-
"});",
|
|
316
|
-
"",
|
|
317
|
-
"client.on('like', (data) => {",
|
|
318
|
-
" console.log(`โค๏ธ ${data.user.uniqueId} liked (total: ${data.totalLikeCount})`);",
|
|
319
|
-
"});",
|
|
320
|
-
"",
|
|
321
|
-
"client.on('member', (data) => {",
|
|
322
|
-
" console.log(`๐ ${data.user.uniqueId} joined`);",
|
|
323
|
-
"});",
|
|
324
|
-
"",
|
|
325
|
-
"client.on('roomUserSeq', (data) => {",
|
|
326
|
-
" console.log(`๐ Viewers: ${data.viewerCount}`);",
|
|
327
|
-
"});",
|
|
328
|
-
"",
|
|
329
|
-
"client.on('disconnected', () => {",
|
|
330
|
-
" console.log('\\nโ Disconnected\\n');",
|
|
331
|
-
"});",
|
|
332
|
-
"",
|
|
333
|
-
"client.on('error', (err) => {",
|
|
334
|
-
" console.error('โ ๏ธ Error:', err.message);",
|
|
335
|
-
"});",
|
|
336
|
-
"",
|
|
337
|
-
"console.log('๐ Connecting to @' + USERNAME + '...');",
|
|
338
|
-
"client.connect().catch((err) => {",
|
|
339
|
-
" console.error('Failed to connect:', err.message);",
|
|
340
|
-
" process.exit(1);",
|
|
341
|
-
"});",
|
|
342
|
-
].join('\n') + '\n');
|
|
343
|
-
|
|
344
|
-
fs.writeFileSync(path.join(fullPath, 'README.md'), [
|
|
345
|
-
'# TikTok Live - Node.js Starter',
|
|
346
|
-
'',
|
|
347
|
-
'A ready-to-run Node.js project connecting to TikTok LIVE streams.',
|
|
348
|
-
'',
|
|
349
|
-
'## Quick Start',
|
|
350
|
-
'',
|
|
351
|
-
'```bash',
|
|
352
|
-
'node index.mjs',
|
|
353
|
-
'```',
|
|
354
|
-
'',
|
|
355
|
-
'## Configuration',
|
|
356
|
-
'',
|
|
357
|
-
'Edit `index.mjs` to change:',
|
|
358
|
-
'- `USERNAME` โ the TikTok @username to watch',
|
|
359
|
-
'- `API_KEY` โ get your own at [tik.tools](https://tik.tools)',
|
|
360
|
-
'',
|
|
361
|
-
'## Events',
|
|
362
|
-
'',
|
|
363
|
-
'The demo listens for: `chat`, `gift`, `like`, `member`, `roomUserSeq`.',
|
|
364
|
-
'See the [full docs](https://www.npmjs.com/package/tiktok-live-api) for all available events.',
|
|
365
|
-
'',
|
|
366
|
-
].join('\n'));
|
|
367
|
-
|
|
368
|
-
} else if (isPython) {
|
|
369
|
-
// โโ Python Starter Kit โโ
|
|
370
|
-
fs.writeFileSync(path.join(fullPath, 'main.py'), [
|
|
371
|
-
'"""TikTok Live - Python Starter Kit"""',
|
|
372
|
-
'',
|
|
373
|
-
'import asyncio',
|
|
374
|
-
'import json',
|
|
375
|
-
'import websockets',
|
|
376
|
-
'',
|
|
377
|
-
'# \u2500\u2500 Configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500',
|
|
378
|
-
"USERNAME = 'gbnews' # TikTok @username",
|
|
379
|
-
"API_KEY = 'demo_tiktokliveapi_public_2026' # Get yours at https://tik.tools",
|
|
380
|
-
'',
|
|
381
|
-
'',
|
|
382
|
-
'async def main():',
|
|
383
|
-
' """Connect to TikTok LIVE and print events."""',
|
|
384
|
-
' uri = f"wss://api.tik.tools?uniqueId={USERNAME}&apiKey={API_KEY}"',
|
|
385
|
-
' print(f"Connecting to @{USERNAME}...")',
|
|
386
|
-
'',
|
|
387
|
-
' try:',
|
|
388
|
-
' async with websockets.connect(uri) as ws:',
|
|
389
|
-
' print(f"Connected to @{USERNAME}")',
|
|
390
|
-
' print()',
|
|
391
|
-
' async for raw in ws:',
|
|
392
|
-
' try:',
|
|
393
|
-
" msg = json.loads(raw)",
|
|
394
|
-
" event = msg.get('event', '')",
|
|
395
|
-
" data = msg.get('data', {})",
|
|
396
|
-
" user = data.get('user', {}).get('uniqueId', '?')",
|
|
397
|
-
'',
|
|
398
|
-
" if event == 'chat':",
|
|
399
|
-
" comment = data.get('comment', '')",
|
|
400
|
-
' print(f"[chat] {user}: {comment}")',
|
|
401
|
-
" elif event == 'gift':",
|
|
402
|
-
" gift = data.get('giftName', 'a gift')",
|
|
403
|
-
" count = data.get('repeatCount', 1)",
|
|
404
|
-
' print(f"[gift] {user} sent {gift} x{count}")',
|
|
405
|
-
" elif event == 'like':",
|
|
406
|
-
" total = data.get('totalLikeCount', 0)",
|
|
407
|
-
' print(f"[like] {user} liked (total: {total})")',
|
|
408
|
-
" elif event == 'member':",
|
|
409
|
-
' print(f"[join] {user} joined")',
|
|
410
|
-
" elif event == 'roomUserSeq':",
|
|
411
|
-
" viewers = data.get('viewerCount', 0)",
|
|
412
|
-
' print(f"[viewers] {viewers}")',
|
|
413
|
-
' except json.JSONDecodeError:',
|
|
414
|
-
' pass',
|
|
415
|
-
' except KeyboardInterrupt:',
|
|
416
|
-
' print("Disconnected.")',
|
|
417
|
-
' except Exception as e:',
|
|
418
|
-
' print(f"Error: {e}")',
|
|
419
|
-
'',
|
|
420
|
-
'',
|
|
421
|
-
'if __name__ == "__main__":',
|
|
422
|
-
' asyncio.run(main())',
|
|
423
|
-
].join('\n') + '\n');
|
|
424
|
-
|
|
425
|
-
fs.writeFileSync(path.join(fullPath, 'requirements.txt'), 'websockets>=12.0\n');
|
|
426
|
-
|
|
427
|
-
fs.writeFileSync(path.join(fullPath, 'README.md'), [
|
|
428
|
-
'# TikTok Live - Python Starter',
|
|
429
|
-
'',
|
|
430
|
-
'A ready-to-run Python project connecting to TikTok LIVE streams.',
|
|
431
|
-
'',
|
|
432
|
-
'## Quick Start',
|
|
433
|
-
'',
|
|
434
|
-
'```bash',
|
|
435
|
-
'pip install -r requirements.txt',
|
|
436
|
-
'python main.py',
|
|
437
|
-
'```',
|
|
438
|
-
'',
|
|
439
|
-
'## Configuration',
|
|
440
|
-
'',
|
|
441
|
-
'Edit `main.py` to change:',
|
|
442
|
-
'- `USERNAME` โ the TikTok @username to watch',
|
|
443
|
-
'- `API_KEY` โ get your own at [tik.tools](https://tik.tools)',
|
|
444
|
-
'',
|
|
445
|
-
'## Events',
|
|
446
|
-
'',
|
|
447
|
-
'The demo listens for: `chat`, `gift`, `like`, `member`, `roomUserSeq`.',
|
|
448
|
-
'',
|
|
449
|
-
].join('\n'));
|
|
450
|
-
|
|
451
|
-
console.log("\n " + B + "๐ฆ Installing " + C.cyan + "websockets" + B + "..." + R + "\n");
|
|
452
|
-
try { execSync('pip install websockets', { cwd: fullPath, stdio: 'inherit' }); } catch (e) {
|
|
453
|
-
console.log(" " + C.yellow + "Note: run 'pip install -r requirements.txt' manually if pip failed." + R);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
} else if (isNuxt) {
|
|
457
|
-
const isNuxt4 = fs.existsSync(path.join(fullPath, 'app', 'app.vue')) || !fs.existsSync(path.join(fullPath, 'app.vue'));
|
|
458
|
-
const baseApp = isNuxt4 ? path.join(fullPath, 'app') : fullPath;
|
|
459
|
-
|
|
460
|
-
const compDir = path.join(baseApp, 'components');
|
|
461
|
-
if (!fs.existsSync(compDir)) fs.mkdirSync(compDir, { recursive: true });
|
|
462
|
-
fs.writeFileSync(path.join(compDir, 'TikTokLive.vue'), VUE_TEMPLATE);
|
|
463
|
-
|
|
464
|
-
const appVue = path.join(baseApp, 'app.vue');
|
|
465
|
-
fs.writeFileSync(appVue, [
|
|
466
|
-
'<template>',
|
|
467
|
-
' <main style="min-height: 100vh; background: #f9fafb; padding-top: 40px;">',
|
|
468
|
-
' <ClientOnly>',
|
|
469
|
-
' <TikTokLive />',
|
|
470
|
-
' </ClientOnly>',
|
|
471
|
-
' </main>',
|
|
472
|
-
'</template>'
|
|
473
|
-
].join('\n') + '\n');
|
|
474
|
-
} else {
|
|
475
|
-
if (isNext) {
|
|
476
|
-
const compDir = path.join(fullPath, 'src', 'components');
|
|
477
|
-
if (!fs.existsSync(compDir)) fs.mkdirSync(compDir, { recursive: true });
|
|
478
|
-
fs.writeFileSync(path.join(compDir, 'TikTokLive.jsx'), REACT_TEMPLATE);
|
|
479
|
-
|
|
480
|
-
// Write page.js โ create the directory if needed
|
|
481
|
-
const pageDir = path.join(fullPath, 'src', 'app');
|
|
482
|
-
if (!fs.existsSync(pageDir)) fs.mkdirSync(pageDir, { recursive: true });
|
|
483
|
-
fs.writeFileSync(path.join(pageDir, 'page.js'), [
|
|
484
|
-
"import TikTokLive from '@/components/TikTokLive';",
|
|
485
|
-
"",
|
|
486
|
-
"export default function Home() {",
|
|
487
|
-
" return (",
|
|
488
|
-
' <main style={{ minHeight: "100vh", background: "#f9fafb", paddingTop: "40px" }}>',
|
|
489
|
-
" <TikTokLive />",
|
|
490
|
-
" </main>",
|
|
491
|
-
" );",
|
|
492
|
-
"}",
|
|
493
|
-
].join('\n') + '\n');
|
|
494
|
-
} else if (isVite) {
|
|
495
|
-
const srcDir = path.join(fullPath, 'src');
|
|
496
|
-
const compDir = path.join(srcDir, 'components');
|
|
497
|
-
if (!fs.existsSync(compDir)) fs.mkdirSync(compDir, { recursive: true });
|
|
498
|
-
fs.writeFileSync(path.join(compDir, 'TikTokLive.jsx'), REACT_TEMPLATE);
|
|
499
|
-
|
|
500
|
-
// Write App.jsx
|
|
501
|
-
fs.writeFileSync(path.join(srcDir, 'App.jsx'), [
|
|
502
|
-
"import TikTokLive from './components/TikTokLive';",
|
|
503
|
-
"",
|
|
504
|
-
"export default function App() {",
|
|
505
|
-
" return (",
|
|
506
|
-
' <main style={{ minHeight: "100vh", background: "#f9fafb", paddingTop: "40px" }}>',
|
|
507
|
-
" <TikTokLive />",
|
|
508
|
-
" </main>",
|
|
509
|
-
" );",
|
|
510
|
-
"}",
|
|
511
|
-
].join('\n') + '\n');
|
|
512
|
-
|
|
513
|
-
// Write main.jsx entry point
|
|
514
|
-
fs.writeFileSync(path.join(srcDir, 'main.jsx'), [
|
|
515
|
-
"import React from 'react';",
|
|
516
|
-
"import ReactDOM from 'react-dom/client';",
|
|
517
|
-
"import App from './App';",
|
|
518
|
-
"",
|
|
519
|
-
"ReactDOM.createRoot(document.getElementById('root')).render(",
|
|
520
|
-
" <React.StrictMode>",
|
|
521
|
-
" <App />",
|
|
522
|
-
" </React.StrictMode>",
|
|
523
|
-
");",
|
|
524
|
-
].join('\n') + '\n');
|
|
525
|
-
|
|
526
|
-
// Clean up TypeScript files that conflict with our JSX files
|
|
527
|
-
for (const f of ['App.tsx', 'App.ts', 'main.tsx', 'main.ts']) {
|
|
528
|
-
const fp = path.join(srcDir, f);
|
|
529
|
-
if (fs.existsSync(fp)) fs.unlinkSync(fp);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Update index.html to reference main.jsx instead of main.ts/main.tsx
|
|
533
|
-
const indexHtml = path.join(fullPath, 'index.html');
|
|
534
|
-
if (fs.existsSync(indexHtml)) {
|
|
535
|
-
let html = fs.readFileSync(indexHtml, 'utf8');
|
|
536
|
-
html = html.replace(/src=\/src\/main\.tsx?/g, 'src=/src/main.jsx');
|
|
537
|
-
html = html.replace(/src="\/src\/main\.tsx?"/g, 'src="/src/main.jsx"');
|
|
538
|
-
fs.writeFileSync(indexHtml, html);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Install dependencies for Vite (create-vite --no-interactive doesn't auto-install)
|
|
542
|
-
if (isVite) {
|
|
543
|
-
const installBase = pm === 'npm' ? 'npm install' : pm === 'yarn' ? 'yarn' : pm === 'pnpm' ? 'pnpm install' : 'bun install';
|
|
544
|
-
console.log("\n " + B + "๐ฆ Installing dependencies..." + R + "\n");
|
|
545
|
-
execSync(installBase, { cwd: fullPath, stdio: 'inherit' });
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
console.log("\n " + C.green + "โ Project successfully scaffolded!" + R + "\n");
|
|
551
|
-
console.log(" " + B + "Next steps:" + R);
|
|
552
|
-
console.log(" cd " + targetDir);
|
|
553
|
-
if (isPython) {
|
|
554
|
-
console.log(" python main.py\n");
|
|
555
|
-
} else if (isNode) {
|
|
556
|
-
console.log(" node index.mjs\n");
|
|
557
|
-
} else {
|
|
558
|
-
console.log(" " + devCmd + "\n");
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
} catch (err) {
|
|
562
|
-
console.error("\n " + C.red + "Error during scaffolding:" + R, err.message);
|
|
563
|
-
process.exit(1);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// โโ Terminal Demo Logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
568
|
-
|
|
569
|
-
async function runDemo() {
|
|
570
|
-
const WS_BASE = 'wss://api.tik.tools';
|
|
571
|
-
const DEMO_KEY = 'demo_tiktokliveapi_public_2026';
|
|
572
|
-
const CHANNELS = [
|
|
573
|
-
'gbnews', 'cgtnofficial', 'france24_en',
|
|
574
|
-
'weathernewslive', 'gbnews', 'bbcnews',
|
|
575
|
-
'skynews', 'tv_asahi_news', 'abc7chicago', 'thairath_news',
|
|
576
|
-
];
|
|
577
|
-
|
|
578
|
-
const TAG = {
|
|
579
|
-
chat: C.cyan + "chat" + R,
|
|
580
|
-
gift: C.yellow + "gift" + R,
|
|
581
|
-
like: C.mag + "like" + R,
|
|
582
|
-
member: C.green + "join" + R,
|
|
583
|
-
follow: C.green + "follow" + R,
|
|
584
|
-
viewer: C.blue + "viewers" + R,
|
|
585
|
-
share: C.white + "share" + R,
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
let target = '';
|
|
589
|
-
let apiKey = DEMO_KEY;
|
|
590
|
-
for (let i = 0; i < args.length; i++) {
|
|
591
|
-
const a = args[i];
|
|
592
|
-
if (a === '--key' || a === '-k') { apiKey = args[++i] || DEMO_KEY; }
|
|
593
|
-
else if (a === '--help' || a === '-h') { help(); process.exit(0); }
|
|
594
|
-
else if (!a.startsWith('-')) { target = a.replace(/^@/, ''); }
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function help() {
|
|
598
|
-
console.log("\n " + B + "tiktok-live-api" + R + " " + D + "Real-time TikTok Live SDK" + R);
|
|
599
|
-
console.log("\n " + B + "Usage" + R);
|
|
600
|
-
console.log(" " + C.cyan + "npx tiktok-live-api" + R + " " + D + "interactive menu (demo or scaffold)" + R);
|
|
601
|
-
console.log(" " + C.cyan + "npx tiktok-live-api" + R + " " + C.white + "@username" + R + " " + D + "connect to a specific user" + R);
|
|
602
|
-
console.log(" " + C.cyan + "npx tiktok-live-api" + R + " " + C.gray + "--key KEY" + R + " " + D + "use your own API key" + R);
|
|
603
|
-
console.log("\n " + D + "Unofficial API by TikTool ยท https://tik.tools" + R + "\n");
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function probe(uid, ms = 8000) {
|
|
607
|
-
return new Promise(resolve => {
|
|
608
|
-
const ws = new WebSocket(WS_BASE + "?uniqueId=" + uid + "&apiKey=" + apiKey);
|
|
609
|
-
const t = setTimeout(() => { ws.close(); resolve(null); }, ms);
|
|
610
|
-
ws.on('message', () => { clearTimeout(t); resolve(ws); });
|
|
611
|
-
ws.on('error', () => { clearTimeout(t); resolve(null); });
|
|
612
|
-
ws.on('close', () => { clearTimeout(t); });
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function ts() {
|
|
617
|
-
const d = new Date();
|
|
618
|
-
return C.gray + String(d.getHours()).padStart(2, '0') + ":" + String(d.getMinutes()).padStart(2, '0') + ":" + String(d.getSeconds()).padStart(2, '0') + R;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function fmt(event, data) {
|
|
622
|
-
const tag = TAG[event] || (C.gray + event.padEnd(7) + R);
|
|
623
|
-
const u = data.user?.uniqueId || '';
|
|
624
|
-
const pad = tag.length < 20 ? ' ' : ' ';
|
|
625
|
-
|
|
626
|
-
switch (event) {
|
|
627
|
-
case 'chat': return ts() + " " + tag + pad + " " + B + u + R + " " + (data.comment || '');
|
|
628
|
-
case 'gift': return ts() + " " + tag + pad + " " + B + u + R + " " + C.yellow + (data.giftName || 'gift') + R + " x" + (data.repeatCount || 1) + " " + D + "(" + (data.diamondCount || 0) + "๐)" + R;
|
|
629
|
-
case 'like': return ts() + " " + tag + pad + " " + B + u + R + " " + D + "total " + (data.totalLikeCount || 0).toLocaleString() + R;
|
|
630
|
-
case 'member': return ts() + " " + tag + pad + " " + C.green + u + R;
|
|
631
|
-
case 'follow': return ts() + " " + tag + " " + C.green + u + R;
|
|
632
|
-
case 'roomUserSeq':
|
|
633
|
-
case 'viewer': return ts() + " " + TAG.viewer + " " + B + (data.viewerCount || 0).toLocaleString() + R;
|
|
634
|
-
case 'share': return ts() + " " + tag + " " + u;
|
|
635
|
-
default: return null;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
console.log("\n " + B + "tiktok-live-api" + R + " " + D + "ยท" + R + " " + D + "Real-time TikTok Live events" + R);
|
|
640
|
-
console.log(" " + D + "Unofficial API by TikTool ยท https://tik.tools" + R + "\n");
|
|
641
|
-
|
|
642
|
-
let ws = null, who = '';
|
|
643
|
-
|
|
644
|
-
if (target) {
|
|
645
|
-
process.stdout.write(" " + D + "connecting to" + R + " " + B + "@" + target + R + " " + D + "..." + R);
|
|
646
|
-
ws = await probe(target);
|
|
647
|
-
if (ws) { who = target; console.log(" " + C.green + "โ" + R); }
|
|
648
|
-
else { console.log(" " + C.red + "โ" + R + "\n\n " + C.red + "Stream not found." + R + " Make sure " + B + "@" + target + R + " is live.\n"); process.exit(1); }
|
|
649
|
-
} else {
|
|
650
|
-
console.log(" " + D + "scanning for live streams..." + R + "\n");
|
|
651
|
-
for (const uid of CHANNELS) {
|
|
652
|
-
process.stdout.write(" " + C.gray + "@" + uid + R);
|
|
653
|
-
ws = await probe(uid);
|
|
654
|
-
if (ws) { who = uid; console.log(" " + C.green + "โ live" + R); break; }
|
|
655
|
-
else { console.log(" " + D + "โ" + R); }
|
|
656
|
-
}
|
|
657
|
-
if (!ws) {
|
|
658
|
-
console.log("\n " + C.red + "No live streams found." + R + " Try: " + C.cyan + "npx tiktok-live-api @username" + R + "\n");
|
|
659
|
-
console.log(" " + D + "Get your own API key at" + R + " " + C.cyan + "https://tik.tools" + R + "\n");
|
|
660
|
-
process.exit(1);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
console.log("\n " + C.green + "โ" + R + " " + B + "@" + who + R + " " + D + "โ streaming events. Ctrl+C to stop." + R);
|
|
665
|
-
console.log(" " + D + "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ" + R + "\n");
|
|
666
|
-
|
|
667
|
-
let count = 0;
|
|
668
|
-
ws.on('message', raw => {
|
|
669
|
-
try {
|
|
670
|
-
const m = JSON.parse(raw.toString());
|
|
671
|
-
const line = fmt(m.event || 'unknown', m.data || m);
|
|
672
|
-
if (line) { count++; console.log(" " + line); }
|
|
673
|
-
} catch { }
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
ws.on('close', () => {
|
|
677
|
-
console.log("\n " + D + "disconnected โ " + B + count + R + D + " events received" + R);
|
|
678
|
-
console.log(" " + D + "Get unlimited access:" + R + " " + C.cyan + "https://tik.tools" + R + "\n");
|
|
679
|
-
process.exit(0);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
ws.on('error', e => console.error(" " + C.red + "error" + R + " " + e.message));
|
|
683
|
-
process.on('SIGINT', () => {
|
|
684
|
-
console.log("\n\n " + D + "stopped โ " + B + count + R + D + " events received" + R);
|
|
685
|
-
console.log(" " + D + "Get unlimited access:" + R + " " + C.cyan + "https://tik.tools" + R + "\n");
|
|
686
|
-
ws.close();
|
|
687
|
-
process.exit(0);
|
|
688
|
-
});
|
|
689
|
-
}
|