diff --git a/generate-php.js b/generate-php.js new file mode 100644 index 0000000..ba6b41d --- /dev/null +++ b/generate-php.js @@ -0,0 +1,167 @@ +import fs from "fs"; +import path from "path"; + +export function generateIndexPHP(buildDir) { + const htmlFile = path.join(buildDir, "index.html"); + const phpFile = path.join(buildDir, "index.php"); + + try { + // Read the generated index.html file + const htmlContent = fs.readFileSync(htmlFile, "utf-8"); + + // Remove the Jinja2 template script block (will be replaced with PHP version) + const htmlWithoutJinja = htmlContent.replace( + /", + ); + + // PHP Template with embedded HTML content and server-side data fetching + const phpTemplate = ` $infoUrl, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'], + CURLOPT_CUSTOMREQUEST => 'GET', + ]); + + $infoResponse = curl_exec($ch); + $userData = null; + + if ($infoResponse !== false) { + $userData = json_decode($infoResponse, true); + } + curl_close($ch); + + // Fetch subscription links + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $subUrl, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_USERAGENT => 'V2rayNG', + CURLOPT_CUSTOMREQUEST => 'GET', + ]); + + $linksResponse = curl_exec($ch); + $links = []; + + if ($linksResponse !== false) { + // Try to decode base64, otherwise split by newlines + $decoded = @base64_decode($linksResponse, true); + $linksText = ($decoded !== false && preg_match('/^(vmess|vless|trojan|ss):\\/\\//', $decoded)) + ? $decoded + : $linksResponse; + $links = array_filter(explode("\\n", trim($linksText)), function($line) { + return !empty($line) && $line !== 'False'; + }); + } + curl_close($ch); + + // Build the initial data script + $initialDataScript = ''; + + // Output HTML with embedded initial data + $html = str_replace( + '', + $initialDataScript, + '${htmlWithoutJinja.replace(/'/g, "\\'")}' + ); + echo $html; + return; + } + + // For non-HTML requests (subscription clients), proxy the request + $requestUrl = $subUrl; + + // Initialize cURL session + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $requestUrl, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => 17, + CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'], + CURLOPT_CUSTOMREQUEST => 'GET', + ]); + + $response = curl_exec($ch); + + // Handle cURL error + if ($response === false) { + die('cURL error: ' . curl_error($ch)); + } + + // Split the headers and body from the response + $headerEndPos = strpos($response, "\\r\\n\\r\\n"); + if ($headerEndPos === false) { + die('Invalid response format.'); + } + + $headerText = substr($response, 0, $headerEndPos); + $responseBody = substr($response, $headerEndPos + 4); + + // Forward the necessary headers from the cURL response + $isValidHeader = false; + foreach (explode("\\r\\n", $headerText) as $i => $line) { + if ($i === 0) continue; + if (strpos($line, ": ") !== false) { + list($key, $value) = explode(": ", $line, 2); + if (in_array(strtolower($key), ['content-disposition', 'content-type', 'subscription-userinfo', 'profile-update-interval'])) { + header("$key: $value"); + $isValidHeader = true; + } + } + } + + if (!$isValidHeader && !$isHtmlRequest) { + die("Error! No valid headers found."); + } + + // Output the response body + echo $responseBody; + curl_close($ch); + + ?> + `; + + // Write the index.php file + fs.writeFileSync(phpFile, phpTemplate, "utf-8"); + console.log("Generated index.php successfully"); + } catch (error) { + console.error("Error generating index.php:", error); + } +} diff --git a/vite.config.ts b/vite.config.ts index 3f4451c..ed53d33 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,13 +3,23 @@ import tailwindcss from "@tailwindcss/vite" import react from "@vitejs/plugin-react" import { defineConfig } from "vite" import { viteSingleFile } from "vite-plugin-singlefile"; +// @ts-expect-error generate-php is a JS utility without type declarations. +import { generateIndexPHP } from "./generate-php" // https://vite.dev/config/ export default defineConfig({ plugins: [ react(), tailwindcss(), - viteSingleFile() + viteSingleFile(), + { + name: "generate-index-php", + apply: "build", + closeBundle() { + const buildPath = "dist" + generateIndexPHP(buildPath) + }, + }, ], resolve: { alias: {