Update _worker.js

完全重写, 优化逻辑
This commit is contained in:
CMLiussss
2024-05-11 17:59:57 +08:00
committed by GitHub
parent a0d287d00e
commit 715a0b05ba

View File

@@ -15,9 +15,7 @@ https://sub.xf.free.hr/auto
https://hy2sub.pages.dev
`
//请将机场订阅链接填入上方
let urls = [];// https://subs.zeabur.app/clash , https://neko-warp.nloli.xyz/neko_warp.yaml
let urls = [];
let subconverter = "apiurl.v1.mk"; //在线订阅转换后端目前使用肥羊的订阅转换功能。支持自建psub 可自行搭建https://github.com/bulianglin/psub
let subconfig = "https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_MultiCountry.ini"; //订阅配置文件
@@ -37,169 +35,203 @@ export default {
MainData = env.LINK || MainData;
if(env.LINKSUB) urls = await ADD(env.LINKSUB);
let links = await ADD(MainData + '\n' + urls.join('\n'));
let link = "";
let linksub = "";
for (let x of links) {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
const timestamp = Math.ceil(currentDate.getTime() / 1000);
const fakeToken = await MD5MD5(`${mytoken}${timestamp}`);
//console.log(`${fakeUserID}\n${fakeHostName}`); // 打印fakeID
let 重新汇总所有链接 = await ADD(MainData + '\n' + urls.join('\n'));
let 自建节点 ="";
let 订阅链接 ="";
for (let x of 重新汇总所有链接) {
if (x.toLowerCase().startsWith('http')) {
linksub += x + '\n';
订阅链接 += x + '\n';
} else {
link += x + '\n';
自建节点 += x + '\n';
}
}
MainData = link;
urls = await ADD(linksub);
let sublinks = request.url ;
//console.log(MainData,urls,sublinks);
let warp = env.WARP ;
if(warp && warp != "") sublinks += '|' + (await ADD(warp)).join('|');
MainData = 自建节点;
urls = await ADD(订阅链接);
if ( !(token == mytoken || url.pathname == ("/"+ mytoken) || url.pathname.includes("/"+ mytoken + "?")) ) {
if ( TG == 1 && url.pathname !== "/" && url.pathname !== "/favicon.ico" ) await sendMessage("#异常访问", request.headers.get('CF-Connecting-IP'), `UA: ${userAgent}</tg-spoiler>\n域名: ${url.hostname}\n<tg-spoiler>入口: ${url.pathname + url.search}</tg-spoiler>`);
//首页改成一个nginx伪装页
return new Response(`
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
`, {
if ( !(token == mytoken || token == fakeToken || url.pathname == ("/"+ mytoken) || url.pathname.includes("/"+ mytoken + "?")) ) {
if ( TG == 1 && url.pathname !== "/" && url.pathname !== "/favicon.ico" ) await sendMessage(`#异常访问 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${userAgent}</tg-spoiler>\n域名: ${url.hostname}\n<tg-spoiler>入口: ${url.pathname + url.search}</tg-spoiler>`);
const envKey = env.URL302 ? 'URL302' : (env.URL ? 'URL' : null);
if (envKey) {
const URLs = await ADD(env[envKey]);
const URL = URLs[Math.floor(Math.random() * URLs.length)];
return envKey === 'URL302' ? Response.redirect(URL, 302) : fetch(new Request(URL, request));
}
return new Response(await nginx(), {
status: 200 ,
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
} else if ( TG == 1 || !userAgent.includes('subconverter') || !userAgent.includes('null')){
await sendMessage("#获取订阅", request.headers.get('CF-Connecting-IP'), `UA: ${userAgentHeader}</tg-spoiler>\n域名: ${url.hostname}\n<tg-spoiler>入口: ${url.pathname + url.search}</tg-spoiler>`);
}
let req_data = MainData;
// 创建一个AbortController对象用于控制fetch请求的取消
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(); // 取消所有请求
}, 1618); // 1.618秒后触发
try {
const responses = await Promise.allSettled(urls.map(url =>
fetch(url, {
method: 'get',
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;',
'User-Agent': `v2rayn/6.42 cmliu/CF-Workers-SUB ${userAgentHeader}`
},
signal: controller.signal // 将AbortController的信号量添加到fetch请求中以便于需要时可以取消请求
}).then(response => {
if (response.ok) {
return response.text().then(content => {
// 这里可以顺便做内容检查
if (content.includes('dns') && content.includes('proxies') && content.includes('proxy-groups') && content.includes('rules')) {
//console.log("clashsub: " + url);
sublinks += "|" + url;
} else if (content.includes('dns') && content.includes('outbounds') && content.includes('inbounds')){
//console.log("singboxsub: " + url);
sublinks += "|" + url;
} else {
return content; // 保证链式调用中的下一个then可以接收到文本内容
}
});
} else {
return ""; // 如果response.ok为false返回空字符串
}
})
));
//console.log(responses);
for (const response of responses) {
if (response.status === 'fulfilled') {
const content = await response.value;
req_data += base64Decode(content) + '\n';
}
}
} catch (error) {
//console.error(error);
} finally {
// 无论成功或失败,最后都清除设置的超时定时器
clearTimeout(timeout);
}
//修复中文错误
const utf8Encoder = new TextEncoder();
const encodedData = utf8Encoder.encode(req_data);
const text = String.fromCharCode.apply(null, encodedData);
//去重
const uniqueLines = new Set(text.split('\n'));
const result = [...uniqueLines].join('\n');
//console.log(result);
const base64Data = btoa(result);
//console.log(base64Data);
//console.log("自建节点: " + MainData,"订阅链接: " + urls,"转换链接: " + sublinks);
let target = "v2ray";
if (userAgent.includes('clash') && !userAgent.includes('nekobox')) {
target = "clash";
} else if (userAgent.includes('sing-box') || userAgent.includes('singbox')) {
target = "singbox";
} else {
return new Response(base64Data ,{
headers: {
"content-type": "text/plain; charset=utf-8",
"Profile-Update-Interval": `${SUBUpdateTime}`,
}
});
}
const subconverterUrl = `https://${subconverter}/sub?target=${target}&url=${encodeURIComponent(sublinks)}&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}`);
await sendMessage(`#获取订阅 ${FileName}`, request.headers.get('CF-Connecting-IP'), `UA: ${userAgentHeader}</tg-spoiler>\n域名: ${url.hostname}\n<tg-spoiler>入口: ${url.pathname + url.search}</tg-spoiler>`);
let 订阅格式 = 'base64';
if (userAgent.includes('null') || userAgent.includes('subconverter') || userAgent.includes('nekobox') || userAgent.includes(('CF-Workers-SUB').toLowerCase())){
订阅格式 = 'base64';
} else if (userAgent.includes('clash') || ( url.searchParams.has('clash') && !userAgent.includes('subconverter'))){
订阅格式 = 'clash';
} else if (userAgent.includes('sing-box') || userAgent.includes('singbox') || ( (url.searchParams.has('sb') || url.searchParams.has('singbox')) && !userAgent.includes('subconverter'))){
订阅格式 = 'singbox';
}
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}`,
}
});
} catch (error) {
return new Response(`Error: ${error.message}`, {
status: 500,
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
}
let subconverterUrl ;
let 订阅转换URL = `${url.origin}/${await MD5MD5(fakeToken)}?token=${fakeToken}`;
//console.log(订阅转换URL);
let req_data = MainData;
// 创建一个AbortController对象用于控制fetch请求的取消
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(); // 取消所有请求
}, 1618); // 1.618秒后触发
try {
const responses = await Promise.allSettled(urls.map(url =>
fetch(url, {
method: 'get',
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;',
'User-Agent': `v2rayn cmliu/CF-Workers-SUB ${userAgentHeader}`
},
signal: controller.signal // 将AbortController的信号量添加到fetch请求中以便于需要时可以取消请求
}).then(response => {
if (response.ok) {
return response.text().then(content => {
// 这里可以顺便做内容检查
if (content.includes('dns') && content.includes('proxies') && content.includes('proxy-groups') && content.includes('rules')) {
//console.log("clashsub: " + url);
订阅转换URL += "|" + url;
} else if (content.includes('dns') && content.includes('outbounds') && content.includes('inbounds')){
//console.log("singboxsub: " + url);
订阅转换URL += "|" + url;
} else {
return content; // 保证链式调用中的下一个then可以接收到文本内容
}
});
} else {
return ""; // 如果response.ok为false返回空字符串
}
})
));
//console.log(responses);
for (const response of responses) {
if (response.status === 'fulfilled') {
const content = await response.value;
req_data += base64Decode(content) + '\n';
}
}
} catch (error) {
//console.error(error);
} finally {
// 无论成功或失败,最后都清除设置的超时定时器
clearTimeout(timeout);
}
//修复中文错误
const utf8Encoder = new TextEncoder();
const encodedData = utf8Encoder.encode(req_data);
const text = String.fromCharCode.apply(null, encodedData);
//去重
const uniqueLines = new Set(text.split('\n'));
const result = [...uniqueLines].join('\n');
console.log(result);
const base64Data = btoa(result);
if (订阅格式 == 'base64'){
return new Response(base64Data ,{
headers: {
"content-type": "text/plain; charset=utf-8",
"Profile-Update-Interval": `${SUBUpdateTime}`,
}
});
} else if (订阅格式 == 'clash'){
subconverterUrl = `https://${subconverter}/sub?target=clash&url=${encodeURIComponent(订阅转换URL)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
} else if (订阅格式 == 'singbox'){
subconverterUrl = `https://${subconverter}/sub?target=singbox&url=${encodeURIComponent(订阅转换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) {
return new Response(base64Data ,{
headers: {
"content-type": "text/plain; charset=utf-8",
"Profile-Update-Interval": `${SUBUpdateTime}`,
}
});
//throw new Error(`Error fetching subconverterUrl: ${subconverterResponse.status} ${subconverterResponse.statusText}`);
}
let 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}`,
},
});
} catch (error) {
return new Response(base64Data ,{
headers: {
"content-type": "text/plain; charset=utf-8",
"Profile-Update-Interval": `${SUBUpdateTime}`,
}
});
}
}
}
};
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 ;
}
async function nginx() {
const text = `
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
`
return text ;
}
async function sendMessage(type, ip, add_data = "") {
if ( BotToken !== '' && ChatID !== ''){
let msg = "";
@@ -223,19 +255,22 @@ async function sendMessage(type, ip, add_data = "") {
}
}
// 将 base64 编码的字符串转换为 utf-8 编码的字符
function base64Decode(str) {
const bytes = new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0)));
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
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 ;
async function MD5MD5(text) {
const encoder = new TextEncoder();
const firstPass = await crypto.subtle.digest('MD5', encoder.encode(text));
const firstPassArray = Array.from(new Uint8Array(firstPass));
const firstHex = firstPassArray.map(b => b.toString(16).padStart(2, '0')).join('');
const secondPass = await crypto.subtle.digest('MD5', encoder.encode(firstHex.slice(7, 27)));
const secondPassArray = Array.from(new Uint8Array(secondPass));
const secondHex = secondPassArray.map(b => b.toString(16).padStart(2, '0')).join('');
return secondHex.toLowerCase();
}