diff --git a/.github/workflows/obfuscator.yml b/.github/workflows/obfuscator.yml new file mode 100644 index 0000000..ead2c92 --- /dev/null +++ b/.github/workflows/obfuscator.yml @@ -0,0 +1,33 @@ +name: Obfuscate and Commit + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + obfuscate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Install dependencies + run: npm install -g javascript-obfuscator + + - name: Obfuscate code + run: | + javascript-obfuscator index.js --output _worker.js + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add _worker.js + git commit -m "Obfuscate _worker.js" || echo "No changes to commit" + git push diff --git a/_worker.js b/_worker.js index 58871db..d2d77c8 100644 --- a/_worker.js +++ b/_worker.js @@ -1,1172 +1 @@ -// @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.cloudflare.cyou']; - -// 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]" - -// Example: user:pass@host:port or host:port -let socks5Address = ''; -// socks5Relay is true, will proxy all traffic to socks5Address, otherwise socks5Address only be used for cloudflare ips -let socks5Relay = false; - -if (!isValidUUID(userID)) { - throw new Error('uuid is not valid'); -} - -let parsedSocks5Address = {}; -let enableSocks = false; - - -export default { - /** - * @param {import("@cloudflare/workers-types").Request} request - * @param {{UUID: string, PROXYIP: string, SOCKS5: string, SOCKS5_RELAY: string}} env - * @param {import("@cloudflare/workers-types").ExecutionContext} _ctx - * @returns {Promise} - */ - async fetch(request, env, _ctx) { - try { - const { UUID, PROXYIP, SOCKS5, SOCKS5_RELAY } = env; - userID = UUID || userID; - proxyIP = PROXYIP || proxyIP; - socks5Address = SOCKS5 || socks5Address; - socks5Relay = SOCKS5_RELAY || socks5Relay; - - if (socks5Address) { - try { - parsedSocks5Address = socks5AddressParser(socks5Address); - enableSocks = true; - } catch (err) { - console.log(err.toString()); - enableSocks = false; - } - } - - const userID_Path = userID.includes(',') ? userID.split(',')[0] : userID; - const url = new URL(request.url); - const host = request.headers.get('Host'); - - if (request.headers.get('Upgrade') !== 'websocket') { - 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}`: - return new Response(getConfig(userID, host), { - status: 200, - headers: { "Content-Type": "text/html; charset=utf-8" }, - }); - case `/sub/${userID_Path}`: - return new Response(btoa(GenSub(userID, host)), { - status: 200, - headers: { "Content-Type": "text/plain;charset=utf-8" }, - }); - case `/bestip/${userID_Path}`: - return fetch(`https://sub.xf.free.hr/auto?host=${host}&uuid=${userID}&path=/`, { headers: request.headers }); - default: - return handleDefaultPath(url, request); - } - } else { - return await ProtocolOverWSHandler(request); - } - } catch (err) { - return new Response(err.toString()); - } - }, -}; - -async function handleDefaultPath(url, request) { - const randomHostname = hostnames[Math.floor(Math.random() * 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'); - - const proxyUrl = 'https://' + randomHostname + url.pathname + url.search; - const modifiedRequest = new Request(proxyUrl, { - method: request.method, - headers: newHeaders, - body: request.body, - redirect: 'manual', - }); - - const proxyResponse = await fetch(modifiedRequest, { redirect: 'manual' }); - if ([301, 302].includes(proxyResponse.status)) { - return new Response(`Redirects to ${randomHostname} are not allowed.`, { - status: 403, - statusText: 'Forbidden', - }); - } - return proxyResponse; -} - -/** - * Handles protocol over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the protocol 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 ProtocolOverWSHandler(request) { - - /** @type {import("@cloudflare/workers-types").WebSocket[]} */ - // @ts-ignore - const webSocketPair = new WebSocketPair(); - const [client, webSocket] = Object.values(webSocketPair); - - webSocket.accept(); - - let address = ''; - let portWithRandomLog = ''; - const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { - console.log(`[${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 isDns = false; - - // ws --> remote - readableWebSocketStream.pipeTo(new WritableStream({ - async write(chunk, controller) { - if (isDns) { - return await handleDNSQuery(chunk, webSocket, null, log); - } - if (remoteSocketWapper.value) { - const writer = remoteSocketWapper.value.writable.getWriter() - await writer.write(chunk); - writer.releaseLock(); - return; - } - - const { - hasError, - message, - addressType, - portRemote = 443, - addressRemote = '', - rawDataIndex, - ProtocolVersion = new Uint8Array([0, 0]), - isUDP, - } = processProtocolHeader(chunk, userID); - address = addressRemote; - portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp ' - } `; - if (hasError) { - // controller.error(message); - throw new Error(message); // cf seems has bug, controller.error will not end stream - // webSocket.close(1000, message); - return; - } - // if UDP but port not DNS port, close it - if (isUDP) { - if (portRemote === 53) { - isDns = true; - } else { - // controller.error('UDP proxy only enable for DNS which is port 53'); - throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream - return; - } - } - // ["version", "附加信息长度 N"] - const ProtocolResponseHeader = new Uint8Array([ProtocolVersion[0], 0]); - const rawClientData = chunk.slice(rawDataIndex); - - if (isDns) { - return handleDNSQuery(rawClientData, webSocket, ProtocolResponseHeader, log); - } - handleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, 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, - // @ts-ignore - 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} protocolResponseHeader The protocol response header. - * @param {function} log The logging function. - * @returns {Promise} The remote socket. - */ -async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, log,) { - async function connectAndWrite(address, port, socks = false) { - /** @type {import("@cloudflare/workers-types").Socket} */ - let tcpSocket; - if (socks5Relay) { - tcpSocket = await socks5Connect(addressType, address, port, log) - } else { - tcpSocket = socks ? await socks5Connect(addressType, address, port, log) - : connect({ - hostname: address, - port: port, - }); - } - remoteSocket.value = tcpSocket; - log(`connected to ${address}:${port}`); - const writer = tcpSocket.writable.getWriter(); - await writer.write(rawClientData); // first write, normal is tls client hello - writer.releaseLock(); - return tcpSocket; - } - - // if the cf connect tcp socket have no incoming data, we retry to redirect ip - async function retry() { - if (enableSocks) { - tcpSocket = await connectAndWrite(addressRemote, portRemote, true); - } else { - tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote); - } - // no matter retry success or not, close websocket - tcpSocket.closed.catch(error => { - console.log('retry tcpSocket closed error', error); - }).finally(() => { - safeCloseWebSocket(webSocket); - }) - remoteSocketToWS(tcpSocket, webSocket, ProtocolResponseHeader, null, log); - } - - let tcpSocket = await connectAndWrite(addressRemote, portRemote); - - // when remoteSocket is ready, pass to websocket - // remote--> ws - remoteSocketToWS(tcpSocket, webSocket, ProtocolResponseHeader, 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/protocol.html -// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw - -/** - * Processes the protocol header buffer and returns an object with the relevant information. - * @param {ArrayBuffer} protocolBuffer The protocol header buffer to process. - * @param {string} userID The user ID to validate against the UUID in the protocol header. - * @returns {{ - * hasError: boolean, - * message?: string, - * addressRemote?: string, - * addressType?: number, - * portRemote?: number, - * rawDataIndex?: number, - * protocolVersion?: Uint8Array, - * isUDP?: boolean - * }} An object with the relevant information extracted from the protocol header buffer. - */ -function processProtocolHeader(protocolBuffer, userID) { - if (protocolBuffer.byteLength < 24) { - return { hasError: true, message: 'invalid data' }; - } - - const dataView = new DataView(protocolBuffer); - const version = dataView.getUint8(0); - const slicedBufferString = stringify(new Uint8Array(protocolBuffer.slice(1, 17))); - - const uuids = userID.includes(',') ? userID.split(",") : [userID]; - const isValidUser = uuids.some(uuid => slicedBufferString === uuid.trim()) || - (uuids.length === 1 && slicedBufferString === uuids[0].trim()); - - console.log(`userID: ${slicedBufferString}`); - - if (!isValidUser) { - return { hasError: true, message: 'invalid user' }; - } - - const optLength = dataView.getUint8(17); - const command = dataView.getUint8(18 + optLength); - - if (command !== 1 && command !== 2) { - return { hasError: true, message: `command ${command} is not supported, command 01-tcp,02-udp,03-mux` }; - } - - const portIndex = 18 + optLength + 1; - const portRemote = dataView.getUint16(portIndex); - const addressType = dataView.getUint8(portIndex + 2); - let addressValue, addressLength, addressValueIndex; - - switch (addressType) { - case 1: - addressLength = 4; - addressValueIndex = portIndex + 3; - addressValue = new Uint8Array(protocolBuffer.slice(addressValueIndex, addressValueIndex + addressLength)).join('.'); - break; - case 2: - addressLength = dataView.getUint8(portIndex + 3); - addressValueIndex = portIndex + 4; - addressValue = new TextDecoder().decode(protocolBuffer.slice(addressValueIndex, addressValueIndex + addressLength)); - break; - case 3: - addressLength = 16; - addressValueIndex = portIndex + 3; - addressValue = Array.from({ length: 8 }, (_, i) => dataView.getUint16(addressValueIndex + i * 2).toString(16)).join(':'); - break; - default: - return { hasError: true, message: `invalid addressType: ${addressType}` }; - } - - if (!addressValue) { - return { hasError: true, message: `addressValue is empty, addressType is ${addressType}` }; - } - - return { - hasError: false, - addressRemote: addressValue, - addressType, - portRemote, - rawDataIndex: addressValueIndex + addressLength, - protocolVersion: new Uint8Array([version]), - isUDP: command === 2 - }; -} - - -/** - * 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} protocolResponseHeader The protocol 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, protocolResponseHeader, retry, log) { - let hasIncomingData = false; - - try { - await remoteSocket.readable.pipeTo( - new WritableStream({ - async write(chunk) { - if (webSocket.readyState !== WS_READY_STATE_OPEN) { - throw new Error('WebSocket is not open'); - } - - hasIncomingData = true; - - if (protocolResponseHeader) { - webSocket.send(await new Blob([protocolResponseHeader, chunk]).arrayBuffer()); - protocolResponseHeader = null; - } else { - webSocket.send(chunk); - } - }, - close() { - log(`Remote connection readable closed. Had incoming data: ${hasIncomingData}`); - }, - abort(reason) { - console.error(`Remote connection readable aborted:`, reason); - }, - }) - ); - } catch (error) { - console.error(`remoteSocketToWS error:`, error.stack || error); - safeCloseWebSocket(webSocket); - } - - if (!hasIncomingData && retry) { - log(`No incoming data, retrying`); - await 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 { - // Convert modified Base64 for URL (RFC 4648) to standard Base64 - base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); - // Decode Base64 string - const binaryStr = atob(base64Str); - // Convert binary string to ArrayBuffer - const buffer = new ArrayBuffer(binaryStr.length); - const view = new Uint8Array(buffer); - for (let i = 0; i < binaryStr.length; i++) { - view[i] = binaryStr.charCodeAt(i); - } - return { earlyData: buffer, error: null }; - } catch (error) { - return { earlyData: null, error }; - } -} - -/** - * Checks if a given string is a valid UUID. - * @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) { - // More precise UUID regex pattern - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][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 = Array.from({ length: 256 }, (_, i) => (i + 0x100).toString(16).slice(1)); - -function unsafeStringify(arr, offset = 0) { - return [ - byteToHex[arr[offset]], - 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]] - ].join('').toLowerCase(); -} - -function stringify(arr, offset = 0) { - const uuid = unsafeStringify(arr, offset); - if (!isValidUUID(uuid)) { - throw new 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} protocolResponseHeader The protocol 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, protocolResponseHeader, log) { - - let isprotocolHeaderSent = 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 (isprotocolHeaderSent) { - webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); - } else { - webSocket.send(await new Blob([protocolResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); - isprotocolHeaderSent = true; - } - } - } - })).catch((error) => { - log('dns udp has error' + error) - }); - - const writer = transformStream.writable.getWriter(); - - return { - /** - * - * @param {Uint8Array} chunk - */ - write(chunk) { - writer.write(chunk); - } - }; -} - -/** - * - * @param {ArrayBuffer} udpChunk - * @param {import("@cloudflare/workers-types").WebSocket} webSocket - * @param {ArrayBuffer} protocolResponseHeader - * @param {(string)=> void} log - */ -async function handleDNSQuery(udpChunk, webSocket, protocolResponseHeader, log) { - // no matter which DNS server client send, we alwasy use hard code one. - // beacsue someof DNS server is not support DNS over TCP - try { - const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug - const dnsPort = 53; - /** @type {ArrayBuffer | null} */ - let vlessHeader = protocolResponseHeader; - /** @type {import("@cloudflare/workers-types").Socket} */ - const tcpSocket = connect({ - hostname: dnsServer, - port: dnsPort, - }); - - log(`connected to ${dnsServer}:${dnsPort}`); - const writer = tcpSocket.writable.getWriter(); - await writer.write(udpChunk); - writer.releaseLock(); - await tcpSocket.readable.pipeTo(new WritableStream({ - async write(chunk) { - if (webSocket.readyState === WS_READY_STATE_OPEN) { - if (vlessHeader) { - webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); - vlessHeader = null; - } else { - webSocket.send(chunk); - } - } - }, - close() { - log(`dns server(${dnsServer}) tcp is close`); - }, - abort(reason) { - console.error(`dns server(${dnsServer}) tcp is abort`, reason); - }, - })); - } catch (error) { - console.error( - `handleDNSQuery have exception, error: ${error.message}` - ); - } -} - - -/** - * - * @param {number} addressType - * @param {string} addressRemote - * @param {number} portRemote - * @param {function} log The logging function. - */ -async function socks5Connect(addressType, addressRemote, portRemote, log) { - const { username, password, hostname, port } = parsedSocks5Address; - // Connect to the SOCKS server - const socket = connect({ - hostname, - port, - }); - - // Request head format (Worker -> Socks Server): - // +----+----------+----------+ - // |VER | NMETHODS | METHODS | - // +----+----------+----------+ - // | 1 | 1 | 1 to 255 | - // +----+----------+----------+ - - // https://en.wikipedia.org/wiki/SOCKS#SOCKS5 - // For METHODS: - // 0x00 NO AUTHENTICATION REQUIRED - // 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929 - const socksGreeting = new Uint8Array([5, 2, 0, 2]); - - const writer = socket.writable.getWriter(); - - await writer.write(socksGreeting); - log('sent socks greeting'); - - const reader = socket.readable.getReader(); - const encoder = new TextEncoder(); - let res = (await reader.read()).value; - // Response format (Socks Server -> Worker): - // +----+--------+ - // |VER | METHOD | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - if (res[0] !== 0x05) { - log(`socks server version error: ${res[0]} expected: 5`); - return; - } - if (res[1] === 0xff) { - log("no acceptable methods"); - return; - } - - // if return 0x0502 - if (res[1] === 0x02) { - log("socks server needs auth"); - if (!username || !password) { - log("please provide username/password"); - return; - } - // +----+------+----------+------+----------+ - // |VER | ULEN | UNAME | PLEN | PASSWD | - // +----+------+----------+------+----------+ - // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | - // +----+------+----------+------+----------+ - const authRequest = new Uint8Array([ - 1, - username.length, - ...encoder.encode(username), - password.length, - ...encoder.encode(password) - ]); - await writer.write(authRequest); - res = (await reader.read()).value; - // expected 0x0100 - if (res[0] !== 0x01 || res[1] !== 0x00) { - log("fail to auth socks server"); - return; - } - } - - // Request data format (Worker -> Socks Server): - // +----+-----+-------+------+----------+----------+ - // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - // ATYP: address type of following address - // 0x01: IPv4 address - // 0x03: Domain name - // 0x04: IPv6 address - // DST.ADDR: desired destination address - // DST.PORT: desired destination port in network octet order - - // addressType - // 1--> ipv4 addressLength =4 - // 2--> domain name - // 3--> ipv6 addressLength =16 - let DSTADDR; // DSTADDR = ATYP + DST.ADDR - switch (addressType) { - case 1: - DSTADDR = new Uint8Array( - [1, ...addressRemote.split('.').map(Number)] - ); - break; - case 2: - DSTADDR = new Uint8Array( - [3, addressRemote.length, ...encoder.encode(addressRemote)] - ); - break; - case 3: - DSTADDR = new Uint8Array( - [4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])] - ); - break; - default: - log(`invild addressType is ${addressType}`); - return; - } - const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]); - await writer.write(socksRequest); - log('sent socks request'); - - res = (await reader.read()).value; - // Response format (Socks Server -> Worker): - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - if (res[1] === 0x00) { - log("socks connection opened"); - } else { - log("fail to open socks connection"); - return; - } - writer.releaseLock(); - reader.releaseLock(); - return socket; -} - -/** - * - * @param {string} address - */ -function socks5AddressParser(address) { - let [latter, former] = address.split("@").reverse(); - let username, password, hostname, port; - if (former) { - const formers = former.split(":"); - if (formers.length !== 2) { - throw new Error('Invalid SOCKS address format'); - } - [username, password] = formers; - } - const latters = latter.split(":"); - port = Number(latters.pop()); - if (isNaN(port)) { - throw new Error('Invalid SOCKS address format'); - } - hostname = latters.join(":"); - const regex = /^\[.*\]$/; - if (hostname.includes(":") && !regex.test(hostname)) { - throw new Error('Invalid SOCKS address format'); - } - return { - username, - password, - hostname, - port, - } -} - - -const at = 'QA=='; -const pt = 'dmxlc3M='; -const ed = 'RUR0dW5uZWw='; - -/** - * - * @param {string} userID - single or comma separated userIDs - * @param {string | null} hostName - * @returns {string} - */ -function getConfig(userIDs, hostName) { - const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`; - - // Split the userIDs into an array - const userIDArray = userIDs.split(","); - - // Prepare output string for each userID - const sublink = `https://${hostName}/sub/${userIDArray[0]}?format=clash` - const subbestip = `https://${hostName}/bestip/${userIDArray[0]}`; - const clash_link = `https://url.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`; - // HTML Head with CSS and FontAwesome library - const htmlHead = ` - - EDtunnel: Configuration - - - - - - - - - - - - - - - - - - - `; - - const header = ` -
-

