diff --git a/_worker.js b/_worker.js index 2150bc2..c158068 100644 --- a/_worker.js +++ b/_worker.js @@ -1,166 +1,220 @@ + +// @ts-nocheck +// version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC. +// @ts-ignore +// https://github.com/bia-pain-bache/BPB-Worker-Panel + import { connect } from 'cloudflare:sockets'; -// t.me/P_tech2024 + // How to generate your own UUID: -// [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()" +// https://www.uuidgenerator.net/ let userID = '2cd61524-9843-43ab-8a0a-df4a744bb859'; -const proxyIPs = ['mtn.ircf.space', 'mkh.ircf.space', 'mci.ircf.space', 'rtl.ircf.space']; -let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; +// https://www.nslookup.io/domains/cdn.xn--b6gac.eu.org/dns-records/ +// https://www.nslookup.io/domains/cdn-all.xn--b6gac.eu.org/dns-records/ +const proxyIPs= ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'edgetunnel.anycast.eu.org']; -let dohURL = 'https://1.1.1.1/dns-query'; -// (dohURL) list : -// https://cloudflare-dns.com/dns-query -// https://dns.google/dns-query -// https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg= -// https://free.shecan.ir/dns-query <--- دی ان اس ایرانی +const defaultHttpPorts = ['80', '8080', '2052', '2082', '2086', '2095', '8880']; +const defaultHttpsPorts = ['443', '8443', '2053', '2083', '2087', '2096']; -// v2board api environment variables (optional) -// now deprecated, please use planetscale.com instead -let nodeId = ''; // 1 +let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; -let apiToken = ''; //abcdefghijklmnopqrstuvwxyz123456 +let dohURL = 'https://cloudflare-dns.com/dns-query'; -let apiHost = ''; // api.v2board.com +let panelVersion = '2.4.1'; if (!isValidUUID(userID)) { - throw new Error('uuid is invalid'); + throw new Error('uuid is not valid'); } export default { - /** - * @param {import("@cloudflare/workers-types").Request} request - * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env - * @param {import("@cloudflare/workers-types").ExecutionContext} ctx - * @returns {Promise} - */ - async fetch(request, env, ctx) { - try { - userID = env.UUID || userID; - proxyIP = env.PROXYIP || proxyIP; - dohURL = env.DNS_RESOLVER_URL || dohURL; - nodeId = env.NODE_ID || nodeId; - apiToken = env.API_TOKEN || apiToken; - apiHost = env.API_HOST || apiHost; - let userID_Path = userID; - if (userID.includes(',')) { - userID_Path = userID.split(',')[0]; - } - const upgradeHeader = request.headers.get('Upgrade'); - if (!upgradeHeader || upgradeHeader !== 'websocket') { - const url = new URL(request.url); - switch (url.pathname) { - case '/cf': - return new Response(JSON.stringify(request.cf, null, 4), { - status: 200, - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - }); - case '/connect': // for test connect to cf socket - const [hostname, port] = ['cloudflare.com', '80']; - console.log(`Connecting to ${hostname}:${port}...`); - - try { - const socket = await connect({ - hostname: hostname, - port: parseInt(port, 10), - }); - - const writer = socket.writable.getWriter(); - - try { - await writer.write(new TextEncoder().encode('GET / HTTP/1.1\r\nHost: ' + hostname + '\r\n\r\n')); - } catch (writeError) { - writer.releaseLock(); - await socket.close(); - return new Response(writeError.message, { status: 500 }); - } - - writer.releaseLock(); - - const reader = socket.readable.getReader(); - let value; - - try { - const result = await reader.read(); - value = result.value; - } catch (readError) { - await reader.releaseLock(); - await socket.close(); - return new Response(readError.message, { status: 500 }); - } - - await reader.releaseLock(); - await socket.close(); - - return new Response(new TextDecoder().decode(value), { status: 200 }); - } catch (connectError) { - return new Response(connectError.message, { status: 500 }); - } - case `/${userID_Path}`: { - const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); - return new Response(`${vlessConfig}`, { - status: 200, - headers: { - "Content-Type": "text/html; charset=utf-8", - } - }); - } - case `/sub/${userID_Path}`: { - const url = new URL(request.url); - const searchParams = url.searchParams; - let vlessConfig = createVLESSSub(userID, request.headers.get('Host')); - - // If 'format' query param equals to 'clash', convert config to base64 - if (searchParams.get('format') === 'clash') { - vlessConfig = btoa(vlessConfig); - } - - // Construct and return response object - return new Response(vlessConfig, { - status: 200, - headers: { - "Content-Type": "text/plain;charset=utf-8", - } - }); - } - - default: - // return new Response('Not found', { status: 404 }); - // For any other path, reverse proxy to 'www.fmprc.gov.cn' and return the original response - url.hostname = Math.random() < 0.5 ? 'www.gov.cn' : 'www.fmprc.gov.cn'; - url.protocol = 'https:'; - request = new Request(url, request); - return await fetch(request); - } - } else { - return await vlessOverWSHandler(request); - } - } catch (err) { - /** @type {Error} */ let e = err; - return new Response(e.toString()); - } - }, + /** + * @param {import("@cloudflare/workers-types").Request} request + * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string}} env + * @param {import("@cloudflare/workers-types").ExecutionContext} ctx + * @returns {Promise} + */ + async fetch(request, env, ctx) { + try { + + userID = env.UUID || userID; + proxyIP = env.PROXYIP || proxyIP; + dohURL = env.DNS_RESOLVER_URL || dohURL; + const upgradeHeader = request.headers.get('Upgrade'); + + if (!upgradeHeader || upgradeHeader !== 'websocket') { + + const url = new URL(request.url); + const searchParams = new URLSearchParams(url.search); + const host = request.headers.get('Host'); + const client = searchParams.get('app'); + + switch (url.pathname) { + + case '/cf': + return new Response(JSON.stringify(request.cf, null, 4), { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=utf-8', + }, + }); + + case `/sub/${userID}`: + + if (client === 'sfa') { + const BestPingSFA = await getSingboxConfig(env, host); + return new Response(`${JSON.stringify(BestPingSFA, null, 4)}`, { status: 200 }); + } + const normalConfigs = await getNormalConfigs(env, host, client); + return new Response(normalConfigs, { status: 200 }); + + case `/fragsub/${userID}`: + + let fragConfigs = await getFragmentConfigs(env, host, 'v2ray'); + fragConfigs = fragConfigs.map(config => config.config); + + return new Response(`${JSON.stringify(fragConfigs, null, 4)}`, { status: 200 }); + + case `/wow/${userID}`: + + const wowConfig = await getWoWConfig(env, client); + return new Response(`${JSON.stringify(wowConfig, null, 4)}`, { status: 200 }); + + case '/panel': + + if (typeof env.bpb !== 'object') { + const errorPage = renderErrorPage('KV Dataset is not properly set!', null, true); + return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); + } + + const isAuth = await Authenticate(request, env); + + if (request.method === 'POST') { + + if (!isAuth) return new Response('Unauthorized', { status: 401 }); + const formData = await request.formData(); + await updateDataset(env, formData); + + return new Response('Success', { status: 200 }); + } + + if (!isAuth) return Response.redirect(`${url.origin}/login`, 302); + if (! await env.bpb.get('proxySettings')) await updateDataset(env); + const fragConfs = await getFragmentConfigs(env, host, 'nekoray'); + const homePage = await renderHomePage(env, host, fragConfs); + + return new Response(homePage, { + status: 200, + headers: { + 'Content-Type': 'text/html', + 'Access-Control-Allow-Origin': url.origin, + 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'Referrer-Policy': 'strict-origin-when-cross-origin' + } + }); + + case '/login': + + if (typeof env.bpb !== 'object') { + const errorPage = renderErrorPage('KV Dataset is not properly set!', null, true); + return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); + } + + const loginAuth = await Authenticate(request, env); + if (loginAuth) return Response.redirect(`${url.origin}/panel`, 302); + + let secretKey = await env.bpb.get('secretKey'); + const pwd = await env.bpb.get('pwd'); + if (!pwd) await env.bpb.put('pwd', 'admin'); + + if (!secretKey) { + secretKey = generateSecretKey(); + await env.bpb.put('secretKey', secretKey); + } + + if (request.method === 'POST') { + const password = await request.text(); + const savedPass = await env.bpb.get('pwd'); + + if (password === savedPass) { + const jwtToken = generateJWTToken(secretKey, password); + const cookieHeader = `jwtToken=${jwtToken}; HttpOnly; Secure; Max-Age=${7 * 24 * 60 * 60}; Path=/; SameSite=Strict`; + + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': cookieHeader, + 'Content-Type': 'text/plain', + } + }); + } else { + return new Response('Method Not Allowed', { status: 405 }); + } + } + + const loginPage = await renderLoginPage(); + + return new Response(loginPage, { + status: 200, + headers: { + 'Content-Type': 'text/html', + 'Access-Control-Allow-Origin': url.origin, + 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'Referrer-Policy': 'strict-origin-when-cross-origin' + } + }); + + case '/logout': + + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': 'jwtToken=; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + 'Content-Type': 'text/plain' + } + }); + + case '/panel/password': + + let passAuth = await Authenticate(request, env); + if (!passAuth) return new Response('Unauthorized!', { status: 401 }); + const newPwd = await request.text(); + const oldPwd = await env.bpb.get('pwd'); + if (newPwd === oldPwd) return new Response('Please enter a new Password!', { status: 400 }); + await env.bpb.put('pwd', newPwd); + + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': 'jwtToken=; Path=/; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + 'Content-Type': 'text/plain', + } + }); + + default: + // return new Response('Not found', { status: 404 }); + url.hostname = 'www.speedtest.net'; + url.protocol = 'https:'; + request = new Request(url, request); + return await fetch(request); + } + } else { + return await vlessOverWSHandler(request); + } + } catch (err) { + /** @type {Error} */ let e = err; + const errorPage = renderErrorPage('Something went wrong!', e.message.toString(), false); + return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); + } + }, }; -/** - * Creates a PlanetScale connection object and returns it. - * @param {{DATABASE_HOST: string, DATABASE_USERNAME: string, DATABASE_PASSWORD: string}} env The environment variables containing the database connection information. - * @returns {Promise} A Promise that resolves to the PlanetScale connection object. - */ -function getPlanetScaleConnection(env) { - const config = { - host: env.DATABASE_HOST, - username: env.DATABASE_USERNAME, - password: env.DATABASE_PASSWORD, - fetch: (url, init) => { - delete (init)["cache"]; - return fetch(url, init); - } - } - return connectdb(config) -} - /** * Handles VLESS over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the VLESS header. * @param {import("@cloudflare/workers-types").Request} request The incoming request object. @@ -173,8 +227,9 @@ async function vlessOverWSHandler(request) { let address = ''; let portWithRandomLog = ''; + let currentDate = new Date(); const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { - console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ''); + console.log(`[${currentDate} ${address}:${portWithRandomLog}] ${info}`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; @@ -239,7 +294,7 @@ async function vlessOverWSHandler(request) { udpStreamWrite(rawClientData); return; } - handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); + handleTCPOutBound(request, remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); }, close() { log(`readableWebSocketStream is close`); @@ -257,81 +312,6 @@ async function vlessOverWSHandler(request) { }); } -let apiResponseCache = null; -let cacheTimeout = null; - -/** - * Fetches the API response from the server and caches it for future use. - * @returns {Promise} A Promise that resolves to the API response object or null if there was an error. - */ -async function fetchApiResponse() { - const requestOptions = { - method: 'GET', - redirect: 'follow' - }; - - try { - const response = await fetch(`https://${apiHost}/api/v1/server/UniProxy/user?node_id=${nodeId}&node_type=v2ray&token=${apiToken}`, requestOptions); - - if (!response.ok) { - console.error('Error: Network response was not ok'); - return null; - } - const apiResponse = await response.json(); - apiResponseCache = apiResponse; - - // Refresh the cache every 5 minutes (300000 milliseconds) - if (cacheTimeout) { - clearTimeout(cacheTimeout); - } - cacheTimeout = setTimeout(() => fetchApiResponse(), 300000); - - return apiResponse; - } catch (error) { - console.error('Error:', error); - return null; - } -} - -/** - * Returns the cached API response if it exists, otherwise fetches the API response from the server and caches it for future use. - * @returns {Promise} A Promise that resolves to the cached API response object or the fetched API response object, or null if there was an error. - */ -async function getApiResponse() { - if (!apiResponseCache) { - return await fetchApiResponse(); - } - return apiResponseCache; -} - -/** - * Checks if a given UUID is present in the API response. - * @param {string} targetUuid The UUID to search for. - * @returns {Promise} A Promise that resolves to true if the UUID is present in the API response, false otherwise. - */ -async function checkUuidInApiResponse(targetUuid) { - // Check if any of the environment variables are empty - if (!nodeId || !apiToken || !apiHost) { - return false; - } - - try { - const apiResponse = await getApiResponse(); - if (!apiResponse) { - return false; - } - const isUuidInResponse = apiResponse.users.some(user => user.uuid === targetUuid); - return isUuidInResponse; - } catch (error) { - console.error('Error:', error); - return false; - } -} - -// Usage example: -// const targetUuid = "65590e04-a94c-4c59-a1f2-571bce925aad"; -// checkUuidInApiResponse(targetUuid).then(result => console.log(result)); - /** * Handles outbound TCP connections. * @@ -344,7 +324,7 @@ async function checkUuidInApiResponse(targetUuid) { * @param {function} log The logging function. * @returns {Promise} The remote socket. */ -async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { +async function handleTCPOutBound(request, remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { /** * Connects to a given address and port and writes data to the socket. @@ -371,7 +351,10 @@ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawCli * @returns {Promise} A Promise that resolves when the retry is complete. */ async function retry() { - const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) + const { pathname } = new URL(request.url); + let panelProxyIP = pathname.split('/')[2]; + panelProxyIP = panelProxyIP ? atob(panelProxyIP) : undefined; + const tcpSocket = await connectAndWrite(panelProxyIP || proxyIP || addressRemote, portRemote); tcpSocket.closed.catch(error => { console.log('retry tcpSocket closed error', error); }).finally(() => { @@ -460,6 +443,7 @@ function processVlessHeader(vlessBuffer, userID) { message: 'invalid data', }; } + const version = new Uint8Array(vlessBuffer.slice(0, 1)); let isValidUser = false; let isUDP = false; @@ -467,7 +451,8 @@ function processVlessHeader(vlessBuffer, userID) { const slicedBufferString = stringify(slicedBuffer); // check if userID is valid uuid or uuids split by , and contains userID in it otherwise return error message to console const uuids = userID.includes(',') ? userID.split(",") : [userID]; - console.log(slicedBufferString, uuids); + // uuid_validator(hostName, slicedBufferString); + // isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()); isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) || uuids.length === 1 && slicedBufferString === uuids[0].trim(); @@ -610,7 +595,7 @@ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, re webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); vlessHeader = null; } else { - console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`); + // console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`); // seems no need rate limit this, CF seems fix this??.. // if (remoteChunkCount > 20000) { // // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M @@ -786,166 +771,2360 @@ async function handleUDPOutBound(webSocket, vlessResponseHeader, log) { /** * - * @param {string} userID - single or comma separated userIDs + * @param {string} userID * @param {string | null} hostName * @returns {string} */ -function getVLESSConfig(userIDs, hostName) { - const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`; - const separator = "---------------------------------------------------------------"; - const hashSeparator = "################################################################"; - - // Split the userIDs into an array - let userIDArray = userIDs.split(','); - - // Prepare output array - let output = []; - let header = []; - - header.push(`\n

