From e96f43fcd18940dfdff1d686f2e82d2613578d06 Mon Sep 17 00:00:00 2001 From: woodli Date: Thu, 1 Feb 2024 17:43:45 +0800 Subject: [PATCH] feat: add chatgpt.md --- src/idea/HarmonyOs.md | 2 +- src/idea/chatgpt.md | 881 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 882 insertions(+), 1 deletion(-) create mode 100644 src/idea/chatgpt.md diff --git a/src/idea/HarmonyOs.md b/src/idea/HarmonyOs.md index a7560f9..e9c21c0 100644 --- a/src/idea/HarmonyOs.md +++ b/src/idea/HarmonyOs.md @@ -1,6 +1,6 @@ --- prev: ./ -next: false +next: ./chatgpt.md --- # 鸿蒙是什么?鸿蒙到底是不是自研的? diff --git a/src/idea/chatgpt.md b/src/idea/chatgpt.md new file mode 100644 index 0000000..452afa9 --- /dev/null +++ b/src/idea/chatgpt.md @@ -0,0 +1,881 @@ +--- +prev: ./HarmonyOs.md +next: false +--- + + +# 如何使用chatgpt + +## 0. 序言 +感谢伟大的cloudflare(白嫖) + +## 1. 搭建代理 + +### 1.1 购买域名 +从域名服务商处购买一个域名,自定义即可,推荐使用.online域名(已知,.gmai.com, .cn, .top域名邮箱已经被OpenAI封禁),便宜而且注册ChatGPT账号时不会被封禁 + + +### 1.2 将域名添加到cloudflare + +https://zhuanlan.zhihu.com/p/620135485# + +### 1.3 添加workers + +- 在cloudflare管理台页面,选择Workers 和 Pages +- 点击创建应用程序,然后点击 **创建Worker** ,名称随意命名 +![](https://img.lzwcyd.cn/img/202402011724243.png) +- 点击部署,然后点击 **编辑代码** +![](https://img.lzwcyd.cn/img/202402011725842.png) + +从[GitHUb](https://github.com/yonggekkk/Cloudflare-workers-pages-vless/blob/main/_worker.js),获取脚本内容 +``` JavaScript +// version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC. +// @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 = '77a571fb-4fd2-4b37-8596-1b7d9728bb5c'; + +const proxyIPs = ["[2a01:4f8:c2c:123f:64:5:ac40:6c0a]"];//['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'edgetunnel.anycast.eu.org']; + +let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; + +let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query + +// v2board api environment variables (optional) deprecated, please use planetscale.com instead + +if (!isValidUUID(userID)) { + throw new Error('uuid is invalid'); +} + +export default { + /** + * @param {import("@cloudflare/workers-types").Request} request + * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env + * @param {import("@cloudflare/workers-types").ExecutionContext} ctx + * @returns {Promise} + */ + async fetch(request, env, ctx) { + // uuid_validator(request); + try { + userID = env.UUID || userID; + proxyIP = env.PROXYIP || proxyIP; + dohURL = env.DNS_RESOLVER_URL || dohURL; + let userID_Path = userID; + if (userID.includes(',')) { + userID_Path = userID.split(',')[0]; + } + const upgradeHeader = request.headers.get('Upgrade'); + if (!upgradeHeader || upgradeHeader !== 'websocket') { + const url = new URL(request.url); + switch (url.pathname) { + case '/cf': + return new Response(JSON.stringify(request.cf, null, 4), { + status: 200, + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + }); + case `/${userID_Path}`: { + const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); + return new Response(`${vlessConfig}`, { + status: 200, + headers: { + "Content-Type": "text/html; charset=utf-8", + } + }); + } + case `/sub/${userID_Path}`: { + const url = new URL(request.url); + const searchParams = url.searchParams; + let vlessConfig = createVLESSSub(userID, request.headers.get('Host')); + // If 'format' query param equals to 'clash', convert config to base64 + if (searchParams.get('format') === 'clash') { + vlessConfig = btoa(vlessConfig); + } + // Construct and return response object + return new Response(vlessConfig, { + status: 200, + headers: { + "Content-Type": "text/plain;charset=utf-8", + } + }); + } + case `/bestip/${userID_Path}`: { + const bestiplink = `https://sub.xf.free.hr/auto?host=${request.headers.get('Host')}&uuid=${userID_Path}` + const reqHeaders = new Headers(request.headers); + const bestipresponse = await fetch(bestiplink, { redirect: 'manual', headers: reqHeaders, }); + // Construct and return response object + return bestipresponse + } + default: + // return new Response('Not found', { status: 404 }); + // For any other path, reverse proxy to 'www.fmprc.gov.cn' and return the original response, caching it in the process + const hostnames = ['www.visa.com', 'www.wto.org']; + url.hostname = hostnames[Math.floor(Math.random() * hostnames.length)]; + url.protocol = 'https:'; + + const newHeaders = new Headers(request.headers); + newHeaders.set('cf-connecting-ip', newHeaders.get('x-forwarded-for') || newHeaders.get('cf-connecting-ip')); + newHeaders.set('x-forwarded-for', newHeaders.get('cf-connecting-ip')); + newHeaders.set('x-real-ip', newHeaders.get('cf-connecting-ip')); + newHeaders.set('referer', 'https://www.google.com/q=edtunnel'); + + request = new Request(url, { + method: request.method, + headers: newHeaders, + body: request.body, + redirect: request.redirect, + }); + + const cache = caches.default; + let response = await cache.match(request); + + if (!response) { + try { + response = await fetch(request, { redirect: 'manual' }); + } catch (err) { + url.protocol = 'http:'; + url.hostname = hostnames[Math.floor(Math.random() * hostnames.length)]; + request = new Request(url, { + method: request.method, + headers: newHeaders, + body: request.body, + redirect: request.redirect, + }); + response = await fetch(request, { redirect: 'manual' }); + } + + const cloneResponse = response.clone(); + ctx.waitUntil(cache.put(request, cloneResponse)); + } + return response; + } + } else { + return await vlessOverWSHandler(request); + } + } catch (err) { + /** @type {Error} */ let e = err; + return new Response(e.toString()); + } + }, +}; + +export async function uuid_validator(request) { + const hostname = request.headers.get('Host'); + const currentDate = new Date(); + + const subdomain = hostname.split('.')[0]; + const year = currentDate.getFullYear(); + const month = String(currentDate.getMonth() + 1).padStart(2, '0'); + const day = String(currentDate.getDate()).padStart(2, '0'); + + const formattedDate = `${year}-${month}-${day}`; + + // const daliy_sub = formattedDate + subdomain + const hashHex = await hashHex_f(subdomain); + // subdomain string contains timestamps utc and uuid string TODO. + console.log(hashHex, subdomain, formattedDate); +} + +export async function hashHex_f(string) { + const encoder = new TextEncoder(); + const data = encoder.encode(string); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); + return hashHex; +} + +/** + * Handles VLESS over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the VLESS header. + * @param {import("@cloudflare/workers-types").Request} request The incoming request object. + * @returns {Promise} A Promise that resolves to a WebSocket response object. + */ +async function vlessOverWSHandler(request) { + const webSocketPair = new WebSocketPair(); + const [client, webSocket] = Object.values(webSocketPair); + webSocket.accept(); + + let address = ''; + let portWithRandomLog = ''; + let currentDate = new Date(); + const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { + console.log(`[${currentDate} ${address}:${portWithRandomLog}] ${info}`, event || ''); + }; + const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; + + const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); + + /** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/ + let remoteSocketWapper = { + value: null, + }; + let udpStreamWrite = null; + let isDns = false; + + // ws --> remote + readableWebSocketStream.pipeTo(new WritableStream({ + async write(chunk, controller) { + if (isDns && udpStreamWrite) { + return udpStreamWrite(chunk); + } + if (remoteSocketWapper.value) { + const writer = remoteSocketWapper.value.writable.getWriter() + await writer.write(chunk); + writer.releaseLock(); + return; + } + + const { + hasError, + message, + portRemote = 443, + addressRemote = '', + rawDataIndex, + vlessVersion = new Uint8Array([0, 0]), + isUDP, + } = processVlessHeader(chunk, userID); + address = addressRemote; + portWithRandomLog = `${portRemote} ${isUDP ? 'udp' : 'tcp'} `; + if (hasError) { + // controller.error(message); + throw new Error(message); // cf seems has bug, controller.error will not end stream + // webSocket.close(1000, message); + return; + } + + // If UDP and not DNS port, close it + if (isUDP && portRemote !== 53) { + throw new Error('UDP proxy only enabled for DNS which is port 53'); + // cf seems has bug, controller.error will not end stream + } + + if (isUDP && portRemote === 53) { + isDns = true; + } + + // ["version", "附加信息长度 N"] + const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); + const rawClientData = chunk.slice(rawDataIndex); + + // TODO: support udp here when cf runtime has udp support + if (isDns) { + const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); + udpStreamWrite = write; + udpStreamWrite(rawClientData); + return; + } + handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); + }, + close() { + log(`readableWebSocketStream is close`); + }, + abort(reason) { + log(`readableWebSocketStream is abort`, JSON.stringify(reason)); + }, + })).catch((err) => { + log('readableWebSocketStream pipeTo error', err); + }); + + return new Response(null, { + status: 101, + webSocket: client, + }); +} + +/** + * Handles outbound TCP connections. + * + * @param {any} remoteSocket + * @param {string} addressRemote The remote address to connect to. + * @param {number} portRemote The remote port to connect to. + * @param {Uint8Array} rawClientData The raw client data to write. + * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to. + * @param {Uint8Array} vlessResponseHeader The VLESS response header. + * @param {function} log The logging function. + * @returns {Promise} The remote socket. + */ +async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { + + /** + * Connects to a given address and port and writes data to the socket. + * @param {string} address The address to connect to. + * @param {number} port The port to connect to. + * @returns {Promise} A Promise that resolves to the connected socket. + */ + async function connectAndWrite(address, port) { + /** @type {import("@cloudflare/workers-types").Socket} */ + const tcpSocket = connect({ + hostname: address, + port: port, + }); + remoteSocket.value = tcpSocket; + log(`connected to ${address}:${port}`); + const writer = tcpSocket.writable.getWriter(); + await writer.write(rawClientData); // first write, nomal is tls client hello + writer.releaseLock(); + return tcpSocket; + } + + /** + * Retries connecting to the remote address and port if the Cloudflare socket has no incoming data. + * @returns {Promise} A Promise that resolves when the retry is complete. + */ + async function retry() { + const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) + tcpSocket.closed.catch(error => { + console.log('retry tcpSocket closed error', error); + }).finally(() => { + safeCloseWebSocket(webSocket); + }) + remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log); + } + + const tcpSocket = await connectAndWrite(addressRemote, portRemote); + + // when remoteSocket is ready, pass to websocket + // remote--> ws + remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log); +} + +/** + * Creates a readable stream from a WebSocket server, allowing for data to be read from the WebSocket. + * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer The WebSocket server to create the readable stream from. + * @param {string} earlyDataHeader The header containing early data for WebSocket 0-RTT. + * @param {(info: string)=> void} log The logging function. + * @returns {ReadableStream} A readable stream that can be used to read data from the WebSocket. + */ +function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { + let readableStreamCancel = false; + const stream = new ReadableStream({ + start(controller) { + webSocketServer.addEventListener('message', (event) => { + const message = event.data; + controller.enqueue(message); + }); + + webSocketServer.addEventListener('close', () => { + safeCloseWebSocket(webSocketServer); + controller.close(); + }); + + webSocketServer.addEventListener('error', (err) => { + log('webSocketServer has error'); + controller.error(err); + }); + const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); + if (error) { + controller.error(error); + } else if (earlyData) { + controller.enqueue(earlyData); + } + }, + + pull(controller) { + // if ws can stop read if stream is full, we can implement backpressure + // https://streams.spec.whatwg.org/#example-rs-push-backpressure + }, + + cancel(reason) { + log(`ReadableStream was canceled, due to ${reason}`) + readableStreamCancel = true; + safeCloseWebSocket(webSocketServer); + } + }); + + return stream; +} + +// https://xtls.github.io/development/protocols/vless.html +// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw + +/** + * Processes the VLESS header buffer and returns an object with the relevant information. + * @param {ArrayBuffer} vlessBuffer The VLESS header buffer to process. + * @param {string} userID The user ID to validate against the UUID in the VLESS header. + * @returns {{ + * hasError: boolean, + * message?: string, + * addressRemote?: string, + * addressType?: number, + * portRemote?: number, + * rawDataIndex?: number, + * vlessVersion?: Uint8Array, + * isUDP?: boolean + * }} An object with the relevant information extracted from the VLESS header buffer. + */ +function processVlessHeader(vlessBuffer, userID) { + if (vlessBuffer.byteLength < 24) { + return { + hasError: true, + message: 'invalid data', + }; + } + + const version = new Uint8Array(vlessBuffer.slice(0, 1)); + let isValidUser = false; + let isUDP = false; + const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17)); + const slicedBufferString = stringify(slicedBuffer); + // check if userID is valid uuid or uuids split by , and contains userID in it otherwise return error message to console + const uuids = userID.includes(',') ? userID.split(",") : [userID]; + // uuid_validator(hostName, slicedBufferString); + + + // isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()); + isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) || uuids.length === 1 && slicedBufferString === uuids[0].trim(); + + console.log(`userID: ${slicedBufferString}`); + + if (!isValidUser) { + return { + hasError: true, + message: 'invalid user', + }; + } + + const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0]; + //skip opt for now + + const command = new Uint8Array( + vlessBuffer.slice(18 + optLength, 18 + optLength + 1) + )[0]; + + // 0x01 TCP + // 0x02 UDP + // 0x03 MUX + if (command === 1) { + isUDP = false; + } else if (command === 2) { + isUDP = true; + } else { + return { + hasError: true, + message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`, + }; + } + const portIndex = 18 + optLength + 1; + const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2); + // port is big-Endian in raw data etc 80 == 0x005d + const portRemote = new DataView(portBuffer).getUint16(0); + + let addressIndex = portIndex + 2; + const addressBuffer = new Uint8Array( + vlessBuffer.slice(addressIndex, addressIndex + 1) + ); + + // 1--> ipv4 addressLength =4 + // 2--> domain name addressLength=addressBuffer[1] + // 3--> ipv6 addressLength =16 + const addressType = addressBuffer[0]; + let addressLength = 0; + let addressValueIndex = addressIndex + 1; + let addressValue = ''; + switch (addressType) { + case 1: + addressLength = 4; + addressValue = new Uint8Array( + vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) + ).join('.'); + break; + case 2: + addressLength = new Uint8Array( + vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) + )[0]; + addressValueIndex += 1; + addressValue = new TextDecoder().decode( + vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) + ); + break; + case 3: + addressLength = 16; + const dataView = new DataView( + vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) + ); + // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + const ipv6 = []; + for (let i = 0; i < 8; i++) { + ipv6.push(dataView.getUint16(i * 2).toString(16)); + } + addressValue = ipv6.join(':'); + // seems no need add [] for ipv6 + break; + default: + return { + hasError: true, + message: `invild addressType is ${addressType}`, + }; + } + if (!addressValue) { + return { + hasError: true, + message: `addressValue is empty, addressType is ${addressType}`, + }; + } + + return { + hasError: false, + addressRemote: addressValue, + addressType, + portRemote, + rawDataIndex: addressValueIndex + addressLength, + vlessVersion: version, + isUDP, + }; +} + + +/** + * Converts a remote socket to a WebSocket connection. + * @param {import("@cloudflare/workers-types").Socket} remoteSocket The remote socket to convert. + * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to connect to. + * @param {ArrayBuffer | null} vlessResponseHeader The VLESS response header. + * @param {(() => Promise) | null} retry The function to retry the connection if it fails. + * @param {(info: string) => void} log The logging function. + * @returns {Promise} A Promise that resolves when the conversion is complete. + */ +async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { + // remote--> ws + let remoteChunkCount = 0; + let chunks = []; + /** @type {ArrayBuffer | null} */ + let vlessHeader = vlessResponseHeader; + let hasIncomingData = false; // check if remoteSocket has incoming data + await remoteSocket.readable + .pipeTo( + new WritableStream({ + start() { + }, + /** + * + * @param {Uint8Array} chunk + * @param {*} controller + */ + async write(chunk, controller) { + hasIncomingData = true; + remoteChunkCount++; + if (webSocket.readyState !== WS_READY_STATE_OPEN) { + controller.error( + 'webSocket.readyState is not open, maybe close' + ); + } + if (vlessHeader) { + webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); + vlessHeader = null; + } else { + // console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`); + // seems no need rate limit this, CF seems fix this??.. + // if (remoteChunkCount > 20000) { + // // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M + // await delay(1); + // } + webSocket.send(chunk); + } + }, + close() { + log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`); + // safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway. + }, + abort(reason) { + console.error(`remoteConnection!.readable abort`, reason); + }, + }) + ) + .catch((error) => { + console.error( + `remoteSocketToWS has exception `, + error.stack || error + ); + safeCloseWebSocket(webSocket); + }); + + // seems is cf connect socket have error, + // 1. Socket.closed will have error + // 2. Socket.readable will be close without any data coming + if (hasIncomingData === false && retry) { + log(`retry`) + retry(); + } +} + +/** + * Decodes a base64 string into an ArrayBuffer. + * @param {string} base64Str The base64 string to decode. + * @returns {{earlyData: ArrayBuffer|null, error: Error|null}} An object containing the decoded ArrayBuffer or null if there was an error, and any error that occurred during decoding or null if there was no error. + */ +function base64ToArrayBuffer(base64Str) { + if (!base64Str) { + return { earlyData: null, error: null }; + } + try { + // go use modified Base64 for URL rfc4648 which js atob not support + base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); + const decode = atob(base64Str); + const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); + return { earlyData: arryBuffer.buffer, error: null }; + } catch (error) { + return { earlyData: null, error }; + } +} + +/** + * Checks if a given string is a valid UUID. + * Note: This is not a real UUID validation. + * @param {string} uuid The string to validate as a UUID. + * @returns {boolean} True if the string is a valid UUID, false otherwise. + */ +function isValidUUID(uuid) { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return uuidRegex.test(uuid); +} + +const WS_READY_STATE_OPEN = 1; +const WS_READY_STATE_CLOSING = 2; +/** + * Closes a WebSocket connection safely without throwing exceptions. + * @param {import("@cloudflare/workers-types").WebSocket} socket The WebSocket connection to close. + */ +function safeCloseWebSocket(socket) { + try { + if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { + socket.close(); + } + } catch (error) { + console.error('safeCloseWebSocket error', error); + } +} + +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 256).toString(16).slice(1)); +} + +function unsafeStringify(arr, offset = 0) { + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); +} + +function stringify(arr, offset = 0) { + const uuid = unsafeStringify(arr, offset); + if (!isValidUUID(uuid)) { + throw TypeError("Stringified UUID is invalid"); + } + return uuid; +} + + +/** + * Handles outbound UDP traffic by transforming the data into DNS queries and sending them over a WebSocket connection. + * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket connection to send the DNS queries over. + * @param {ArrayBuffer} vlessResponseHeader The VLESS response header. + * @param {(string) => void} log The logging function. + * @returns {{write: (chunk: Uint8Array) => void}} An object with a write method that accepts a Uint8Array chunk to write to the transform stream. + */ +async function handleUDPOutBound(webSocket, vlessResponseHeader, log) { + + let isVlessHeaderSent = false; + const transformStream = new TransformStream({ + start(controller) { + + }, + transform(chunk, controller) { + // udp message 2 byte is the the length of udp data + // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message + for (let index = 0; index < chunk.byteLength;) { + const lengthBuffer = chunk.slice(index, index + 2); + const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); + const udpData = new Uint8Array( + chunk.slice(index + 2, index + 2 + udpPakcetLength) + ); + index = index + 2 + udpPakcetLength; + controller.enqueue(udpData); + } + }, + flush(controller) { + } + }); + + // only handle dns udp for now + transformStream.readable.pipeTo(new WritableStream({ + async write(chunk) { + const resp = await fetch(dohURL, // dns server url + { + method: 'POST', + headers: { + 'content-type': 'application/dns-message', + }, + body: chunk, + }) + const dnsQueryResult = await resp.arrayBuffer(); + const udpSize = dnsQueryResult.byteLength; + // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16))); + const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]); + if (webSocket.readyState === WS_READY_STATE_OPEN) { + log(`doh success and dns message length is ${udpSize}`); + if (isVlessHeaderSent) { + webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); + } else { + webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); + isVlessHeaderSent = true; + } + } + } + })).catch((error) => { + log('dns udp has error' + error) + }); + + const writer = transformStream.writable.getWriter(); + + return { + /** + * + * @param {Uint8Array} chunk + */ + write(chunk) { + writer.write(chunk); + } + }; +} + +/** + * + * @param {string} userID - single or comma separated userIDs + * @param {string | null} hostName + * @returns {string} + */ +function getVLESSConfig(userID, hostName) { + const wvlessws = `vless://${userID}@www.visa.com:8880?encryption=none&security=none&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`; + const pvlesswstls = `vless://${userID}@www.visa.com:8443?encryption=none&security=tls&type=ws&host=${hostName}&sni=${hostName}&fp=random&path=%2F%3Fed%3D2048#${hostName}`; + + if (hostName.includes('pages.dev')) { + return ` +

