cloudflare worker.js应用程序脚本代码

// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->.

// @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 = 'f164b705-6d8d-4153-adad-0bb78d046358';

const proxyIPs = ['23.162.136.169', '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<Response>}

*/

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') === 'text') {

vlessConfig = vlessConfig;

}

// Construct and return response object

return new Response(btoa(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.fmprc.gov.cn', 'www.xuexi.cn', 'www.gov.cn', 'mail.gov.cn', 'www.mofcom.gov.cn', 'www.gfbzb.gov.cn', 'www.miit.gov.cn', 'www.12377.cn'];

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<Response>} 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<void>} 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<import("@cloudflare/workers-types").Socket>} 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<void>} 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<void>) | null} retry The function to retry the connection if it fails.

* @param {(info: string) => void} log The logging function.

* @returns {Promise<void>} A Promise that resolves when the conversion is complete.

*/

async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {

// remote--> ws

let remoteChunkCount = 0;

let chunks = [];

/** @type {ArrayBuffer | null} */

let vlessHeader = vlessResponseHeader;

let hasIncomingData = false; // check if remoteSocket has incoming data

await remoteSocket.readable

.pipeTo(

new WritableStream({

start() {

},

/**

*

* @param {Uint8Array} chunk

* @param {*} controller

*/

async write(chunk, controller) {

hasIncomingData = true;

remoteChunkCount++;

if (webSocket.readyState !== WS_READY_STATE_OPEN) {

controller.error(

'webSocket.readyState is not open, maybe close'

);

}

if (vlessHeader) {

webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());

vlessHeader = null;

} else {

// console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`);

// seems no need rate limit this, CF seems fix this??..

// if (remoteChunkCount > 20000) {

// // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M

// await delay(1);

// }

webSocket.send(chunk);

}

},

close() {

log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);

// safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.

},

abort(reason) {

console.error(`remoteConnection!.readable abort`, reason);

},

})

)

.catch((error) => {

console.error(

`remoteSocketToWS has exception `,

error.stack || error

);

safeCloseWebSocket(webSocket);

});

// seems is cf connect socket have error,

// 1. Socket.closed will have error

// 2. Socket.readable will be close without any data coming

if (hasIncomingData === false && retry) {

log(`retry`)

retry();

}

}

/**

* Decodes a base64 string into an ArrayBuffer.

* @param {string} base64Str The base64 string to decode.

* @returns {{earlyData: ArrayBuffer|null, error: Error|null}} An object containing the decoded ArrayBuffer or null if there was an error, and any error that occurred during decoding or null if there was no error.

*/

function base64ToArrayBuffer(base64Str) {

if (!base64Str) {

return { earlyData: null, error: null };

}

try {

// go use modified Base64 for URL rfc4648 which js atob not support

base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');

const decode = atob(base64Str);

const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));

return { earlyData: arryBuffer.buffer, error: null };

} catch (error) {

return { earlyData: null, error };

}

}

/**

* Checks if a given string is a valid UUID.

* Note: This is not a real UUID validation.

* @param {string} uuid The string to validate as a UUID.

* @returns {boolean} True if the string is a valid UUID, false otherwise.

*/

function isValidUUID(uuid) {

const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

return uuidRegex.test(uuid);

}

const WS_READY_STATE_OPEN = 1;

const WS_READY_STATE_CLOSING = 2;

/**

* Closes a WebSocket connection safely without throwing exceptions.

* @param {import("@cloudflare/workers-types").WebSocket} socket The WebSocket connection to close.

*/

function safeCloseWebSocket(socket) {

try {

if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {

socket.close();

}

} catch (error) {

console.error('safeCloseWebSocket error', error);

}

}

const byteToHex = [];

for (let i = 0; i < 256; ++i) {

byteToHex.push((i + 256).toString(16).slice(1));

}

function unsafeStringify(arr, offset = 0) {

return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();

}

function stringify(arr, offset = 0) {

const uuid = unsafeStringify(arr, offset);

if (!isValidUUID(uuid)) {

throw TypeError("Stringified UUID is invalid");

}

return uuid;

}


 

