diff --git a/worker.js b/worker.js index 57c667a..6b20285 100644 --- a/worker.js +++ b/worker.js @@ -1,278 +1,276 @@ // Developed by Surfboardv2ray, https://github.com/Surfboardv2ray/Trojan-worker -// Version 1.4 +// Version 1.4.1 // Tips: Change your subLinks accordingly. Note that only ws+tls+443 configs will work. // Your subscription link will be: 'https://{your_worker_address}.workers.dev/sub/{your_clean_ip}' // To get xxx number of configs, use '?n=xxx' at the end of your subscription link, for instance: // 'https://{your_worker_address}.workers.dev/sub/{your_clean_ip}?n=50' will return only 50 configs, randomly. const subLinks = [ - 'https://raw.githubusercontent.com/Surfboardv2ray/Proxy-sorter/main/ws_tls/proxies/wstls', - 'https://raw.githubusercontent.com/Surfboardv2ray/TGParse/refs/heads/main/configtg.txt', - 'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/refs/heads/main/protocols/trojan', - 'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/refs/heads/main/protocols/vmess', - 'https://raw.githubusercontent.com/MhdiTaheri/V2rayCollector_Py/refs/heads/main/sub/Mix/mix.txt', - 'https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.txt', -]; - -export default { - async fetch(request) { - let url = new URL(request.url); - let pathSegments = url.pathname.split('/').filter(segment => segment !== ''); - let realhostname = pathSegments[0] || ''; - let realpathname = pathSegments[1] || ''; - - if (url.pathname === '/') { - // Return the HTML content - return new Response(` - - - - - - - Trojan-worker - - - -
-

Developed by Surfboardv2ray

-

https://github.com/Surfboardv2ray/Trojan-worker

-

Your Subscription link will be:

- - -
- - - - `, { - headers: { 'Content-Type': 'text/html' }, - }); - } - - let trojanPaths = new Set(); - let vlessPaths = new Set(); - let vmessPaths = new Set(); - - if (url.pathname.startsWith('/sub')) { - let newConfigs = ''; - - for (let subLink of subLinks) { - try { - let resp = await fetch(subLink); - if (!resp.ok) continue; - let subConfigs = await resp.text(); - let isBase64Encoded = false; - - try { atob(subConfigs); isBase64Encoded = true; } catch (e) { isBase64Encoded = false; } - if (isBase64Encoded) subConfigs = atob(subConfigs); - - subConfigs = subConfigs.split(/\r?\n/); - - for (let subConfig of subConfigs) { - subConfig = subConfig.trim(); - if (subConfig === '') continue; - + 'https://raw.githubusercontent.com/Surfboardv2ray/Proxy-sorter/main/ws_tls/proxies/wstls', + 'https://raw.githubusercontent.com/Surfboardv2ray/TGParse/refs/heads/main/configtg.txt', + 'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/refs/heads/main/protocols/trojan', + 'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/refs/heads/main/protocols/vmess', + 'https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.txt', + ]; + + export default { + async fetch(request) { + let url = new URL(request.url); + let pathSegments = url.pathname.split('/').filter(segment => segment !== ''); + let realhostname = pathSegments[0] || ''; + let realpathname = pathSegments[1] || ''; + + if (url.pathname === '/') { + return new Response(` + + + + + + + Trojan-worker + + + +
+

Developed by Surfboardv2ray

+

https://github.com/Surfboardv2ray/Trojan-worker

+

Your Subscription link will be:

