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 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)** &nbsp;|&nbsp; ๐Ÿ **Python SDK โ†’ [tik.tools/guides/python-tiktok-live](https://tik.tools/guides/python-tiktok-live)** &nbsp;|&nbsp; ๐Ÿ”Œ **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
  [![npm](https://img.shields.io/npm/v/tiktok-live-api)](https://www.npmjs.com/package/tiktok-live-api)
10
12
  [![npm downloads](https://img.shields.io/npm/dm/tiktok-live-api)](https://www.npmjs.com/package/tiktok-live-api)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/)
12
14
  [![License](https://img.shields.io/npm/l/tiktok-live-api)](https://github.com/tiktool/tiktok-live-api/blob/main/LICENSE)
13
- [![Discord](https://img.shields.io/discord/1482387222912172159?logo=discord&label=Discord&color=5865F2)](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` | Battle event | `type`, `teams`, `scores` |
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
- ## Live Captions (Speech-to-Text)
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 { TikTokCaptions } from 'tiktok-live-api';
209
+ import { TikTokLive } from 'tiktok-live-api';
220
210
 
221
- <<<<<<< HEAD
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
- ### Reference
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
- ## ๐ŸŽค Real-Time Live Captions
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
- - **Auto-detect language** โ€” Automatically identifies the spoken language
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
- ### Quick Start
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 '@tiktool/live';
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
- | Sandbox | 50 | 1 (5 min) | Free |
427
- | Basic | 10,000 | 3 (8h) | $7/week |
428
- | Pro | 75,000 | 50 (8h) | $15/week |
429
- | Ultra | 300,000 | 500 (8h) | $45/week |
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&region=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.2.19",
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
- "bin": {
16
- "tiktok-live-api": "./bin/demo.mjs"
17
- },
18
- "files": [
19
- "dist",
20
- "bin",
21
- "README.md",
22
- "LICENSE"
23
- ],
24
- "scripts": {
25
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
26
- "prepublishOnly": "npm run build"
27
- },
28
- "keywords": [
29
- "tiktok",
30
- "tiktok-live",
31
- "tiktok-api",
32
- "tiktok-live-api",
33
- "tiktok-live-connector",
34
- "tiktok-live-sdk",
35
- "tiktok-websocket",
36
- "tiktok-chat",
37
- "tiktok-gifts",
38
- "tiktok-bot",
39
- "tiktok-data",
40
- "tiktok-stream",
41
- "tiktok-events",
42
- "tiktok-viewer",
43
- "live-streaming",
44
- "live-chat",
45
- "webcast",
46
- "websocket",
47
- "real-time",
48
- "speech-to-text",
49
- "captions",
50
- "transcription",
51
- "translation",
52
- "tiktok-live-python",
53
- "tiktok-tools",
54
- "euler-stream",
55
- "tiktoklive",
56
- "tiktok-connector",
57
- "tiktok-live-node",
58
- "tiktok-live-js",
59
- "tiktok-scraper",
60
- "tiktok-monitoring",
61
- "livestream-api"
62
- ],
63
- "author": {
64
- "name": "TikTool",
65
- "email": "support@tik.tools",
66
- "url": "https://tik.tools"
67
- },
68
- "license": "MIT",
69
- "repository": {
70
- "type": "git",
71
- "url": "https://github.com/tiktool/tiktok-live-api"
72
- },
73
- "bugs": {
74
- "url": "https://github.com/tiktool/tiktok-live-api/issues"
75
- },
76
- "homepage": "https://tik.tools",
77
- "dependencies": {
78
- "ws": "^8.16.0"
79
- },
80
- "devDependencies": {
81
- "@types/ws": "^8.5.10",
82
- "tsup": "^8.0.0",
83
- "typescript": "^5.3.0"
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
- }