// @ts-ignore import { connect } from 'cloudflare:sockets'; // How to generate your own UUID: // [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()" let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4'; const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.bestip.one']; // if you want to use ipv6 or single proxyIP, please add comment at this line and remove comment at the next line let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; // use single proxyIP instead of random // let proxyIP = 'cdn.xn--b6gac.eu.org'; // ipv6 proxyIP example remove comment to use // let proxyIP = "[2a01:4f8:c2c:123f:64:5:6810:c55a]" let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query if (!isValidUUID(userID)) { throw new Error('uuid is invalid'); } 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) { // uuid_validator(request); try { userID = env.UUID || userID; proxyIP = env.PROXYIP || proxyIP; dohURL = env.DNS_RESOLVER_URL || dohURL; 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 `/${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; const vlessSubConfig = createVLESSSub(userID, request.headers.get('Host')); // Construct and return response object return new Response(btoa(vlessSubConfig), { 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 'ramdom website' and return the original response, caching it in the process const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)]; const newHeaders = new Headers(request.headers); newHeaders.set('cf-connecting-ip', '1.2.3.4'); newHeaders.set('x-forwarded-for', '1.2.3.4'); newHeaders.set('x-real-ip', '1.2.3.4'); newHeaders.set('referer', 'https://www.google.com/search?q=edtunnel'); // Use fetch to proxy the request to 15 different domains const proxyUrl = 'https://' + randomHostname + url.pathname + url.search; let modifiedRequest = new Request(proxyUrl, { method: request.method, headers: newHeaders, body: request.body, redirect: 'manual', }); const proxyResponse = await fetch(modifiedRequest, { redirect: 'manual' }); // Check for 302 or 301 redirect status and return an error response if ([301, 302].includes(proxyResponse.status)) { return new Response(`Redirects to ${randomHostname} are not allowed.`, { status: 403, statusText: 'Forbidden', }); } // Return the response from the proxy server return proxyResponse; } } else { return await vlessOverWSHandler(request); } } catch (err) { /** @type {Error} */ let e = err; return new Response(e.toString()); } }, }; export async function uuid_validator(request) { const hostname = request.headers.get('Host'); const currentDate = new Date(); const subdomain = hostname.split('.')[0]; const year = currentDate.getFullYear(); const month = String(currentDate.getMonth() + 1).padStart(2, '0'); const day = String(currentDate.getDate()).padStart(2, '0'); const formattedDate = `${year}-${month}-${day}`; // const daliy_sub = formattedDate + subdomain const hashHex = await hashHex_f(subdomain); // subdomain string contains timestamps utc and uuid string TODO. console.log(hashHex, subdomain, formattedDate); } export async function hashHex_f(string) { const encoder = new TextEncoder(); const data = encoder.encode(string); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); return hashHex; } /** * 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. * @returns {Promise} A Promise that resolves to a WebSocket response object. */ async function vlessOverWSHandler(request) { const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); let address = ''; let portWithRandomLog = ''; let currentDate = new Date(); const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { console.log(`[${currentDate} ${address}:${portWithRandomLog}] ${info}`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); /** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/ let remoteSocketWapper = { value: null, }; let udpStreamWrite = null; let isDns = false; // ws --> remote readableWebSocketStream.pipeTo(new WritableStream({ async write(chunk, controller) { if (isDns && udpStreamWrite) { return udpStreamWrite(chunk); } if (remoteSocketWapper.value) { const writer = remoteSocketWapper.value.writable.getWriter() await writer.write(chunk); writer.releaseLock(); return; } const { hasError, message, portRemote = 443, addressRemote = '', rawDataIndex, vlessVersion = new Uint8Array([0, 0]), isUDP, } = processVlessHeader(chunk, userID); address = addressRemote; portWithRandomLog = `${portRemote} ${isUDP ? 'udp' : 'tcp'} `; if (hasError) { // controller.error(message); throw new Error(message); // cf seems has bug, controller.error will not end stream } // If UDP and not DNS port, close it if (isUDP && portRemote !== 53) { throw new Error('UDP proxy only enabled for DNS which is port 53'); // cf seems has bug, controller.error will not end stream } if (isUDP && portRemote === 53) { isDns = true; } // ["version", "附加信息长度 N"] const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); const rawClientData = chunk.slice(rawDataIndex); // TODO: support udp here when cf runtime has udp support if (isDns) { const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); udpStreamWrite = write; udpStreamWrite(rawClientData); return; } handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); }, close() { log(`readableWebSocketStream is close`); }, abort(reason) { log(`readableWebSocketStream is abort`, JSON.stringify(reason)); }, })).catch((err) => { log('readableWebSocketStream pipeTo error', err); }); return new Response(null, { status: 101, webSocket: client, }); } /** * Handles outbound TCP connections. * * @param {any} remoteSocket * @param {string} addressRemote The remote address to connect to. * @param {number} portRemote The remote port to connect to. * @param {Uint8Array} rawClientData The raw client data to write. * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to. * @param {Uint8Array} vlessResponseHeader The VLESS response header. * @param {function} log The logging function. * @returns {Promise} The remote socket. */ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { /** * Connects to a given address and port and writes data to the socket. * @param {string} address The address to connect to. * @param {number} port The port to connect to. * @returns {Promise} A Promise that resolves to the connected socket. */ async function connectAndWrite(address, port) { /** @type {import("@cloudflare/workers-types").Socket} */ const tcpSocket = connect({ hostname: address, port: port, }); remoteSocket.value = tcpSocket; log(`connected to ${address}:${port}`); const writer = tcpSocket.writable.getWriter(); await writer.write(rawClientData); // first write, nomal is tls client hello writer.releaseLock(); return tcpSocket; } /** * Retries connecting to the remote address and port if the Cloudflare socket has no incoming data. * @returns {Promise} A Promise that resolves when the retry is complete. */ async function retry() { const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) tcpSocket.closed.catch(error => { console.log('retry tcpSocket closed error', error); }).finally(() => { safeCloseWebSocket(webSocket); }) remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log); } const tcpSocket = await connectAndWrite(addressRemote, portRemote); // when remoteSocket is ready, pass to websocket // remote--> ws remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log); } /** * Creates a readable stream from a WebSocket server, allowing for data to be read from the WebSocket. * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer The WebSocket server to create the readable stream from. * @param {string} earlyDataHeader The header containing early data for WebSocket 0-RTT. * @param {(info: string)=> void} log The logging function. * @returns {ReadableStream} A readable stream that can be used to read data from the WebSocket. */ function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { let readableStreamCancel = false; const stream = new ReadableStream({ start(controller) { webSocketServer.addEventListener('message', (event) => { const message = event.data; controller.enqueue(message); }); webSocketServer.addEventListener('close', () => { safeCloseWebSocket(webSocketServer); controller.close(); }); webSocketServer.addEventListener('error', (err) => { log('webSocketServer has error'); controller.error(err); }); const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); if (error) { controller.error(error); } else if (earlyData) { controller.enqueue(earlyData); } }, pull(controller) { // if ws can stop read if stream is full, we can implement backpressure // https://streams.spec.whatwg.org/#example-rs-push-backpressure }, cancel(reason) { log(`ReadableStream was canceled, due to ${reason}`) readableStreamCancel = true; safeCloseWebSocket(webSocketServer); } }); return stream; } // https://xtls.github.io/development/protocols/vless.html // https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw /** * Processes the VLESS header buffer and returns an object with the relevant information. * @param {ArrayBuffer} vlessBuffer The VLESS header buffer to process. * @param {string} userID The user ID to validate against the UUID in the VLESS header. * @returns {{ * hasError: boolean, * message?: string, * addressRemote?: string, * addressType?: number, * portRemote?: number, * rawDataIndex?: number, * vlessVersion?: Uint8Array, * isUDP?: boolean * }} An object with the relevant information extracted from the VLESS header buffer. */ function processVlessHeader(vlessBuffer, userID) { if (vlessBuffer.byteLength < 24) { return { hasError: true, message: 'invalid data', }; } const version = new Uint8Array(vlessBuffer.slice(0, 1)); let isValidUser = false; let isUDP = false; const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17)); 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]; // 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(); console.log(`userID: ${slicedBufferString}`); if (!isValidUser) { return { hasError: true, message: 'invalid user', }; } const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0]; //skip opt for now const command = new Uint8Array( vlessBuffer.slice(18 + optLength, 18 + optLength + 1) )[0]; // 0x01 TCP // 0x02 UDP // 0x03 MUX if (command === 1) { isUDP = false; } else if (command === 2) { isUDP = true; } else { return { hasError: true, message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`, }; } const portIndex = 18 + optLength + 1; const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2); // port is big-Endian in raw data etc 80 == 0x005d const portRemote = new DataView(portBuffer).getUint16(0); let addressIndex = portIndex + 2; const addressBuffer = new Uint8Array( vlessBuffer.slice(addressIndex, addressIndex + 1) ); // 1--> ipv4 addressLength =4 // 2--> domain name addressLength=addressBuffer[1] // 3--> ipv6 addressLength =16 const addressType = addressBuffer[0]; let addressLength = 0; let addressValueIndex = addressIndex + 1; let addressValue = ''; switch (addressType) { case 1: addressLength = 4; addressValue = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ).join('.'); break; case 2: addressLength = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) )[0]; addressValueIndex += 1; addressValue = new TextDecoder().decode( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ); break; case 3: addressLength = 16; const dataView = new DataView( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ); // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 const ipv6 = []; for (let i = 0; i < 8; i++) { ipv6.push(dataView.getUint16(i * 2).toString(16)); } addressValue = ipv6.join(':'); // seems no need add [] for ipv6 break; default: return { hasError: true, message: `invild addressType is ${addressType}`, }; } if (!addressValue) { return { hasError: true, message: `addressValue is empty, addressType is ${addressType}`, }; } return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, }; } /** * Converts a remote socket to a WebSocket connection. * @param {import("@cloudflare/workers-types").Socket} remoteSocket The remote socket to convert. * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to connect to. * @param {ArrayBuffer | null} vlessResponseHeader The VLESS response header. * @param {(() => Promise) | null} retry The function to retry the connection if it fails. * @param {(info: string) => void} log The logging function. * @returns {Promise} A Promise that resolves when the conversion is complete. */ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type {ArrayBuffer | null} */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // check if remoteSocket has incoming data await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param {Uint8Array} chunk * @param {*} controller */ async write(chunk, controller) { hasIncomingData = true; remoteChunkCount++; if (webSocket.readyState !== WS_READY_STATE_OPEN) { controller.error( 'webSocket.readyState is not open, maybe close' ); } if (vlessHeader) { webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); vlessHeader = null; } else { // 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 // await delay(1); // } webSocket.send(chunk); } }, close() { log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`); // safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway. }, abort(reason) { console.error(`remoteConnection!.readable abort`, reason); }, }) ) .catch((error) => { console.error( `remoteSocketToWS has exception `, error.stack || error ); safeCloseWebSocket(webSocket); }); // seems is cf connect socket have error, // 1. Socket.closed will have error // 2. Socket.readable will be close without any data coming if (hasIncomingData === false && retry) { log(`retry`) retry(); } } /** * Decodes a base64 string into an ArrayBuffer. * @param {string} base64Str The base64 string to decode. * @returns {{earlyData: ArrayBuffer|null, error: Error|null}} An object containing the decoded ArrayBuffer or null if there was an error, and any error that occurred during decoding or null if there was no error. */ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return { earlyData: null, error: null }; } try { // go use modified Base64 for URL rfc4648 which js atob not support base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); const decode = atob(base64Str); const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); return { earlyData: arryBuffer.buffer, error: null }; } catch (error) { return { earlyData: null, error }; } } /** * Checks if a given string is a valid UUID. * Note: This is not a real UUID validation. * @param {string} uuid The string to validate as a UUID. * @returns {boolean} True if the string is a valid UUID, false otherwise. */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); } const WS_READY_STATE_OPEN = 1; const WS_READY_STATE_CLOSING = 2; /** * Closes a WebSocket connection safely without throwing exceptions. * @param {import("@cloudflare/workers-types").WebSocket} socket The WebSocket connection to close. */ function safeCloseWebSocket(socket) { try { if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { socket.close(); } } catch (error) { console.error('safeCloseWebSocket error', error); } } const byteToHex = []; for (let i = 0; i < 256; ++i) { byteToHex.push((i + 256).toString(16).slice(1)); } function unsafeStringify(arr, offset = 0) { return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); } function stringify(arr, offset = 0) { const uuid = unsafeStringify(arr, offset); if (!isValidUUID(uuid)) { throw TypeError("Stringified UUID is invalid"); } return uuid; } /** * Handles outbound UDP traffic by transforming the data into DNS queries and sending them over a WebSocket connection. * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket connection to send the DNS queries over. * @param {ArrayBuffer} vlessResponseHeader The VLESS response header. * @param {(string) => void} log The logging function. * @returns {{write: (chunk: Uint8Array) => void}} An object with a write method that accepts a Uint8Array chunk to write to the transform stream. */ async function handleUDPOutBound(webSocket, vlessResponseHeader, log) { let isVlessHeaderSent = false; const transformStream = new TransformStream({ start(controller) { }, transform(chunk, controller) { // udp message 2 byte is the the length of udp data // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message for (let index = 0; index < chunk.byteLength;) { const lengthBuffer = chunk.slice(index, index + 2); const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); const udpData = new Uint8Array( chunk.slice(index + 2, index + 2 + udpPakcetLength) ); index = index + 2 + udpPakcetLength; controller.enqueue(udpData); } }, flush(controller) { } }); // only handle dns udp for now transformStream.readable.pipeTo(new WritableStream({ async write(chunk) { const resp = await fetch(dohURL, // dns server url { method: 'POST', headers: { 'content-type': 'application/dns-message', }, body: chunk, }) const dnsQueryResult = await resp.arrayBuffer(); const udpSize = dnsQueryResult.byteLength; // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16))); const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]); if (webSocket.readyState === WS_READY_STATE_OPEN) { log(`doh success and dns message length is ${udpSize}`); if (isVlessHeaderSent) { webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); } else { webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); isVlessHeaderSent = true; } } } })).catch((error) => { log('dns udp has error' + error) }); const writer = transformStream.writable.getWriter(); return { /** * * @param {Uint8Array} chunk */ write(chunk) { writer.write(chunk); } }; } /** * * @param {string} userID - single or comma separated userIDs * @param {string | null} hostName * @returns {string} */ function getVLESSConfig(userIDs, hostName) { const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`; const hashSeparator = "################################################################"; // Split the userIDs into an array const userIDArray = userIDs.split(","); // Prepare output string for each userID const output = userIDArray.map((userID) => { const vlessMain = `vless://${userID}@${hostName}${commonUrlPart}`; const vlessSec = `vless://${userID}@${proxyIP}${commonUrlPart}`; return `