+ + +
+ + + + `, { + headers: { 'Content-Type': 'text/html' }, + }); + } + + let trojanPaths = new Set(); + let vlessPaths = new Set(); + let vmessPaths = new Set(); + + if (url.pathname.startsWith('/sub')) { + let newConfigs = ''; + + for (let subLink of subLinks) { try { - if (subConfig.startsWith('vmess://')) { - let vmessData = subConfig.replace('vmess://', ''); - vmessData = atob(vmessData); - let vmessConfig = JSON.parse(vmessData); - - if (vmessConfig.sni && !isIp(vmessConfig.sni) && vmessConfig.net === 'ws' && vmessConfig.port === 443) { - if (shouldSkipHost(vmessConfig.sni)) continue; - - let configNew = { - v: '2', - ps: `Node-${vmessConfig.sni}`, - add: realpathname === '' ? url.hostname : realpathname, - port: vmessConfig.port, - id: vmessConfig.id, - net: vmessConfig.net, - type: 'ws', - host: url.hostname, - path: `/${vmessConfig.sni}${vmessConfig.path}`, - tls: vmessConfig.tls, - sni: url.hostname, - aid: '0', - scy: 'auto', - fp: 'chrome', - alpn: 'http/1.1', - }; - - let fullPath = configNew.path; - if (!vmessPaths.has(fullPath)) { - vmessPaths.add(fullPath); - let encodedVmess = 'vmess://' + btoa(JSON.stringify(configNew)); - newConfigs += encodedVmess + '\n'; - } - } - } else if (subConfig.startsWith('vless://')) { - let vlessParts = subConfig.replace('vless://', '').split('@'); - if (vlessParts.length !== 2) continue; - - let uuid = vlessParts[0]; - let remainingParts = vlessParts[1].split('?'); - if (remainingParts.length !== 2) continue; - - let [ipPort, params] = remainingParts; - let [ip, port] = ipPort.split(':'); - if (!port) continue; - - let queryParams = new URLSearchParams(params); - let security = queryParams.get('security'); - let sni = queryParams.get('sni'); - let type = queryParams.get('type'); - - if (sni && !isIp(sni) && security === 'tls' && port === '443' && type === 'ws') { - if (shouldSkipHost(sni)) continue; - - let newPath = `/${sni}${queryParams.get('path') || ''}`; - if (!vlessPaths.has(newPath)) { - vlessPaths.add(newPath); - let newVlessConfig = `vless://${uuid}@${realpathname === '' ? url.hostname : realpathname}:${port}?encryption=none&security=${security}&sni=${url.hostname}&alpn=http/1.1&fp=chrome&allowInsecure=1&type=ws&host=${url.hostname}&path=${newPath}#Node-${sni}`; - newConfigs += newVlessConfig + '\n'; - } - } - } else if (subConfig.startsWith('trojan://')) { - let lastHashIndex = subConfig.lastIndexOf('#'); - let configWithoutRemark = subConfig; - let remark = ''; - if (lastHashIndex !== -1) { - configWithoutRemark = subConfig.substring(0, lastHashIndex); - remark = subConfig.substring(lastHashIndex + 1); - } - - let trojanURL = configWithoutRemark.replace('trojan://', ''); - let [passwordAndHost, params] = trojanURL.split('?'); - if (!params) continue; - - let [password, hostAndPort] = passwordAndHost.split('@'); - if (!hostAndPort) continue; - - let [ip, port] = hostAndPort.split(':'); - if (!port) continue; - - let queryParams = new URLSearchParams(params); - let security = queryParams.get('security'); - let sni = queryParams.get('sni'); - let type = queryParams.get('type'); - - if (sni && !isIp(sni) && security === 'tls' && port === '443' && type === 'ws') { - if (shouldSkipHost(sni)) continue; - - let newPath = `/${sni}${decodeURIComponent(queryParams.get('path') || '')}`; - if (!trojanPaths.has(newPath)) { - trojanPaths.add(newPath); - let newTrojanConfig = `trojan://${password}@${realpathname === '' ? url.hostname : realpathname}:${port}?security=${security}&sni=${url.hostname}&alpn=http/1.1&fp=chrome&allowInsecure=1&type=ws&host=${url.hostname}&path=${encodeURIComponent(newPath)}#${remark ? encodeURIComponent(remark) : `Node-${sni}`}`; - newConfigs += newTrojanConfig + '\n'; + let resp = await fetch(subLink); + if (!resp.ok) continue; + let subConfigs = await resp.text(); + let isBase64Encoded = false; + + try { atob(subConfigs); isBase64Encoded = true; } catch (e) { isBase64Encoded = false; } + if (isBase64Encoded) subConfigs = atob(subConfigs); + + subConfigs = subConfigs.split(/\r?\n/); + + for (let subConfig of subConfigs) { + subConfig = subConfig.trim(); + if (subConfig === '') continue; + + try { + if (subConfig.startsWith('vmess://')) { + let vmessData = subConfig.replace('vmess://', ''); + vmessData = atob(vmessData); + let vmessConfig = JSON.parse(vmessData); + + if (vmessConfig.sni && !isIp(vmessConfig.sni) && vmessConfig.net === 'ws' && vmessConfig.port === 443) { + if (shouldSkipHost(vmessConfig.sni)) continue; + + let configNew = { + v: '2', + ps: `Node-${vmessConfig.sni}`, + add: realpathname === '' ? url.hostname : realpathname, + port: vmessConfig.port, + id: vmessConfig.id, + net: vmessConfig.net, + type: 'ws', + host: url.hostname, + path: `/${vmessConfig.sni}${vmessConfig.path}`, + tls: vmessConfig.tls, + sni: url.hostname, + aid: '0', + scy: 'auto', + fp: 'chrome', + alpn: 'http/1.1', + }; + + let fullPath = configNew.path; + if (!vmessPaths.has(fullPath)) { + vmessPaths.add(fullPath); + let encodedVmess = 'vmess://' + btoa(JSON.stringify(configNew)); + newConfigs += encodedVmess + '\n'; + } + } + } else if (subConfig.startsWith('vless://')) { + let vlessParts = subConfig.replace('vless://', '').split('@'); + if (vlessParts.length !== 2) continue; + + let uuid = vlessParts[0]; + let remainingParts = vlessParts[1].split('?'); + if (remainingParts.length !== 2) continue; + + let [ipPort, params] = remainingParts; + let [ip, port] = ipPort.split(':'); + if (!port) continue; + + let queryParams = new URLSearchParams(params); + let security = queryParams.get('security'); + let sni = queryParams.get('sni'); + let type = queryParams.get('type'); + if (sni && !isIp(sni) && security === 'tls' && port === '443' && type === 'ws') { + if (shouldSkipHost(sni)) continue; + + let newPath = `/${sni}${decodeURIComponent(queryParams.get('path') || '')}`; + if (!vlessPaths.has(newPath)) { + vlessPaths.add(newPath); + let newVlessConfig = `vless://${uuid}@${realpathname === '' ? url.hostname : realpathname}:${port}?encryption=none&security=${security}&sni=${url.hostname}&alpn=http/1.1&fp=chrome&allowInsecure=1&type=ws&host=${url.hostname}&path=${newPath}#Node-${sni}`; + newConfigs += newVlessConfig + '\n'; + } + } + } + else if (subConfig.startsWith('trojan://')) { + let lastHashIndex = subConfig.lastIndexOf('#'); + let configWithoutRemark = subConfig; + let remark = ''; + if (lastHashIndex !== -1) { + configWithoutRemark = subConfig.substring(0, lastHashIndex); + remark = subConfig.substring(lastHashIndex + 1); + } + + let trojanURL = configWithoutRemark.replace('trojan://', ''); + let [passwordAndHost, params] = trojanURL.split('?'); + if (!params) continue; + + let [password, hostAndPort] = passwordAndHost.split('@'); + if (!hostAndPort) continue; + + let [ip, port] = hostAndPort.split(':'); + if (!port) continue; + + let queryParams = new URLSearchParams(params); + let security = queryParams.get('security'); + let sni = queryParams.get('sni'); + let type = queryParams.get('type'); + + if (sni && !isIp(sni) && security === 'tls' && port === '443' && type === 'ws') { + if (shouldSkipHost(sni)) continue; + + let newPath = `/${sni}${decodeURIComponent(queryParams.get('path') || '')}`; + if (!trojanPaths.has(newPath)) { + trojanPaths.add(newPath); + let newTrojanConfig = `trojan://${password}@${realpathname === '' ? url.hostname : realpathname}:${port}?security=${security}&sni=${url.hostname}&alpn=http/1.1&fp=chrome&allowInsecure=1&type=ws&host=${url.hostname}&path=${encodeURIComponent(newPath)}#Node-${sni}`; + newConfigs += newTrojanConfig + '\n'; + } + } } + } catch (error) { + continue; } } } catch (error) { continue; } } - } catch (error) { - continue; + + const nParam = url.searchParams.get('n'); + let responseConfigs = newConfigs.trim().split('\n').filter(line => line !== ''); + + if (nParam && !isNaN(nParam) && parseInt(nParam) > 0) { + const n = Math.min(parseInt(nParam), responseConfigs.length); + const randomConfigs = getRandomItems(responseConfigs, n); + return new Response(randomConfigs.join('\n'), { + headers: { 'Content-Type': 'text/plain' }, + }); + } + + return new Response(newConfigs, { + headers: { 'Content-Type': 'text/plain' }, + }); + } else { + const url = new URL(request.url); + const splitted = url.pathname.replace(/^\/*/, '').split('/'); + const address = splitted[0]; + url.pathname = splitted.slice(1).join('/'); + url.hostname = address; + url.protocol = 'https'; + return fetch(new Request(url, request)); } + }, + }; + + function shouldSkipHost(host) { + return host && (host.toLowerCase().includes("workers.dev") || host.toLowerCase().includes("pages.dev")); + } + + function getRandomItems(array, count) { + const shuffled = array.sort(() => 0.5 - Math.random()); + return shuffled.slice(0, count); + } + + function isIp(ipstr) { + try { + if (!ipstr) return false; + const ipv4Regex = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/; + if (!ipv4Regex.test(ipstr)) return false; + let segments = ipstr.split('.'); + if (segments[3] === '0') return false; + return true; + } catch (e) { + return false; } - - const nParam = url.searchParams.get('n'); - let responseConfigs = newConfigs.trim().split('\n').filter(line => line !== ''); - - if (nParam && !isNaN(nParam) && parseInt(nParam) > 0) { - const n = Math.min(parseInt(nParam), responseConfigs.length); - const randomConfigs = getRandomItems(responseConfigs, n); - return new Response(randomConfigs.join('\n'), { - headers: { 'Content-Type': 'text/plain' }, - }); - } - - return new Response(newConfigs, { - headers: { 'Content-Type': 'text/plain' }, - }); - } else { - const url = new URL(request.url); - const splitted = url.pathname.replace(/^\/*/, '').split('/'); - const address = splitted[0]; - url.pathname = splitted.slice(1).join('/'); - url.hostname = address; - url.protocol = 'https'; - return fetch(new Request(url, request)); } - }, -}; - -function shouldSkipHost(host) { - return host && (host.toLowerCase().includes("workers.dev") || host.toLowerCase().includes("pages.dev")); -} - -function getRandomItems(array, count) { - const shuffled = array.sort(() => 0.5 - Math.random()); - return shuffled.slice(0, count); -} - -function isIp(ipstr) { - try { - if (!ipstr) return false; - const ipv4Regex = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/; - if (!ipv4Regex.test(ipstr)) return false; - let segments = ipstr.split('.'); - if (segments[3] === '0') return false; - return true; - } catch (e) { - return false; - } -}