Save
This commit is contained in:
parent
0c23b971b3
commit
70c4865817
65
admin.php
65
admin.php
@ -125,7 +125,7 @@ $default_language = $settings['default_language'] ?? 'en';
|
||||
<main class="container mb-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card-admin p-4 shadow-lg">
|
||||
<div class="card-admin p-4 shadow-lg mb-4">
|
||||
<h2 class="mb-4 fw-bold"><?= __('site_settings') ?></h2>
|
||||
|
||||
<?php if ($message): ?>
|
||||
@ -200,10 +200,73 @@ $default_language = $settings['default_language'] ?? 'en';
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Diagnostics Card -->
|
||||
<div class="card-admin p-4 shadow-lg border-warning border-opacity-25">
|
||||
<h4 class="mb-3 fw-bold text-warning"><i class="fa-solid fa-microchip me-2"></i> System Diagnostics</h4>
|
||||
<p class="text-secondary small">Check if your hosting (cPanel/Other) is compatible with the TikTok Bridge.</p>
|
||||
|
||||
<div id="diagnosticsOutput" class="p-3 bg-black rounded border border-secondary mb-3" style="font-family: monospace; font-size: 0.85rem; max-height: 300px; overflow-y: auto;">
|
||||
Loading diagnostics...
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-warning btn-sm" id="runDiagnosticsBtn">
|
||||
<i class="fa-solid fa-rotate me-1"></i> Run Diagnostics
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const diagOutput = document.getElementById('diagnosticsOutput');
|
||||
const runBtn = document.getElementById('runDiagnosticsBtn');
|
||||
|
||||
async function runDiagnostics() {
|
||||
diagOutput.innerHTML = '<span class="text-secondary">Running diagnostics...</span>';
|
||||
try {
|
||||
const resp = await fetch('api/bridge_control.php?action=debug&username=admin'); // username doesn't matter for debug
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success && data.env) {
|
||||
let html = '<table class="table table-dark table-sm table-borderless mb-0">';
|
||||
for (const [key, value] of Object.entries(data.env)) {
|
||||
let valDisplay = value;
|
||||
let colorClass = '';
|
||||
|
||||
if (value === true) { valDisplay = '<span class="text-success">YES</span>'; }
|
||||
else if (value === false) { valDisplay = '<span class="text-danger">NO</span>'; }
|
||||
else if (!value) { valDisplay = '<span class="text-secondary">N/A</span>'; }
|
||||
|
||||
html += `<tr><td class="text-secondary" style="width: 40%">${key}:</td><td>${valDisplay}</td></tr>`;
|
||||
}
|
||||
html += '</table>';
|
||||
|
||||
if (!data.env.shell_exec_enabled) {
|
||||
html += '<div class="mt-3 text-danger small"><strong>Warning:</strong> shell_exec is disabled. The TikTok bridge cannot start. Please enable it in cPanel MultiPHP INI Editor or contact your host.</div>';
|
||||
}
|
||||
if (!data.env.node_path || data.env.node_path === 'node') {
|
||||
html += '<div class="mt-2 text-warning small"><strong>Notice:</strong> Node binary not explicitly found. Ensure "node" is in your system PATH.</div>';
|
||||
}
|
||||
if (!data.env.node_modules_exists) {
|
||||
html += '<div class="mt-2 text-danger small"><strong>Warning:</strong> node_modules folder is missing. Run "npm install" on your server.</div>';
|
||||
}
|
||||
|
||||
diagOutput.innerHTML = html;
|
||||
} else {
|
||||
diagOutput.innerHTML = '<span class="text-danger">Error fetching diagnostics: ' + (data.error || 'Unknown error') + '</span>';
|
||||
}
|
||||
} catch (err) {
|
||||
diagOutput.innerHTML = '<span class="text-danger">Network Error: Could not reach bridge control API.</span>';
|
||||
}
|
||||
}
|
||||
|
||||
runBtn.addEventListener('click', runDiagnostics);
|
||||
runDiagnostics(); // Run on load
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -18,31 +18,144 @@ if (empty($username)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Helper to check if a function is enabled
|
||||
function is_enabled($func) {
|
||||
return function_exists($func) && !in_array($func, array_map('trim', explode(',', ini_get('disable_functions'))));
|
||||
}
|
||||
|
||||
if (!is_enabled('shell_exec')) {
|
||||
echo json_encode(['success' => false, 'error' => 'PHP function shell_exec is disabled on this server. Contact your hosting provider.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Find node path
|
||||
function get_node_path() {
|
||||
$path = trim((string)shell_exec('which node'));
|
||||
if ($path && file_exists($path)) return $path;
|
||||
|
||||
$common_paths = ['/usr/local/bin/node', '/usr/bin/node', '/bin/node', '/opt/node/bin/node'];
|
||||
foreach ($common_paths as $p) {
|
||||
if (file_exists($p)) return $p;
|
||||
}
|
||||
return 'node'; // Fallback to PATH
|
||||
}
|
||||
|
||||
$node = get_node_path();
|
||||
|
||||
// Create logs directory if not exists
|
||||
$logs_dir = __DIR__ . '/../logs';
|
||||
if (!is_dir($logs_dir)) {
|
||||
@mkdir($logs_dir, 0755, true);
|
||||
}
|
||||
|
||||
$pid_file = "$logs_dir/bridge_{$username}_{$user_id}.pid";
|
||||
$log_file = "$logs_dir/bridge_{$username}_{$user_id}.log";
|
||||
|
||||
function is_running($pid_file) {
|
||||
if (!file_exists($pid_file)) return false;
|
||||
$pid = (int)file_get_contents($pid_file);
|
||||
if ($pid <= 0) return false;
|
||||
|
||||
// Check if process exists (posix_getpgid is safer but might be disabled)
|
||||
if (function_exists('posix_getpgid')) {
|
||||
return @posix_getpgid($pid) !== false;
|
||||
}
|
||||
|
||||
// Fallback to pgrep or ps
|
||||
$output = shell_exec("ps -p $pid");
|
||||
return (strpos($output, (string)$pid) !== false);
|
||||
}
|
||||
|
||||
function kill_process($pid_file, $username, $user_id) {
|
||||
if (file_exists($pid_file)) {
|
||||
$pid = (int)file_get_contents($pid_file);
|
||||
if ($pid > 0) {
|
||||
shell_exec("kill -9 $pid > /dev/null 2>&1");
|
||||
}
|
||||
@unlink($pid_file);
|
||||
}
|
||||
// Also try pkill as backup
|
||||
shell_exec("pkill -f \"node .*tiktok_bridge.js $username .* $user_id\"");
|
||||
}
|
||||
|
||||
if ($action === 'start') {
|
||||
// 1. Kill any existing bridge for this user
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username $user_id\"");
|
||||
// 1. Kill any existing bridge
|
||||
kill_process($pid_file, $username, $user_id);
|
||||
|
||||
// 2. Start new bridge
|
||||
$bridge_script = __DIR__ . '/../tiktok_bridge.js';
|
||||
|
||||
// Check if node_modules exist
|
||||
if (!is_dir(__DIR__ . '/../node_modules')) {
|
||||
echo json_encode(['success' => false, 'error' => 'node_modules folder not found. Please run "npm install" on your server.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$cmd = sprintf(
|
||||
"node %s %s %s %s %s %s %s > /dev/null 2>&1 &",
|
||||
escapeshellarg(__DIR__ . '/../tiktok_bridge.js'),
|
||||
"%s %s %s %s %s %s %s %s > %s 2>&1 & echo $!",
|
||||
$node,
|
||||
escapeshellarg($bridge_script),
|
||||
escapeshellarg($username),
|
||||
escapeshellarg(DB_HOST),
|
||||
escapeshellarg(DB_USER),
|
||||
escapeshellarg(DB_PASS),
|
||||
escapeshellarg(DB_NAME),
|
||||
escapeshellarg($user_id)
|
||||
escapeshellarg($user_id),
|
||||
escapeshellarg($log_file)
|
||||
);
|
||||
|
||||
shell_exec($cmd);
|
||||
$pid = trim((string)shell_exec($cmd));
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Bridge started for @$username"]);
|
||||
if ($pid > 0) {
|
||||
file_put_contents($pid_file, $pid);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Bridge started for @$username",
|
||||
'pid' => $pid,
|
||||
'log' => "logs/" . basename($log_file)
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => "Failed to start bridge. Check $log_file for errors.",
|
||||
'node_path' => $node
|
||||
]);
|
||||
}
|
||||
|
||||
} elseif ($action === 'stop') {
|
||||
shell_exec("pkill -f \"node tiktok_bridge.js $username $user_id\"");
|
||||
kill_process($pid_file, $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 $user_id\"");
|
||||
echo json_encode(['success' => true, 'running' => !empty($output)]);
|
||||
$running = is_running($pid_file);
|
||||
|
||||
// Check log for errors if not running but was supposed to
|
||||
$last_log = "";
|
||||
if (!$running && file_exists($log_file)) {
|
||||
$last_log = trim((string)shell_exec("tail -n 5 " . escapeshellarg($log_file)));
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'running' => $running,
|
||||
'last_log' => $last_log
|
||||
]);
|
||||
|
||||
} elseif ($action === 'debug') {
|
||||
// Special action to check environment
|
||||
$env = [
|
||||
'php_version' => PHP_VERSION,
|
||||
'shell_exec_enabled' => is_enabled('shell_exec'),
|
||||
'node_path' => $node,
|
||||
'node_version' => trim((string)shell_exec("$node -v")),
|
||||
'npm_version' => trim((string)shell_exec("npm -v")),
|
||||
'node_modules_exists' => is_dir(__DIR__ . '/../node_modules'),
|
||||
'db_host' => DB_HOST,
|
||||
'db_user' => DB_USER,
|
||||
'os' => PHP_OS,
|
||||
];
|
||||
echo json_encode(['success' => true, 'env' => $env]);
|
||||
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid action']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ if (!username) {
|
||||
}
|
||||
|
||||
async function start() {
|
||||
console.log(`TikTok Bridge starting for @${username} (User ID: ${userId})`);
|
||||
console.log(`[${new Date().toISOString()}] TikTok Bridge starting for @${username} (User ID: ${userId})`);
|
||||
|
||||
let connection;
|
||||
try {
|
||||
@ -19,18 +19,24 @@ async function start() {
|
||||
password: dbPass || '',
|
||||
database: dbName || 'app_db'
|
||||
});
|
||||
console.log('Connected to database');
|
||||
console.log(`[${new Date().toISOString()}] Connected to database: ${dbName} at ${dbHost}`);
|
||||
} catch (err) {
|
||||
console.error('DB Connection failed:', err);
|
||||
console.error(`[${new Date().toISOString()}] DB Connection failed:`, err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tiktokConnection = new WebcastPushConnection(username);
|
||||
// Create a new connection instance
|
||||
let tiktokConnection = new WebcastPushConnection(username, {
|
||||
processInitialData: false,
|
||||
enableExtendedGiftInfo: true,
|
||||
requestPollingIntervalMs: 2000
|
||||
});
|
||||
|
||||
tiktokConnection.connect().then(state => {
|
||||
console.log(`Connected to TikTok Live @${username} (Room ID: ${state.roomId})`);
|
||||
console.log(`[${new Date().toISOString()}] Connected to TikTok Live @${username} (Room ID: ${state.roomId})`);
|
||||
}).catch(err => {
|
||||
console.error('TikTok Connection failed:', err);
|
||||
console.error(`[${new Date().toISOString()}] TikTok Connection failed:`, err.message);
|
||||
console.error('Check if the username is correct and the user is currently LIVE.');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@ -42,11 +48,15 @@ async function start() {
|
||||
[username, data.uniqueId, data.comment, userId || null]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error inserting chat:', err);
|
||||
console.error('Error inserting chat:', err.message);
|
||||
}
|
||||
});
|
||||
|
||||
tiktokConnection.on('gift', async (data) => {
|
||||
if (data.giftType === 1 && !data.repeatEnd) {
|
||||
// Wait for repeatEnd for streaks
|
||||
return;
|
||||
}
|
||||
const giftMsg = `sent ${data.repeatCount}x ${data.giftName}!`;
|
||||
console.log(`[Gift] ${data.uniqueId}: ${giftMsg}`);
|
||||
try {
|
||||
@ -55,30 +65,47 @@ async function start() {
|
||||
[username, data.uniqueId, giftMsg, userId || null]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error inserting gift:', err);
|
||||
console.error('Error inserting gift:', err.message);
|
||||
}
|
||||
});
|
||||
|
||||
tiktokConnection.on('member', (data) => {
|
||||
console.log(`[Join] ${data.uniqueId} joined the stream`);
|
||||
});
|
||||
|
||||
tiktokConnection.on('disconnected', () => {
|
||||
console.log('TikTok connection disconnected');
|
||||
console.log(`[${new Date().toISOString()}] TikTok connection disconnected`);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
tiktokConnection.on('streamEnd', () => {
|
||||
console.log(`[${new Date().toISOString()}] Stream ended by host`);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
tiktokConnection.on('error', (err) => {
|
||||
console.error('TikTok error:', err);
|
||||
console.error(`[${new Date().toISOString()}] TikTok error:`, err.message);
|
||||
});
|
||||
|
||||
// Keep alive
|
||||
// Keep DB connection alive
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await connection.query('SELECT 1');
|
||||
} catch (err) {
|
||||
console.error('DB connection lost, reconnecting...');
|
||||
connection = await mysql.createConnection({
|
||||
host: dbHost, user: dbUser, password: dbPass, database: dbName
|
||||
});
|
||||
console.log(`[${new Date().toISOString()}] DB connection lost, reconnecting...`);
|
||||
try {
|
||||
connection = await mysql.createConnection({
|
||||
host: dbHost, user: dbUser, password: dbPass, database: dbName
|
||||
});
|
||||
} catch (reconnectErr) {
|
||||
console.error('Reconnection failed:', reconnectErr.message);
|
||||
}
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
start();
|
||||
// Handle termination signals
|
||||
process.on('SIGINT', () => process.exit(0));
|
||||
process.on('SIGTERM', () => process.exit(0));
|
||||
|
||||
start();
|
||||
Loading…
x
Reference in New Issue
Block a user