==========================配置详解==============================

+ +

################################################################

+

CF-pages-vless+ws+tls节点,分享链接如下:

+ +

${pvlesswstls}

+ +

---------------------------------------------------------------

+

注意:如果 ${hostName} 在本地网络打不开(中国移动用户注意)

+

客户端选项的伪装域名(host)必须改为你在CF解析完成的自定义域名

+

---------------------------------------------------------------

+

客户端必要文明参数如下:

+

客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)

+

端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096)

+

用户ID(uuid):${userID}

+

传输协议(network):ws 或者 websocket

+

伪装域名(host):${hostName}

+

路径(path):/?ed=2048

+

传输安全(TLS):开启

+

跳过证书验证(allowlnsecure):false

+

################################################################

+ `; + + } else if (hostName.includes('workers.dev')) { + return ` +

==========================配置详解==============================

+ +

################################################################

+

一、CF-workers-vless+ws节点,分享链接如下:

+ +

${wvlessws}

+ +

---------------------------------------------------------------

+

注意:当前节点无需使用CF解析完成的域名,客户端选项的TLS选项必须关闭

+

---------------------------------------------------------------

+

客户端必要文明参数如下:

+

客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)

+

端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095)

+

用户ID(uuid):${userID}

+

传输协议(network):ws 或者 websocket