/**

* Handles outbound UDP traffic by transforming the data into DNS queries and sending them over a WebSocket connection.

* @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket connection to send the DNS queries over.

* @param {ArrayBuffer} vlessResponseHeader The VLESS response header.

* @param {(string) => void} log The logging function.

* @returns {{write: (chunk: Uint8Array) => void}} An object with a write method that accepts a Uint8Array chunk to write to the transform stream.

*/

async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {

let isVlessHeaderSent = false;

const transformStream = new TransformStream({

start(controller) {

},

transform(chunk, controller) {

// udp message 2 byte is the the length of udp data

// TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message

for (let index = 0; index < chunk.byteLength;) {

const lengthBuffer = chunk.slice(index, index + 2);

const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);

const udpData = new Uint8Array(

chunk.slice(index + 2, index + 2 + udpPakcetLength)

);

index = index + 2 + udpPakcetLength;

controller.enqueue(udpData);

}

},

flush(controller) {

}

});

// only handle dns udp for now

transformStream.readable.pipeTo(new WritableStream({

async write(chunk) {

const resp = await fetch(dohURL, // dns server url

{

method: 'POST',

headers: {

'content-type': 'application/dns-message',

},

body: chunk,

})

const dnsQueryResult = await resp.arrayBuffer();

const udpSize = dnsQueryResult.byteLength;

// console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));

const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);

if (webSocket.readyState === WS_READY_STATE_OPEN) {

log(`doh success and dns message length is ${udpSize}`);

if (isVlessHeaderSent) {

webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());

} else {

webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());

isVlessHeaderSent = true;

}

}

}

})).catch((error) => {

log('dns udp has error' + error)

});

const writer = transformStream.writable.getWriter();

return {

/**

*

* @param {Uint8Array} chunk

*/

write(chunk) {

writer.write(chunk);

}

};

}

/**

*

* @param {string} userID - single or comma separated userIDs

* @param {string | null} hostName

* @returns {string}

*/

function getVLESSConfig(userIDs, hostName) {

const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`;

const separator = "---------------------------------------------------------------";

const hashSeparator = "################################################################";

// Split the userIDs into an array

let userIDArray = userIDs.split(',');

// Prepare output array

let output = [];

let header = [];

const sublink = `https://${hostName}/sub/${userIDArray[0]}`

const clash_link = `https://subconverter.do.xn--b6gac.eu.org/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;

header.push(`\n<p align="center"><img src="https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky" alt="图片描述" style="margin-bottom: -50px;">`);

header.push(`\n<b style=" font-size: 15px;" >Welcome! This function generates configuration for VLESS protocol. If you found this useful, please check our GitHub project for more:</b>\n`);

header.push(`<b style=" font-size: 15px;" >欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个star:</b>\n`);

header.push(`\n<a href="https://github.com/3Kmfi6HP/EDtunnel" target="_blank">EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a>\n`);

header.push(`\n<iframe src="https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large" frameborder="0" scrolling="0" width="170" height="30" title="GitHub"></iframe>\n\n`.replace(/USERNAME/g, "3Kmfi6HP").replace(/REPOSITORY/g, "EDtunnel"));

header.push(`<a href="//${hostName}/sub/${userIDArray[0]}" target="_blank">VLESS 节点订阅连接</a>\n<a href="clash://install-config?url=${encodeURIComponent(clash_link)}" target="_blank">Clash for Windows 节点订阅连接</a>\n<a href="${clash_link}" target="_blank">Clash 节点订阅连接2</a></p>\n`);

header.push(``);

// Generate output string for each userID

userIDArray.forEach((userID) => {

const vlessMain = `vless://${userID}@${hostName}${commonUrlPart}`;

const vlessSec = `vless://${userID}@${proxyIP}${commonUrlPart}`;

output.push(`UUID: ${userID}`);

output.push(`${hashSeparator}\nv2ray default ip\n${separator}\n${vlessMain}\n${separator}`);

output.push(`${hashSeparator}\nv2ray with best ip\n${separator}\n${vlessSec}\n${separator}`);

});

output.push(`${hashSeparator}\n# Clash Proxy Provider 配置格式(configuration format)\nproxy-groups:\n - name: UseProvider\n type: select\n use:\n - provider1\n proxies:\n - Proxy\n - DIRECT\nproxy-providers:\n provider1:\n type: http\n url: https://${hostName}/sub/${userIDArray[0]}?format=clash\n interval: 3600\n path: ./provider1.yaml\n health-check:\n enable: true\n interval: 600\n # lazy: true\n url: http://www.gstatic.com/generate_204\n\n${hashSeparator}`);

