diff --git a/README.md b/README.md index effc2c7..53a69eb 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,10 @@ Telegram交流群:[@CMLiussss](https://t.me/CMLiussss),**感谢[Alice Networ | 变量名 | 示例 | 必填 | 备注 | YT | |--------|---------|-|-----|-----| | UUID | `90cd4a77-141a-43c9-991b-08263cfe9c10` |✅| Powershell -NoExit -Command "[guid]::NewGuid()"| [Video](https://www.youtube.com/watch?v=s91zjpw3-P8&t=72s) | -| PROXYIP | `proxyip.fxxk.dedyn.io:443` |❌| 备选作为访问CFCDN站点的代理节点(支持多ProxyIP, ProxyIP之间使用`,`或`换行`作间隔) | [Video](https://www.youtube.com/watch?v=s91zjpw3-P8&t=166s) | +| KEY | `token` |❌| 动态UUID秘钥,使用变量`KEY`的时候,将不再启用变量`UUID`| | +| TIME | `7` |❌| 动态UUID有效时间(单位:天)| | +| UPTIME | `3` |❌| 动态UUID有效时间(默认:北京时间`3`点更新) | | +| PROXYIP | `proxyip.fxxk.dedyn.io:443` |❌| 备选作为访问CFCDN站点的代理节点(支持自定义ProxyIP端口, 支持多ProxyIP, ProxyIP之间使用`,`或`换行`作间隔) | [Video](https://www.youtube.com/watch?v=s91zjpw3-P8&t=166s) | | SOCKS5 | `user:password@127.0.0.1:1080` |❌| 优先作为访问CFCDN站点的SOCKS5代理(支持多socks5, socks5之间使用`,`或`换行`作间隔) | [Video](https://www.youtube.com/watch?v=s91zjpw3-P8&t=826s) | | GO2SOCKS5 | `blog.cmliussss.com`,`*.ip111.cn`,`*google.com` |❌| 设置`SOCKS5`变量之后,可设置强制使用socks5访问名单(`*`可作为通配符,`换行`作多元素间隔) || | ADD | `icook.tw:2053#官方优选域名` |❌| 本地优选TLS域名/优选IP(支持多元素之间`,`或`换行`作间隔) || @@ -158,6 +161,12 @@ Telegram交流群:[@CMLiussss](https://t.me/CMLiussss),**感谢[Alice Networ | CFKEY | `c6a944b5c956b6c18c2352880952bced8b85e` |❌| CF账户Global API Key(与`CFEMAIL`都填上后, 订阅信息将显示请求使用量, 小白别用) | | | CFPORTS | `2053`,`2096`,`8443` |❌| CF账户标准端口列表 | | +**注意: 填入`KEY`后将不再启用`UUID`!请二选一使用!!!** +1. 填入`KEY`后,永久订阅地址为`https://[YOUR-URL]/[YOUR-KEY]`; +2. 填入`KEY`后,临时订阅地址为`https://[YOUR-URL]/[YOUR-UUID]`; +3. **动态UUID**的订阅使用时间为**1**个`TIME`有效时间周期; +4. **动态UUID**的节点使用时间为**2**个`TIME`有效时间周期(也就是动态UUID失效的了,节点也可继续使用一个周期,只是无法继续更新订阅); + **注意: 填入`SOCKS5`后将不再启用`PROXYIP`!请二选一使用!!!** **注意: 填入`SUB`后将不再启用`ADD*`类变量生成的订阅内容!请二选一使用!!!** diff --git a/_worker.js b/_worker.js index 9312dd8..5f5e5de 100644 --- a/_worker.js +++ b/_worker.js @@ -77,6 +77,9 @@ let proxyhosts = [];//本地代理域名池 let proxyhostsURL = 'https://raw.githubusercontent.com/cmliu/CFcdnVmess2sub/main/proxyhosts';//在线代理域名池URL let RproxyIP = 'false'; let httpsPorts = ["2053","2083","2087","2096","8443"]; +let effectiveTime = 7;//有效时间 单位:天 +let updateTime = 3;//更新时间 +let userIDLow; export default { /** * @param {import("@cloudflare/workers-types").Request} request @@ -97,7 +100,14 @@ export default { fakeUserID = fakeUserIDMD5.slice(0, 8) + "-" + fakeUserIDMD5.slice(8, 12) + "-" + fakeUserIDMD5.slice(12, 16) + "-" + fakeUserIDMD5.slice(16, 20) + "-" + fakeUserIDMD5.slice(20); fakeHostName = fakeUserIDMD5.slice(6, 9) + "." + fakeUserIDMD5.slice(13, 19); //console.log(`虚假UUID: ${fakeUserID}`); // 打印fakeID - + if (env.KEY) { + const userIDs = await generateDynamicUUID(env.KEY); + userID = userIDs[0]; + userIDLow = userIDs[1]; + //console.log(`启用动态UUID\n秘钥KEY: ${env.KEY}\nUUIDNow: ${userID}\nUUIDLow: ${userIDLow}`); + effectiveTime = env.TIME || effectiveTime; + updateTime = env.UPTIME || updateTime; + } proxyIP = env.PROXYIP || proxyIP; proxyIPs = await ADD(proxyIP); proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; @@ -146,9 +156,8 @@ export default { FileName = env.SUBNAME || FileName; if (url.searchParams.has('notls')) noTLS = 'true'; if (!upgradeHeader || upgradeHeader !== 'websocket') { - // const url = new URL(request.url); - switch (url.pathname.toLowerCase()) { - case '/': + const 路径 = url.pathname.toLowerCase(); + if (路径 == '/') { if (env.URL302) return Response.redirect(env.URL302, 302); else if (env.URL) return await proxyURL(env.URL, url); else return new Response(JSON.stringify(request.cf, null, 4), { @@ -157,12 +166,12 @@ export default { 'content-type': 'application/json', }, }); - case `/${fakeUserID}`: - const fakeConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, 'CF-Workers-SUB', RproxyIP, url); + } else if (路径 == `/${fakeUserID}`) { + const fakeConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, 'CF-Workers-SUB', RproxyIP, url, env); return new Response(`${fakeConfig}`, { status: 200 }); - case `/${userID}`: { + } else if (路径 == `/${env.KEY}` || 路径 == `/${userID}`) { await sendMessage(`#获取订阅 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${UA}\n域名: ${url.hostname}\n入口: ${url.pathname + url.search}`); - const vlessConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, UA, RproxyIP, url); + const vlessConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, UA, RproxyIP, url, env); const now = Date.now(); //const timestamp = Math.floor(now / 1000); const today = new Date(now); @@ -208,8 +217,7 @@ export default { } }); } - } - default: + } else { if (env.URL302) return Response.redirect(env.URL302, 302); else if (env.URL) return await proxyURL(env.URL, url); else return new Response('不用怀疑!你UUID就是错的!!!', { status: 404 }); @@ -243,6 +251,7 @@ export default { } else { enableSocks = false; } + return await vlessOverWSHandler(request); } } catch (err) { @@ -419,7 +428,7 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR } else { // 否则,尝试使用预设的代理 IP(如果有)或原始地址重试连接 if (!proxyIP || proxyIP == '') { - proxyIP = atob('cHJveHlpcC5meHhrLmRlZHluLmlv'); + proxyIP = atob('cHJveHlpcC50cDEuY21saXVzc3NzLmNvbQ=='); } else if (proxyIP.includes(']:')) { portRemote = proxyIP.split(']:')[1] || portRemote; proxyIP = proxyIP.split(']:')[0] || proxyIP; @@ -427,6 +436,7 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR portRemote = proxyIP.split(':')[1] || portRemote; proxyIP = proxyIP.split(':')[0] || proxyIP; } + if (proxyIP.includes('.tp')) portRemote = proxyIP.split('.tp')[1].split('.')[0] || portRemote; tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote); } // 无论重试是否成功,都要关闭 WebSocket(可能是为了重新建立连接) @@ -560,9 +570,15 @@ function processVlessHeader(vlessBuffer, userID) { let isUDP = false; // 验证用户 ID(接下来的 16 个字节) - if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) { - isValidUser = true; + function isUserIDValid(userID, userIDLow, buffer) { + const userIDArray = new Uint8Array(buffer.slice(1, 17)); + const userIDString = stringify(userIDArray); + return userIDString === userID || userIDString === userIDLow; } + + // 使用函数验证 + isValidUser = isUserIDValid(userID, userIDLow, vlessBuffer); + // 如果用户 ID 无效,返回错误 if (!isValidUser) { return { @@ -1251,17 +1267,18 @@ function checkSUB(host) { if ((!sub || sub == '') && (addresses.length + addressesapi.length + addressesnotls.length + addressesnotlsapi.length + addressescsv.length) == 0){ addresses = [ 'Join.my.Telegram.channel.CMLiussss.to.unlock.more.premium.nodes.cf.090227.xyz#加入我的频道t.me/CMLiussss解锁更多优选节点', + '127.0.0.1:1234#CFnat', 'visa.cn:443', - 'www.visa.com:8443', - 'cis.visa.com:2053', - 'africa.visa.com:2083', - 'www.visa.com.sg:2087', - 'www.visaeurope.at:2096', - 'www.visa.com.mt:8443', - 'qa.visamiddleeast.com', + 'singapore.com:8443', + 'japan.com:2053', + 'brazil.com:2083', + 'russia.com:2087', + 'www.gov.ua:2096', + 'www.gco.gov.qa:8443', + 'www.gov.se', 'time.is', 'www.wto.org:8443', - 'chatgpt.com:2087', + 'fbi.gov:2087', 'icook.hk', //'104.17.0.0#IPv4', '[2606:4700::]#IPv6' @@ -1269,9 +1286,9 @@ function checkSUB(host) { if (host.includes(".workers.dev")) addressesnotls = [ 'usa.visa.com:2095', 'myanmar.visa.com:8080', - 'www.visa.com.tw:8880', + 'dynadot.com:8880', 'www.visaeurope.ch:2052', - 'www.visa.com.br:2082', + 'shopify.com:2082', 'www.visasoutheasteurope.com:2086' ]; } @@ -1329,7 +1346,8 @@ let subParams = ['sub','base64','b64','clash','singbox','sb']; * @param {string} UA * @returns {Promise} */ -async function getVLESSConfig(userID, hostName, sub, UA, RproxyIP, _url) { +async function getVLESSConfig(userID, hostName, sub, UA, RproxyIP, _url, env) { + const uuid = (_url.pathname == `/${env.KEY}`) ? env.KEY : userID; checkSUB(hostName); const userAgent = UA.toLowerCase(); const Config = 配置信息(userID , hostName); @@ -1373,7 +1391,7 @@ async function getVLESSConfig(userID, hostName, sub, UA, RproxyIP, _url) { else socks5List += `\n ${go2Socks5s.join('\n ')}\n`; } - let 订阅器 = ''; + let 订阅器 = '\n'; if (!sub || sub == '') { if (enableSocks) 订阅器 += `CFCDN(访问方式): Socks5\n ${newSocks5s.join('\n ')}\n${socks5List}`; else if (proxyIP && proxyIP != '') 订阅器 += `CFCDN(访问方式): ProxyIP\n ${proxyIPs.join('\n ')}\n`; @@ -1392,36 +1410,36 @@ async function getVLESSConfig(userID, hostName, sub, UA, RproxyIP, _url) { 订阅器 += `\nSUB(优选订阅生成器): ${sub}`; } + if (env.KEY && _url.pathname !== `/${env.KEY}`) 订阅器 = ''; + else 订阅器 += `\nSUBAPI(订阅转换后端): ${subProtocol}://${subconverter}\nSUBCONFIG(订阅转换配置文件): ${subconfig}`; + const 动态UUID = (uuid != userID) ? `TOKEN: ${uuid}\nUUIDNow: ${userID}\nUUIDLow: ${userIDLow}\nTIME(动态UUID有效时间): ${effectiveTime} 天\nUPTIME(动态UUID更新时间): ${updateTime} 时(北京时间)\n\n` : ""; return ` ################################################################ Subscribe / sub 订阅地址, 支持 Base64、clash-meta、sing-box 订阅格式 --------------------------------------------------------------- 快速自适应订阅地址: -https://${proxyhost}${hostName}/${userID} -https://${proxyhost}${hostName}/${userID}?sub +https://${proxyhost}${hostName}/${uuid} +https://${proxyhost}${hostName}/${uuid}?sub Base64订阅地址: -https://${proxyhost}${hostName}/${userID}?b64 -https://${proxyhost}${hostName}/${userID}?base64 +https://${proxyhost}${hostName}/${uuid}?b64 +https://${proxyhost}${hostName}/${uuid}?base64 clash订阅地址: -https://${proxyhost}${hostName}/${userID}?clash +https://${proxyhost}${hostName}/${uuid}?clash singbox订阅地址: -https://${proxyhost}${hostName}/${userID}?sb -https://${proxyhost}${hostName}/${userID}?singbox +https://${proxyhost}${hostName}/${uuid}?sb +https://${proxyhost}${hostName}/${uuid}?singbox --------------------------------------------------------------- ################################################################ ${FileName} 配置信息 --------------------------------------------------------------- -HOST: ${hostName} +${动态UUID}HOST: ${hostName} UUID: ${userID} FKID: ${fakeUserID} UA: ${UA} - ${订阅器} -SUBAPI(订阅转换后端): ${subProtocol}://${subconverter} -SUBCONFIG(订阅转换配置文件): ${subconfig} --------------------------------------------------------------- ################################################################ v2ray @@ -1903,4 +1921,44 @@ async function sendMessage(type, ip, add_data = "") { function isValidIPv4(address) { const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return ipv4Regex.test(address); +} + +function generateDynamicUUID(key) { + // 获取当前时间是当年的第几周(以北京时间凌晨3点为界) + function getWeekOfYear() { + const now = new Date(); + const timezoneOffset = 8; // 北京时间相对于UTC的时区偏移+8小时 + const adjustedNow = new Date(now.getTime() + timezoneOffset * 60 * 60 * 1000); + const start = new Date(2007, 6, 7, 3, 0, 0); // 固定起始日期为2007年7月7日的凌晨3点 + + // 计算当前时间与今年1月1日凌晨3点的差距 + const diff = adjustedNow - start; + + // 一周的毫秒数(7天) + const oneWeek = 1000 * 60 * 60 * 24 * effectiveTime; + + // 返回当前是第几周,向上取整 + return [Math.ceil(diff / oneWeek), Math.ceil((diff / oneWeek)- 1)]; + } + + const passwdTimes = getWeekOfYear(); // 获取当前周数并存储 + const passwdTime = passwdTimes[0]; + const passwdTimeLow = passwdTimes[1]; + + // 生成 UUID 的辅助函数 + function generateUUID(baseString) { + const hashBuffer = new TextEncoder().encode(baseString); + return crypto.subtle.digest('SHA-256', hashBuffer).then((hash) => { + const hashArray = Array.from(new Uint8Array(hash)); + const hexHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + let uuid = hexHash.substr(0, 8) + '-' + hexHash.substr(8, 4) + '-4' + hexHash.substr(13, 3) + '-' + (parseInt(hexHash.substr(16, 2), 16) & 0x3f | 0x80).toString(16) + hexHash.substr(18, 2) + '-' + hexHash.substr(20, 12); + return uuid; + }); + } + + // 生成两个 UUID + const currentUUIDPromise = generateUUID(key + passwdTime); + const previousUUIDPromise = generateUUID(key + passwdTimeLow); + + return Promise.all([currentUUIDPromise, previousUUIDPromise]); } \ No newline at end of file