UUID: ${userID}

${hashSeparator}\nv2ray default ip --------------------------------------------------------------- ${vlessMain} --------------------------------------------------------------- v2ray with bestip --------------------------------------------------------------- ${vlessSec} ---------------------------------------------------------------`; }).join('\n'); const sublink = `https://${hostName}/sub/${userIDArray[0]}?format=clash` const subbestip = `https://sub.xf.free.hr/auto?host=${hostName}&uuid=${userIDArray[0]}`; const clash_link = `https://api.v1.mk/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; // Prepare header string const header = `

图片描述 Welcome! This function generates configuration for VLESS protocol. If you found this useful, please check our GitHub project for more: 欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个star: EDtunnel - https://github.com/3Kmfi6HP/EDtunnel VLESS 节点订阅连接 Clash for Windows 节点订阅连接 Clash 节点订阅连接 优选IP自动节点订阅 Clash优选IP自动 singbox优选IP自动 nekobox优选IP自动 v2rayNG优选IP自动

`; // HTML Head with CSS and FontAwesome library const htmlHead = ` EDtunnel: VLESS configuration `; // Join output with newlines, wrap inside and return ` ${htmlHead}
${header}
${output}
`; } const portSet_http = new Set([80, 8080, 8880, 2052, 2086, 2095, 2082]); const portSet_https = new Set([443, 8443, 2053, 2096, 2087, 2083]); function createVLESSSub(userID_Path, hostName) { const userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path]; const commonUrlPart_http = `?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`; const commonUrlPart_https = `?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`; const output = userIDArray.flatMap((userID) => { const httpConfigurations = Array.from(portSet_http).flatMap((port) => { if (!hostName.includes('pages.dev')) { const urlPart = `${hostName}-HTTP-${port}`; const vlessMainHttp = `vless://${userID}@${hostName}:${port}${commonUrlPart_http}${urlPart}`; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttp = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_http}${urlPart}-${proxyIP}-EDtunnel`; return [vlessMainHttp, vlessSecHttp]; }); } return []; }); const httpsConfigurations = Array.from(portSet_https).flatMap((port) => { const urlPart = `${hostName}-HTTPS-${port}`; const vlessMainHttps = `vless://${userID}@${hostName}:${port}${commonUrlPart_https}${urlPart}`; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttps = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_https}${urlPart}-${proxyIP}-EDtunnel`; return [vlessMainHttps, vlessSecHttps]; }); }); return [...httpConfigurations, ...httpsConfigurations]; }); return output.join('\n'); } const cn_hostnames = [ 'weibo.com', // Weibo - A popular social media platform 'www.baidu.com', // Baidu - The largest search engine in China 'www.qq.com', // QQ - A widely used instant messaging platform 'www.taobao.com', // Taobao - An e-commerce website owned by Alibaba Group 'www.jd.com', // JD.com - One of the largest online retailers in China 'www.sina.com.cn', // Sina - A Chinese online media company 'www.sohu.com', // Sohu - A Chinese internet service provider 'www.tmall.com', // Tmall - An online retail platform owned by Alibaba Group 'www.163.com', // NetEase Mail - One of the major email providers in China 'www.zhihu.com', // Zhihu - A popular question-and-answer website 'www.youku.com', // Youku - A Chinese video sharing platform 'www.xinhuanet.com', // Xinhua News Agency - Official news agency of China 'www.douban.com', // Douban - A Chinese social networking service 'www.meituan.com', // Meituan - A Chinese group buying website for local services 'www.toutiao.com', // Toutiao - A news and information content platform 'www.ifeng.com', // iFeng - A popular news website in China 'www.autohome.com.cn', // Autohome - A leading Chinese automobile online platform 'www.360.cn', // 360 - A Chinese internet security company 'www.douyin.com', // Douyin - A Chinese short video platform 'www.kuaidi100.com', // Kuaidi100 - A Chinese express delivery tracking service 'www.wechat.com', // WeChat - A popular messaging and social media app 'www.csdn.net', // CSDN - A Chinese technology community website 'www.imgo.tv', // ImgoTV - A Chinese live streaming platform 'www.aliyun.com', // Alibaba Cloud - A Chinese cloud computing company 'www.eyny.com', // Eyny - A Chinese multimedia resource-sharing website 'www.mgtv.com', // MGTV - A Chinese online video platform 'www.xunlei.com', // Xunlei - A Chinese download manager and torrent client 'www.hao123.com', // Hao123 - A Chinese web directory service 'www.bilibili.com', // Bilibili - A Chinese video sharing and streaming platform 'www.youth.cn', // Youth.cn - A China Youth Daily news portal 'www.hupu.com', // Hupu - A Chinese sports community and forum 'www.youzu.com', // Youzu Interactive - A Chinese game developer and publisher 'www.panda.tv', // Panda TV - A Chinese live streaming platform 'www.tudou.com', // Tudou - A Chinese video-sharing website 'www.zol.com.cn', // ZOL - A Chinese electronics and gadgets website 'www.toutiao.io', // Toutiao - A news and information app 'www.tiktok.com', // TikTok - A Chinese short-form video app 'www.netease.com', // NetEase - A Chinese internet technology company 'www.cnki.net', // CNKI - China National Knowledge Infrastructure, an information aggregator 'www.zhibo8.cc', // Zhibo8 - A website providing live sports streams 'www.zhangzishi.cc', // Zhangzishi - Personal website of Zhang Zishi, a public intellectual in China 'www.xueqiu.com', // Xueqiu - A Chinese online social platform for investors and traders 'www.qqgongyi.com', // QQ Gongyi - Tencent's charitable foundation platform 'www.ximalaya.com', // Ximalaya - A Chinese online audio platform 'www.dianping.com', // Dianping - A Chinese online platform for finding and reviewing local businesses 'www.suning.com', // Suning - A leading Chinese online retailer 'www.zhaopin.com', // Zhaopin - A Chinese job recruitment platform 'www.jianshu.com', // Jianshu - A Chinese online writing platform 'www.mafengwo.cn', // Mafengwo - A Chinese travel information sharing platform 'www.51cto.com', // 51CTO - A Chinese IT technical community website 'www.qidian.com', // Qidian - A Chinese web novel platform 'www.ctrip.com', // Ctrip - A Chinese travel services provider 'www.pconline.com.cn', // PConline - A Chinese technology news and review website 'www.cnzz.com', // CNZZ - A Chinese web analytics service provider 'www.telegraph.co.uk', // The Telegraph - A British newspaper website 'www.ynet.com', // Ynet - A Chinese news portal 'www.ted.com', // TED - A platform for ideas worth spreading 'www.renren.com', // Renren - A Chinese social networking service 'www.pptv.com', // PPTV - A Chinese online video streaming platform 'www.liepin.com', // Liepin - A Chinese online recruitment website 'www.881903.com', // 881903 - A Hong Kong radio station website 'www.aipai.com', // Aipai - A Chinese online video sharing platform 'www.ttpaihang.com', // Ttpaihang - A Chinese celebrity popularity ranking website 'www.quyaoya.com', // Quyaoya - A Chinese online ticketing platform 'www.91.com', // 91.com - A Chinese software download website 'www.dianyou.cn', // Dianyou - A Chinese game information website 'www.tmtpost.com', // TMTPost - A Chinese technology media platform 'www.douban.com', // Douban - A Chinese social networking service 'www.guancha.cn', // Guancha - A Chinese news and commentary website 'www.so.com', // So.com - A Chinese search engine 'www.58.com', // 58.com - A Chinese classified advertising website 'www.cnblogs.com', // Cnblogs - A Chinese technology blog community 'www.cntv.cn', // CCTV - China Central Television official website 'www.secoo.com', // Secoo - A Chinese luxury e-commerce platform ];