+

伪装域名(host):${hostName}

+

路径(path):/?ed=2048

+

################################################################

+ + +

################################################################

+ +

二、CF-workers-vless+ws+tls节点注意:请在浏览器地址栏输入:你设置的自定义域名/你设置的UUID

+

防止小白过多的操作失误,必须设置自定义域名后才能使用Workers方式的TLS模式,否则,建议只使用vless+ws节点即可

+

提示:使用pages方式部署,联通、电信用户大概率可以直接使用TLS模式,无需设置自定义域名

+

pages方式部署可参考此视频教程:https://youtu.be/McdRoLZeTqg

+ +

################################################################ + `; + } else { + return ` +

==========================配置详解==============================

+ +

=====使用自定义域名查看配置,请确认使用的是workers还是pages=====

+ +

################################################################

+

一、CF-workers-vless+ws节点,分享链接如下:

+ +

${wvlessws}

+ +

---------------------------------------------------------------

+

注意:当前节点无需使用CF解析完成的域名,客户端选项的TLS选项必须关闭

+

---------------------------------------------------------------

+

客户端必要文明参数如下:

+

客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)

+

端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095)

+

用户ID(uuid):${userID}

+

传输协议(network):ws 或者 websocket

+

伪装域名(host):${hostName}

+

路径(path):/?ed=2048

