// 部署完成后在网址后面加上这个,获取订阅器默认节点,/auto let mytoken= ['auto'];//快速订阅访问入口, 留空则不启动快速订阅 // 设置优选地址,不带端口号默认443,TLS订阅生成 let addresses = [ 'icook.tw:2053#官方优选域名', 'cloudflare.cfgo.cc#优选官方线路', ]; // 设置优选地址api接口 let addressesapi = [ 'https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesapi.txt', //可参考内容格式 自行搭建。 //'https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesipv6api.txt', //IPv6优选内容格式 自行搭建。 ]; // 设置优选地址,不带端口号默认80,noTLS订阅生成 let addressesnotls = [ 'www.visa.com.sg#官方优选域名', 'www.wto.org:8080#官方优选域名', 'www.who.int:8880#官方优选域名', ]; // 设置优选noTLS地址api接口 let addressesnotlsapi = [ 'https://raw.githubusercontent.com/cmliu/CFcdnVmess2sub/main/addressesapi.txt', //可参考内容格式 自行搭建。 ]; let DLS = 8;//速度下限 let addressescsv = [ //'https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressescsv.csv', //iptest测速结果文件。 ]; let subconverter = "apiurl.v1.mk"; //在线订阅转换后端,目前使用肥羊的订阅转换功能。支持自建psub 可自行搭建https://github.com/bulianglin/psub let subconfig = "https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_Full_MultiMode.ini"; //订阅转换配置文件 let noTLS = false; //改为 true , 将不做域名判断 始终返回noTLS节点 let link = ''; let edgetunnel = 'ed'; let RproxyIP = 'false'; let proxyIPs = [ 'proxyip.aliyun.fxxk.dedyn.io', 'proxyip.multacom.fxxk.dedyn.io', 'proxyip.vultr.fxxk.dedyn.io', ]; let CMproxyIPs = [ //{ proxyIP: "proxyip.fxxk.dedyn.io", type: "HK" }, ]; let BotToken =''; let ChatID =''; let proxyhosts = [//本地代理域名池 //'ppfv2tl9veojd-maillazy.pages.dev', ]; let proxyhostsURL = 'https://raw.githubusercontent.com/cmliu/CFcdnVmess2sub/main/proxyhosts';//在线代理域名池URL let EndPS = '';//节点名备注内容 let FileName = 'WorkerVless2sub'; let SUBUpdateTime = 6; let total = 99;//PB //let timestamp = now; let timestamp = 4102329600000;//2099-12-31 const regex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[.*\]):?(\d+)?#?(.*)?$/; async function sendMessage(type, ip, add_data = "") { if ( BotToken !== '' && ChatID !== ''){ let msg = ""; const response = await fetch(`http://ip-api.com/json/${ip}?lang=zh-CN`); if (response.status == 200) { const ipInfo = await response.json(); msg = `${type}\nIP: ${ip}\n国家: ${ipInfo.country}\n城市: ${ipInfo.city}\n组织: ${ipInfo.org}\nASN: ${ipInfo.as}\n${add_data}`; } else { msg = `${type}\nIP: ${ip}\n${add_data}`; } let url = "https://api.telegram.org/bot"+ BotToken +"/sendMessage?chat_id=" + ChatID + "&parse_mode=HTML&text=" + encodeURIComponent(msg); return fetch(url, { method: 'get', headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;', 'Accept-Encoding': 'gzip, deflate, br', 'User-Agent': 'Mozilla/5.0 Chrome/90.0.4430.72' } }); } } async function getAddressesapi(api) { if (!api || api.length === 0) { return []; } let newapi = ""; try { const responses = await Promise.allSettled(api.map(apiUrl => fetch(apiUrl,{ method: 'get', headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;', 'User-Agent': 'cmliu/WorkerVless2sub' } }).then(response => response.ok ? response.text() : Promise.reject()))); for (const response of responses) { if (response.status === 'fulfilled') { const content = await response.value; newapi += content + '\n'; } } } catch (error) { console.error(error); } const newAddressesapi = await ADD(newapi); /* let newAddressesapi = []; for (const apiUrl of api) { try { const response = await fetch(apiUrl); if (!response.ok) { console.error('获取地址时出错:', response.status, response.statusText); continue; } const text = await response.text(); let lines; if (text.includes('\r\n')){ lines = text.split('\r\n'); } else { lines = text.split('\n'); } //const regex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?(#.*)?$/; const apiAddresses = lines.map(line => { const match = line.match(regex); return match ? match[0] : null; }).filter(Boolean); newAddressesapi = newAddressesapi.concat(apiAddresses); } catch (error) { console.error('获取地址时出错:', error); continue; } } */ return newAddressesapi; } async function getAddressescsv(tls) { if (!addressescsv || addressescsv.length === 0) { return []; } let newAddressescsv = []; for (const csvUrl of addressescsv) { try { const response = await fetch(csvUrl); if (!response.ok) { console.error('获取CSV地址时出错:', response.status, response.statusText); continue; } const text = await response.text();// 使用正确的字符编码解析文本内容 let lines; if (text.includes('\r\n')){ lines = text.split('\r\n'); } else { lines = text.split('\n'); } // 检查CSV头部是否包含必需字段 const header = lines[0].split(','); const tlsIndex = header.indexOf('TLS'); const speedIndex = header.length - 1; // 最后一个字段 const ipAddressIndex = 0;// IP地址在 CSV 头部的位置 const portIndex = 1;// 端口在 CSV 头部的位置 const dataCenterIndex = tlsIndex + 1; // 数据中心是 TLS 的后一个字段 if (tlsIndex === -1) { console.error('CSV文件缺少必需的字段'); continue; } // 从第二行开始遍历CSV行 for (let i = 1; i < lines.length; i++) { const columns = lines[i].split(','); // 检查TLS是否为"TRUE"且速度大于DLS if (columns[tlsIndex].toUpperCase() === tls && parseFloat(columns[speedIndex]) > DLS) { const ipAddress = columns[ipAddressIndex]; const port = columns[portIndex]; const dataCenter = columns[dataCenterIndex]; const formattedAddress = `${ipAddress}:${port}#${dataCenter}`; newAddressescsv.push(formattedAddress); } } } catch (error) { console.error('获取CSV地址时出错:', error); continue; } } return newAddressescsv; } async function ADD(envadd) { var addtext = envadd.replace(/[ "'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 //console.log(addtext); if (addtext.charAt(0) == ',') addtext = addtext.slice(1); if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1); const add = addtext.split(','); //console.log(add); return add ; } let protocol; export default { async fetch (request, env) { if (env.TOKEN) mytoken = await ADD(env.TOKEN); //mytoken = env.TOKEN.split(',') || mytoken; BotToken = env.TGTOKEN || BotToken; ChatID = env.TGID || ChatID; subconverter = env.SUBAPI || subconverter; subconfig = env.SUBCONFIG || subconfig; FileName = env.SUBNAME || FileName; EndPS = env.PS || EndPS; const userAgentHeader = request.headers.get('User-Agent'); const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; const url = new URL(request.url); const format = url.searchParams.get('format') ? url.searchParams.get('format').toLowerCase() : "null"; let host = ""; let uuid = ""; let path = ""; let sni = ""; let UD = Math.floor(((timestamp - Date.now())/timestamp * 99 * 1099511627776 * 1024)/2); total = total * 1099511627776 * 1024; let expire= Math.floor(timestamp / 1000) ; link = env.LINK || link; const links = await ADD(link); link = links.join('\n'); if (env.ADD) addresses = await ADD(env.ADD); if (env.ADDAPI) addressesapi = await ADD(env.ADDAPI); if (env.ADDNOTLS) addressesnotls = await ADD(env.ADDNOTLS); if (env.ADDNOTLSAPI) addressesnotlsapi = await ADD(env.ADDNOTLSAPI); if (env.ADDCSV) addressescsv = await ADD(env.ADDCSV); DLS = env.DLS || DLS; /* console.log(` addresses: ${addresses} addressesapi: ${addressesapi} addressesnotls: ${addressesnotls} addressesnotlsapi: ${addressesnotlsapi} addressescsv: ${addressescsv} DLS: ${DLS} `); */ if (env.PROXYIP) proxyIPs = await ADD(env.PROXYIP); //console.log(proxyIPs); if (mytoken.length > 0 && mytoken.some(token => url.pathname.includes(token))) { host = "null"; if (env.HOST) { const hosts = await ADD(env.HOST); host = hosts[Math.floor(Math.random() * hosts.length)]; } uuid = env.UUID || "null"; path = env.PATH || "/?ed=2560"; sni = env.SNI || host; edgetunnel = env.ED || edgetunnel; RproxyIP = env.RPROXYIP || RproxyIP; if (host == "null" || uuid == "null" ){ let 空字段; if (host == "null" && uuid == "null") 空字段 = "HOST/UUID"; else if (host == "null") 空字段 = "HOST"; else if (uuid == "null") 空字段 = "UUID"; EndPS += ` 订阅器内置节点 ${空字段} 未设置!!!`; } const hasSos = url.searchParams.has('sos'); if (hasSos) { const hy2Url = "https://hy2sub.pages.dev"; try { const subconverterResponse = await fetch(hy2Url); if (!subconverterResponse.ok) { throw new Error(`Error fetching lzUrl: ${subconverterResponse.status} ${subconverterResponse.statusText}`); } const base64Text = await subconverterResponse.text(); link += '\n' + atob(base64Text); // 进行 Base64 解码 } catch (error) { // 错误处理 } } await sendMessage("#获取订阅", request.headers.get('CF-Connecting-IP'), `UA: ${userAgent}\n域名: ${url.hostname}\n入口: ${url.pathname + url.search}`); } else { host = url.searchParams.get('host'); uuid = url.searchParams.get('uuid'); path = url.searchParams.get('path'); sni = url.searchParams.get('sni') || host; edgetunnel = url.searchParams.get('edgetunnel') || edgetunnel; RproxyIP = url.searchParams.get('proxyip') || RproxyIP; if (!url.pathname.includes("/sub")) { const responseText = ` 路径必须包含 "/sub" The path must contain "/sub" مسیر باید شامل "/sub" باشد ${url.origin}/sub?host=[your host]&uuid=[your uuid]&path=[your path] https://github.com/cmliu/WorkerVless2sub `; return new Response(responseText, { status: 400, headers: { 'content-type': 'text/plain; charset=utf-8' }, }); } if (!host || !uuid) { const responseText = ` 缺少必填参数:host 和 uuid Missing required parameters: host and uuid پارامترهای ضروری وارد نشده: هاست و یوآی‌دی ${url.origin}/sub?host=[your host]&uuid=[your uuid]&path=[your path] https://github.com/cmliu/WorkerVless2sub `; return new Response(responseText, { status: 400, headers: { 'content-type': 'text/plain; charset=utf-8' }, }); } if (!path || path.trim() === '') { path = '/?ed=2560'; } else { // 如果第一个字符不是斜杠,则在前面添加一个斜杠 path = (path[0] === '/') ? path : '/' + path; } } noTLS = host.toLowerCase().includes('notls') || host.toLowerCase().includes('worker') || host.toLowerCase().includes('trycloudflare') || noTLS; if(env.NOTLS == 'true')noTLS = true; if (userAgent.includes('telegram') || userAgent.includes('twitter') || userAgent.includes('miaoko')) { return new Response('Hello World!'); } else if ((userAgent.includes('clash') || (format === 'clash' && !userAgent.includes('subconverter'))) && !userAgent.includes('nekobox')) { const subconverterUrl = `https://${subconverter}/sub?target=clash&url=${encodeURIComponent(request.url)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; try { const subconverterResponse = await fetch(subconverterUrl); if (!subconverterResponse.ok) { throw new Error(`Error fetching subconverterUrl: ${subconverterResponse.status} ${subconverterResponse.statusText}`); } const subconverterContent = await subconverterResponse.text(); return new Response(subconverterContent, { headers: { "Content-Disposition": `attachment; filename*=utf-8''${encodeURIComponent(FileName)}; filename=${FileName}`, "content-type": "text/plain; charset=utf-8", "Profile-Update-Interval": `${SUBUpdateTime}`, "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, }, }); } catch (error) { return new Response(`Error: ${error.message}`, { status: 500, headers: { 'content-type': 'text/plain; charset=utf-8' }, }); } } else if (userAgent.includes('sing-box') || userAgent.includes('singbox') || (format === 'singbox' && !userAgent.includes('subconverter'))){ const subconverterUrl = `https://${subconverter}/sub?target=singbox&url=${encodeURIComponent(request.url)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`; try { const subconverterResponse = await fetch(subconverterUrl); if (!subconverterResponse.ok) { throw new Error(`Error fetching subconverterUrl: ${subconverterResponse.status} ${subconverterResponse.statusText}`); } const subconverterContent = await subconverterResponse.text(); return new Response(subconverterContent, { headers: { "Content-Disposition": `attachment; filename*=utf-8''${encodeURIComponent(FileName)}; filename=${FileName}`, "content-type": "text/plain; charset=utf-8", "Profile-Update-Interval": `${SUBUpdateTime}`, "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, }, }); } catch (error) { return new Response(`Error: ${error.message}`, { status: 500, headers: { 'content-type': 'text/plain; charset=utf-8' }, }); } } else { if(host.includes('workers.dev') || host.includes('pages.dev')) { if (proxyhostsURL) { try { const response = await fetch(proxyhostsURL); if (!response.ok) { console.error('获取地址时出错:', response.status, response.statusText); return; // 如果有错误,直接返回 } const text = await response.text(); const lines = text.split('\n'); // 过滤掉空行或只包含空白字符的行 const nonEmptyLines = lines.filter(line => line.trim() !== ''); proxyhosts = proxyhosts.concat(nonEmptyLines); } catch (error) { console.error('获取地址时出错:', error); } } // 使用Set对象去重 proxyhosts = [...new Set(proxyhosts)]; } const newAddressesapi = await getAddressesapi(addressesapi); const newAddressescsv = await getAddressescsv('TRUE'); addresses = addresses.concat(newAddressesapi); addresses = addresses.concat(newAddressescsv); // 使用Set对象去重 const uniqueAddresses = [...new Set(addresses)]; let notlsresponseBody; if(noTLS == true){ const newAddressesnotlsapi = await getAddressesapi(addressesnotlsapi); const newAddressesnotlscsv = await getAddressescsv('FALSE'); addressesnotls = addressesnotls.concat(newAddressesnotlsapi); addressesnotls = addressesnotls.concat(newAddressesnotlscsv); const uniqueAddressesnotls = [...new Set(addressesnotls)]; notlsresponseBody = uniqueAddressesnotls.map(address => { let port = "80"; let addressid = address; const match = addressid.match(regex); if (!match) { if (address.includes(':') && address.includes('#')) { const parts = address.split(':'); address = parts[0]; const subParts = parts[1].split('#'); port = subParts[0]; addressid = subParts[1]; } else if (address.includes(':')) { const parts = address.split(':'); address = parts[0]; port = parts[1]; } else if (address.includes('#')) { const parts = address.split('#'); address = parts[0]; addressid = parts[1]; } if (addressid.includes(':')) { addressid = addressid.split(':')[0]; } } else { address = match[1]; port = match[2] || port; addressid = match[3] || address; } //console.log(address, port, addressid); if (edgetunnel.trim() === 'cmliu' && RproxyIP.trim() === 'true') { // 将addressid转换为小写 let lowerAddressid = addressid.toLowerCase(); // 初始化找到的proxyIP为null let foundProxyIP = null; // 遍历CMproxyIPs数组查找匹配项 for (let item of CMproxyIPs) { if (lowerAddressid.includes(item.type.toLowerCase())) { foundProxyIP = item.proxyIP; break; // 找到匹配项,跳出循环 } } if (foundProxyIP) { // 如果找到匹配的proxyIP,赋值给path path = `/proxyIP=${foundProxyIP}`; } else { // 如果没有找到匹配项,随机选择一个proxyIP const randomProxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; path = `/proxyIP=${randomProxyIP}`; } } const vlessLink = `vless://${uuid}@${address}:${port}?encryption=none&security=&type=ws&host=${host}&path=${encodeURIComponent(path)}#${encodeURIComponent(addressid + EndPS)}`; return vlessLink; }).join('\n'); } const responseBody = uniqueAddresses.map(address => { let port = "443"; let addressid = address; const match = addressid.match(regex); if (!match) { if (address.includes(':') && address.includes('#')) { const parts = address.split(':'); address = parts[0]; const subParts = parts[1].split('#'); port = subParts[0]; addressid = subParts[1]; } else if (address.includes(':')) { const parts = address.split(':'); address = parts[0]; port = parts[1]; } else if (address.includes('#')) { const parts = address.split('#'); address = parts[0]; addressid = parts[1]; } if (addressid.includes(':')) { addressid = addressid.split(':')[0]; } } else { address = match[1]; port = match[2] || port; addressid = match[3] || address; } //console.log(address, port, addressid); if (edgetunnel.trim() === 'cmliu' && RproxyIP.trim() === 'true') { // 将addressid转换为小写 let lowerAddressid = addressid.toLowerCase(); // 初始化找到的proxyIP为null let foundProxyIP = null; // 遍历CMproxyIPs数组查找匹配项 for (let item of CMproxyIPs) { if (lowerAddressid.includes(item.type.toLowerCase())) { foundProxyIP = item.proxyIP; break; // 找到匹配项,跳出循环 } } if (foundProxyIP) { // 如果找到匹配的proxyIP,赋值给path path = `/proxyIP=${foundProxyIP}`; } else { // 如果没有找到匹配项,随机选择一个proxyIP const randomProxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; path = `/proxyIP=${randomProxyIP}`; } } let 伪装域名 = host ; let 最终路径 = path ; let 节点备注 = EndPS ; if(proxyhosts && (host.includes('.workers.dev') || host.includes('pages.dev'))) { 最终路径 = `/${host}${path}`; 伪装域名 = proxyhosts[Math.floor(Math.random() * proxyhosts.length)]; 节点备注 = `${EndPS} 已启用临时域名中转服务,请尽快绑定自定义域!`; sni = 伪装域名; } const vlessLink = `vless://${uuid}@${address}:${port}?encryption=none&security=tls&sni=${sni}&fp=random&type=ws&host=${伪装域名}&path=${encodeURIComponent(最终路径)}#${encodeURIComponent(addressid + 节点备注)}`; return vlessLink; }).join('\n'); let combinedContent = responseBody; // 合并内容 if (link) { combinedContent += '\n' + link; console.log("link: " + link) } if (notlsresponseBody) { combinedContent += '\n' + notlsresponseBody; console.log("notlsresponseBody: " + notlsresponseBody); } const base64Response = btoa(combinedContent); // 重新进行 Base64 编码 const response = new Response(base64Response, { headers: { //"Content-Disposition": `attachment; filename*=utf-8''${encodeURIComponent(FileName)}; filename=${FileName}`, "content-type": "text/plain; charset=utf-8", "Profile-Update-Interval": `${SUBUpdateTime}`, "Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${total}; expire=${expire}`, }, }); return response; } } };