EDtunnel: Protocol Configuration

- -

Welcome! This function generates configuration for the vless protocol. If you found this useful, please check our GitHub project:

-

EDtunnel - https://github.com/6Kmfi6HP/EDtunnel

-
- -
-

Options Explained:

-
    -
  • VLESS Subscription: Direct link for VLESS protocol configuration. Suitable for clients supporting VLESS.
  • -
  • Clash Subscription: Opens the Clash client with pre-configured settings. Best for Clash users on mobile devices.
  • -
  • Clash Link: A web link to convert the VLESS config to Clash format. Useful for manual import or troubleshooting.
  • -
  • Best IP Subscription: Provides a curated list of optimal server IPs for many different countries.
  • -
-

Choose the option that best fits your client and needs. For most users, the VLESS or Clash Subscription will be the easiest to use.

-
-
- `; - - const configOutput = userIDArray.map((userID) => { - const protocolMain = atob(pt) + '://' + userID + atob(at) + hostName + commonUrlPart; - const protocolSec = atob(pt) + '://' + userID + atob(at) + proxyIP + commonUrlPart; - return ` -
-

UUID: ${userID}

-

Default IP Configuration

-
-
${protocolMain}
- -
- -

Best IP Configuration

-
-
${protocolSec}
- -
-
- `; - }).join(''); - - return ` - - ${htmlHead} - - ${header} - ${configOutput} - - - `; -} - -const HttpPort = new Set([80, 8080, 8880, 2052, 2086, 2095, 2082]); -const HttpsPort = new Set([443, 8443, 2053, 2096, 2087, 2083]); - -function GenSub(ไอดีผู้ใช้_เส้นทาง, ชื่อโฮสต์) { - const อาร์เรย์ไอดีผู้ใช้ = ไอดีผู้ใช้_เส้นทาง.includes(',') ? ไอดีผู้ใช้_เส้นทาง.split(',') : [ไอดีผู้ใช้_เส้นทาง]; - const randomPath = () => '/' + Math.random().toString(36).substring(2, 15) + '?ed=2048'; - const ส่วนUrlทั่วไปHttp = `?encryption=none&security=none&fp=random&type=ws&host=${ชื่อโฮสต์}&path=${encodeURIComponent(randomPath())}#`; - const ส่วนUrlทั่วไปHttps = `?encryption=none&security=tls&sni=${ชื่อโฮสต์}&fp=random&type=ws&host=${ชื่อโฮสต์}&path=%2F%3Fed%3D2048#`; - - const ผลลัพธ์ = อาร์เรย์ไอดีผู้ใช้.flatMap((ไอดีผู้ใช้) => { - const PartHttp = Array.from(HttpPort).flatMap((พอร์ต) => { - if (!ชื่อโฮสต์.includes('pages.dev')) { - const ส่วนUrl = `${ชื่อโฮสต์}-HTTP-${พอร์ต}`; - const protocolหลักHttp = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + ชื่อโฮสต์ + ':' + พอร์ต + ส่วนUrlทั่วไปHttp + ส่วนUrl; - return proxyIPs.flatMap((proxyIP) => { - const protocolรองHttp = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + proxyIP + ':' + พอร์ต + ส่วนUrlทั่วไปHttp + ส่วนUrl + '-' + proxyIP + '-' + atob(ed); - return [protocolหลักHttp, protocolรองHttp]; - }); - } - return []; - }); - - const PartHttps = Array.from(HttpsPort).flatMap((พอร์ต) => { - const ส่วนUrl = `${ชื่อโฮสต์}-HTTPS-${พอร์ต}`; - const protocolหลักHttps = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + ชื่อโฮสต์ + ':' + พอร์ต + ส่วนUrlทั่วไปHttps + ส่วนUrl; - return proxyIPs.flatMap((proxyIP) => { - const protocolรองHttps = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + proxyIP + ':' + พอร์ต + ส่วนUrlทั่วไปHttps + ส่วนUrl + '-' + proxyIP + '-' + atob(ed); - return [protocolหลักHttps, protocolรองHttps]; - }); - }); - - return [...PartHttp, ...PartHttps]; - }); - - return ผลลัพธ์.join('\n'); -} - -const 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 -]; +const a0_0x4cc23b=a0_0xbc10;function a0_0x332b(){const _0x29f035=['www.163.com','\x0a\x20\x20\x0a\x20\x20\x20\x20','Stringified\x20UUID\x20is\x20invalid','weibo.com','floor','send','sec-websocket-protocol','5947844beXVgk','substring','ReadableStream\x20was\x20canceled,\x20due\x20to\x20','command\x20','body','www.881903.com','www.meituan.com','\x0a\x20\x20\x20\x20\x20\x20\x20\x20