// HTML Head with CSS

const htmlHead = `

<head>

<title>EDtunnel: VLESS configuration</title>

<meta name="description" content="This is a tool for generating VLESS protocol configurations. Give us a star on GitHub https://github.com/3Kmfi6HP/EDtunnel if you found it useful!">

<meta name="keywords" content="EDtunnel, cloudflare pages, cloudflare worker, severless">

<meta name="viewport" content="width=device-width, initial-scale=1">

<meta property="og:site_name" content="EDtunnel: VLESS configuration" />

<meta property="og:type" content="website" />

<meta property="og:title" content="EDtunnel - VLESS configuration and subscribe output" />

<meta property="og:description" content="Use cloudflare pages and worker severless to implement vless protocol" />

<meta property="og:url" content="https://${hostName}/" />

<meta property="og:image" content="https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${encodeURIComponent(`vless://${userIDs.split(',')[0]}@${hostName}${commonUrlPart}`)}" />

<meta name="twitter:card" content="summary_large_image" />

<meta name="twitter:title" content="EDtunnel - VLESS configuration and subscribe output" />

<meta name="twitter:description" content="Use cloudflare pages and worker severless to implement vless protocol" />

<meta name="twitter:url" content="https://${hostName}/" />

<meta name="twitter:image" content="https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky" />

<meta property="og:image:width" content="1500" />

<meta property="og:image:height" content="1500" />

<style>

body {

font-family: Arial, sans-serif;

background-color: #f0f0f0;

color: #333;

padding: 10px;

}

a {

color: #1a0dab;

text-decoration: none;

}

img {

max-width: 100%;

height: auto;

}

pre {

white-space: pre-wrap;

word-wrap: break-word;

background-color: #fff;

border: 1px solid #ddd;

padding: 15px;

margin: 10px 0;

}

/* Dark mode */

@media (prefers-color-scheme: dark) {

body {

background-color: #333;

color: #f0f0f0;

}

a {

color: #9db4ff;

}

pre {

background-color: #282a36;

border-color: #6272a4;

}

}

</style>

</head>

`;

// Join output with newlines, wrap inside <html> and <body>

return `

<html>

${htmlHead}

<body>

<pre style="

background-color: transparent;

border: none;

">${header.join('')}</pre><pre>${output.join('\n')}</pre>

</body>

</html>`;

}


 

function createVLESSSub(userID_Path, hostName) {

let portArray_http = [80, 8080, 8880, 2052, 2086, 2095, 2082];

let portArray_https = [443, 8443, 2053, 2096, 2087, 2083];

// Split the userIDs into an array

let userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path];

// Prepare output array

let output = [];

// Generate output string for each userID

userIDArray.forEach((userID) => {

// Check if the hostName is a Cloudflare Pages domain, if not, generate HTTP configurations

// reasons: pages.dev not support http only https

if (!hostName.includes('pages.dev')) {

// Iterate over all ports for http

portArray_http.forEach((port) => {

const commonUrlPart_http = `:${port}?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}-HTTP-${port}`;

const vlessMainHttp = `vless://${userID}@${hostName}${commonUrlPart_http}`;

// For each proxy IP, generate a VLESS configuration and add to output

proxyIPs.forEach((proxyIP) => {

const vlessSecHttp = `vless://${userID}@${proxyIP}${commonUrlPart_http}-${proxyIP}-EDtunnel`;

output.push(`${vlessMainHttp}`);

output.push(`${vlessSecHttp}`);

});

});

}

// Iterate over all ports for https

portArray_https.forEach((port) => {

const commonUrlPart_https = `:${port}?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}-HTTPS-${port}`;

const vlessMainHttps = `vless://${userID}@${hostName}${commonUrlPart_https}`;

// For each proxy IP, generate a VLESS configuration and add to output

proxyIPs.forEach((proxyIP) => {

const vlessSecHttps = `vless://${userID}@${proxyIP}${commonUrlPart_https}-${proxyIP}-EDtunnel`;

output.push(`${vlessMainHttps}`);

output.push(`${vlessSecHttps}`);

});

});

});

// Join output with newlines

return output.join('\n');

}

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SnailSTU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值