- description -

`); -header.push(`\n

👋خوش آمدید: با اینکه سریعترین اینترنت (داخلی) رو در سطح استان داریم میتوانید از کانفیگ های Vless زیر برای کاهش سرعت جهت جلوگیری از تصادفات اینترنتی استفاده کنید.

\n`); -header.push(`

اگر کانفیگ های VLESS برات کار کرد میتونی به این برنامه یک ستاره 🌟 بدی.

\n`); -header.push(`\n

pp-worker - https://github.com/Ptechgithub/pp-worker

\n`); -header.push(`\n

\n\n`.replace(/USERNAME/g, "Ptechgithub").replace(/REPOSITORY/g, "pp-worker")); -header.push(`

✅️ لیست کانفیگ های VLESS

\n

✅️ لیست کانفیگ های CLASH

\n`); -header.push(``); - - - - // Generate output string for each userID - userIDArray.forEach((userID) => { - const vlessMain = `vless://${userID}@${hostName}${commonUrlPart}`; - const vlessSec = `vless://${userID}@${proxyIP}${commonUrlPart}`; - output.push(`UUID: ${userID}`); - output.push(`${hashSeparator}\nv2ray default ip\n💫 کانفیگ با دامین پیش فرض\n${separator}\n1️⃣\n${vlessMain}\n${separator}`); - output.push(`${hashSeparator}\nv2ray with best ip\n🌟 کانفیگ با آی پی سالم کلودفلر\n${separator}\n2️⃣\n${vlessSec}\n${separator}`); - }); - output.push(`${hashSeparator}\n# Clash Proxy Provider configuration format\nproxy-groups:\n - name: UseProvider\n type: select\n use:\n - provider1\n proxies:\n - Proxy\n - DIRECT\nproxy-providers:\n provider1:\n type: http\n url: https://${hostName}/sub/${userIDArray[0]}?format=clash\n interval: 3600\n path: ./provider1.yaml\n health-check:\n enable: true\n interval: 600\n # lazy: true\n url: http://www.gstatic.com/generate_204\n\n${hashSeparator}`); - // HTML Head with CSS - const htmlHead = ` - - pp-worker: VLESS configuration - - - - - - - - - - - - - - - - +const getNormalConfigs = async (env, hostName, client) => { + let proxySettings = {}; + let vlessWsTls = ''; + + try { + proxySettings = await env.bpb.get("proxySettings", {type: 'json'}); + } catch (error) { + console.log(error); + throw new Error(`An error occurred while getting normal configs - ${error}`); + } + + const { cleanIPs, proxyIP, ports } = proxySettings; + const resolved = await resolveDNS(hostName); + const Addresses = [ + hostName, + 'www.speedtest.net', + ...resolved.ipv4, + ...resolved.ipv6.map((ip) => `[${ip}]`), + ...(cleanIPs ? cleanIPs.split(',') : []) + ]; + + ports.forEach(port => { + Addresses.forEach((addr, index) => { + + vlessWsTls += 'vless' + `://${userID}@${addr}:${port}?encryption=none&type=ws&host=${ + randomUpperCase(hostName)}${ + defaultHttpsPorts.includes(port) + ? `&security=tls&sni=${ + randomUpperCase(hostName) + }&fp=randomized&alpn=${ + client === 'singbox' ? 'http/1.1' : 'h2,http/1.1' + }` + : ''}&path=${`/${getRandomPath(16)}${proxyIP ? `/${btoa(proxyIP)}` : ''}`}${ + client === 'singbox' + ? '&eh=Sec-WebSocket-Protocol&ed=2560' + : encodeURIComponent('?ed=2560') + }#${encodeURIComponent(generateRemark(index, port))}\n`; + }); + }); + + return btoa(vlessWsTls); +} - + + + +