Default\x20IP\x20Configuration

\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
','\x22\x20class=\x22btn\x22\x20target=\x22_blank\x22>\x20Clash\x20Link\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20Best\x20IP\x20Subscription\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20

Options\x20Explained:

\x0a\x20\x20\x20\x20\x20\x20\x20\x20
    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
  • VLESS\x20Subscription:\x20Direct\x20link\x20for\x20VLESS\x20protocol\x20configuration.\x20Suitable\x20for\x20clients\x20supporting\x20VLESS.
  • \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
  • Clash\x20Subscription:\x20Opens\x20the\x20Clash\x20client\x20with\x20pre-configured\x20settings.\x20Best\x20for\x20Clash\x20users\x20on\x20mobile\x20devices.
  • \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
  • Clash\x20Link:\x20A\x20web\x20link\x20to\x20convert\x20the\x20VLESS\x20config\x20to\x20Clash\x20format.\x20Useful\x20for\x20manual\x20import\x20or\x20troubleshooting.
  • \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
  • Best\x20IP\x20Subscription:\x20Provides\x20a\x20curated\x20list\x20of\x20optimal\x20server\x20IPs\x20for\x20many\x20different\x20countries.
  • \x0a\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20

Choose\x20the\x20option\x20that\x20best\x20fits\x20your\x20client\x20and\x20needs.\x20For\x20most\x20users,\x20the\x20VLESS\x20or\x20Clash\x20Subscription\x20will\x20be\x20the\x20easiest\x20to\x20use.

