This commit is contained in:
Flatlogic Bot 2026-02-26 20:17:29 +00:00
parent 0c23b971b3
commit 70c4865817
3 changed files with 231 additions and 28 deletions

View File

@ -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>

View File

@ -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']);
}
}

View File

@ -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();