BPB Panel ${panelVersion} 💦

+
+

FRAGMENT SETTINGS ⚙️

+
+
+ + +
+
+ + +
+
+ +
+ + - + +
+
+
+ +
+ + - + +
+
+
+ + +
+

FRAGMENT ROUTING ⚙️

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

PROXY IP ⚙️

+
+ + +
+

CLEAN IP ⚙️

+
+ + +
+
+ + + + +
+

PORTS ⚙️

+
+ + + + + + + + + + ${hostName.includes('pages.dev') ? '' : ` + + + `} +
Config typePorts
TLS${(await buildPortsBlock()).httpsPortsBlock}
Non TLS${(await buildPortsBlock()).httpPortsBlock}
+
+

WARP ENDPOINT ⚙️

+
+ + +
+
+ + +
+
+
+ +
+
+
+
+

NORMAL CONFIGS 🔗

+
+ + + + + + + + + + + + + + + + + +
ApplicationSubscription
+
+ verified + v2rayNG +
+
+ verified + v2rayN +
+
+ verified + Shadowrocket +
+
+ verified + Streisand +
+
+ verified + Hiddify +
+
+ verified + Nekoray (Xray) +
+
+ + +
+
+ verified + Nekobox +
+
+ verified + Nekoray (Sing-Box) +
+
+ +
+
+ verified + Sing-box - Best Ping +
+
+ + +
+
+