\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20','\x0a\x20\x20\x0a\x20\x20\x20\x20EDtunnel:\x20Configuration\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20Clash\x20Subscription\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20Copy\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20','trim','www.youku.com','writable','www.mafengwo.cn','method','6401eJYENw','getWriter',')\x20tcp\x20is\x20close','www.xinhuanet.com','www.sina.com.cn','www.cntv.cn','Forbidden','1.2.3.4','www.zhihu.com','getReader','invalid\x20addressType:\x20','readableWebSocketStream\x20pipeTo\x20error','dns\x20server(','\x20expected:\x205','&uuid=','6646409kLXvSV','-HTTP-','remoteSocketToWS\x20error:','length','test','?ed=2048','toLowerCase','https://www.google.com/search?q=edtunnel','charCodeAt','/sub/','join','www.ted.com','flatMap','https://','arrayBuffer','www.ximalaya.com','some','encode','www.zhaopin.com','www.tudou.com','write','application/json;charset=utf-8','www.so.com','www.toutiao.io','no\x20acceptable\x20methods','www.csdn.net','webSocketServer\x20has\x20error','toString','www.dianping.com','error','readableWebSocketStream\x20is\x20close','please\x20provide\x20username/password','byteLength','pages.dev','search','109512qhELLl','www.360.cn','/\x27\x20/>\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20

EDtunnel:\x20Protocol\x20Configuration

\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20

Welcome!\x20This\x20function\x20generates\x20configuration\x20for\x20the\x20vless\x20protocol.\x20If\x20you\x20found\x20this\x20useful,\x20please\x20check\x20our\x20GitHub\x20project:

\x0a\x20\x20\x20\x20\x20\x20

EDtunnel\x20-\x20https://github.com/6Kmfi6HP/EDtunnel

\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20

UUID:\x20','www.91.com','RUR0dW5uZWw=','readyState','www.cnblogs.com','www.kuaidi100.com','www.netease.com','www.pconline.com.cn','finally','www.ifeng.com','www.zol.com.cn','getUint16','socks\x20server\x20needs\x20auth','&fp=randomized&type=ws&host=','UDP\x20proxy\x20only\x20enable\x20for\x20DNS\x20which\x20is\x20port\x2053','pipeTo','Invalid\x20SOCKS\x20address\x20format','4094046CwakFO',':443?encryption=none&security=tls&sni=','releaseLock','uuid\x20is\x20not\x20valid',')\x20tcp\x20is\x20abort','safeCloseWebSocket\x20error:','www.imgo.tv','www.58.com','www.quyaoya.com','dmxlc3M=','/\x27\x20/>\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x0a\x20\x20\x0a\x20\x20','dns\x20udp\x20has\x20error','status','set','\x0a\x20\x20\x0a\x20\x20','?format=clash','www.liepin.com','?encryption=none&security=tls&sni=','includes','www.zhangzishi.cc','userID:\x20','text/plain;charset=utf-8','www.zhibo8.cc','&path=/','www.tmtpost.com','www.hupu.com','headers','www.xueqiu.com','random','cf-connecting-ip','www.guancha.cn','No\x20incoming\x20data,\x20retrying','closed','8.8.4.4','manual','fail\x20to\x20auth\x20socks\x20server'];a0_0x332b=function(){return _0x29f035;};return a0_0x332b();}(function(_0x3f1026,_0x1acda0){const _0x5803fd=a0_0xbc10,_0x26b2b6=_0x3f1026();while(!![]){try{const _0x3b979b=parseInt(_0x5803fd(0x171))/0x1*(parseInt(_0x5803fd(0xeb))/0x2)+parseInt(_0x5803fd(0x112))/0x3+parseInt(_0x5803fd(0xde))/0x4*(-parseInt(_0x5803fd(0xe5))/0x5)+-parseInt(_0x5803fd(0xe0))/0x6*(parseInt(_0x5803fd(0x13d))/0x7)+-parseInt(_0x5803fd(0xd8))/0x8+parseInt(_0x5803fd(0x15e))/0x9*(parseInt(_0x5803fd(0x152))/0xa)+-parseInt(_0x5803fd(0xb5))/0xb;if(_0x3b979b===_0x1acda0)break;else _0x26b2b6['push'](_0x26b2b6['shift']());}catch(_0x4a2dcc){_0x26b2b6['push'](_0x26b2b6['shift']());}}}(a0_0x332b,0xd2492));import{connect}from'cloudflare:sockets';let userID='d342d11e-d424-4583-b36e-524ab1f0afa4';const proxyIPs=['cdn.xn--b6gac.eu.org','cdn-all.xn--b6gac.eu.org',a0_0x4cc23b(0xf7)];let proxyIP=proxyIPs[Math[a0_0x4cc23b(0x13a)](Math[a0_0x4cc23b(0x12e)]()*proxyIPs[a0_0x4cc23b(0xb8)])],socks5Address='',socks5Relay=![];if(!isValidUUID(userID))throw new Error(a0_0x4cc23b(0x115));let parsedSocks5Address={},enableSocks=![];export default{async 'fetch'(_0x13e0f1,_0x3e8a33,_0x3bbc86){const _0x2c8ae2=a0_0x4cc23b;try{const {UUID:_0x7122b8,PROXYIP:_0x4ef254,SOCKS5:_0xeadcc1,SOCKS5_RELAY:_0x5370ed}=_0x3e8a33;userID=_0x7122b8||userID,proxyIP=_0x4ef254||proxyIP,socks5Address=_0xeadcc1||socks5Address,socks5Relay=_0x5370ed||socks5Relay;if(socks5Address)try{parsedSocks5Address=socks5AddressParser(socks5Address),enableSocks=!![];}catch(_0xfb5b77){console[_0x2c8ae2(0xfa)](_0xfb5b77[_0x2c8ae2(0xd0)]()),enableSocks=![];}const _0x180a08=userID['includes'](',')?userID[_0x2c8ae2(0x149)](',')[0x0]:userID,_0xb0b4fa=new URL(_0x13e0f1['url']),_0xf4ee79=_0x13e0f1[_0x2c8ae2(0x12c)][_0x2c8ae2(0x167)]('Host');if(_0x13e0f1['headers'][_0x2c8ae2(0x167)]('Upgrade')!==_0x2c8ae2(0xf4))switch(_0xb0b4fa[_0x2c8ae2(0x15a)]){case _0x2c8ae2(0xf3):return new Response(JSON[_0x2c8ae2(0x147)](_0x13e0f1['cf'],null,0x4),{'status':0xc8,'headers':{'Content-Type':_0x2c8ae2(0xca)}});case'/'+_0x180a08:return new Response(getConfig(userID,_0xf4ee79),{'status':0xc8,'headers':{'Content-Type':_0x2c8ae2(0xef)}});case _0x2c8ae2(0xbe)+_0x180a08:return new Response(btoa(GenSub(userID,_0xf4ee79)),{'status':0xc8,'headers':{'Content-Type':_0x2c8ae2(0x127)}});case'/bestip/'+_0x180a08:return fetch(_0x2c8ae2(0xf6)+_0xf4ee79+_0x2c8ae2(0xb4)+userID+_0x2c8ae2(0x129),{'headers':_0x13e0f1[_0x2c8ae2(0x12c)]});default:return handleDefaultPath(_0xb0b4fa,_0x13e0f1);}else return await ProtocolOverWSHandler(_0x13e0f1);}catch(_0x1a3aac){return new Response(_0x1a3aac[_0x2c8ae2(0xd0)]());}}};async function handleDefaultPath(_0x499d3b,_0x26902e){const _0x2003f7=a0_0x4cc23b,_0x16873c=hostnames[Math[_0x2003f7(0x13a)](Math[_0x2003f7(0x12e)]()*hostnames[_0x2003f7(0xb8)])],_0x42646c=new Headers(_0x26902e[_0x2003f7(0x12c)]);_0x42646c[_0x2003f7(0x11f)](_0x2003f7(0x12f),_0x2003f7(0xad)),_0x42646c['set'](_0x2003f7(0xf1),_0x2003f7(0xad)),_0x42646c[_0x2003f7(0x11f)]('x-real-ip',_0x2003f7(0xad)),_0x42646c[_0x2003f7(0x11f)]('referer',_0x2003f7(0xbc));const _0x4b9060=_0x2003f7(0xc2)+_0x16873c+_0x499d3b[_0x2003f7(0x15a)]+_0x499d3b[_0x2003f7(0xd7)],_0x438772=new Request(_0x4b9060,{'method':_0x26902e[_0x2003f7(0x170)],'headers':_0x42646c,'body':_0x26902e[_0x2003f7(0x141)],'redirect':'manual'}),_0x16e64c=await fetch(_0x438772,{'redirect':_0x2003f7(0x134)});if([0x12d,0x12e]['includes'](_0x16e64c[_0x2003f7(0x11e)]))return new Response('Redirects\x20to\x20'+_0x16873c+'\x20are\x20not\x20allowed.',{'status':0x193,'statusText':_0x2003f7(0xac)});return _0x16e64c;}async function ProtocolOverWSHandler(_0x3890b8){const _0x369132=a0_0x4cc23b,_0x58ab61=new WebSocketPair(),[_0x1c7b9c,_0x290dcd]=Object['values'](_0x58ab61);_0x290dcd[_0x369132(0xe3)]();let _0x13f62c='',_0x1deb0a='';const _0xfa362c=(_0x52654d,_0x49b5ec)=>{const _0x44e7e0=_0x369132;console[_0x44e7e0(0xfa)]('['+_0x13f62c+':'+_0x1deb0a+']\x20'+_0x52654d,_0x49b5ec||'');},_0x9f11a9=_0x3890b8[_0x369132(0x12c)]['get'](_0x369132(0x13c))||'',_0x50d9bf=makeReadableWebSocketStream(_0x290dcd,_0x9f11a9,_0xfa362c);let _0x21ed96={'value':null},_0x5c8a1d=![];return _0x50d9bf[_0x369132(0x110)](new WritableStream({async 'write'(_0x5d1d43,_0x18796f){const _0x2da7f4=_0x369132;if(_0x5c8a1d)return await handleDNSQuery(_0x5d1d43,_0x290dcd,null,_0xfa362c);if(_0x21ed96[_0x2da7f4(0x15b)]){const _0x1ee8c0=_0x21ed96[_0x2da7f4(0x15b)]['writable'][_0x2da7f4(0x172)]();await _0x1ee8c0['write'](_0x5d1d43),_0x1ee8c0[_0x2da7f4(0x114)]();return;}const {hasError:_0xd6831,message:_0x34fd8c,addressType:_0x192ade,portRemote:portRemote=0x1bb,addressRemote:addressRemote='',rawDataIndex:_0xc03f9f,ProtocolVersion:ProtocolVersion=new Uint8Array([0x0,0x0]),isUDP:_0x240a19}=processProtocolHeader(_0x5d1d43,userID);_0x13f62c=addressRemote,_0x1deb0a=portRemote+'--'+Math[_0x2da7f4(0x12e)]()+'\x20'+(_0x240a19?'udp\x20':_0x2da7f4(0xf2))+'\x20';if(_0xd6831){throw new Error(_0x34fd8c);return;}if(_0x240a19){if(portRemote===0x35)_0x5c8a1d=!![];else{throw new Error(_0x2da7f4(0x10f));return;}}const _0x196990=new Uint8Array([ProtocolVersion[0x0],0x0]),_0x1097a5=_0x5d1d43[_0x2da7f4(0xfc)](_0xc03f9f);if(_0x5c8a1d)return handleDNSQuery(_0x1097a5,_0x290dcd,_0x196990,_0xfa362c);handleTCPOutBound(_0x21ed96,_0x192ade,addressRemote,portRemote,_0x1097a5,_0x290dcd,_0x196990,_0xfa362c);},'close'(){const _0x25bd5b=_0x369132;_0xfa362c(_0x25bd5b(0xd3));},'abort'(_0x5b709e){const _0x228fe0=_0x369132;_0xfa362c(_0x228fe0(0xff),JSON[_0x228fe0(0x147)](_0x5b709e));}}))[_0x369132(0x163)](_0x286910=>{const _0x55c246=_0x369132;_0xfa362c(_0x55c246(0xb1),_0x286910);}),new Response(null,{'status':0x65,'webSocket':_0x1c7b9c});}async function handleTCPOutBound(_0xd54510,_0x5ef3aa,_0x275cd6,_0x378260,_0x3f5428,_0x1f60a2,_0x45f479,_0x38b8ee){async function _0x36871e(_0x1cedbc,_0x32aa58,_0x2c930a=![]){const _0x5db1e5=a0_0xbc10;let _0x3f7cdb;socks5Relay?_0x3f7cdb=await socks5Connect(_0x5ef3aa,_0x1cedbc,_0x32aa58,_0x38b8ee):_0x3f7cdb=_0x2c930a?await socks5Connect(_0x5ef3aa,_0x1cedbc,_0x32aa58,_0x38b8ee):connect({'hostname':_0x1cedbc,'port':_0x32aa58});_0xd54510[_0x5db1e5(0x15b)]=_0x3f7cdb,_0x38b8ee('connected\x20to\x20'+_0x1cedbc+':'+_0x32aa58);const _0x1dffa7=_0x3f7cdb[_0x5db1e5(0x16e)]['getWriter']();return await _0x1dffa7[_0x5db1e5(0xc9)](_0x3f5428),_0x1dffa7['releaseLock'](),_0x3f7cdb;}async function _0x5cec82(){const _0x2aa351=a0_0xbc10;enableSocks?_0x755e5c=await _0x36871e(_0x275cd6,_0x378260,!![]):_0x755e5c=await _0x36871e(proxyIP||_0x275cd6,_0x378260),_0x755e5c[_0x2aa351(0x132)][_0x2aa351(0x163)](_0x4ee599=>{const _0x3b25ce=_0x2aa351;console[_0x3b25ce(0xfa)]('retry\x20tcpSocket\x20closed\x20error',_0x4ee599);})[_0x2aa351(0x109)](()=>{safeCloseWebSocket(_0x1f60a2);}),remoteSocketToWS(_0x755e5c,_0x1f60a2,_0x45f479,null,_0x38b8ee);}let _0x755e5c=await _0x36871e(_0x275cd6,_0x378260);remoteSocketToWS(_0x755e5c,_0x1f60a2,_0x45f479,_0x5cec82,_0x38b8ee);}function makeReadableWebSocketStream(_0x3a34c7,_0x205aba,_0x41f42f){let _0x10c7c9=![];const _0xc10241=new ReadableStream({'start'(_0x4c1b3f){const _0x169144=a0_0xbc10;_0x3a34c7[_0x169144(0x160)](_0x169144(0x159),_0x4675ef=>{const _0x2d5b43=_0x169144,_0x4154da=_0x4675ef[_0x2d5b43(0x151)];_0x4c1b3f[_0x2d5b43(0x166)](_0x4154da);}),_0x3a34c7[_0x169144(0x160)](_0x169144(0x15f),()=>{safeCloseWebSocket(_0x3a34c7),_0x4c1b3f['close']();}),_0x3a34c7['addEventListener'](_0x169144(0xd2),_0x224382=>{const _0x3b7690=_0x169144;_0x41f42f(_0x3b7690(0xcf)),_0x4c1b3f['error'](_0x224382);});const {earlyData:_0x8963fe,error:_0x155fc3}=base64ToArrayBuffer(_0x205aba);if(_0x155fc3)_0x4c1b3f[_0x169144(0xd2)](_0x155fc3);else _0x8963fe&&_0x4c1b3f['enqueue'](_0x8963fe);},'pull'(_0x26ba72){},'cancel'(_0x3f8e2d){const _0x1147fb=a0_0xbc10;_0x41f42f(_0x1147fb(0x13f)+_0x3f8e2d),_0x10c7c9=!![],safeCloseWebSocket(_0x3a34c7);}});return _0xc10241;}function processProtocolHeader(_0x5b4f38,_0x2888a6){const _0x3f7854=a0_0x4cc23b;if(_0x5b4f38[_0x3f7854(0xd5)]<0x18)return{'hasError':!![],'message':_0x3f7854(0xfb)};const _0x4e343a=new DataView(_0x5b4f38),_0x1bb86e=_0x4e343a[_0x3f7854(0xe2)](0x0),_0x4aed5b=stringify(new Uint8Array(_0x5b4f38[_0x3f7854(0xfc)](0x1,0x11))),_0x5c562d=_0x2888a6[_0x3f7854(0x124)](',')?_0x2888a6[_0x3f7854(0x149)](','):[_0x2888a6],_0x44e4f5=_0x5c562d[_0x3f7854(0xc5)](_0x4dbdb0=>_0x4aed5b===_0x4dbdb0[_0x3f7854(0x16c)]())||_0x5c562d[_0x3f7854(0xb8)]===0x1&&_0x4aed5b===_0x5c562d[0x0][_0x3f7854(0x16c)]();console['log'](_0x3f7854(0x126)+_0x4aed5b);if(!_0x44e4f5)return{'hasError':!![],'message':'invalid\x20user'};const _0x51a90e=_0x4e343a[_0x3f7854(0xe2)](0x11),_0x2cf119=_0x4e343a[_0x3f7854(0xe2)](0x12+_0x51a90e);if(_0x2cf119!==0x1&&_0x2cf119!==0x2)return{'hasError':!![],'message':_0x3f7854(0x140)+_0x2cf119+'\x20is\x20not\x20supported,\x20command\x2001-tcp,02-udp,03-mux'};const _0xf2645a=0x12+_0x51a90e+0x1,_0x221572=_0x4e343a[_0x3f7854(0x10c)](_0xf2645a),_0x17277d=_0x4e343a['getUint8'](_0xf2645a+0x2);let _0x2f975d,_0x586af2,_0xb11a6b;switch(_0x17277d){case 0x1:_0x586af2=0x4,_0xb11a6b=_0xf2645a+0x3,_0x2f975d=new Uint8Array(_0x5b4f38[_0x3f7854(0xfc)](_0xb11a6b,_0xb11a6b+_0x586af2))[_0x3f7854(0xbf)]('.');break;case 0x2:_0x586af2=_0x4e343a[_0x3f7854(0xe2)](_0xf2645a+0x3),_0xb11a6b=_0xf2645a+0x4,_0x2f975d=new TextDecoder()[_0x3f7854(0xe8)](_0x5b4f38[_0x3f7854(0xfc)](_0xb11a6b,_0xb11a6b+_0x586af2));break;case 0x3:_0x586af2=0x10,_0xb11a6b=_0xf2645a+0x3,_0x2f975d=Array[_0x3f7854(0x14f)]({'length':0x8},(_0x19d838,_0x3aeba0)=>_0x4e343a['getUint16'](_0xb11a6b+_0x3aeba0*0x2)['toString'](0x10))['join'](':');break;default:return{'hasError':!![],'message':_0x3f7854(0xb0)+_0x17277d};}if(!_0x2f975d)return{'hasError':!![],'message':_0x3f7854(0x157)+_0x17277d};return{'hasError':![],'addressRemote':_0x2f975d,'addressType':_0x17277d,'portRemote':_0x221572,'rawDataIndex':_0xb11a6b+_0x586af2,'protocolVersion':new Uint8Array([_0x1bb86e]),'isUDP':_0x2cf119===0x2};}async function remoteSocketToWS(_0x26dbc9,_0x5f0c86,_0x3401d9,_0x540a8b,_0x445b20){const _0x300244=a0_0x4cc23b;let _0x3c7416=![];try{await _0x26dbc9[_0x300244(0x16a)][_0x300244(0x110)](new WritableStream({async 'write'(_0x314917){const _0x58a966=_0x300244;if(_0x5f0c86[_0x58a966(0x104)]!==WS_READY_STATE_OPEN)throw new Error(_0x58a966(0xe6));_0x3c7416=!![],_0x3401d9?(_0x5f0c86['send'](await new Blob([_0x3401d9,_0x314917])[_0x58a966(0xc3)]()),_0x3401d9=null):_0x5f0c86[_0x58a966(0x13b)](_0x314917);},'close'(){const _0x171e7f=_0x300244;_0x445b20(_0x171e7f(0x100)+_0x3c7416);},'abort'(_0x50b563){const _0x305d83=_0x300244;console[_0x305d83(0xd2)]('Remote\x20connection\x20readable\x20aborted:',_0x50b563);}}));}catch(_0x5b717c){console['error'](_0x300244(0xb7),_0x5b717c[_0x300244(0x164)]||_0x5b717c),safeCloseWebSocket(_0x5f0c86);}!_0x3c7416&&_0x540a8b&&(_0x445b20(_0x300244(0x131)),await _0x540a8b());}function base64ToArrayBuffer(_0x35d41f){const _0x248b77=a0_0x4cc23b;if(!_0x35d41f)return{'earlyData':null,'error':null};try{_0x35d41f=_0x35d41f[_0x248b77(0xdd)](/-/g,'+')[_0x248b77(0xdd)](/_/g,'/');const _0x243b27=atob(_0x35d41f),_0x10c12d=new ArrayBuffer(_0x243b27[_0x248b77(0xb8)]),_0x4e7edd=new Uint8Array(_0x10c12d);for(let _0xbf5476=0x0;_0xbf5476<_0x243b27['length'];_0xbf5476++){_0x4e7edd[_0xbf5476]=_0x243b27[_0x248b77(0xbd)](_0xbf5476);}return{'earlyData':_0x10c12d,'error':null};}catch(_0x3183a2){return{'earlyData':null,'error':_0x3183a2};}}function isValidUUID(_0x4c6c6d){const _0x30eff2=a0_0x4cc23b,_0x1f573e=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;return _0x1f573e[_0x30eff2(0xb9)](_0x4c6c6d);}const WS_READY_STATE_OPEN=0x1,WS_READY_STATE_CLOSING=0x2;function safeCloseWebSocket(_0x3eb210){const _0x5a1894=a0_0x4cc23b;try{(_0x3eb210[_0x5a1894(0x104)]===WS_READY_STATE_OPEN||_0x3eb210['readyState']===WS_READY_STATE_CLOSING)&&_0x3eb210[_0x5a1894(0x15f)]();}catch(_0x518559){console[_0x5a1894(0xd2)](_0x5a1894(0x117),_0x518559);}}const byteToHex=Array[a0_0x4cc23b(0x14f)]({'length':0x100},(_0x2f3f79,_0x3f76d0)=>(_0x3f76d0+0x100)[a0_0x4cc23b(0xd0)](0x10)[a0_0x4cc23b(0xfc)](0x1));function a0_0xbc10(_0x44f19f,_0x461d57){const _0x332be0=a0_0x332b();return a0_0xbc10=function(_0xbc1066,_0x1aa678){_0xbc1066=_0xbc1066-0xac;let _0xba66ce=_0x332be0[_0xbc1066];return _0xba66ce;},a0_0xbc10(_0x44f19f,_0x461d57);}function unsafeStringify(_0x27d91d,_0x34c887=0x0){const _0x3052ad=a0_0x4cc23b;return[byteToHex[_0x27d91d[_0x34c887]],byteToHex[_0x27d91d[_0x34c887+0x1]],byteToHex[_0x27d91d[_0x34c887+0x2]],byteToHex[_0x27d91d[_0x34c887+0x3]],'-',byteToHex[_0x27d91d[_0x34c887+0x4]],byteToHex[_0x27d91d[_0x34c887+0x5]],'-',byteToHex[_0x27d91d[_0x34c887+0x6]],byteToHex[_0x27d91d[_0x34c887+0x7]],'-',byteToHex[_0x27d91d[_0x34c887+0x8]],byteToHex[_0x27d91d[_0x34c887+0x9]],'-',byteToHex[_0x27d91d[_0x34c887+0xa]],byteToHex[_0x27d91d[_0x34c887+0xb]],byteToHex[_0x27d91d[_0x34c887+0xc]],byteToHex[_0x27d91d[_0x34c887+0xd]],byteToHex[_0x27d91d[_0x34c887+0xe]],byteToHex[_0x27d91d[_0x34c887+0xf]]][_0x3052ad(0xbf)]('')[_0x3052ad(0xbb)]();}function stringify(_0x9a4113,_0x2c1fc7=0x0){const _0x45be7a=a0_0x4cc23b,_0x19d340=unsafeStringify(_0x9a4113,_0x2c1fc7);if(!isValidUUID(_0x19d340))throw new TypeError(_0x45be7a(0x138));return _0x19d340;}async function handleUDPOutBound(_0x1e702e,_0xb2ff4c,_0x2a6401){const _0x16a228=a0_0x4cc23b;let _0x5e2a83=![];const _0x175ef5=new TransformStream({'start'(_0x2b1cea){},'transform'(_0x4aec70,_0x320924){const _0x5d72f2=a0_0xbc10;for(let _0x3ae5c0=0x0;_0x3ae5c0<_0x4aec70[_0x5d72f2(0xd5)];){const _0x513c1b=_0x4aec70[_0x5d72f2(0xfc)](_0x3ae5c0,_0x3ae5c0+0x2),_0x1a96ee=new DataView(_0x513c1b)['getUint16'](0x0),_0x3503b9=new Uint8Array(_0x4aec70[_0x5d72f2(0xfc)](_0x3ae5c0+0x2,_0x3ae5c0+0x2+_0x1a96ee));_0x3ae5c0=_0x3ae5c0+0x2+_0x1a96ee,_0x320924[_0x5d72f2(0x166)](_0x3503b9);}},'flush'(_0x559aae){}});_0x175ef5[_0x16a228(0x16a)][_0x16a228(0x110)](new WritableStream({async 'write'(_0x2ac474){const _0x200f0c=_0x16a228,_0x4b8ad1=await fetch(dohURL,{'method':_0x200f0c(0xea),'headers':{'content-type':_0x200f0c(0x161)},'body':_0x2ac474}),_0x53e2bc=await _0x4b8ad1[_0x200f0c(0xc3)](),_0x35fed6=_0x53e2bc['byteLength'],_0x42de97=new Uint8Array([_0x35fed6>>0x8&0xff,_0x35fed6&0xff]);_0x1e702e[_0x200f0c(0x104)]===WS_READY_STATE_OPEN&&(_0x2a6401(_0x200f0c(0x14e)+_0x35fed6),_0x5e2a83?_0x1e702e[_0x200f0c(0x13b)](await new Blob([_0x42de97,_0x53e2bc])[_0x200f0c(0xc3)]()):(_0x1e702e[_0x200f0c(0x13b)](await new Blob([_0xb2ff4c,_0x42de97,_0x53e2bc])[_0x200f0c(0xc3)]()),_0x5e2a83=!![]));}}))[_0x16a228(0x163)](_0x1641aa=>{const _0x522247=_0x16a228;_0x2a6401(_0x522247(0x11d)+_0x1641aa);});const _0x4eac10=_0x175ef5[_0x16a228(0x16e)][_0x16a228(0x172)]();return{'write'(_0x70d6e4){_0x4eac10['write'](_0x70d6e4);}};}async function handleDNSQuery(_0x25ea08,_0x54ec99,_0x54263d,_0x3dfaf1){const _0x14fd74=a0_0x4cc23b;try{const _0x3d6a03=_0x14fd74(0x133),_0x561254=0x35;let _0x21b100=_0x54263d;const _0x1e0903=connect({'hostname':_0x3d6a03,'port':_0x561254});_0x3dfaf1('connected\x20to\x20'+_0x3d6a03+':'+_0x561254);const _0x1817b2=_0x1e0903[_0x14fd74(0x16e)][_0x14fd74(0x172)]();await _0x1817b2[_0x14fd74(0xc9)](_0x25ea08),_0x1817b2[_0x14fd74(0x114)](),await _0x1e0903['readable'][_0x14fd74(0x110)](new WritableStream({async 'write'(_0xd1fd2b){const _0x361b75=_0x14fd74;_0x54ec99[_0x361b75(0x104)]===WS_READY_STATE_OPEN&&(_0x21b100?(_0x54ec99[_0x361b75(0x13b)](await new Blob([_0x21b100,_0xd1fd2b])['arrayBuffer']()),_0x21b100=null):_0x54ec99[_0x361b75(0x13b)](_0xd1fd2b));},'close'(){const _0x3e5cc2=_0x14fd74;_0x3dfaf1(_0x3e5cc2(0xb2)+_0x3d6a03+_0x3e5cc2(0x173));},'abort'(_0x21b5f1){const _0x18c7e4=_0x14fd74;console['error'](_0x18c7e4(0xb2)+_0x3d6a03+_0x18c7e4(0x116),_0x21b5f1);}}));}catch(_0x4262af){console[_0x14fd74(0xd2)]('handleDNSQuery\x20have\x20exception,\x20error:\x20'+_0x4262af[_0x14fd74(0x159)]);}}async function socks5Connect(_0x15eeba,_0x1346da,_0x3e7833,_0x4cd7b4){const _0x410e29=a0_0x4cc23b,{username:_0x3be790,password:_0x3621d0,hostname:_0x40bb17,port:_0x26e1d9}=parsedSocks5Address,_0xec1bb3=connect({'hostname':_0x40bb17,'port':_0x26e1d9}),_0x633ef4=new Uint8Array([0x5,0x2,0x0,0x2]),_0x44625a=_0xec1bb3[_0x410e29(0x16e)][_0x410e29(0x172)]();await _0x44625a[_0x410e29(0xc9)](_0x633ef4),_0x4cd7b4('sent\x20socks\x20greeting');const _0x539644=_0xec1bb3[_0x410e29(0x16a)][_0x410e29(0xaf)](),_0x1a6ee6=new TextEncoder();let _0xbc287=(await _0x539644[_0x410e29(0xe1)]())[_0x410e29(0x15b)];if(_0xbc287[0x0]!==0x5){_0x4cd7b4(_0x410e29(0x154)+_0xbc287[0x0]+_0x410e29(0xb3));return;}if(_0xbc287[0x1]===0xff){_0x4cd7b4(_0x410e29(0xcd));return;}if(_0xbc287[0x1]===0x2){_0x4cd7b4(_0x410e29(0x10d));if(!_0x3be790||!_0x3621d0){_0x4cd7b4(_0x410e29(0xd4));return;}const _0x1e2697=new Uint8Array([0x1,_0x3be790[_0x410e29(0xb8)],..._0x1a6ee6[_0x410e29(0xc6)](_0x3be790),_0x3621d0[_0x410e29(0xb8)],..._0x1a6ee6[_0x410e29(0xc6)](_0x3621d0)]);await _0x44625a[_0x410e29(0xc9)](_0x1e2697),_0xbc287=(await _0x539644[_0x410e29(0xe1)]())['value'];if(_0xbc287[0x0]!==0x1||_0xbc287[0x1]!==0x0){_0x4cd7b4(_0x410e29(0x135));return;}}let _0x26c36e;switch(_0x15eeba){case 0x1:_0x26c36e=new Uint8Array([0x1,..._0x1346da['split']('.')[_0x410e29(0x14c)](Number)]);break;case 0x2:_0x26c36e=new Uint8Array([0x3,_0x1346da[_0x410e29(0xb8)],..._0x1a6ee6['encode'](_0x1346da)]);break;case 0x3:_0x26c36e=new Uint8Array([0x4,..._0x1346da[_0x410e29(0x149)](':')['flatMap'](_0x1bc35c=>[parseInt(_0x1bc35c[_0x410e29(0xfc)](0x0,0x2),0x10),parseInt(_0x1bc35c['slice'](0x2),0x10)])]);break;default:_0x4cd7b4(_0x410e29(0xe7)+_0x15eeba);return;}const _0x4eaae1=new Uint8Array([0x5,0x1,0x0,..._0x26c36e,_0x3e7833>>0x8,_0x3e7833&0xff]);await _0x44625a[_0x410e29(0xc9)](_0x4eaae1),_0x4cd7b4('sent\x20socks\x20request'),_0xbc287=(await _0x539644[_0x410e29(0xe1)]())[_0x410e29(0x15b)];if(_0xbc287[0x1]===0x0)_0x4cd7b4('socks\x20connection\x20opened');else{_0x4cd7b4('fail\x20to\x20open\x20socks\x20connection');return;}return _0x44625a[_0x410e29(0x114)](),_0x539644[_0x410e29(0x114)](),_0xec1bb3;}function socks5AddressParser(_0x1a2d9c){const _0x41a4fb=a0_0x4cc23b;let [_0x28547f,_0x1f8d4a]=_0x1a2d9c[_0x41a4fb(0x149)]('@')['reverse'](),_0x2282fa,_0x2dff1e,_0x435660,_0x5727dc;if(_0x1f8d4a){const _0x11b1d6=_0x1f8d4a[_0x41a4fb(0x149)](':');if(_0x11b1d6[_0x41a4fb(0xb8)]!==0x2)throw new Error(_0x41a4fb(0x111));[_0x2282fa,_0x2dff1e]=_0x11b1d6;}const _0x3d0a9f=_0x28547f['split'](':');_0x5727dc=Number(_0x3d0a9f['pop']());if(isNaN(_0x5727dc))throw new Error('Invalid\x20SOCKS\x20address\x20format');_0x435660=_0x3d0a9f[_0x41a4fb(0xbf)](':');const _0x23b2de=/^\[.*\]$/;if(_0x435660[_0x41a4fb(0x124)](':')&&!_0x23b2de[_0x41a4fb(0xb9)](_0x435660))throw new Error(_0x41a4fb(0x111));return{'username':_0x2282fa,'password':_0x2dff1e,'hostname':_0x435660,'port':_0x5727dc};}const at='QA==',pt=a0_0x4cc23b(0x11b),ed=a0_0x4cc23b(0x103);function getConfig(_0x1cf514,_0x10ea21){const _0x490f13=a0_0x4cc23b,_0x57c938=_0x490f13(0x113)+_0x10ea21+_0x490f13(0x10e)+_0x10ea21+'&path=%2F%3Fed%3D2048#'+_0x10ea21,_0x3f3b83=_0x1cf514[_0x490f13(0x149)](','),_0x2a1aa9=_0x490f13(0xc2)+_0x10ea21+_0x490f13(0xbe)+_0x3f3b83[0x0]+_0x490f13(0x121),_0x6da029=_0x490f13(0xc2)+_0x10ea21+'/bestip/'+_0x3f3b83[0x0],_0x69d282=_0x490f13(0xed)+encodeURIComponent(_0x2a1aa9)+'&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true',_0x45e84b=_0x490f13(0x15d)+_0x10ea21+_0x490f13(0xda)+_0x10ea21+_0x490f13(0x11c),_0x151b4a=_0x490f13(0xf0)+_0x10ea21+_0x490f13(0xbe)+_0x3f3b83[0x0]+'\x22\x20class=\x22btn\x22\x20target=\x22_blank\x22>\x20VLESS\x20Subscription\x0a\x20\x20\x20\x20\x20\x20\x20\x20{const _0x1fc5c5=_0x490f13,_0x534b21=atob(pt)+_0x1fc5c5(0xee)+_0x2ab217+atob(at)+_0x10ea21+_0x57c938,_0x210a7c=atob(pt)+_0x1fc5c5(0xee)+_0x2ab217+atob(at)+proxyIP+_0x57c938;return _0x1fc5c5(0x101)+_0x2ab217+_0x1fc5c5(0x144)+_0x534b21+_0x1fc5c5(0xfd)+_0x534b21+'\x22)\x27>\x20Copy\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20

