Good Versi
This commit is contained in:
parent
62bc71e2f3
commit
3f421e9a53
137
admin.php
137
admin.php
@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/languages/helper.php';
|
||||
|
||||
// Authentication and Authorization check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
@ -27,15 +28,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$head_ads = $_POST['head_ads'] ?? '';
|
||||
$body_ads = $_POST['body_ads'] ?? '';
|
||||
$openai_api_key = $_POST['openai_api_key'] ?? '';
|
||||
$site_name = $_POST['site_name'] ?? 'TikTok Live AI Assistant';
|
||||
$default_language = $_POST['default_language'] ?? 'en';
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE site_settings SET setting_value = ? WHERE setting_key = 'head_ads'");
|
||||
$stmt->execute([$head_ads]);
|
||||
// Handle File Uploads
|
||||
$upload_dir = 'assets/images/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0775, true);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE site_settings SET setting_value = ? WHERE setting_key = 'body_ads'");
|
||||
$stmt->execute([$body_ads]);
|
||||
$site_icon = $_POST['current_site_icon'] ?? 'assets/images/logo.png';
|
||||
if (!empty($_FILES['site_icon']['name'])) {
|
||||
$icon_name = 'logo_' . time() . '.' . pathinfo($_FILES['site_icon']['name'], PATHINFO_EXTENSION);
|
||||
if (move_uploaded_file($_FILES['site_icon']['tmp_name'], $upload_dir . $icon_name)) {
|
||||
$site_icon = $upload_dir . $icon_name;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE site_settings SET setting_value = ? WHERE setting_key = 'openai_api_key'");
|
||||
$stmt->execute([$openai_api_key]);
|
||||
$site_favicon = $_POST['current_site_favicon'] ?? 'favicon.ico';
|
||||
if (!empty($_FILES['site_favicon']['name'])) {
|
||||
$fav_name = 'favicon_' . time() . '.' . pathinfo($_FILES['site_favicon']['name'], PATHINFO_EXTENSION);
|
||||
if (move_uploaded_file($_FILES['site_favicon']['tmp_name'], $upload_dir . $fav_name)) {
|
||||
$site_favicon = $upload_dir . $fav_name;
|
||||
}
|
||||
}
|
||||
|
||||
$settings_to_update = [
|
||||
'head_ads' => $head_ads,
|
||||
'body_ads' => $body_ads,
|
||||
'openai_api_key' => $openai_api_key,
|
||||
'site_name' => $site_name,
|
||||
'site_icon' => $site_icon,
|
||||
'site_favicon' => $site_favicon,
|
||||
'default_language' => $default_language
|
||||
];
|
||||
|
||||
foreach ($settings_to_update as $key => $value) {
|
||||
$stmt = $pdo->prepare("INSERT INTO site_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = ?");
|
||||
$stmt->execute([$key, $value, $value]);
|
||||
}
|
||||
|
||||
$message = "Settings updated successfully!";
|
||||
}
|
||||
@ -46,21 +77,26 @@ $settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$head_ads = $settings['head_ads'] ?? '';
|
||||
$body_ads = $settings['body_ads'] ?? '';
|
||||
$openai_api_key = $settings['openai_api_key'] ?? '';
|
||||
$site_name = $settings['site_name'] ?? 'TikTok Live AI Assistant';
|
||||
$site_icon = $settings['site_icon'] ?? 'assets/images/logo.png';
|
||||
$site_favicon = $settings['site_favicon'] ?? 'favicon.ico';
|
||||
$default_language = $settings['default_language'] ?? 'en';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="<?= get_lang() ?>">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Admin Panel - TikTok Live AI</title>
|
||||
<title><?= __('admin') ?> - <?= htmlspecialchars($site_name) ?></title>
|
||||
<link rel="icon" type="image/x-icon" href="<?= htmlspecialchars($site_favicon) ?>">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; background-color: #0f0f0f; color: #fff; }
|
||||
.card-admin { background-color: #1a1a1a; border: 1px solid #333; border-radius: 12px; }
|
||||
.form-control { background-color: #262626; border-color: #444; color: #fff; }
|
||||
.form-control:focus { background-color: #2d2d2d; border-color: #00f2ea; color: #fff; box-shadow: 0 0 0 0.25rem rgba(0, 242, 234, 0.25); }
|
||||
.form-control, .form-select { background-color: #262626; border-color: #444; color: #fff; }
|
||||
.form-control:focus, .form-select:focus { background-color: #2d2d2d; border-color: #00f2ea; color: #fff; box-shadow: 0 0 0 0.25rem rgba(0, 242, 234, 0.25); }
|
||||
.btn-save { background-color: #00f2ea; color: #000; font-weight: 700; border-radius: 8px; border: none; padding: 12px 24px; }
|
||||
.btn-save:hover { background-color: #00d8d1; color: #000; }
|
||||
.text-tiktok-cyan { color: #00f2ea; }
|
||||
@ -74,48 +110,80 @@ $openai_api_key = $settings['openai_api_key'] ?? '';
|
||||
<span class="text-tiktok-cyan">TikTok</span> Live Admin
|
||||
</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="text-secondary small me-3">Logged in as <?= htmlspecialchars($_SESSION['username']) ?></span>
|
||||
<a href="/" class="btn btn-outline-light btn-sm me-2">Back to Home</a>
|
||||
<span class="text-secondary small me-3"><?= __('hi') ?>, <?= htmlspecialchars($_SESSION['username']) ?></span>
|
||||
<a href="/" class="btn btn-outline-light btn-sm me-2"><?= __('back_to_home') ?></a>
|
||||
<form action="admin.php" method="POST" class="m-0 d-inline">
|
||||
<button type="submit" name="logout" class="btn btn-danger btn-sm">Logout</button>
|
||||
<button type="submit" name="logout" class="btn btn-danger btn-sm"><?= __('logout') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<main class="container mb-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-10">
|
||||
<div class="card-admin p-4 shadow-lg">
|
||||
<h2 class="mb-4 fw-bold">Site Settings</h2>
|
||||
<h2 class="mb-4 fw-bold"><?= __('site_settings') ?></h2>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success bg-dark text-success border-success"><?= $message ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-4">
|
||||
<label for="openai_api_key" class="form-label fw-semibold text-secondary small">OpenAI API Key</label>
|
||||
<input type="password" name="openai_api_key" id="openai_api_key" class="form-control" placeholder="sk-..." value="<?= htmlspecialchars($openai_api_key) ?>">
|
||||
<div class="form-text text-muted">If left empty, the application will use the Flatlogic AI Proxy.</div>
|
||||
</div>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label for="site_name" class="form-label fw-semibold text-secondary small"><?= __('site_name') ?></label>
|
||||
<input type="text" name="site_name" id="site_name" class="form-control" value="<?= htmlspecialchars($site_name) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="default_language" class="form-label fw-semibold text-secondary small"><?= __('language') ?></label>
|
||||
<select name="default_language" id="default_language" class="form-select">
|
||||
<option value="en" <?= $default_language === 'en' ? 'selected' : '' ?>>English</option>
|
||||
<option value="id" <?= $default_language === 'id' ? 'selected' : '' ?>>Indonesia</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="border-secondary my-4">
|
||||
<div class="col-md-6">
|
||||
<label for="site_icon" class="form-label fw-semibold text-secondary small"><?= __('site_icon') ?></label>
|
||||
<input type="file" name="site_icon" id="site_icon" class="form-control">
|
||||
<input type="hidden" name="current_site_icon" value="<?= htmlspecialchars($site_icon) ?>">
|
||||
<?php if ($site_icon): ?>
|
||||
<div class="mt-2 small text-secondary">Current: <img src="<?= $site_icon ?>" height="30" class="ms-2"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="head_ads" class="form-label fw-semibold text-secondary small">Head Scripts (for JS Ads, Meta tags, etc.)</label>
|
||||
<textarea name="head_ads" id="head_ads" rows="4" class="form-control" placeholder="Paste your <head> scripts here..."><?= htmlspecialchars($head_ads) ?></textarea>
|
||||
<div class="form-text text-muted">These scripts will be placed inside the <head> tag.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="site_favicon" class="form-label fw-semibold text-secondary small"><?= __('site_favicon') ?></label>
|
||||
<input type="file" name="site_favicon" id="site_favicon" class="form-control">
|
||||
<input type="hidden" name="current_site_favicon" value="<?= htmlspecialchars($site_favicon) ?>">
|
||||
<?php if ($site_favicon): ?>
|
||||
<div class="mt-2 small text-secondary">Current: <img src="<?= $site_favicon ?>" height="20" class="ms-2"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="body_ads" class="form-label fw-semibold text-secondary small">Body Scripts (for floating ads, analytics, etc.)</label>
|
||||
<textarea name="body_ads" id="body_ads" rows="4" class="form-control" placeholder="Paste your <body> scripts here..."><?= htmlspecialchars($body_ads) ?></textarea>
|
||||
<div class="form-text text-muted">These scripts will be placed at the bottom of the <body> tag.</div>
|
||||
<div class="col-12">
|
||||
<hr class="border-secondary my-2">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="openai_api_key" class="form-label fw-semibold text-secondary small"><?= __('openai_key') ?></label>
|
||||
<input type="password" name="openai_api_key" id="openai_api_key" class="form-control" placeholder="sk-..." value="<?= htmlspecialchars($openai_api_key) ?>">
|
||||
<div class="form-text text-muted">If left empty, the application will use the Flatlogic AI Proxy.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="head_ads" class="form-label fw-semibold text-secondary small"><?= __('head_scripts') ?></label>
|
||||
<textarea name="head_ads" id="head_ads" rows="4" class="form-control" placeholder="Paste your <head> scripts here..."><?= htmlspecialchars($head_ads) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="body_ads" class="form-label fw-semibold text-secondary small"><?= __('body_scripts') ?></label>
|
||||
<textarea name="body_ads" id="body_ads" rows="4" class="form-control" placeholder="Paste your <body> scripts here..."><?= htmlspecialchars($body_ads) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid mt-5">
|
||||
<button type="submit" class="btn btn-save">Save All Settings</button>
|
||||
<button type="submit" class="btn btn-save"><?= __('save_settings') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -123,5 +191,6 @@ $openai_api_key = $settings['openai_api_key'] ?? '';
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -1,7 +1,15 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
session_start();
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// Authentication check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$username = $_GET['username'] ?? '';
|
||||
|
||||
@ -12,29 +20,29 @@ if (empty($username)) {
|
||||
|
||||
if ($action === 'start') {
|
||||
// 1. Kill any existing bridge for this user
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username\"");
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username $user_id\"");
|
||||
|
||||
// 2. Start new bridge
|
||||
$cmd = sprintf(
|
||||
"node %s %s %s %s %s %s > /dev/null 2>&1 &",
|
||||
"node %s %s %s %s %s %s %s > /dev/null 2>&1 &",
|
||||
escapeshellarg(__DIR__ . '/../tiktok_bridge.js'),
|
||||
escapeshellarg($username),
|
||||
escapeshellarg(DB_HOST),
|
||||
escapeshellarg(DB_USER),
|
||||
escapeshellarg(DB_PASS),
|
||||
escapeshellarg(DB_NAME)
|
||||
escapeshellarg(DB_NAME),
|
||||
escapeshellarg($user_id)
|
||||
);
|
||||
|
||||
shell_exec($cmd);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Bridge started for @$username"]);
|
||||
} elseif ($action === 'stop') {
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username\"");
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username $user_id\"");
|
||||
echo json_encode(['success' => true, 'message' => "Bridge stopped for @$username"]);
|
||||
} elseif ($action === 'status') {
|
||||
$output = shell_exec("pgrep -f \"node tiktok_bridge.js $username\"");
|
||||
$output = shell_exec("pgrep -f \"node tiktok_bridge.js $username $user_id\"");
|
||||
echo json_encode(['success' => true, 'running' => !empty($output)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid action']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,18 +1,27 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
session_start();
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||
|
||||
// Authentication check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$username = $_GET['username'] ?? '';
|
||||
|
||||
if (empty($username)) {
|
||||
echo json_encode(['events' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Fetch pending comments (those without AI replies)
|
||||
$stmt = db()->prepare("SELECT * FROM tiktok_history WHERE username = ? AND ai_reply IS NULL ORDER BY created_at ASC LIMIT 5");
|
||||
$stmt->execute([$username]);
|
||||
// 1. Fetch pending comments (those without AI replies) for this specific user
|
||||
$stmt = db()->prepare("SELECT * FROM tiktok_history WHERE username = ? AND user_id = ? AND ai_reply IS NULL ORDER BY created_at ASC LIMIT 5");
|
||||
$stmt->execute([$username, $user_id]);
|
||||
$pending = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$results = [];
|
||||
@ -55,11 +64,10 @@ try {
|
||||
];
|
||||
}
|
||||
|
||||
// 4. Also fetch recently processed comments (last 10 seconds)
|
||||
// To ensure the frontend shows them even if they were processed by another call
|
||||
// 4. Also fetch recently processed comments (last 5 seconds) for this user
|
||||
$since = date('Y-m-d H:i:s', time() - 5);
|
||||
$stmt = db()->prepare("SELECT * FROM tiktok_history WHERE username = ? AND ai_reply IS NOT NULL AND created_at >= ? ORDER BY created_at DESC LIMIT 10");
|
||||
$stmt->execute([$username, $since]);
|
||||
$stmt = db()->prepare("SELECT * FROM tiktok_history WHERE username = ? AND user_id = ? AND ai_reply IS NOT NULL AND created_at >= ? ORDER BY created_at DESC LIMIT 10");
|
||||
$stmt->execute([$username, $user_id, $since]);
|
||||
$processed = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Merge if needed, but for polling, we just return the newly processed ones usually
|
||||
@ -68,4 +76,4 @@ try {
|
||||
} catch (Exception $e) {
|
||||
error_log("Update Error: " . $e->getMessage());
|
||||
echo json_encode(['error' => $e->getMessage(), 'events' => []]);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,16 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
session_start();
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||
|
||||
// Authentication check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data || empty($data['comment']) || empty($data['username'])) {
|
||||
@ -14,16 +22,40 @@ $username = $data['username'];
|
||||
$comment_author = $data['author'] ?? 'Anonymous';
|
||||
$comment_text = $data['comment'];
|
||||
|
||||
// Fetch user personality
|
||||
$personality = 'funny';
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT ai_personality FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$personality = $stmt->fetchColumn() ?: 'funny';
|
||||
} catch (Exception $e) {
|
||||
// Fallback to funny
|
||||
}
|
||||
|
||||
$personality_instructions = "";
|
||||
switch ($personality) {
|
||||
case 'serious':
|
||||
$personality_instructions = "Respond in a serious, professional, and formal tone.";
|
||||
break;
|
||||
case 'expert':
|
||||
$personality_instructions = "Respond as an expert, providing highly informative, accurate, and insightful information.";
|
||||
break;
|
||||
case 'funny':
|
||||
default:
|
||||
$personality_instructions = "Respond in a funny, engaging, and lighthearted way, using jokes or playful language.";
|
||||
break;
|
||||
}
|
||||
|
||||
// Prompt for AI
|
||||
$prompt = "You are a helpful and funny AI assistant for a TikTok live stream.
|
||||
$prompt = "You are a helpful AI assistant for a TikTok live stream. $personality_instructions
|
||||
The streamer is '$username'.
|
||||
The user '$comment_author' just said: '$comment_text'.
|
||||
Respond to them in a short, engaging, and friendly way (max 20 words).
|
||||
Respond to them in a short way (max 20 words).
|
||||
Keep it suitable for a live stream audience.";
|
||||
|
||||
$resp = LocalAIApi::createResponse([
|
||||
'input' => [
|
||||
['role' => 'system', 'content' => 'TikTok Live AI Assistant'],
|
||||
['role' => 'system', 'content' => "TikTok Live AI Assistant - Personality: $personality"],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
],
|
||||
]);
|
||||
@ -37,8 +69,8 @@ if (!empty($resp['success'])) {
|
||||
|
||||
// Persist to DB
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO tiktok_history (username, comment_author, comment_text, ai_reply) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $comment_author, $comment_text, $ai_reply]);
|
||||
$stmt = db()->prepare("INSERT INTO tiktok_history (username, comment_author, comment_text, ai_reply, user_id) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $comment_author, $comment_text, $ai_reply, $user_id]);
|
||||
} catch (Exception $e) {
|
||||
// Log error but continue
|
||||
}
|
||||
@ -48,4 +80,4 @@ echo json_encode([
|
||||
'reply' => $ai_reply,
|
||||
'author' => $comment_author,
|
||||
'comment' => $comment_text
|
||||
]);
|
||||
]);
|
||||
|
||||
25
api/update_personality.php
Normal file
25
api/update_personality.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
session_start();
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$personality = $data['personality'] ?? 'funny';
|
||||
|
||||
$allowed_personalities = ['funny', 'serious', 'expert'];
|
||||
if (!in_array($personality, $allowed_personalities)) {
|
||||
$personality = 'funny';
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("UPDATE users SET ai_personality = ? WHERE id = ?");
|
||||
$stmt->execute([$personality, $_SESSION['user_id']]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@ -4,8 +4,9 @@
|
||||
--bg-dark: #121212;
|
||||
--bg-surface: #1e1e1e;
|
||||
--border-secondary: rgba(255, 255, 255, 0.1);
|
||||
--text-light: #f8f9fa;
|
||||
--text-secondary: #adb5bd;
|
||||
--text-light: #ffffff;
|
||||
--text-secondary: #ced4da; /* Brighter than adb5bd */
|
||||
--text-muted: #adb5bd;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -15,6 +16,10 @@ body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, .card-title, .navbar-brand {
|
||||
color: var(--text-light) !important;
|
||||
}
|
||||
|
||||
.bg-surface {
|
||||
background-color: var(--bg-surface);
|
||||
}
|
||||
@ -27,6 +32,15 @@ body {
|
||||
color: var(--tiktok-red) !important;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.small.text-secondary {
|
||||
color: var(--text-secondary) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.btn-tiktok-cyan {
|
||||
background-color: var(--tiktok-cyan);
|
||||
color: #000;
|
||||
@ -125,13 +139,21 @@ body {
|
||||
.form-control, .form-select {
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem 0.75rem;
|
||||
background-color: #262626 !important;
|
||||
border-color: #444 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: #888 !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
background-color: #1a1a1a;
|
||||
border-color: var(--tiktok-cyan);
|
||||
background-color: #2d2d2d !important;
|
||||
border-color: var(--tiktok-cyan) !important;
|
||||
box-shadow: 0 0 0 0.25rem rgba(0, 242, 234, 0.1);
|
||||
color: #fff;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.toast {
|
||||
@ -238,3 +260,19 @@ body {
|
||||
.admin-link:hover {
|
||||
color: var(--tiktok-cyan);
|
||||
}
|
||||
|
||||
/* Table readability */
|
||||
.table {
|
||||
color: var(--text-light) !important;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
color: var(--text-secondary) !important;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 466 KiB |
@ -1,5 +1,7 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
if (!connectBtn) return; // Exit if not logged in
|
||||
|
||||
const disconnectBtn = document.getElementById('disconnectBtn');
|
||||
const tiktokUsernameInput = document.getElementById('tiktokUsername');
|
||||
const connectionStatus = document.getElementById('connectionStatus');
|
||||
@ -14,6 +16,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const historyTableBody = document.getElementById('historyTableBody');
|
||||
const commentCountBadge = document.getElementById('commentCount');
|
||||
const toastContainer = document.getElementById('toastContainer');
|
||||
const aiPersonality = document.getElementById('aiPersonality');
|
||||
|
||||
let isConnected = false;
|
||||
let commentCount = 0;
|
||||
@ -24,6 +27,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Load voices
|
||||
function populateVoiceList() {
|
||||
if (!synth) return;
|
||||
voices = synth.getVoices().sort(function (a, b) {
|
||||
const aname = a.name.toUpperCase();
|
||||
const bname = b.name.toUpperCase();
|
||||
@ -31,15 +35,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
else if (aname > bname) return 1;
|
||||
return 0;
|
||||
});
|
||||
voiceSelect.innerHTML = '';
|
||||
voices.forEach((voice, i) => {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = `${voice.name} (${voice.lang})`;
|
||||
if (voice.default) option.textContent += ' -- DEFAULT';
|
||||
option.setAttribute('data-lang', voice.lang);
|
||||
option.setAttribute('data-name', voice.name);
|
||||
voiceSelect.appendChild(option);
|
||||
});
|
||||
if (voiceSelect) {
|
||||
voiceSelect.innerHTML = '';
|
||||
voices.forEach((voice, i) => {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = `${voice.name} (${voice.lang})`;
|
||||
if (voice.default) option.textContent += ' -- DEFAULT';
|
||||
option.setAttribute('data-lang', voice.lang);
|
||||
option.setAttribute('data-name', voice.name);
|
||||
voiceSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
populateVoiceList();
|
||||
@ -47,9 +53,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
speechSynthesis.onvoiceschanged = populateVoiceList;
|
||||
}
|
||||
|
||||
rateRange.addEventListener('input', () => {
|
||||
rateValue.textContent = rateRange.value;
|
||||
});
|
||||
if (rateRange) {
|
||||
rateRange.addEventListener('input', () => {
|
||||
if (rateValue) rateValue.textContent = rateRange.value;
|
||||
});
|
||||
}
|
||||
|
||||
if (aiPersonality) {
|
||||
aiPersonality.addEventListener('change', async () => {
|
||||
const personality = aiPersonality.value;
|
||||
try {
|
||||
const resp = await fetch('api/update_personality.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ personality })
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
showToast('Settings Saved', 'AI personality updated successfully.', 'success');
|
||||
} else {
|
||||
showToast('Error', data.error || 'Failed to update personality', 'danger');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('Network Error', 'Could not reach settings API.', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function startBridge(username) {
|
||||
try {
|
||||
@ -58,9 +87,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (data.success) {
|
||||
isConnected = true;
|
||||
connectBtn.classList.add('d-none');
|
||||
disconnectBtn.classList.remove('d-none');
|
||||
connectionStatus.innerHTML = '<span class="status-dot bg-success me-1 pulse"></span> Live connection active: @' + username;
|
||||
emptyFeed.classList.add('d-none');
|
||||
if (disconnectBtn) disconnectBtn.classList.remove('d-none');
|
||||
if (connectionStatus) connectionStatus.innerHTML = '<span class="status-dot bg-success me-1 pulse"></span> Live connection active: @' + username;
|
||||
if (emptyFeed) emptyFeed.classList.add('d-none');
|
||||
showToast('Connected', `Started listening to @${username}. Looking for comments...`, 'success');
|
||||
|
||||
startPolling(username);
|
||||
@ -77,8 +106,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
await fetch(`api/bridge_control.php?action=stop&username=${username}`);
|
||||
isConnected = false;
|
||||
connectBtn.classList.remove('d-none');
|
||||
disconnectBtn.classList.add('d-none');
|
||||
connectionStatus.innerHTML = '<span class="status-dot bg-secondary me-1"></span> Disconnected';
|
||||
if (disconnectBtn) disconnectBtn.classList.add('d-none');
|
||||
if (connectionStatus) connectionStatus.innerHTML = '<span class="status-dot bg-secondary me-1"></span> Disconnected';
|
||||
showToast('Disconnected', 'Stopped bridge.', 'warning');
|
||||
|
||||
if (pollInterval) clearInterval(pollInterval);
|
||||
@ -114,7 +143,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
function addEventToFeed(event) {
|
||||
commentCount++;
|
||||
commentCountBadge.textContent = `${commentCount} events`;
|
||||
if (commentCountBadge) commentCountBadge.textContent = `${commentCount} events`;
|
||||
|
||||
const isSystem = event.comment.includes('sent') && event.comment.includes('x');
|
||||
|
||||
@ -131,7 +160,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<div class="ai-reply-box mt-1">AI: ${event.reply}</div>
|
||||
`;
|
||||
|
||||
commentFeed.prepend(commentItem);
|
||||
if (commentFeed) commentFeed.prepend(commentItem);
|
||||
|
||||
// TTS
|
||||
speak(event.reply);
|
||||
@ -149,35 +178,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
startBridge(username);
|
||||
});
|
||||
|
||||
disconnectBtn.addEventListener('click', () => {
|
||||
const username = tiktokUsernameInput.value.trim();
|
||||
stopBridge(username);
|
||||
});
|
||||
if (disconnectBtn) {
|
||||
disconnectBtn.addEventListener('click', () => {
|
||||
const username = tiktokUsernameInput.value.trim();
|
||||
stopBridge(username);
|
||||
});
|
||||
}
|
||||
|
||||
simulateBtn.addEventListener('click', async () => {
|
||||
const commentText = manualCommentInput.value.trim();
|
||||
const username = tiktokUsernameInput.value.trim();
|
||||
if (!commentText || !username) return;
|
||||
|
||||
try {
|
||||
// Manually insert into DB to test
|
||||
await fetch('api/process_comment.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ author: 'TestUser', comment: commentText, username })
|
||||
});
|
||||
manualCommentInput.value = '';
|
||||
// Polling will pick it up
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
if (simulateBtn) {
|
||||
simulateBtn.addEventListener('click', async () => {
|
||||
const commentText = manualCommentInput.value.trim();
|
||||
const username = tiktokUsernameInput.value.trim();
|
||||
if (!commentText || !username) return;
|
||||
|
||||
try {
|
||||
// Manually insert into DB to test
|
||||
await fetch('api/process_comment.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ author: 'TestUser', comment: commentText, username })
|
||||
});
|
||||
manualCommentInput.value = '';
|
||||
// Polling will pick it up
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if (!text) return;
|
||||
if (!text || !synth) return;
|
||||
|
||||
const utterThis = new SpeechSynthesisUtterance(text);
|
||||
const selectedOption = voiceSelect.selectedOptions[0]?.getAttribute('data-name');
|
||||
const selectedOption = voiceSelect?.selectedOptions[0]?.getAttribute('data-name');
|
||||
if (selectedOption) {
|
||||
for (let i = 0; i < voices.length; i++) {
|
||||
if (voices[i].name === selectedOption) {
|
||||
@ -186,11 +219,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
utterThis.pitch = 1;
|
||||
utterThis.rate = rateRange.value;
|
||||
utterThis.rate = rateRange?.value || 1.0;
|
||||
synth.speak(utterThis);
|
||||
}
|
||||
|
||||
function updateHistoryTable(event) {
|
||||
if (!historyTableBody) return;
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'border-secondary';
|
||||
const time = new Date(event.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
@ -214,6 +248,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function showToast(title, message, type = 'info') {
|
||||
if (!toastContainer) return;
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const toastHtml = `
|
||||
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
@ -229,11 +264,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
||||
const toastElement = document.getElementById(toastId);
|
||||
const toast = new bootstrap.Toast(toastElement);
|
||||
toast.show();
|
||||
|
||||
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||
toastElement.remove();
|
||||
});
|
||||
if (toastElement) {
|
||||
const toast = new bootstrap.Toast(toastElement);
|
||||
toast.show();
|
||||
|
||||
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||
toastElement.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
BIN
assets/pasted-20260226-193641-2eb2b83c.jpg
Normal file
BIN
assets/pasted-20260226-193641-2eb2b83c.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
2
db/migrations/add_ai_personality.sql
Normal file
2
db/migrations/add_ai_personality.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add ai_personality column to users table
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS ai_personality VARCHAR(50) DEFAULT 'funny' AFTER role;
|
||||
4
db/migrations/site_settings_extended.sql
Normal file
4
db/migrations/site_settings_extended.sql
Normal file
@ -0,0 +1,4 @@
|
||||
INSERT INTO site_settings (setting_key, setting_value) VALUES ('site_name', 'TikTok Live AI Assistant') ON DUPLICATE KEY UPDATE setting_key=setting_key;
|
||||
INSERT INTO site_settings (setting_key, setting_value) VALUES ('site_icon', 'assets/images/logo.png') ON DUPLICATE KEY UPDATE setting_key=setting_key;
|
||||
INSERT INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'favicon.ico') ON DUPLICATE KEY UPDATE setting_key=setting_key;
|
||||
INSERT INTO site_settings (setting_key, setting_value) VALUES ('default_language', 'en') ON DUPLICATE KEY UPDATE setting_key=setting_key;
|
||||
27
includes/pexels.php
Normal file
27
includes/pexels.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
function pexels_key() {
|
||||
$k = getenv('PEXELS_KEY');
|
||||
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
|
||||
}
|
||||
|
||||
function pexels_get($url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
function download_to($srcUrl, $destPath) {
|
||||
$data = file_get_contents($srcUrl);
|
||||
if ($data === false) return false;
|
||||
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
|
||||
return file_put_contents($destPath, $data) !== false;
|
||||
}
|
||||
408
index.php
408
index.php
@ -1,215 +1,259 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
// index.php - Main Dashboard
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once 'db/config.php';
|
||||
require_once 'languages/helper.php';
|
||||
|
||||
$pdo = db();
|
||||
// Fetch settings
|
||||
$stmt = $pdo->query("SELECT setting_key, setting_value FROM site_settings");
|
||||
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$head_ads = $settings['head_ads'] ?? '';
|
||||
$body_ads = $settings['body_ads'] ?? '';
|
||||
// Check if user is logged in
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$stmt = db()->prepare("SELECT username, role, ai_personality FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
session_destroy();
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$is_admin = ($user['role'] ?? 'user') === 'admin';
|
||||
$ai_personality = $user['ai_personality'] ?? 'Expert';
|
||||
|
||||
// Simple SEO meta
|
||||
$page_title = __('welcome') . " - AI Assistant";
|
||||
$meta_description = "AI Dashboard for TikTok automation and more.";
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $lang ?>">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>TikTok Live AI Assistant</title>
|
||||
<?php
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Real-time TikTok Live AI Comment Reader and Auto-Responder with TTS.';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
|
||||
<!-- Custom Head Scripts -->
|
||||
<?= $head_ads ?>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $page_title ?></title>
|
||||
<meta name="description" content="<?= $meta_description ?>">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<style>
|
||||
/* Minor overrides for layout */
|
||||
.ai-avatar {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid var(--tiktok-cyan);
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
box-shadow: 0 0 20px rgba(0, 242, 234, 0.4);
|
||||
margin: 0 auto;
|
||||
}
|
||||
.ai-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.status-dot.pulse {
|
||||
animation: pulse-animation 1.5s infinite;
|
||||
}
|
||||
@keyframes pulse-animation {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.2); opacity: 0.5; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black border-bottom border-secondary sticky-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold d-flex align-items-center" href="/">
|
||||
<span class="text-tiktok-cyan me-1">TikTok</span>
|
||||
<span class="text-tiktok-red me-2">Live</span>
|
||||
<span class="badge bg-danger rounded-pill pulse">AI LIVE</span>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-surface shadow-sm sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="index.php">
|
||||
<span class="text-tiktok-red">AI</span> <span class="text-tiktok-cyan">DASHBOARD</span>
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMain">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarMain">
|
||||
<div class="ms-auto d-flex align-items-center flex-column flex-lg-row">
|
||||
<div id="connectionStatus" class="me-lg-3 my-2 my-lg-0 small text-secondary">
|
||||
<span class="status-dot bg-secondary me-1"></span> Disconnected
|
||||
</div>
|
||||
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<?php if (($_SESSION['role'] ?? 'user') === 'admin'): ?>
|
||||
<a href="admin.php" class="nav-link text-tiktok-cyan me-lg-3 my-1 my-lg-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-gear-fill me-1" viewBox="0 0 16 16">
|
||||
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
||||
</svg>
|
||||
Admin
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<span class="text-secondary small me-lg-3 my-1 my-lg-0">Hi, <?= htmlspecialchars($_SESSION['username']) ?></span>
|
||||
<a href="logout.php" class="btn btn-outline-danger btn-sm my-1 my-lg-0">Logout</a>
|
||||
<?php else: ?>
|
||||
<a href="login.php" class="nav-link text-light me-lg-3 my-1 my-lg-0">Login</a>
|
||||
<a href="register.php" class="btn btn-tiktok-cyan btn-sm my-1 my-lg-0">Register</a>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="index.php"><i class="fa-solid fa-house me-1"></i> Dashboard</a>
|
||||
</li>
|
||||
<?php if ($is_admin): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="admin.php"><i class="fa-solid fa-user-shield me-1"></i> Admin</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<div class="navbar-nav align-items-center">
|
||||
<span class="nav-item me-3 text-secondary small">
|
||||
<i class="fa-solid fa-user me-1"></i> <?= htmlspecialchars($user['username']) ?>
|
||||
</span>
|
||||
<div class="nav-item dropdown me-2">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="langDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fa-solid fa-globe"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end bg-surface border-secondary shadow">
|
||||
<li><a class="dropdown-item text-light" href="?lang=id">Indonesia</a></li>
|
||||
<li><a class="dropdown-item text-light" href="?lang=en">English</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="logout.php" class="btn btn-outline-danger btn-sm rounded-pill px-3">
|
||||
<i class="fa-solid fa-right-from-bracket me-1"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-4">
|
||||
<div class="row g-4">
|
||||
<!-- Left Column: Controls & Input -->
|
||||
<div class="col-lg-4">
|
||||
<!-- AI Avatar Improved -->
|
||||
<div class="ai-avatar-container">
|
||||
<div class="ai-avatar">
|
||||
<img src="assets/images/ai_avatar.jpg" alt="AI Avatar">
|
||||
<div class="ai-avatar-glow"></div>
|
||||
<div class="ai-badge">AI ACTIVE</div>
|
||||
</div>
|
||||
<h4 class="mt-3 fw-bold text-tiktok-cyan">Your AI Assistant</h4>
|
||||
<p class="text-secondary small">I am here to interact with your live stream audience!</p>
|
||||
</div>
|
||||
|
||||
<div class="card bg-surface border-secondary h-100 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4 fw-bold">Configuration</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tiktokUsername" class="form-label small text-secondary">TikTok Username</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-dark border-secondary text-secondary">@</span>
|
||||
<input type="text" id="tiktokUsername" class="form-control bg-dark border-secondary text-light" placeholder="username" value="tiktok_star">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<button id="connectBtn" class="btn btn-tiktok-cyan w-100 fw-bold py-2 shadow-sm">Connect to Live</button>
|
||||
<button id="disconnectBtn" class="btn btn-outline-danger w-100 fw-bold py-2 mt-2 shadow-sm d-none">Disconnect</button>
|
||||
</div>
|
||||
|
||||
<hr class="border-secondary my-4">
|
||||
|
||||
<h6 class="mb-3 fw-bold">TTS Settings</h6>
|
||||
<div class="mb-3">
|
||||
<label for="voiceSelect" class="form-label small text-secondary">Voice</label>
|
||||
<select id="voiceSelect" class="form-select bg-dark border-secondary text-light"></select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rateRange" class="form-label small text-secondary">Speed: <span id="rateValue">1.0</span></label>
|
||||
<input type="range" class="form-range" id="rateRange" min="0.5" max="2" step="0.1" value="1.0">
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="autoReplyToggle" checked>
|
||||
<label class="form-check-label" for="autoReplyToggle">AI Auto-Reply</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Live Feed -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card bg-surface border-secondary shadow-sm h-100">
|
||||
<div class="card-header bg-transparent border-secondary d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold">Live Comment Feed</h5>
|
||||
<span class="badge bg-dark border border-secondary text-secondary" id="commentCount">0 comments</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="commentFeed" class="comment-feed p-3 overflow-auto" style="height: 500px;">
|
||||
<div class="text-center text-secondary py-5" id="emptyFeed">
|
||||
<p>Enter a username and connect to start seeing live comments.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-secondary">
|
||||
<div class="input-group">
|
||||
<input type="text" id="manualComment" class="form-control bg-dark border-secondary text-light" placeholder="Simulate a comment...">
|
||||
<button id="simulateBtn" class="btn btn-outline-tiktok-cyan">Simulate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-5 fw-bold mb-2"><?= __('welcome') ?>, <span class="text-tiktok-red"><?= htmlspecialchars($user['username']) ?></span>!</h1>
|
||||
<p class="lead text-secondary"><?= __('hero_text') ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Stats / History -->
|
||||
<div class="row g-4 mt-4">
|
||||
<div class="col-12">
|
||||
<div class="row g-4">
|
||||
<!-- Left Column: Controls & AI personality -->
|
||||
<div class="col-lg-4">
|
||||
<!-- AI Avatar Display -->
|
||||
<div class="card bg-surface border-secondary mb-4 shadow-sm py-4">
|
||||
<div class="card-body text-center">
|
||||
<div class="ai-avatar mb-3">
|
||||
<img src="assets/images/ai_avatar.jpg?v=<?php echo time(); ?>" alt="AI Avatar">
|
||||
</div>
|
||||
<h5 class="fw-bold text-tiktok-cyan mb-1"><?= __('welcome') ?></h5>
|
||||
<div id="connectionStatus" class="small text-secondary mb-3">
|
||||
<span class="status-dot bg-secondary me-1"></span> Disconnected
|
||||
</div>
|
||||
<hr class="border-secondary opacity-25">
|
||||
<div class="text-start px-3">
|
||||
<label class="form-label text-secondary small fw-bold mb-1"><?= __('personality') ?></label>
|
||||
<select class="form-select bg-dark text-light border-secondary" id="aiPersonality">
|
||||
<option value="Funny" <?= $ai_personality == 'Funny' ? 'selected' : '' ?>><?= __('personality_funny') ?></option>
|
||||
<option value="Serious" <?= $ai_personality == 'Serious' ? 'selected' : '' ?>><?= __('personality_serious') ?></option>
|
||||
<option value="Expert" <?= $ai_personality == 'Expert' ? 'selected' : '' ?>><?= __('personality_expert') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Config Card -->
|
||||
<div class="card bg-surface border-secondary mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4 fw-bold"><i class="fa-brands fa-tiktok me-2"></i> <?= __('config') ?></h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tiktokUsername" class="form-label text-secondary small fw-bold"><?= __('tiktok_username') ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-dark border-secondary text-secondary">@</span>
|
||||
<input type="text" class="form-control bg-dark text-light border-secondary" id="tiktokUsername" placeholder="username">
|
||||
</div>
|
||||
<div class="form-text text-secondary mt-1 small">Enter your TikTok username that is currently live.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-tiktok-cyan text-dark fw-bold" id="connectBtn">
|
||||
<i class="fa-solid fa-plug me-2"></i> <?= __('connect') ?>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger fw-bold d-none" id="disconnectBtn">
|
||||
<i class="fa-solid fa-power-off me-2"></i> <?= __('disconnect') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TTS Settings -->
|
||||
<div class="card bg-surface border-secondary shadow-sm">
|
||||
<div class="card-header bg-transparent border-secondary">
|
||||
<h5 class="mb-0 fw-bold">Recent AI Interactions</h5>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3 fw-bold"><i class="fa-solid fa-volume-high me-2"></i> <?= __('tts_settings') ?></h5>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-bold"><?= __('voice') ?></label>
|
||||
<select class="form-select bg-dark text-light border-secondary" id="voiceSelect"></select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-bold d-flex justify-content-between">
|
||||
<span><?= __('speed') ?></span>
|
||||
<span id="rateValue" class="text-tiktok-cyan">1.0</span>
|
||||
</label>
|
||||
<input type="range" class="form-range" id="rateRange" min="0.5" max="2" step="0.1" value="1">
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="autoReplyToggle" checked>
|
||||
<label class="form-check-label text-secondary small fw-bold" for="autoReplyToggle"><?= __('auto_reply') ?></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Live Feed & History -->
|
||||
<div class="col-lg-8">
|
||||
<!-- Comment Feed -->
|
||||
<div class="card bg-dark border-secondary mb-4 shadow-sm" style="min-height: 500px;">
|
||||
<div class="card-header bg-surface border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h5 class="mb-0 fw-bold"><i class="fa-solid fa-stream me-2 text-tiktok-cyan"></i> <?= __('live_feed') ?></h5>
|
||||
<span class="badge bg-tiktok-red" id="commentCount">0 <?= __('comments') ?></span>
|
||||
</div>
|
||||
<div class="card-body p-0 d-flex flex-column">
|
||||
<div id="commentFeed" class="p-3 flex-grow-1" style="height: 400px; overflow-y: auto;">
|
||||
<div id="emptyFeed" class="text-center py-5 opacity-50">
|
||||
<i class="fa-solid fa-satellite-dish fa-3x mb-3 text-secondary"></i>
|
||||
<p><?= __('no_history') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bg-surface border-top border-secondary mt-auto">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control bg-dark text-light border-secondary" id="manualComment" placeholder="Simulate a comment...">
|
||||
<button class="btn btn-outline-tiktok-cyan fw-bold" id="simulateBtn"><?= __('simulate') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History -->
|
||||
<div class="card bg-surface border-secondary shadow-sm">
|
||||
<div class="card-header bg-transparent border-secondary py-3">
|
||||
<h5 class="mb-0 fw-bold"><i class="fa-solid fa-history me-2 text-secondary"></i> <?= __('recent_interactions') ?></h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover mb-0">
|
||||
<thead>
|
||||
<tr class="border-secondary">
|
||||
<th class="border-secondary text-secondary small">Time</th>
|
||||
<th class="border-secondary text-secondary small">User</th>
|
||||
<th class="border-secondary text-secondary small">Comment</th>
|
||||
<th class="border-secondary text-secondary small">AI Reply</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="historyTableBody">
|
||||
<?php
|
||||
try {
|
||||
$stmt = db()->query("SELECT * FROM tiktok_history ORDER BY created_at DESC LIMIT 5");
|
||||
$rows = $stmt->fetchAll();
|
||||
if (empty($rows)) {
|
||||
echo '<tr><td colspan="4" class="text-center text-secondary py-4">No history yet.</td></tr>';
|
||||
} else {
|
||||
foreach ($rows as $row) {
|
||||
echo "<tr class='border-secondary'>";
|
||||
echo "<td class='text-secondary small'>" . date('H:i:s', strtotime($row['created_at'])) . "</td>";
|
||||
echo "<td><strong>" . htmlspecialchars($row['comment_author']) . "</strong></td>";
|
||||
echo "<td class='text-secondary'>" . htmlspecialchars($row['comment_text']) . "</td>";
|
||||
echo "<td class='text-tiktok-cyan'>" . htmlspecialchars($row['ai_reply']) . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo '<tr><td colspan="4" class="text-center text-danger py-4">Error loading history.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-dark table-hover mb-0">
|
||||
<thead>
|
||||
<tr class="border-secondary text-secondary">
|
||||
<th class="small fw-bold"><?= __('time') ?></th>
|
||||
<th class="small fw-bold"><?= __('user') ?></th>
|
||||
<th class="small fw-bold"><?= __('comment') ?></th>
|
||||
<th class="small fw-bold"><?= __('ai_reply') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="historyTableBody">
|
||||
<tr id="noHistoryRow">
|
||||
<td colspan="4" class="text-center py-4 text-secondary"><?= __('no_history') ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="text-center py-4 text-secondary small border-top border-secondary mt-5">
|
||||
<p>© <?= date('Y') ?> TikTok Live AI Assistant. Built with LAMP + OpenAI.</p>
|
||||
<p><a href="admin.php" class="text-secondary text-decoration-none">Admin Settings</a></p>
|
||||
<footer class="py-4 mt-5 border-top border-secondary">
|
||||
<div class="container text-center">
|
||||
<p class="text-secondary small mb-0"><?= __('footer_text') ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div id="toastContainer" class="toast-container position-fixed bottom-0 end-0 p-3"></div>
|
||||
<!-- Toast Container -->
|
||||
<div id="toastContainer" class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index: 1100"></div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
||||
|
||||
<!-- Custom Body Scripts -->
|
||||
<?= $body_ads ?>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
43
languages/en.php
Normal file
43
languages/en.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
return [
|
||||
'welcome' => 'Welcome to TikTok Live AI',
|
||||
'hero_text' => 'Enhance your live stream with AI-powered interactions. Connect with your audience like never before.',
|
||||
'login_to_start' => 'Please login or register to connect to TikTok Live.',
|
||||
'config' => 'Configuration',
|
||||
'tiktok_username' => 'TikTok Username',
|
||||
'connect' => 'Connect to Live',
|
||||
'disconnect' => 'Disconnect',
|
||||
'tts_settings' => 'TTS Settings',
|
||||
'voice' => 'Voice',
|
||||
'speed' => 'Speed',
|
||||
'auto_reply' => 'AI Auto-Reply',
|
||||
'live_feed' => 'Live Comment Feed',
|
||||
'comments' => 'comments',
|
||||
'simulate' => 'Simulate',
|
||||
'recent_interactions' => 'Recent AI Interactions',
|
||||
'time' => 'Time',
|
||||
'user' => 'User',
|
||||
'comment' => 'Comment',
|
||||
'ai_reply' => 'AI Reply',
|
||||
'no_history' => 'No history yet.',
|
||||
'hi' => 'Hi',
|
||||
'logout' => 'Logout',
|
||||
'login' => 'Login',
|
||||
'register' => 'Register',
|
||||
'admin' => 'Admin',
|
||||
'footer_text' => 'TikTok Live AI Assistant. Built with LAMP + OpenAI.',
|
||||
'back_to_home' => 'Back to Home',
|
||||
'save_settings' => 'Save All Settings',
|
||||
'site_settings' => 'Site Settings',
|
||||
'openai_key' => 'OpenAI API Key',
|
||||
'head_scripts' => 'Head Scripts',
|
||||
'body_scripts' => 'Body Scripts',
|
||||
'site_name' => 'Site Name',
|
||||
'site_icon' => 'Site Icon',
|
||||
'site_favicon' => 'Site Favicon',
|
||||
'language' => 'Language',
|
||||
'personality' => 'AI Personality',
|
||||
'personality_funny' => 'Funny',
|
||||
'personality_serious' => 'Serious',
|
||||
'personality_expert' => 'Expert',
|
||||
];
|
||||
25
languages/helper.php
Normal file
25
languages/helper.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
function get_lang() {
|
||||
if (!isset($_SESSION['lang'])) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT setting_value FROM site_settings WHERE setting_key = 'default_language'");
|
||||
$default_lang = $stmt->fetchColumn() ?: 'en';
|
||||
$_SESSION['lang'] = $default_lang;
|
||||
}
|
||||
|
||||
if (isset($_GET['lang']) && in_array($_GET['lang'], ['en', 'id'])) {
|
||||
$_SESSION['lang'] = $_GET['lang'];
|
||||
}
|
||||
|
||||
return $_SESSION['lang'];
|
||||
}
|
||||
|
||||
function __($key) {
|
||||
static $translations = null;
|
||||
if ($translations === null) {
|
||||
$lang = get_lang();
|
||||
$translations = require __DIR__ . "/$lang.php";
|
||||
}
|
||||
|
||||
return $translations[$key] ?? $key;
|
||||
}
|
||||
43
languages/id.php
Normal file
43
languages/id.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
return [
|
||||
'welcome' => 'Selamat datang di TikTok Live AI',
|
||||
'hero_text' => 'Tingkatkan siaran langsung Anda dengan interaksi berbasis AI. Terhubung dengan audiens Anda tidak seperti sebelumnya.',
|
||||
'login_to_start' => 'Silakan login atau daftar untuk terhubung ke TikTok Live.',
|
||||
'config' => 'Konfigurasi',
|
||||
'tiktok_username' => 'Username TikTok',
|
||||
'connect' => 'Hubungkan ke Live',
|
||||
'disconnect' => 'Putuskan Koneksi',
|
||||
'tts_settings' => 'Pengaturan TTS',
|
||||
'voice' => 'Suara',
|
||||
'speed' => 'Kecepatan',
|
||||
'auto_reply' => 'Balasan Otomatis AI',
|
||||
'live_feed' => 'Umpan Komentar Langsung',
|
||||
'comments' => 'komentar',
|
||||
'simulate' => 'Simulasi',
|
||||
'recent_interactions' => 'Interaksi AI Terbaru',
|
||||
'time' => 'Waktu',
|
||||
'user' => 'Pengguna',
|
||||
'comment' => 'Komentar',
|
||||
'ai_reply' => 'Balasan AI',
|
||||
'no_history' => 'Belum ada riwayat.',
|
||||
'hi' => 'Halo',
|
||||
'logout' => 'Keluar',
|
||||
'login' => 'Masuk',
|
||||
'register' => 'Daftar',
|
||||
'admin' => 'Admin',
|
||||
'footer_text' => 'Asisten AI TikTok Live. Dibuat dengan LAMP + OpenAI.',
|
||||
'back_to_home' => 'Kembali ke Beranda',
|
||||
'save_settings' => 'Simpan Semua Pengaturan',
|
||||
'site_settings' => 'Pengaturan Situs',
|
||||
'openai_key' => 'Kunci API OpenAI',
|
||||
'head_scripts' => 'Skrip Head',
|
||||
'body_scripts' => 'Skrip Body',
|
||||
'site_name' => 'Nama Situs',
|
||||
'site_icon' => 'Ikon Situs',
|
||||
'site_favicon' => 'Favicon Situs',
|
||||
'language' => 'Bahasa',
|
||||
'personality' => 'Kepribadian AI',
|
||||
'personality_funny' => 'Lucu',
|
||||
'personality_serious' => 'Serius',
|
||||
'personality_expert' => 'Pakar',
|
||||
];
|
||||
@ -1,15 +1,15 @@
|
||||
const { WebcastPushConnection } = require('tiktok-live-connector');
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
const [,, username, dbHost, dbUser, dbPass, dbName] = process.argv;
|
||||
const [,, username, dbHost, dbUser, dbPass, dbName, userId] = process.argv;
|
||||
|
||||
if (!username) {
|
||||
console.error('Usage: node tiktok_bridge.js <username> <dbHost> <dbUser> <dbPass> <dbName>');
|
||||
console.error('Usage: node tiktok_bridge.js <username> <dbHost> <dbUser> <dbPass> <dbName> <userId>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function start() {
|
||||
console.log(`TikTok Bridge starting for @${username}`);
|
||||
console.log(`TikTok Bridge starting for @${username} (User ID: ${userId})`);
|
||||
|
||||
let connection;
|
||||
try {
|
||||
@ -38,8 +38,8 @@ async function start() {
|
||||
console.log(`[Chat] ${data.uniqueId}: ${data.comment}`);
|
||||
try {
|
||||
await connection.execute(
|
||||
'INSERT INTO tiktok_history (username, comment_author, comment_text, created_at) VALUES (?, ?, ?, NOW())',
|
||||
[username, data.uniqueId, data.comment]
|
||||
'INSERT INTO tiktok_history (username, comment_author, comment_text, user_id, created_at) VALUES (?, ?, ?, ?, NOW())',
|
||||
[username, data.uniqueId, data.comment, userId || null]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error inserting chat:', err);
|
||||
@ -51,8 +51,8 @@ async function start() {
|
||||
console.log(`[Gift] ${data.uniqueId}: ${giftMsg}`);
|
||||
try {
|
||||
await connection.execute(
|
||||
'INSERT INTO tiktok_history (username, comment_author, comment_text, created_at) VALUES (?, ?, ?, NOW())',
|
||||
[username, data.uniqueId, giftMsg]
|
||||
'INSERT INTO tiktok_history (username, comment_author, comment_text, user_id, created_at) VALUES (?, ?, ?, ?, NOW())',
|
||||
[username, data.uniqueId, giftMsg, userId || null]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error inserting gift:', err);
|
||||
@ -81,4 +81,4 @@ async function start() {
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
start();
|
||||
start();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user