tiktok-live-api 1.2.11 → 1.2.13

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.
Files changed (2) hide show
  1. package/bin/demo.mjs +236 -62
  2. package/package.json +1 -1
package/bin/demo.mjs CHANGED
@@ -15,9 +15,9 @@ const REACT_TEMPLATE = [
15
15
  "",
16
16
  "export default function TikTokLiveComponent() {",
17
17
  " const [events, setEvents] = useState([]);",
18
- " const [username, setUsername] = useState('aljazeeraenglish');",
18
+ " const [username, setUsername] = useState('gbnews');",
19
19
  " const [apiKey, setApiKey] = useState('demo_tiktokliveapi_public_2026');",
20
- " const [inputUsername, setInputUsername] = useState('aljazeeraenglish');",
20
+ " const [inputUsername, setInputUsername] = useState('gbnews');",
21
21
  " const [connected, setConnected] = useState(false);",
22
22
  " const clientRef = useRef(null);",
23
23
  "",
@@ -59,7 +59,7 @@ const REACT_TEMPLATE = [
59
59
  " <form onSubmit={handleConnect} style={{ display: 'flex', flexDirection: 'column', gap: '16px', marginBottom: '24px' }}>",
60
60
  " <div>",
61
61
  " <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: 500, marginBottom: '6px' }}>TikTok Username</label>",
62
- " <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' }} placeholder=\"e.g. aljazeeraenglish\" />",
62
+ " <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' }} placeholder=\"e.g. gbnews\" />",
63
63
  " </div>",
64
64
  " <div>",
65
65
  " <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: 500, marginBottom: '6px' }}>API Key</label>",
@@ -95,7 +95,7 @@ const VUE_TEMPLATE = [
95
95
  ' <form @submit.prevent="handleConnect" style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px">',
96
96
  " <div>",
97
97
  ' <label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 6px">TikTok Username</label>',
98
- ' <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. aljazeeraenglish" />',
98
+ ' <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" />',
99
99
  " </div>",
100
100
  " <div>",
101
101
  ' <label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 6px">API Key</label>',
@@ -119,53 +119,52 @@ const VUE_TEMPLATE = [
119
119
  "</template>",
120
120
  "",
121
121
  "<script setup>",
122
- "import { ref, watch, onMounted } from 'vue'",
122
+ "import { ref, onMounted, onUnmounted } from 'vue'",
123
123
  "",
124
- "const inputUsername = ref('aljazeeraenglish')",
125
- "const activeUsername = ref('aljazeeraenglish')",
124
+ "const inputUsername = ref('gbnews')",
125
+ "const activeUsername = ref('gbnews')",
126
126
  "const apiKey = ref('demo_tiktokliveapi_public_2026')",
127
+ "const connected = ref(false)",
127
128
  "const events = ref([])",
129
+ "let ws = null",
128
130
  "",
129
- "let hook = null;",
130
- "try { if (typeof useTikTokLive !== 'undefined') hook = useTikTokLive; } catch (e) { }",
131
- "const proxyUsername = { replace: (r, s) => (activeUsername.value || '').replace(r, s) }",
132
- "const tiktok = hook ? hook(proxyUsername, { apiKey, autoConnect: false }) : { connected: ref(false), allEvents: ref([]) }",
133
- "const connected = tiktok.connected",
134
- "",
135
- "watch(() => tiktok.allEvents.value, (all) => {",
136
- " if (all && all.length) {",
137
- " const last = all[all.length - 1];",
138
- " if (last.type === 'chat') addEvent(`💬 ${last.data.user.uniqueId}: ${last.data.comment}`)",
139
- " if (last.type === 'gift') addEvent(`🎁 ${last.data.user.uniqueId} sent ${last.data.giftName}`)",
140
- " if (last.type === 'like') addEvent(`❤️ ${last.data.user.uniqueId} liked`)",
141
- " if (last.type === 'member') addEvent(`👋 ${last.data.user.uniqueId} joined`)",
142
- " if (last.type === 'connected') addEvent('✅ Connected successfully!')",
143
- " if (last.type === 'disconnected') addEvent('❌ Disconnected from stream.')",
144
- " }",
145
- "}, { deep: true })",
146
- "",
147
- "watch(() => tiktok.error ? tiktok.error.value : null, (e) => {",
148
- " if(e) addEvent('⚠️ Error: ' + e)",
149
- "})",
150
- "",
151
- "const addEvent = (str) => {",
131
+ "function addEvent(str) {",
152
132
  " events.value.push(str)",
153
- " if(events.value.length > 50) events.value.shift()",
133
+ " if (events.value.length > 50) events.value.shift()",
154
134
  "}",
155
135
  "",
156
- "const handleConnect = () => {",
157
- " if (tiktok.disconnect) tiktok.disconnect()",
136
+ "function connectStream() {",
137
+ " if (ws) { ws.close(); ws = null }",
158
138
  " events.value = []",
139
+ " connected.value = false",
159
140
  " activeUsername.value = inputUsername.value",
160
- " if (!activeUsername.value) return",
161
- " if (tiktok.connect) {",
162
- " tiktok.connect().catch(e => addEvent('⚠️ Failed: ' + e.message))",
141
+ " const user = (activeUsername.value || '').replace(/^@/, '')",
142
+ " if (!user) return",
143
+ " const key = apiKey.value || ''",
144
+ " try {",
145
+ " ws = new WebSocket('wss://api.tik.tools?uniqueId=' + user + '&apiKey=' + key)",
146
+ " } catch (e) { addEvent('⚠️ Connection failed: ' + e.message); return }",
147
+ " ws.onopen = () => { connected.value = true; addEvent('✅ Connected to @' + user) }",
148
+ " ws.onclose = () => { connected.value = false; ws = null; addEvent('❌ Disconnected') }",
149
+ " ws.onerror = () => { addEvent('⚠️ WebSocket error') }",
150
+ " ws.onmessage = (evt) => {",
151
+ " try {",
152
+ " const msg = JSON.parse(evt.data)",
153
+ " const t = msg.event || ''",
154
+ " const d = msg.data || {}",
155
+ " const u = d.user ? d.user.uniqueId : ''",
156
+ " if (t === 'chat') addEvent('💬 ' + u + ': ' + d.comment)",
157
+ " else if (t === 'gift') addEvent('🎁 ' + u + ' sent ' + (d.giftName || 'a gift'))",
158
+ " else if (t === 'like') addEvent('❤️ ' + u + ' liked')",
159
+ " else if (t === 'member') addEvent('👋 ' + u + ' joined')",
160
+ " } catch (e) {}",
163
161
  " }",
164
162
  "}",
165
163
  "",
166
- "onMounted(() => {",
167
- " handleConnect()",
168
- "})",
164
+ "const handleConnect = () => { connectStream() }",
165
+ "",
166
+ "onMounted(() => { connectStream() })",
167
+ "onUnmounted(() => { if (ws) ws.close() })",
169
168
  "</script>"
170
169
  ].join('\n') + "\n";
171
170
 
@@ -194,10 +193,12 @@ if (!isInteractive) {
194
193
  console.log(" 1) " + C.cyan + "Watch Live Terminal Demo" + R);
195
194
  console.log(" 2) " + C.green + "Scaffold a Nuxt 3 (Vue) App" + R);
196
195
  console.log(" 3) " + C.blue + "Scaffold a Next.js (React) App" + R);
197
- console.log(" 4) " + C.yellow + "Scaffold a Vite (React) App" + R + "\n");
196
+ console.log(" 4) " + C.yellow + "Scaffold a Vite (React) App" + R);
197
+ console.log(" 5) " + C.mag + "Scaffold a Plain Node.js Project" + R);
198
+ console.log(" 6) " + C.pink + "Scaffold a Python Project" + R + "\n");
198
199
 
199
200
  function promptAction() {
200
- rl.question(" Choose [1-4]: ", (answer) => {
201
+ rl.question(" Choose [1-6]: ", (answer) => {
201
202
  handleMenu(answer.trim());
202
203
  });
203
204
  }
@@ -208,7 +209,7 @@ if (!isInteractive) {
208
209
  return runDemo();
209
210
  }
210
211
 
211
- if (!['2', '3', '4'].includes(choice)) {
212
+ if (!['2', '3', '4', '5', '6'].includes(choice)) {
212
213
  console.log(" " + C.red + "Invalid choice." + R);
213
214
  return promptAction();
214
215
  }
@@ -216,6 +217,11 @@ if (!isInteractive) {
216
217
  rl.question("\n Target directory (e.g. ./my-app) [./tiktok-live-project]: ", (dir) => {
217
218
  dir = dir.trim() || './tiktok-live-project';
218
219
 
220
+ if (choice === '6') {
221
+ rl.close();
222
+ return runScaffold(choice, dir, 'pip');
223
+ }
224
+
219
225
  console.log("\n Select Package Manager:");
220
226
  console.log(" 1) npm " + D + "(Default)" + R);
221
227
  console.log(" 2) yarn");
@@ -242,6 +248,8 @@ function runScaffold(choice, targetDir, pm) {
242
248
  const isNuxt = choice === '2';
243
249
  const isNext = choice === '3';
244
250
  const isVite = choice === '4';
251
+ const isNode = choice === '5';
252
+ const isPython = choice === '6';
245
253
  const fullPath = path.resolve(process.cwd(), targetDir);
246
254
 
247
255
  const npxCmd = pm === 'bun' ? 'bunx' : (pm === 'pnpm' ? 'pnpm dlx' : 'npx');
@@ -251,7 +259,11 @@ function runScaffold(choice, targetDir, pm) {
251
259
  console.log("\n " + B + "🚀 Scaffolding project into " + targetDir + " using " + pm + "..." + R + "\n");
252
260
 
253
261
  try {
254
- if (isNuxt) {
262
+ if (isNode) {
263
+ if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
264
+ } else if (isPython) {
265
+ if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
266
+ } else if (isNuxt) {
255
267
  execSync(npxCmd + ' nuxi@latest init "' + targetDir + '" --packageManager ' + pm, { stdio: 'inherit' });
256
268
  } else if (isNext) {
257
269
  execSync(npxCmd + ' create-next-app@latest "' + targetDir + '" --js --eslint --tailwind --app --src-dir --import-alias "@/*" --use-' + pm, { stdio: 'inherit' });
@@ -260,11 +272,174 @@ function runScaffold(choice, targetDir, pm) {
260
272
  execSync(createCmd + ' "' + targetDir + '" -- --template react', { stdio: 'inherit' });
261
273
  }
262
274
 
263
- if (isNuxt) {
264
- console.log("\\n " + B + "📦 Installing " + C.cyan + "tiktok-live-nuxt" + B + " Module..." + R + "\\n");
265
- const nuxtInstallCmd = pm === 'npm' ? 'npm install tiktok-live-nuxt' : pm === 'yarn' ? 'yarn add tiktok-live-nuxt' : pm === 'pnpm' ? 'pnpm add tiktok-live-nuxt' : 'bun add tiktok-live-nuxt';
266
- execSync(nuxtInstallCmd, { cwd: fullPath, stdio: 'inherit' });
275
+ if (isNode) {
276
+ // ── Plain Node.js Starter Kit ──
277
+ const pkgJson = {
278
+ name: path.basename(fullPath),
279
+ version: '1.0.0',
280
+ type: 'module',
281
+ scripts: { start: 'node index.mjs', dev: 'node --watch index.mjs' },
282
+ dependencies: {}
283
+ };
284
+ fs.writeFileSync(path.join(fullPath, 'package.json'), JSON.stringify(pkgJson, null, 2) + '\n');
285
+
286
+ console.log("\n " + B + "📦 Installing " + C.cyan + "tiktok-live-api" + B + " SDK..." + R + "\n");
287
+ execSync(installCmd, { cwd: fullPath, stdio: 'inherit' });
288
+
289
+ fs.writeFileSync(path.join(fullPath, 'index.mjs'), [
290
+ "import { TikTokLive } from 'tiktok-live-api';",
291
+ "",
292
+ "// ── Configuration ───────────────────────────────────────",
293
+ "const USERNAME = 'gbnews'; // TikTok @username",
294
+ "const API_KEY = 'demo_tiktokliveapi_public_2026'; // Get yours at https://tik.tools",
295
+ "",
296
+ "// ── Connect ─────────────────────────────────────────────",
297
+ "const client = new TikTokLive(USERNAME, { apiKey: API_KEY });",
298
+ "",
299
+ "client.on('connected', () => {",
300
+ " console.log('\\n✅ Connected to @' + USERNAME + '\\n');",
301
+ "});",
302
+ "",
303
+ "client.on('chat', (data) => {",
304
+ " console.log(`💬 ${data.user.uniqueId}: ${data.comment}`);",
305
+ "});",
306
+ "",
307
+ "client.on('gift', (data) => {",
308
+ " console.log(`🎁 ${data.user.uniqueId} sent ${data.giftName} x${data.repeatCount}`);",
309
+ "});",
310
+ "",
311
+ "client.on('like', (data) => {",
312
+ " console.log(`❤️ ${data.user.uniqueId} liked (total: ${data.totalLikeCount})`);",
313
+ "});",
314
+ "",
315
+ "client.on('member', (data) => {",
316
+ " console.log(`👋 ${data.user.uniqueId} joined`);",
317
+ "});",
318
+ "",
319
+ "client.on('roomUserSeq', (data) => {",
320
+ " console.log(`👀 Viewers: ${data.viewerCount}`);",
321
+ "});",
322
+ "",
323
+ "client.on('disconnected', () => {",
324
+ " console.log('\\n❌ Disconnected\\n');",
325
+ "});",
326
+ "",
327
+ "client.on('error', (err) => {",
328
+ " console.error('⚠️ Error:', err.message);",
329
+ "});",
330
+ "",
331
+ "console.log('🔄 Connecting to @' + USERNAME + '...');",
332
+ "client.connect().catch((err) => {",
333
+ " console.error('Failed to connect:', err.message);",
334
+ " process.exit(1);",
335
+ "});",
336
+ ].join('\n') + '\n');
337
+
338
+ fs.writeFileSync(path.join(fullPath, 'README.md'), [
339
+ '# TikTok Live - Node.js Starter',
340
+ '',
341
+ 'A ready-to-run Node.js project connecting to TikTok LIVE streams.',
342
+ '',
343
+ '## Quick Start',
344
+ '',
345
+ '```bash',
346
+ 'node index.mjs',
347
+ '```',
348
+ '',
349
+ '## Configuration',
350
+ '',
351
+ 'Edit `index.mjs` to change:',
352
+ '- `USERNAME` — the TikTok @username to watch',
353
+ '- `API_KEY` — get your own at [tik.tools](https://tik.tools)',
354
+ '',
355
+ '## Events',
356
+ '',
357
+ 'The demo listens for: `chat`, `gift`, `like`, `member`, `roomUserSeq`.',
358
+ 'See the [full docs](https://www.npmjs.com/package/tiktok-live-api) for all available events.',
359
+ '',
360
+ ].join('\n'));
361
+
362
+ } else if (isPython) {
363
+ // ── Python Starter Kit ──
364
+ fs.writeFileSync(path.join(fullPath, 'main.py'), [
365
+ '"""TikTok Live - Python Starter Kit"""',
366
+ '',
367
+ 'import asyncio',
368
+ 'import json',
369
+ 'import websockets',
370
+ '',
371
+ '# ── Configuration ───────────────────────────────────────',
372
+ "USERNAME = 'gbnews' # TikTok @username",
373
+ "API_KEY = 'demo_tiktokliveapi_public_2026' # Get yours at https://tik.tools",
374
+ '',
375
+ '',
376
+ 'async def main():',
377
+ ' """Connect to TikTok LIVE and print events."""',
378
+ ' uri = f"wss://api.tik.tools?uniqueId={USERNAME}&apiKey={API_KEY}"',
379
+ ' print(f"🔄 Connecting to @{USERNAME}...")',
380
+ '',
381
+ ' try:',
382
+ ' async with websockets.connect(uri) as ws:',
383
+ ' print(f"✅ Connected to @{USERNAME}\\n")',
384
+ ' async for raw in ws:',
385
+ ' try:',
386
+ ' msg = json.loads(raw)',
387
+ ' event = msg.get("event", "")',
388
+ ' data = msg.get("data", {})',
389
+ ' user = data.get("user", {}).get("uniqueId", "?")',
390
+ '',
391
+ ' if event == "chat":',
392
+ ' print(f"💬 {user}: {data.get(\"comment\", \"\")}")',
393
+ ' elif event == "gift":',
394
+ ' print(f"🎁 {user} sent {data.get(\"giftName\", \"a gift\")} x{data.get(\"repeatCount\", 1)}")',
395
+ ' elif event == "like":',
396
+ ' print(f"❤️ {user} liked (total: {data.get(\"totalLikeCount\", 0)})")',
397
+ ' elif event == "member":',
398
+ ' print(f"👋 {user} joined")',
399
+ ' elif event == "roomUserSeq":',
400
+ ' print(f"👀 Viewers: {data.get(\"viewerCount\", 0)}")',
401
+ ' except json.JSONDecodeError:',
402
+ ' pass',
403
+ ' except Exception as e:',
404
+ ' print(f"⚠️ Error: {e}")',
405
+ '',
406
+ '',
407
+ 'if __name__ == "__main__":',
408
+ ' asyncio.run(main())',
409
+ ].join('\n') + '\n');
410
+
411
+ fs.writeFileSync(path.join(fullPath, 'requirements.txt'), 'websockets>=12.0\n');
412
+
413
+ fs.writeFileSync(path.join(fullPath, 'README.md'), [
414
+ '# TikTok Live - Python Starter',
415
+ '',
416
+ 'A ready-to-run Python project connecting to TikTok LIVE streams.',
417
+ '',
418
+ '## Quick Start',
419
+ '',
420
+ '```bash',
421
+ 'pip install -r requirements.txt',
422
+ 'python main.py',
423
+ '```',
424
+ '',
425
+ '## Configuration',
426
+ '',
427
+ 'Edit `main.py` to change:',
428
+ '- `USERNAME` — the TikTok @username to watch',
429
+ '- `API_KEY` — get your own at [tik.tools](https://tik.tools)',
430
+ '',
431
+ '## Events',
432
+ '',
433
+ 'The demo listens for: `chat`, `gift`, `like`, `member`, `roomUserSeq`.',
434
+ '',
435
+ ].join('\n'));
436
+
437
+ console.log("\n " + B + "📦 Installing " + C.cyan + "websockets" + B + "..." + R + "\n");
438
+ try { execSync('pip install websockets', { cwd: fullPath, stdio: 'inherit' }); } catch (e) {
439
+ console.log(" " + C.yellow + "Note: run 'pip install -r requirements.txt' manually if pip failed." + R);
440
+ }
267
441
 
442
+ } else if (isNuxt) {
268
443
  const isNuxt4 = fs.existsSync(path.join(fullPath, 'app', 'app.vue')) || !fs.existsSync(path.join(fullPath, 'app.vue'));
269
444
  const baseApp = isNuxt4 ? path.join(fullPath, 'app') : fullPath;
270
445
 
@@ -276,21 +451,14 @@ function runScaffold(choice, targetDir, pm) {
276
451
  fs.writeFileSync(appVue, [
277
452
  '<template>',
278
453
  ' <main style="min-height: 100vh; background: #f9fafb; padding-top: 40px;">',
279
- ' <TikTokLive />',
454
+ ' <ClientOnly>',
455
+ ' <TikTokLive />',
456
+ ' </ClientOnly>',
280
457
  ' </main>',
281
458
  '</template>'
282
459
  ].join('\n') + '\n');
283
-
284
- const nuxtConfig = path.join(fullPath, 'nuxt.config.ts');
285
- if (fs.existsSync(nuxtConfig)) {
286
- let config = fs.readFileSync(nuxtConfig, 'utf8');
287
- if (config.includes('defineNuxtConfig({')) {
288
- config = config.replace('defineNuxtConfig({', "defineNuxtConfig({\n modules: ['tiktok-live-nuxt'],\n");
289
- fs.writeFileSync(nuxtConfig, config);
290
- }
291
- }
292
460
  } else {
293
- console.log("\\n " + B + "📦 Installing " + C.cyan + "tiktok-live-api" + B + " SDK..." + R + "\\n");
461
+ console.log("\n " + B + "📦 Installing " + C.cyan + "tiktok-live-api" + B + " SDK..." + R + "\n");
294
462
  execSync(installCmd, { cwd: fullPath, stdio: 'inherit' });
295
463
 
296
464
  if (isNext) {
@@ -337,10 +505,16 @@ function runScaffold(choice, targetDir, pm) {
337
505
  console.log("\n " + C.green + "✔ Project successfully scaffolded!" + R + "\n");
338
506
  console.log(" " + B + "Next steps:" + R);
339
507
  console.log(" cd " + targetDir);
340
- if (isVite && pm !== 'bun') {
341
- console.log(" " + pm + " install");
508
+ if (isPython) {
509
+ console.log(" python main.py\n");
510
+ } else if (isNode) {
511
+ console.log(" node index.mjs\n");
512
+ } else {
513
+ if (isVite && pm !== 'bun') {
514
+ console.log(" " + pm + " install");
515
+ }
516
+ console.log(" " + devCmd + "\n");
342
517
  }
343
- console.log(" " + devCmd + "\n");
344
518
 
345
519
  } catch (err) {
346
520
  console.error("\n " + C.red + "Error during scaffolding:" + R, err.message);
@@ -354,7 +528,7 @@ async function runDemo() {
354
528
  const WS_BASE = 'wss://api.tik.tools';
355
529
  const DEMO_KEY = 'demo_tiktokliveapi_public_2026';
356
530
  const CHANNELS = [
357
- 'aljazeeraenglish', 'cgtnofficial', 'france24_en',
531
+ 'gbnews', 'cgtnofficial', 'france24_en',
358
532
  'weathernewslive', 'gbnews', 'bbcnews',
359
533
  'skynews', 'tv_asahi_news', 'abc7chicago', 'thairath_news',
360
534
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiktok-live-api",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
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
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",