Best\x20IP\x20Configuration

\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
'+_0x210a7c+_0x1fc5c5(0xfd)+_0x210a7c+_0x1fc5c5(0x16b);})[_0x490f13(0xbf)]('');return _0x490f13(0x120)+_0x45e84b+_0x490f13(0x137)+_0x151b4a+_0x490f13(0xf9)+_0x57ea30+'\x0a\x20\x20\x0a\x20\x20\x0a\x20\x20';}const HttpPort=new Set([0x50,0x1f90,0x22b0,0x804,0x826,0x82f,0x822]),HttpsPort=new Set([0x1bb,0x20fb,0x805,0x830,0x827,0x823]);function GenSub(_0x71215c,_0x2233e9){const _0x2e3d53=a0_0x4cc23b,_0x509311=_0x71215c[_0x2e3d53(0x124)](',')?_0x71215c[_0x2e3d53(0x149)](','):[_0x71215c],_0x2d0779=()=>'/'+Math['random']()[_0x2e3d53(0xd0)](0x24)[_0x2e3d53(0x13e)](0x2,0xf)+_0x2e3d53(0xba),_0x4e9dca=_0x2e3d53(0x150)+_0x2233e9+'&path='+encodeURIComponent(_0x2d0779())+'#',_0x2d7f10=_0x2e3d53(0x123)+_0x2233e9+_0x2e3d53(0x156)+_0x2233e9+_0x2e3d53(0x165),_0x3a05f3=_0x509311[_0x2e3d53(0xc1)](_0x36b17e=>{const _0x4b6e5b=_0x2e3d53,_0xdf0d05=Array[_0x4b6e5b(0x14f)](HttpPort)[_0x4b6e5b(0xc1)](_0x226e51=>{const _0xe4c6e3=_0x4b6e5b;if(!_0x2233e9[_0xe4c6e3(0x124)](_0xe4c6e3(0xd6))){const _0x455a06=_0x2233e9+_0xe4c6e3(0xb6)+_0x226e51,_0x5856e6=atob(pt)+'://'+_0x36b17e+atob(at)+_0x2233e9+':'+_0x226e51+_0x4e9dca+_0x455a06;return proxyIPs['flatMap'](_0x13b02d=>{const _0x4636d6=_0xe4c6e3,_0x55b655=atob(pt)+_0x4636d6(0xee)+_0x36b17e+atob(at)+_0x13b02d+':'+_0x226e51+_0x4e9dca+_0x455a06+'-'+_0x13b02d+'-'+atob(ed);return[_0x5856e6,_0x55b655];});}return[];}),_0x11f163=Array[_0x4b6e5b(0x14f)](HttpsPort)[_0x4b6e5b(0xc1)](_0x341ef5=>{const _0xb67a1c=_0x4b6e5b,_0x1485dc=_0x2233e9+_0xb67a1c(0xf8)+_0x341ef5,_0x29f2b6=atob(pt)+_0xb67a1c(0xee)+_0x36b17e+atob(at)+_0x2233e9+':'+_0x341ef5+_0x2d7f10+_0x1485dc;return proxyIPs['flatMap'](_0x195a74=>{const _0x212d10=_0xb67a1c,_0x24442a=atob(pt)+_0x212d10(0xee)+_0x36b17e+atob(at)+_0x195a74+':'+_0x341ef5+_0x2d7f10+_0x1485dc+'-'+_0x195a74+'-'+atob(ed);return[_0x29f2b6,_0x24442a];});});return[..._0xdf0d05,..._0x11f163];});return _0x3a05f3[_0x2e3d53(0xbf)]('\x0a');}const hostnames=[a0_0x4cc23b(0x139),a0_0x4cc23b(0xf5),'www.qq.com','www.taobao.com',a0_0x4cc23b(0xec),a0_0x4cc23b(0x175),'www.sohu.com','www.tmall.com',a0_0x4cc23b(0x136),a0_0x4cc23b(0xae),a0_0x4cc23b(0x16d),a0_0x4cc23b(0x174),'www.douban.com',a0_0x4cc23b(0x143),'www.toutiao.com',a0_0x4cc23b(0x10a),a0_0x4cc23b(0x162),a0_0x4cc23b(0xd9),'www.douyin.com',a0_0x4cc23b(0x106),a0_0x4cc23b(0xdb),a0_0x4cc23b(0xce),a0_0x4cc23b(0x118),a0_0x4cc23b(0x146),a0_0x4cc23b(0xdf),a0_0x4cc23b(0x14b),'www.xunlei.com',a0_0x4cc23b(0xe9),'www.bilibili.com','www.youth.cn',a0_0x4cc23b(0x12b),'www.youzu.com','www.panda.tv',a0_0x4cc23b(0xc8),a0_0x4cc23b(0x10b),a0_0x4cc23b(0xcc),a0_0x4cc23b(0x148),a0_0x4cc23b(0x107),a0_0x4cc23b(0x14a),a0_0x4cc23b(0x128),a0_0x4cc23b(0x125),a0_0x4cc23b(0x12d),'www.qqgongyi.com',a0_0x4cc23b(0xc4),a0_0x4cc23b(0xd1),a0_0x4cc23b(0xfe),a0_0x4cc23b(0xc7),a0_0x4cc23b(0x14d),a0_0x4cc23b(0x16f),'www.51cto.com','www.qidian.com',a0_0x4cc23b(0xdc),a0_0x4cc23b(0x108),'www.cnzz.com',a0_0x4cc23b(0x168),a0_0x4cc23b(0x158),a0_0x4cc23b(0xc0),'www.renren.com',a0_0x4cc23b(0x155),a0_0x4cc23b(0x122),a0_0x4cc23b(0x142),'www.aipai.com','www.ttpaihang.com',a0_0x4cc23b(0x11a),a0_0x4cc23b(0x102),a0_0x4cc23b(0xe4),a0_0x4cc23b(0x12a),'www.douban.com',a0_0x4cc23b(0x130),a0_0x4cc23b(0xcb),a0_0x4cc23b(0x119),a0_0x4cc23b(0x105),a0_0x4cc23b(0x176),a0_0x4cc23b(0x153)];
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..58871db
--- /dev/null
+++ b/index.js
@@ -0,0 +1,1172 @@
+// @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.cloudflare.cyou'];
+
+// 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]"
+
+// Example:  user:pass@host:port  or  host:port
+let socks5Address = '';
+// socks5Relay is true, will proxy all traffic to socks5Address, otherwise socks5Address only be used for cloudflare ips
+let socks5Relay = false;
+
+if (!isValidUUID(userID)) {
+	throw new Error('uuid is not valid');
+}
+
+let parsedSocks5Address = {};
+let enableSocks = false;
+
+
+export default {
+	/**
+	 * @param {import("@cloudflare/workers-types").Request} request
+	 * @param {{UUID: string, PROXYIP: string, SOCKS5: string, SOCKS5_RELAY: string}} env
+	 * @param {import("@cloudflare/workers-types").ExecutionContext} _ctx
+	 * @returns {Promise}
+	 */
+	async fetch(request, env, _ctx) {
+		try {
+			const { UUID, PROXYIP, SOCKS5, SOCKS5_RELAY } = env;
+			userID = UUID || userID;
+			proxyIP = PROXYIP || proxyIP;
+			socks5Address = SOCKS5 || socks5Address;
+			socks5Relay = SOCKS5_RELAY || socks5Relay;
+
+			if (socks5Address) {
+				try {
+					parsedSocks5Address = socks5AddressParser(socks5Address);
+					enableSocks = true;
+				} catch (err) {
+					console.log(err.toString());
+					enableSocks = false;
+				}
+			}
+
+			const userID_Path = userID.includes(',') ? userID.split(',')[0] : userID;
+			const url = new URL(request.url);
+			const host = request.headers.get('Host');
+
+			if (request.headers.get('Upgrade') !== 'websocket') {
+				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}`:
+						return new Response(getConfig(userID, host), {
+							status: 200,
+							headers: { "Content-Type": "text/html; charset=utf-8" },
+						});
+					case `/sub/${userID_Path}`:
+						return new Response(btoa(GenSub(userID, host)), {
+							status: 200,
+							headers: { "Content-Type": "text/plain;charset=utf-8" },
+						});
+					case `/bestip/${userID_Path}`:
+						return fetch(`https://sub.xf.free.hr/auto?host=${host}&uuid=${userID}&path=/`, { headers: request.headers });
+					default:
+						return handleDefaultPath(url, request);
+				}
+			} else {
+				return await ProtocolOverWSHandler(request);
+			}
+		} catch (err) {
+			return new Response(err.toString());
+		}
+	},
+};
+
+async function handleDefaultPath(url, request) {
+	const randomHostname = hostnames[Math.floor(Math.random() * 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');
+
+	const proxyUrl = 'https://' + randomHostname + url.pathname + url.search;
+	const modifiedRequest = new Request(proxyUrl, {
+		method: request.method,
+		headers: newHeaders,
+		body: request.body,
+		redirect: 'manual',
+	});
+
+	const proxyResponse = await fetch(modifiedRequest, { redirect: 'manual' });
+	if ([301, 302].includes(proxyResponse.status)) {
+		return new Response(`Redirects to ${randomHostname} are not allowed.`, {
+			status: 403,
+			statusText: 'Forbidden',
+		});
+	}
+	return proxyResponse;
+}
+
+/**
+ * Handles protocol over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the protocol 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 ProtocolOverWSHandler(request) {
+
+	/** @type {import("@cloudflare/workers-types").WebSocket[]} */
+	// @ts-ignore
+	const webSocketPair = new WebSocketPair();
+	const [client, webSocket] = Object.values(webSocketPair);
+
+	webSocket.accept();
+
+	let address = '';
+	let portWithRandomLog = '';
+	const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
+		console.log(`[${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 isDns = false;
+
+	// ws --> remote
+	readableWebSocketStream.pipeTo(new WritableStream({
+		async write(chunk, controller) {
+			if (isDns) {
+				return await handleDNSQuery(chunk, webSocket, null, log);
+			}
+			if (remoteSocketWapper.value) {
+				const writer = remoteSocketWapper.value.writable.getWriter()
+				await writer.write(chunk);
+				writer.releaseLock();
+				return;
+			}
+
+			const {
+				hasError,
+				message,
+				addressType,
+				portRemote = 443,
+				addressRemote = '',
+				rawDataIndex,
+				ProtocolVersion = new Uint8Array([0, 0]),
+				isUDP,
+			} = processProtocolHeader(chunk, userID);
+			address = addressRemote;
+			portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
+				} `;
+			if (hasError) {
+				// controller.error(message);
+				throw new Error(message); // cf seems has bug, controller.error will not end stream
+				// webSocket.close(1000, message);
+				return;
+			}
+			// if UDP but port not DNS port, close it
+			if (isUDP) {
+				if (portRemote === 53) {
+					isDns = true;
+				} else {
+					// controller.error('UDP proxy only enable for DNS which is port 53');
+					throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream
+					return;
+				}
+			}
+			// ["version", "附加信息长度 N"]
+			const ProtocolResponseHeader = new Uint8Array([ProtocolVersion[0], 0]);
+			const rawClientData = chunk.slice(rawDataIndex);
+
+			if (isDns) {
+				return handleDNSQuery(rawClientData, webSocket, ProtocolResponseHeader, log);
+			}
+			handleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, 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,
+		// @ts-ignore
+		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} protocolResponseHeader The protocol response header.
+ * @param {function} log The logging function.
+ * @returns {Promise} The remote socket.
+ */
+async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, log,) {
+	async function connectAndWrite(address, port, socks = false) {
+		/** @type {import("@cloudflare/workers-types").Socket} */
+		let tcpSocket;
+		if (socks5Relay) {
+			tcpSocket = await socks5Connect(addressType, address, port, log)
+		} else {
+			tcpSocket = socks ? await socks5Connect(addressType, address, port, log)
+				: connect({
+					hostname: address,
+					port: port,
+				});
+		}
+		remoteSocket.value = tcpSocket;
+		log(`connected to ${address}:${port}`);
+		const writer = tcpSocket.writable.getWriter();
+		await writer.write(rawClientData); // first write, normal is tls client hello
+		writer.releaseLock();
+		return tcpSocket;
+	}
+
+	// if the cf connect tcp socket have no incoming data, we retry to redirect ip
+	async function retry() {
+		if (enableSocks) {
+			tcpSocket = await connectAndWrite(addressRemote, portRemote, true);
+		} else {
+			tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote);
+		}
+		// no matter retry success or not, close websocket
+		tcpSocket.closed.catch(error => {
+			console.log('retry tcpSocket closed error', error);
+		}).finally(() => {
+			safeCloseWebSocket(webSocket);
+		})
+		remoteSocketToWS(tcpSocket, webSocket, ProtocolResponseHeader, null, log);
+	}
+
+	let tcpSocket = await connectAndWrite(addressRemote, portRemote);
+
+	// when remoteSocket is ready, pass to websocket
+	// remote--> ws
+	remoteSocketToWS(tcpSocket, webSocket, ProtocolResponseHeader, 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/protocol.html
+// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
+
+/**
+ * Processes the protocol header buffer and returns an object with the relevant information.
+ * @param {ArrayBuffer} protocolBuffer The protocol header buffer to process.
+ * @param {string} userID The user ID to validate against the UUID in the protocol header.
+ * @returns {{
+ *  hasError: boolean,
+ *  message?: string,
+ *  addressRemote?: string,
+ *  addressType?: number,
+ *  portRemote?: number,
+ *  rawDataIndex?: number,
+ *  protocolVersion?: Uint8Array,
+ *  isUDP?: boolean
+ * }} An object with the relevant information extracted from the protocol header buffer.
+ */
+function processProtocolHeader(protocolBuffer, userID) {
+	if (protocolBuffer.byteLength < 24) {
+		return { hasError: true, message: 'invalid data' };
+	}
+
+	const dataView = new DataView(protocolBuffer);
+	const version = dataView.getUint8(0);
+	const slicedBufferString = stringify(new Uint8Array(protocolBuffer.slice(1, 17)));
+
+	const uuids = userID.includes(',') ? userID.split(",") : [userID];
+	const isValidUser = uuids.some(uuid => slicedBufferString === uuid.trim()) ||
+		(uuids.length === 1 && slicedBufferString === uuids[0].trim());
+
+	console.log(`userID: ${slicedBufferString}`);
+
+	if (!isValidUser) {
+		return { hasError: true, message: 'invalid user' };
+	}
+
+	const optLength = dataView.getUint8(17);
+	const command = dataView.getUint8(18 + optLength);
+
+	if (command !== 1 && command !== 2) {
+		return { hasError: true, message: `command ${command} is not supported, command 01-tcp,02-udp,03-mux` };
+	}
+
+	const portIndex = 18 + optLength + 1;
+	const portRemote = dataView.getUint16(portIndex);
+	const addressType = dataView.getUint8(portIndex + 2);
+	let addressValue, addressLength, addressValueIndex;
+
+	switch (addressType) {
+		case 1:
+			addressLength = 4;
+			addressValueIndex = portIndex + 3;
+			addressValue = new Uint8Array(protocolBuffer.slice(addressValueIndex, addressValueIndex + addressLength)).join('.');
+			break;
+		case 2:
+			addressLength = dataView.getUint8(portIndex + 3);
+			addressValueIndex = portIndex + 4;
+			addressValue = new TextDecoder().decode(protocolBuffer.slice(addressValueIndex, addressValueIndex + addressLength));
+			break;
+		case 3:
+			addressLength = 16;
+			addressValueIndex = portIndex + 3;
+			addressValue = Array.from({ length: 8 }, (_, i) => dataView.getUint16(addressValueIndex + i * 2).toString(16)).join(':');
+			break;
+		default:
+			return { hasError: true, message: `invalid addressType: ${addressType}` };
+	}
+
+	if (!addressValue) {
+		return { hasError: true, message: `addressValue is empty, addressType is ${addressType}` };
+	}
+
+	return {
+		hasError: false,
+		addressRemote: addressValue,
+		addressType,
+		portRemote,
+		rawDataIndex: addressValueIndex + addressLength,
+		protocolVersion: new Uint8Array([version]),
+		isUDP: command === 2
+	};
+}
+
+
+/**
+ * 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} protocolResponseHeader The protocol 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, protocolResponseHeader, retry, log) {
+	let hasIncomingData = false;
+
+	try {
+		await remoteSocket.readable.pipeTo(
+			new WritableStream({
+				async write(chunk) {
+					if (webSocket.readyState !== WS_READY_STATE_OPEN) {
+						throw new Error('WebSocket is not open');
+					}
+
+					hasIncomingData = true;
+
+					if (protocolResponseHeader) {
+						webSocket.send(await new Blob([protocolResponseHeader, chunk]).arrayBuffer());
+						protocolResponseHeader = null;
+					} else {
+						webSocket.send(chunk);
+					}
+				},
+				close() {
+					log(`Remote connection readable closed. Had incoming data: ${hasIncomingData}`);
+				},
+				abort(reason) {
+					console.error(`Remote connection readable aborted:`, reason);
+				},
+			})
+		);
+	} catch (error) {
+		console.error(`remoteSocketToWS error:`, error.stack || error);
+		safeCloseWebSocket(webSocket);
+	}
+
+	if (!hasIncomingData && retry) {
+		log(`No incoming data, retrying`);
+		await 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 {
+		// Convert modified Base64 for URL (RFC 4648) to standard Base64
+		base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
+		// Decode Base64 string
+		const binaryStr = atob(base64Str);
+		// Convert binary string to ArrayBuffer
+		const buffer = new ArrayBuffer(binaryStr.length);
+		const view = new Uint8Array(buffer);
+		for (let i = 0; i < binaryStr.length; i++) {
+			view[i] = binaryStr.charCodeAt(i);
+		}
+		return { earlyData: buffer, error: null };
+	} catch (error) {
+		return { earlyData: null, error };
+	}
+}
+
+/**
+ * Checks if a given string is a valid UUID.
+ * @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) {
+	// More precise UUID regex pattern
+	const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][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 = Array.from({ length: 256 }, (_, i) => (i + 0x100).toString(16).slice(1));
+
+function unsafeStringify(arr, offset = 0) {
+	return [
+		byteToHex[arr[offset]],
+		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]]
+	].join('').toLowerCase();
+}
+
+function stringify(arr, offset = 0) {
+	const uuid = unsafeStringify(arr, offset);
+	if (!isValidUUID(uuid)) {
+		throw new 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} protocolResponseHeader The protocol 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, protocolResponseHeader, log) {
+
+	let isprotocolHeaderSent = 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 (isprotocolHeaderSent) {
+					webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
+				} else {
+					webSocket.send(await new Blob([protocolResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
+					isprotocolHeaderSent = true;
+				}
+			}
+		}
+	})).catch((error) => {
+		log('dns udp has error' + error)
+	});
+
+	const writer = transformStream.writable.getWriter();
+
+	return {
+		/**
+		 * 
+		 * @param {Uint8Array} chunk 
+		 */
+		write(chunk) {
+			writer.write(chunk);
+		}
+	};
+}
+
+/**
+ * 
+ * @param {ArrayBuffer} udpChunk 
+ * @param {import("@cloudflare/workers-types").WebSocket} webSocket 
+ * @param {ArrayBuffer} protocolResponseHeader 
+ * @param {(string)=> void} log 
+ */
+async function handleDNSQuery(udpChunk, webSocket, protocolResponseHeader, log) {
+	// no matter which DNS server client send, we alwasy use hard code one.
+	// beacsue someof DNS server is not support DNS over TCP
+	try {
+		const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug
+		const dnsPort = 53;
+		/** @type {ArrayBuffer | null} */
+		let vlessHeader = protocolResponseHeader;
+		/** @type {import("@cloudflare/workers-types").Socket} */
+		const tcpSocket = connect({
+			hostname: dnsServer,
+			port: dnsPort,
+		});
+
+		log(`connected to ${dnsServer}:${dnsPort}`);
+		const writer = tcpSocket.writable.getWriter();
+		await writer.write(udpChunk);
+		writer.releaseLock();
+		await tcpSocket.readable.pipeTo(new WritableStream({
+			async write(chunk) {
+				if (webSocket.readyState === WS_READY_STATE_OPEN) {
+					if (vlessHeader) {
+						webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
+						vlessHeader = null;
+					} else {
+						webSocket.send(chunk);
+					}
+				}
+			},
+			close() {
+				log(`dns server(${dnsServer}) tcp is close`);
+			},
+			abort(reason) {
+				console.error(`dns server(${dnsServer}) tcp is abort`, reason);
+			},
+		}));
+	} catch (error) {
+		console.error(
+			`handleDNSQuery have exception, error: ${error.message}`
+		);
+	}
+}
+
+
+/**
+ * 
+ * @param {number} addressType
+ * @param {string} addressRemote
+ * @param {number} portRemote
+ * @param {function} log The logging function.
+ */
+async function socks5Connect(addressType, addressRemote, portRemote, log) {
+	const { username, password, hostname, port } = parsedSocks5Address;
+	// Connect to the SOCKS server
+	const socket = connect({
+		hostname,
+		port,
+	});
+
+	// Request head format (Worker -> Socks Server):
+	// +----+----------+----------+
+	// |VER | NMETHODS | METHODS  |
+	// +----+----------+----------+
+	// | 1  |    1     | 1 to 255 |
+	// +----+----------+----------+
+
+	// https://en.wikipedia.org/wiki/SOCKS#SOCKS5
+	// For METHODS:
+	// 0x00 NO AUTHENTICATION REQUIRED
+	// 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929
+	const socksGreeting = new Uint8Array([5, 2, 0, 2]);
+
+	const writer = socket.writable.getWriter();
+
+	await writer.write(socksGreeting);
+	log('sent socks greeting');
+
+	const reader = socket.readable.getReader();
+	const encoder = new TextEncoder();
+	let res = (await reader.read()).value;
+	// Response format (Socks Server -> Worker):
+	// +----+--------+
+	// |VER | METHOD |
+	// +----+--------+
+	// | 1  |   1    |
+	// +----+--------+
+	if (res[0] !== 0x05) {
+		log(`socks server version error: ${res[0]} expected: 5`);
+		return;
+	}
+	if (res[1] === 0xff) {
+		log("no acceptable methods");
+		return;
+	}
+
+	// if return 0x0502
+	if (res[1] === 0x02) {
+		log("socks server needs auth");
+		if (!username || !password) {
+			log("please provide username/password");
+			return;
+		}
+		// +----+------+----------+------+----------+
+		// |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
+		// +----+------+----------+------+----------+
+		// | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
+		// +----+------+----------+------+----------+
+		const authRequest = new Uint8Array([
+			1,
+			username.length,
+			...encoder.encode(username),
+			password.length,
+			...encoder.encode(password)
+		]);
+		await writer.write(authRequest);
+		res = (await reader.read()).value;
+		// expected 0x0100
+		if (res[0] !== 0x01 || res[1] !== 0x00) {
+			log("fail to auth socks server");
+			return;
+		}
+	}
+
+	// Request data format (Worker -> Socks Server):
+	// +----+-----+-------+------+----------+----------+
+	// |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
+	// +----+-----+-------+------+----------+----------+
+	// | 1  |  1  | X'00' |  1   | Variable |    2     |
+	// +----+-----+-------+------+----------+----------+
+	// ATYP: address type of following address
+	// 0x01: IPv4 address
+	// 0x03: Domain name
+	// 0x04: IPv6 address
+	// DST.ADDR: desired destination address
+	// DST.PORT: desired destination port in network octet order
+
+	// addressType
+	// 1--> ipv4  addressLength =4
+	// 2--> domain name
+	// 3--> ipv6  addressLength =16
+	let DSTADDR;	// DSTADDR = ATYP + DST.ADDR
+	switch (addressType) {
+		case 1:
+			DSTADDR = new Uint8Array(
+				[1, ...addressRemote.split('.').map(Number)]
+			);
+			break;
+		case 2:
+			DSTADDR = new Uint8Array(
+				[3, addressRemote.length, ...encoder.encode(addressRemote)]
+			);
+			break;
+		case 3:
+			DSTADDR = new Uint8Array(
+				[4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])]
+			);
+			break;
+		default:
+			log(`invild  addressType is ${addressType}`);
+			return;
+	}
+	const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]);
+	await writer.write(socksRequest);
+	log('sent socks request');
+
+	res = (await reader.read()).value;
+	// Response format (Socks Server -> Worker):
+	//  +----+-----+-------+------+----------+----------+
+	// |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
+	// +----+-----+-------+------+----------+----------+
+	// | 1  |  1  | X'00' |  1   | Variable |    2     |
+	// +----+-----+-------+------+----------+----------+
+	if (res[1] === 0x00) {
+		log("socks connection opened");
+	} else {
+		log("fail to open socks connection");
+		return;
+	}
+	writer.releaseLock();
+	reader.releaseLock();
+	return socket;
+}
+
+/**
+ * 
+ * @param {string} address
+ */
+function socks5AddressParser(address) {
+	let [latter, former] = address.split("@").reverse();
+	let username, password, hostname, port;
+	if (former) {
+		const formers = former.split(":");
+		if (formers.length !== 2) {
+			throw new Error('Invalid SOCKS address format');
+		}
+		[username, password] = formers;
+	}
+	const latters = latter.split(":");
+	port = Number(latters.pop());
+	if (isNaN(port)) {
+		throw new Error('Invalid SOCKS address format');
+	}
+	hostname = latters.join(":");
+	const regex = /^\[.*\]$/;
+	if (hostname.includes(":") && !regex.test(hostname)) {
+		throw new Error('Invalid SOCKS address format');
+	}
+	return {
+		username,
+		password,
+		hostname,
+		port,
+	}
+}
+
+
+const at = 'QA==';
+const pt = 'dmxlc3M=';
+const ed = 'RUR0dW5uZWw=';
+
+/**
+ *
+ * @param {string} userID - single or comma separated userIDs
+ * @param {string | null} hostName
+ * @returns {string}
+ */
+function getConfig(userIDs, hostName) {
+	const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`;
+
+	// Split the userIDs into an array
+	const userIDArray = userIDs.split(",");
+
+	// Prepare output string for each userID
+	const sublink = `https://${hostName}/sub/${userIDArray[0]}?format=clash`
+	const subbestip = `https://${hostName}/bestip/${userIDArray[0]}`;
+	const clash_link = `https://url.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`;
+	// HTML Head with CSS and FontAwesome library
+	const htmlHead = `
+  
+    EDtunnel: Configuration
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+
+    
+    
+  
+  `;
+
+	const header = `
+    
+

EDtunnel: Protocol Configuration

+ +

Welcome! This function generates configuration for the vless protocol. If you found this useful, please check our GitHub project:

+

EDtunnel - https://github.com/6Kmfi6HP/EDtunnel

+
+ +
+

Options Explained:

+
    +
  • VLESS Subscription: Direct link for VLESS protocol configuration. Suitable for clients supporting VLESS.
  • +
  • Clash Subscription: Opens the Clash client with pre-configured settings. Best for Clash users on mobile devices.
  • +
  • Clash Link: A web link to convert the VLESS config to Clash format. Useful for manual import or troubleshooting.
  • +
  • Best IP Subscription: Provides a curated list of optimal server IPs for many different countries.
  • +
+

Choose the option that best fits your client and needs. For most users, the VLESS or Clash Subscription will be the easiest to use.

+
+
+ `; + + const configOutput = userIDArray.map((userID) => { + const protocolMain = atob(pt) + '://' + userID + atob(at) + hostName + commonUrlPart; + const protocolSec = atob(pt) + '://' + userID + atob(at) + proxyIP + commonUrlPart; + return ` +
+

UUID: ${userID}

+

Default IP Configuration

+
+
${protocolMain}
+ +
+ +

Best IP Configuration

+
+
${protocolSec}
+ +
+
+ `; + }).join(''); + + return ` + + ${htmlHead} + + ${header} + ${configOutput} + + + `; +} + +const HttpPort = new Set([80, 8080, 8880, 2052, 2086, 2095, 2082]); +const HttpsPort = new Set([443, 8443, 2053, 2096, 2087, 2083]); + +function GenSub(ไอดีผู้ใช้_เส้นทาง, ชื่อโฮสต์) { + const อาร์เรย์ไอดีผู้ใช้ = ไอดีผู้ใช้_เส้นทาง.includes(',') ? ไอดีผู้ใช้_เส้นทาง.split(',') : [ไอดีผู้ใช้_เส้นทาง]; + const randomPath = () => '/' + Math.random().toString(36).substring(2, 15) + '?ed=2048'; + const ส่วนUrlทั่วไปHttp = `?encryption=none&security=none&fp=random&type=ws&host=${ชื่อโฮสต์}&path=${encodeURIComponent(randomPath())}#`; + const ส่วนUrlทั่วไปHttps = `?encryption=none&security=tls&sni=${ชื่อโฮสต์}&fp=random&type=ws&host=${ชื่อโฮสต์}&path=%2F%3Fed%3D2048#`; + + const ผลลัพธ์ = อาร์เรย์ไอดีผู้ใช้.flatMap((ไอดีผู้ใช้) => { + const PartHttp = Array.from(HttpPort).flatMap((พอร์ต) => { + if (!ชื่อโฮสต์.includes('pages.dev')) { + const ส่วนUrl = `${ชื่อโฮสต์}-HTTP-${พอร์ต}`; + const protocolหลักHttp = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + ชื่อโฮสต์ + ':' + พอร์ต + ส่วนUrlทั่วไปHttp + ส่วนUrl; + return proxyIPs.flatMap((proxyIP) => { + const protocolรองHttp = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + proxyIP + ':' + พอร์ต + ส่วนUrlทั่วไปHttp + ส่วนUrl + '-' + proxyIP + '-' + atob(ed); + return [protocolหลักHttp, protocolรองHttp]; + }); + } + return []; + }); + + const PartHttps = Array.from(HttpsPort).flatMap((พอร์ต) => { + const ส่วนUrl = `${ชื่อโฮสต์}-HTTPS-${พอร์ต}`; + const protocolหลักHttps = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + ชื่อโฮสต์ + ':' + พอร์ต + ส่วนUrlทั่วไปHttps + ส่วนUrl; + return proxyIPs.flatMap((proxyIP) => { + const protocolรองHttps = atob(pt) + '://' + ไอดีผู้ใช้ + atob(at) + proxyIP + ':' + พอร์ต + ส่วนUrlทั่วไปHttps + ส่วนUrl + '-' + proxyIP + '-' + atob(ed); + return [protocolหลักHttps, protocolรองHttps]; + }); + }); + + return [...PartHttp, ...PartHttps]; + }); + + return ผลลัพธ์.join('\n'); +} + +const 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 +]; diff --git a/package.json b/package.json index 59ce7bb..c76e3fd 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,14 @@ "scripts": { "deploy": "wrangler deploy", "build": "wrangler deploy --dry-run", - "dev": "wrangler dev --remote" + "dev": "wrangler dev --remote", + "obfuscate": "javascript-obfuscator _worker.js" }, "author": "", "license": "ISC", "devDependencies": { "@cloudflare/workers-types": "^4.20230710.1", + "javascript-obfuscator": "^4.1.1", "wrangler": "^3.2.0" } }