FRAGMENT SUB ⛓️

+
+ + + + + + + + + +
ApplicationFragment Subscription
+
+ verified + v2rayNG +
+
+ verified + v2rayN +
+
+ verified + MahsaNG +
+
+ verified + Streisand +
+
+ + +
+
+

WARP SUB 🔗

+
+ + + + + + + + + + + + + +
ApplicationSubscription
+
+ verified + v2rayNG +
+
+ verified + v2rayN +
+
+ verified + MahsaNG +
+
+ verified + Streisand +
+
+ + +
+
+ verified + Hiddify +
+
+ verified + Singbox +
+
+ +
+
+

FRAGMENT - NEKORAY ⛓️

+
+ + + + + + ${await genCustomConfRow(fragConfigs)} +
Config AddressFragment Config
+
+ +
+ +
+
+ +
+ + + + + `; + + return html; +} + +const renderLoginPage = async () => { + const html = ` + + + + + + User Login + + -
${header.join('')}
${output.join('\n')}
+
+

BPB Panel ${panelVersion} 💦

+
+

User Login

+
+
+ + +
+
+ +
+
+
+ -`; + `; + + return html; } +const renderErrorPage = (message, error, refer) => { + return ` + + -function createVLESSSub(userID_Path, hostName) { - let portArray_http = [80, 8080, 8880, 2052, 2086, 2095]; - let portArray_https = [443, 8443, 2053, 2096, 2087, 2083]; + + + + Error Page + + - // Split the userIDs into an array - let userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path]; + +
+

BPB Panel ${panelVersion} 💦

+
+

${message} ${refer + ? 'Please try again or refer to documents' + : ''} +

+

${error ? `⚠️ ${error}` : ''}

+
+
+ - // Prepare output array - let output = []; + `; +} - // Generate output string for each userID - userIDArray.forEach((userID) => { - // Check if the hostName is a Cloudflare Pages domain, if not, generate HTTP configurations - // reasons: pages.dev not support http only https - if (!hostName.includes('pages.dev')) { - // Iterate over all ports for http - portArray_http.forEach((port) => { - const commonUrlPart_http = `:${port}?encryption=none&security=none&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}-HTTP`; - const vlessMainHttp = `vless://${userID}@${hostName}${commonUrlPart_http}`; +const xrayConfigTemp = { + remarks: "", + log: { + loglevel: "warning", + }, + dns: {}, + inbounds: [ + { + port: 10808, + protocol: "socks", + settings: { + auth: "noauth", + udp: true, + userLevel: 8, + }, + sniffing: { + destOverride: ["http", "tls"], + enabled: true, + routeOnly: true + }, + tag: "socks-in", + }, + { + port: 10809, + protocol: "http", + settings: { + auth: "noauth", + udp: true, + userLevel: 8, + }, + sniffing: { + destOverride: ["http", "tls"], + enabled: true, + routeOnly: true + }, + tag: "http-in", + }, + { + listen: "127.0.0.1", + port: 10853, + protocol: "dokodemo-door", + settings: { + address: "1.1.1.1", + network: "tcp,udp", + port: 53 + }, + tag: "dns-in" + } + ], + outbounds: [ + { + tag: "fragment", + protocol: "freedom", + settings: { + fragment: { + packets: "tlshello", + length: "", + interval: "", + }, + }, + streamSettings: { + sockopt: { + tcpKeepAliveIdle: 100, + tcpNoDelay: true, + }, + }, + }, + { + protocol: "dns", + tag: "dns-out" + }, + { + protocol: "freedom", + settings: {}, + tag: "direct", + }, + { + protocol: "blackhole", + settings: { + response: { + type: "http", + }, + }, + tag: "block", + }, + ], + policy: { + levels: { + 8: { + connIdle: 300, + downlinkOnly: 1, + handshake: 4, + uplinkOnly: 1, + } + }, + system: { + statsOutboundUplink: true, + statsOutboundDownlink: true, + } + }, + routing: { + domainStrategy: "IPIfNonMatch", + rules: [], + balancers: [ + { + tag: "all", + selector: ["prox"], + strategy: { + type: "leastPing", + }, + } + ] + }, + observatory: { + probeInterval: "3m", + probeURL: "https://api.github.com/_private/browser/stats", + subjectSelector: ["prox"], + EnableConcurrency: true, + }, + stats: {}, +}; + +const xrayOutboundTemp = +{ + mux: { + concurrency: 8, + enabled: true, + xudpConcurrency: 8, + xudpProxyUDP443: "reject" + }, + protocol: "vless", + settings: { + vnext: [ + { + address: "", + port: 443, + users: [ + { + encryption: "none", + flow: "", + id: "", + level: 8, + security: "auto" + } + ] + } + ] + }, + streamSettings: { + network: "ws", + security: "tls", + sockopt: { + dialerProxy: "fragment", + tcpKeepAliveIdle: 100, + tcpNoDelay: true + }, + tlsSettings: { + allowInsecure: false, + fingerprint: "chrome", + alpn: ["h2", "http/1.1"], + serverName: "" + }, + wsSettings: { + headers: {Host: ""}, + path: "" + }, + grpcSettings: { + authority: "", + multiMode: false, + serviceName: "" + }, + realitySettings: { + fingerprint: "", + publicKey: "", + serverName: "", + shortId: "", + spiderX: "" + }, + tcpSettings: { + header: { + request: { + headers: { + Host: [] + }, + method: "GET", + path: [], + version: "1.1" + }, + response: { + headers: { + "Content-Type": ["application/octet-stream"] + }, + reason: "OK", + status: "200", + version: "1.1" + }, + type: "http" + } + } + }, + tag: "proxy" +}; - // For each proxy IP, generate a VLESS configuration and add to output - proxyIPs.forEach((proxyIP) => { - const vlessSecHttp = `vless://${userID}@${proxyIP}${commonUrlPart_http}-${proxyIP}-pp-worker`; - output.push(`${vlessMainHttp}`); - output.push(`${vlessSecHttp}`); - }); - }); - } - // Iterate over all ports for https - portArray_https.forEach((port) => { - const commonUrlPart_https = `:${port}?encryption=none&security=tls&sni=${hostName}&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}-HTTPS`; - const vlessMainHttps = `vless://${userID}@${hostName}${commonUrlPart_https}`; - - // For each proxy IP, generate a VLESS configuration and add to output - proxyIPs.forEach((proxyIP) => { - const vlessSecHttps = `vless://${userID}@${proxyIP}${commonUrlPart_https}-${proxyIP}-pp-worker`; - output.push(`${vlessMainHttps}`); - output.push(`${vlessSecHttps}`); - }); - }); - }); +const singboxConfigTemp = { + log: { + level: "warn", + timestamp: true + }, + dns: { + servers: [ + { + tag: "dns-remote", + address: "https://8.8.8.8/dns-query", + address_resolver: "dns-direct" + }, + { + tag: "dns-direct", + address: "8.8.8.8", + address_resolver: "dns-local", + detour: "direct" + }, + { + tag: "dns-local", + address: "local", + detour: "direct" + }, + { + tag: "dns-block", + address: "rcode://success" + } + ], + rules: [ + { + domain_suffix: [".ir"], + server: "dns-direct" + }, + { + outbound: "direct", + server: "dns-direct" + } + ], + independent_cache: true + }, + inbounds: [ + { + type: "direct", + tag: "dns-in", + listen: "127.0.0.1", + listen_port: 6450, + override_address: "8.8.8.8", + override_port: 53 + }, + { + type: "tun", + tag: "tun-in", + mtu: 9000, + inet4_address: "172.19.0.1/28", + auto_route: true, + strict_route: true, + endpoint_independent_nat: true, + stack: "mixed", + sniff: true, + sniff_override_destination: true + }, + { + type: "mixed", + tag: "mixed-in", + listen: "127.0.0.1", + listen_port: 2080, + sniff: true, + sniff_override_destination: true + } + ], + outbounds: [ + { + type: "selector", + tag: "proxy", + outbounds: ["💦 Best-Ping 💥"] + }, + { + type: "urltest", + tag: "💦 Best-Ping 💥", + outbounds: [], + url: "https://www.gstatic.com/generate_204", + interval: "3m", + tolerance: 50 + }, + { + type: "direct", + tag: "direct" + }, + { + type: "block", + tag: "block" + }, + { + type: "dns", + tag: "dns-out" + } + ], + route: { + rules: [ + { + port: 53, + outbound: "dns-out" + }, + { + inbound: "dns-in", + outbound: "dns-out" + }, + { + network: "udp", + port: 443, + port_range: [], + outbound: "block" + }, + { + ip_is_private: true, + outbound: "direct" + }, + { + rule_set: [ + "geosite-category-ads-all", + "geosite-malware", + "geosite-phishing", + "geosite-cryptominers", + "geoip-malware", + "geoip-phishing" + ], + outbound: "block" + }, + { + rule_set: ["geosite-ir", "geoip-ir"], + outbound: "direct" + }, + { + ip_cidr: ["224.0.0.0/3", "ff00::/8"], + source_ip_cidr: ["224.0.0.0/3", "ff00::/8"], + outbound: "block" + } + ], + rule_set: [ + { + type: "remote", + tag: "geosite-ir", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geosite-category-ads-all", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geosite-malware", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-malware.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geosite-phishing", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-phishing.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geosite-cryptominers", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-cryptominers.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geoip-ir", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geoip-malware", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-malware.srs", + download_detour: "direct" + }, + { + type: "remote", + tag: "geoip-phishing", + format: "binary", + url: "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-phishing.srs", + download_detour: "direct" + } + ], + auto_detect_interface: true, + override_android_vpn: true, + final: "proxy" + }, + experimental: { + clash_api: { + external_controller: "0.0.0.0:9090", + external_ui: "yacd", + external_ui_download_url: "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip", + external_ui_download_detour: "direct", + secret: "", + default_mode: "rule" + } + } +}; - // Join output with newlines - return output.join('\n'); -} +const singboxOutboundTemp = { + type: "vless", + server: "", + server_port: 443, + uuid: "", + domain_strategy: "prefer_ipv6", + packet_encoding: "", + tls: { + alpn: [ + "http/1.1" + ], + enabled: true, + insecure: false, + server_name: "", + utls: { + enabled: true, + fingerprint: "randomized" + } + }, + transport: { + early_data_header_name: "Sec-WebSocket-Protocol", + max_early_data: 2560, + headers: { + Host: "" + }, + path: "/", + type: "ws" + }, + tag: "" +}; + +const xrayWgOutboundTemp = { + protocol: "wireguard", + settings: { + address: [], + mtu: 1280, + peers: [ + { + endpoint: "engage.cloudflareclient.com:2408", + publicKey: "" + } + ], + reserved: [], + secretKey: "" + }, + streamSettings: { + sockopt: { + dialerProxy: "" + } + }, + tag: "proxy" +}; + +const singboxWgOutboundTemp = { + local_address: [], + mtu: 1280, + peer_public_key: "", + pre_shared_key: "", + private_key: "", + reserved: "", + server: "engage.cloudflareclient.com", + server_port: 2408, + type: "wireguard", + domain_strategy: "prefer_ipv6", + detour: "", + tag: "" +};