tiktok-live-api 1.2.13 → 1.2.16

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 +103 -89
  2. package/package.json +1 -1
package/bin/demo.mjs CHANGED
@@ -11,75 +11,82 @@ import path from 'path';
11
11
  const REACT_TEMPLATE = [
12
12
  '"use client";',
13
13
  "import React, { useEffect, useState, useRef } from 'react';",
14
- "import { TikTokLive } from 'tiktok-live-api';",
15
14
  "",
16
15
  "export default function TikTokLiveComponent() {",
17
16
  " const [events, setEvents] = useState([]);",
18
- " const [username, setUsername] = useState('gbnews');",
19
- " const [apiKey, setApiKey] = useState('demo_tiktokliveapi_public_2026');",
20
17
  " const [inputUsername, setInputUsername] = useState('gbnews');",
18
+ " const [apiKey, setApiKey] = useState('demo_tiktokliveapi_public_2026');",
21
19
  " const [connected, setConnected] = useState(false);",
22
- " const clientRef = useRef(null);",
20
+ " const wsRef = useRef(null);",
23
21
  "",
24
- " useEffect(() => {",
25
- " if (!username) return;",
26
- " if (clientRef.current) { clientRef.current.disconnect(); }",
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; }",
27
29
  " setEvents([]);",
28
30
  " setConnected(false);",
29
- " ",
30
- " const client = new TikTokLive(username, { apiKey });",
31
- " clientRef.current = client;",
32
- " ",
33
- " client.on('chat', (e) => setEvents(prev => [...prev.slice(-40), \"💬 \" + e.user.uniqueId + \": \" + e.comment]));",
34
- " client.on('gift', (e) => setEvents(prev => [...prev.slice(-40), \"🎁 \" + e.user.uniqueId + \" sent \" + e.giftName]));",
35
- " client.on('member', (e) => setEvents(prev => [...prev.slice(-40), \"👋 \" + e.user.uniqueId + \" joined\"]));",
36
- " client.on('like', (e) => setEvents(prev => [...prev.slice(-40), \"❤️ \" + e.user.uniqueId + \" liked\"]));",
37
- " client.on('connected', () => setConnected(true));",
38
- " client.on('disconnected', () => setConnected(false));",
39
- " ",
40
- " client.connect().catch(() => setConnected(false));",
41
- " ",
42
- " return () => { if (clientRef.current) { clientRef.current.disconnect(); } };",
43
- " }, [username, apiKey]);",
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
+ " }",
44
54
  "",
45
- " const handleConnect = (e) => {",
46
- " e.preventDefault();",
47
- " setUsername(inputUsername);",
48
- " };",
55
+ " useEffect(() => { return () => { if (wsRef.current) wsRef.current.close(); }; }, []);",
49
56
  "",
50
57
  " return (",
51
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' }}>",
52
59
  " <div style={{ padding: '24px', borderBottom: '1px solid #e5e7eb' }}>",
53
- " <h2 style={{ margin: '0 0 8px 0', fontSize: '1.5rem', fontWeight: 600 }}>TikTok Live SDK</h2>",
54
- " <p style={{ margin: 0, fontSize: '0.875rem', color: '#6b7280' }}>",
55
- " Get your own API key and unlock unlimited connections at <a href=\"https://tik.tools\" target=\"_blank\" style={{ color: '#3b82f6', textDecoration: 'none' }}>tik.tools</a>",
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>',
56
63
  " </p>",
57
64
  " </div>",
58
65
  " <div style={{ padding: '24px' }}>",
59
- " <form onSubmit={handleConnect} style={{ display: 'flex', flexDirection: 'column', gap: '16px', marginBottom: '24px' }}>",
66
+ " <form onSubmit={connectStream} style={{ display: 'flex', flexDirection: 'column', gap: '16px', marginBottom: '24px' }}>",
60
67
  " <div>",
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. gbnews\" />",
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" />',
63
70
  " </div>",
64
71
  " <div>",
65
- " <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: 500, marginBottom: '6px' }}>API Key</label>",
66
- " <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' }} />",
67
- " <p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#9ca3af' }}>The demo key is rate-limited. Replace with your own key for production.</p>",
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>",
68
75
  " </div>",