+

################################################################

+ +

################################################################

+

二、CF-workers-vless+ws+tls 或者 CF-pages-vless+ws+tls节点,分享链接如下:

+ +

${pvlesswstls}

+ +

---------------------------------------------------------------

+

注意:客户端选项的伪装域名(host)必须改为你在CF解析完成的自定义域名

+

---------------------------------------------------------------

+

客户端必要文明参数如下:

+

客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)

+

端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096)

+

用户ID(uuid):${userID}

+

传输协议(network):ws 或者 websocket

+

伪装域名(host):${hostName}

+

路径(path):/?ed=2048

+

传输安全(TLS):开启

+

跳过证书验证(allowlnsecure):false

+

################################################################

+ `; + } + } +``` +![](https://img.lzwcyd.cn/img/202402011729819.png) + +- 点击 **保存并部署** 切记uuid要记录下来 +- 回到workers 和 Pages中,点击你刚刚创建的Worker ,点击添加自定义域 +![](https://img.lzwcyd.cn/img/202402011733419.png) +输入你自定义的域名地址,例如,chatgpt-proxy.test.online, test.online为你之前添加进入cloudflare的域名 +![](https://img.lzwcyd.cn/img/202402011734246.png) +点击添加自定义域,等待1-3分钟 +访问你刚刚的自定义域名地址+"/"+uuid, 例如:https://chatgpt-proxy.test.online/77a571fb-4fd2-4b37-8596-1b7d9728bb5c +内容如下: +![](https://img.lzwcyd.cn/img/202402011737516.png) +复制 vless://开头的文本 + +### 1.4 下载v2rayN + +参照百度 + +### 1.5 使用代理 + +打开v2rayN +点击服务器选择从剪切板导入批量URL +![](https://img.lzwcyd.cn/img/202402011740253.png) +选择代理模式 +![](https://img.lzwcyd.cn/img/202402011742289.png) + +此时即可访问Google + +## 2. 注册ChatGPT账号 +