69
- " <button type=\"submit\" style={{ background: '#000', color: '#fff', padding: '10px 16px', borderRadius: '6px', border: 'none', fontWeight: 500, cursor: 'pointer' }}>Connect Stream</button>",
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>',
70
77
  " </form>",
71
78
  " <div style={{ marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>",
72
79
  " <div style={{ width: 10, height: 10, borderRadius: '50%', background: connected ? '#10b981' : '#ef4444', transition: 'background 0.3s' }}></div>",
73
- " <span style={{ fontSize: '0.875rem', color: '#374151', fontWeight: 500 }}>{connected ? 'Connected to @' + username : 'Disconnected'}</span>",
80
+ " <span style={{ fontSize: '0.875rem', color: '#111827', fontWeight: 600 }}>{connected ? 'Connected' : 'Disconnected'}</span>",
74
81
  " </div>",
75
82
  " <div style={{ height: 350, overflowY: 'auto', background: '#f9fafb', padding: '12px', borderRadius: '8px', border: '1px solid #e5e7eb', fontSize: '0.875rem', display: 'flex', flexDirection: 'column' }}>",
76
- " {events.map((e, i) => <div key={i} style={{ padding: '6px 0', borderBottom: '1px solid #f3f4f6', wordBreak: 'break-word' }}>{e}</div>)}",
77
- " {events.length === 0 && <div style={{ color: '#9ca3af', textAlign: 'center', margin: 'auto 0' }}>{connected ? 'Waiting for events...' : 'Connect to a valid stream to see events'}</div>}",
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>}",
78
85
  " </div>",
79
86
  " </div>",
80
87
  " </div>",
81
88
  " );",
82
- "}"
89
+ "}",
83
90
  ].join('\n') + "\n";
84
91
 
85
92
  const VUE_TEMPLATE = [
@@ -368,7 +375,7 @@ function runScaffold(choice, targetDir, pm) {
368
375
  'import json',
369
376
  'import websockets',
370
377
  '',
371
- '# ── Configuration ───────────────────────────────────────',
378
+ '# \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',
372
379
  "USERNAME = 'gbnews' # TikTok @username",
373
380
  "API_KEY = 'demo_tiktokliveapi_public_2026' # Get yours at https://tik.tools",
374
381
  '',
@@ -376,32 +383,40 @@ function runScaffold(choice, targetDir, pm) {
376
383
  'async def main():',
377
384
  ' """Connect to TikTok LIVE and print events."""',
378
385
  ' uri = f"wss://api.tik.tools?uniqueId={USERNAME}&apiKey={API_KEY}"',
379
- ' print(f"🔄 Connecting to @{USERNAME}...")',
386
+ ' print(f"Connecting to @{USERNAME}...")',
380
387
  '',
381
388
  ' try:',
382
389
  ' async with websockets.connect(uri) as ws:',
383
- ' print(f"Connected to @{USERNAME}\\n")',
390
+ ' print(f"Connected to @{USERNAME}")',
391
+ ' print()',
384
392
  ' async for raw in ws:',
385
393
  ' try:',
386
- ' msg = json.loads(raw)',
387
- ' event = msg.get("event", "")',
388
- ' data = msg.get("data", {})',
389
- ' user = data.get("user", {}).get("uniqueId", "?")',
394
+ " msg = json.loads(raw)",
395
+ " event = msg.get('event', '')",
396
+ " data = msg.get('data', {})",
397
+ " user = data.get('user', {}).get('uniqueId', '?')",
390
398
  '',
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)}")',
399
+ " if event == 'chat':",
400
+ " comment = data.get('comment', '')",
401
+ ' print(f"[chat] {user}: {comment}")',
402
+ " elif event == 'gift':",
403
+ " gift = data.get('giftName', 'a gift')",
404
+ " count = data.get('repeatCount', 1)",
405
+ ' print(f"[gift] {user} sent {gift} x{count}")',
406
+ " elif event == 'like':",
407
+ " total = data.get('totalLikeCount', 0)",
408
+ ' print(f"[like] {user} liked (total: {total})")',
409
+ " elif event == 'member':",
410
+ ' print(f"[join] {user} joined")',
411
+ " elif event == 'roomUserSeq':",
412
+ " viewers = data.get('viewerCount', 0)",
413
+ ' print(f"[viewers] {viewers}")',
401
414
  ' except json.JSONDecodeError:',
402
415
  ' pass',
416
+ ' except KeyboardInterrupt:',
417
+ ' print("Disconnected.")',
403
418
  ' except Exception as e:',
404
- ' print(f"⚠️ Error: {e}")',
419
+ ' print(f"Error: {e}")',
405
420
  '',
406
421
  '',
407
422
  'if __name__ == "__main__":',
@@ -458,47 +473,46 @@ function runScaffold(choice, targetDir, pm) {
458
473
  '</template>'
459
474
  ].join('\n') + '\n');
460
475
  } else {
461
- console.log("\n " + B + "📦 Installing " + C.cyan + "tiktok-live-api" + B + " SDK..." + R + "\n");
462
- execSync(installCmd, { cwd: fullPath, stdio: 'inherit' });
463
-
464
476
  if (isNext) {
465
477
  const compDir = path.join(fullPath, 'src', 'components');
466
478
  if (!fs.existsSync(compDir)) fs.mkdirSync(compDir, { recursive: true });
467
479
  fs.writeFileSync(path.join(compDir, 'TikTokLive.jsx'), REACT_TEMPLATE);
468
480
 
469
- const pageJs = path.join(fullPath, 'src', 'app', 'page.js');
470
- if (fs.existsSync(pageJs)) {
471
- fs.writeFileSync(pageJs, [
472
- "import TikTokLive from '@/components/TikTokLive';",
473
- "",
474
- "export default function Home() {",
475
- " return (",
476
- ' <main style={{ minHeight: "100vh", background: "#f9fafb", paddingTop: "40px" }}>',
477
- " <TikTokLive />",
478
- " </main>",
479
- " );",
480
- "}"
481
- ].join('\n') + '\n');
482
- }
481
+ // Write page.js create the directory if needed
482
+ const pageDir = path.join(fullPath, 'src', 'app');
483
+ if (!fs.existsSync(pageDir)) fs.mkdirSync(pageDir, { recursive: true });
484
+ fs.writeFileSync(path.join(pageDir, 'page.js'), [
485
+ "import TikTokLive from '@/components/TikTokLive';",
486
+ "",
487
+ "export default function Home() {",
488
+ " return (",
489
+ ' <main style={{ minHeight: "100vh", background: "#f9fafb", paddingTop: "40px" }}>',
490
+ " <TikTokLive />",
491
+ " </main>",
492
+ " );",
493
+ "}",
494
+ ].join('\n') + '\n');
483
495
  } else if (isVite) {
484
496
  const compDir = path.join(fullPath, 'src', 'components');
485
497
  if (!fs.existsSync(compDir)) fs.mkdirSync(compDir, { recursive: true });
486
498
  fs.writeFileSync(path.join(compDir, 'TikTokLive.jsx'), REACT_TEMPLATE);
487
499
 
488
- const appJsx = path.join(fullPath, 'src', 'App.jsx');
489
- if (fs.existsSync(appJsx)) {
490
- fs.writeFileSync(appJsx, [
491
- "import TikTokLive from './components/TikTokLive';",
492
- "",
493
- "export default function App() {",
494
- " return (",
495
- ' <main style={{ minHeight: "100vh", background: "#f9fafb", paddingTop: "40px" }}>',
496
- " <TikTokLive />",
497
- " </main>",
498
- " );",
499
- "}"
500
- ].join('\n') + '\n');
501
- }
500
+ // Write App.jsx — overwrite whatever create-vite generated (could be .jsx or .tsx)
501
+ const appContent = [
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
+ fs.writeFileSync(path.join(fullPath, 'src', 'App.jsx'), appContent);
513
+ // Also remove App.tsx if it exists (create-vite sometimes uses TypeScript)
514
+ const appTsx = path.join(fullPath, 'src', 'App.tsx');
515
+ if (fs.existsSync(appTsx)) fs.unlinkSync(appTsx);
502
516
  }
503
517
  }
504
518
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiktok-live-api",
3
- "version": "1.2.13",
3
+ "version": "1.2.16",
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",