forked from cmliu/CF-Workers-SUB
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
156 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
name: Upstream Sync | ||
|
||
permissions: | ||
contents: write | ||
|
||
on: | ||
schedule: | ||
- cron: "0 0 * * *" # every day | ||
workflow_dispatch: | ||
|
||
jobs: | ||
sync_latest_from_upstream: | ||
name: Sync latest commits from upstream repo | ||
runs-on: ubuntu-latest | ||
if: ${{ github.event.repository.fork }} | ||
|
||
steps: | ||
# Step 1: run a standard checkout action | ||
- name: Checkout target repo | ||
uses: actions/checkout@v3 | ||
|
||
# Step 2: run the sync action | ||
- name: Sync upstream changes | ||
id: sync | ||
uses: aormsby/[email protected] | ||
with: | ||
upstream_sync_repo: cmliu/CF-Workers-SUB | ||
upstream_sync_branch: main | ||
target_sync_branch: main | ||
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set | ||
|
||
# Set test_mode true to run tests instead of the true action!! | ||
test_mode: false | ||
|
||
- name: Sync check | ||
if: failure() | ||
run: | | ||
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次,详细教程请查看项目README.md " | ||
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork. Please refer to the project README.md for instructions. " | ||
exit 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,7 @@ export default { | |
mytoken = env.TOKEN || mytoken; | ||
BotToken = env.TGTOKEN || BotToken; | ||
ChatID = env.TGID || ChatID; | ||
TG = env.TG || TG; | ||
TG = env.TG || TG; | ||
subconverter = env.SUBAPI || subconverter; | ||
if( subconverter.includes("http://") ){ | ||
subconverter = subconverter.split("//")[1]; | ||
|
@@ -88,6 +88,10 @@ export default { | |
订阅格式 = 'singbox'; | ||
} else if (userAgent.includes('surge') || ( url.searchParams.has('surge') && !userAgent.includes('subconverter'))){ | ||
订阅格式 = 'surge'; | ||
} else if (userAgent.includes('quantumult%20x') || (url.searchParams.has('quanx') && !userAgent.includes('subconverter'))){ | ||
订阅格式 = 'quanx'; | ||
} else if (userAgent.includes('loon') || (url.searchParams.has('loon') && !userAgent.includes('subconverter'))){ | ||
订阅格式 = 'loon'; | ||
} | ||
|
||
let subconverterUrl ; | ||
|
@@ -99,6 +103,8 @@ export default { | |
if (url.searchParams.has('clash')) 追加UA = 'clash'; | ||
else if(url.searchParams.has('singbox')) 追加UA = 'singbox'; | ||
else if(url.searchParams.has('surge')) 追加UA = 'surge'; | ||
else if(url.searchParams.has('quanx')) 追加UA = 'Quantumult%20X'; | ||
else if(url.searchParams.has('loon')) 追加UA = 'Loon'; | ||
|
||
const 请求订阅响应内容 = await getSUB(urls ,request ,追加UA, userAgentHeader); | ||
console.log(请求订阅响应内容); | ||
|
@@ -159,6 +165,10 @@ export default { | |
subconverterUrl = `${subProtocol}://${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`; | ||
} else if (订阅格式 == 'surge'){ | ||
subconverterUrl = `${subProtocol}://${subconverter}/sub?target=surge&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 (订阅格式 == 'quanx'){ | ||
subconverterUrl = `${subProtocol}://${subconverter}/sub?target=quanx&url=${encodeURIComponent(订阅转换URL)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&udp=true`; | ||
} else if (订阅格式 == 'loon'){ | ||
subconverterUrl = `${subProtocol}://${subconverter}/sub?target=loon&url=${encodeURIComponent(订阅转换URL)}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false`; | ||
} | ||
//console.log(订阅转换URL); | ||
try { | ||
|
@@ -199,7 +209,7 @@ export default { | |
}; | ||
|
||
async function ADD(envadd) { | ||
var addtext = envadd.replace(/[ "'|\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 | ||
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); | ||
|
@@ -270,15 +280,15 @@ function base64Decode(str) { | |
|
||
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(); | ||
} | ||
|
||
|
@@ -348,97 +358,103 @@ async function proxyURL(proxyURL, url) { | |
} | ||
|
||
async function getSUB(api, request, 追加UA, userAgentHeader) { | ||
if (!api || api.length === 0) { | ||
return []; | ||
} | ||
let newapi = ""; | ||
let 订阅转换URLs = ""; | ||
const controller = new AbortController(); // 创建一个AbortController实例,用于取消请求 | ||
const timeout = setTimeout(() => { | ||
controller.abort(); // 2秒后取消所有请求 | ||
}, 2000); | ||
|
||
try { | ||
// 使用Promise.allSettled等待所有API请求完成,无论成功或失败 | ||
const responses = await Promise.allSettled(api.map(apiUrl => getUrl(request, apiUrl, 追加UA, userAgentHeader).then(response => response.ok ? response.text() : Promise.reject(response)))); | ||
|
||
// 遍历所有响应 | ||
const modifiedResponses = responses.map((response, index) => { | ||
// 检查是否请求成功 | ||
if (response.status === 'rejected') { | ||
const reason = response.reason; | ||
if (reason && reason.name === 'AbortError') { | ||
return { | ||
status: '超时', | ||
value: null, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
} | ||
console.error(`请求失败: ${api[index]}, 错误信息: ${reason.status} ${reason.statusText}`); | ||
return { | ||
status: '请求失败', | ||
value: null, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
} | ||
return { | ||
status: response.status, | ||
value: response.value, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
}); | ||
|
||
console.log(modifiedResponses); // 输出修改后的响应数组 | ||
|
||
for (const response of modifiedResponses) { | ||
// 检查响应状态是否为'fulfilled' | ||
if (response.status === 'fulfilled') { | ||
const content = await response.value || 'null'; // 获取响应的内容 | ||
if (content.includes('proxies') && content.includes('proxy-groups')) { | ||
// Clash 配置 | ||
订阅转换URLs += "|" + response.apiUrl; | ||
} else if (content.includes('outbounds') && content.includes('inbounds')) { | ||
// Singbox 配置 | ||
订阅转换URLs += "|" + response.apiUrl; | ||
} else { | ||
if (content.includes('://')) { | ||
newapi += content + '\n'; | ||
} else { | ||
newapi += base64Decode(content) + '\n'; // 解码并追加内容 | ||
} | ||
} | ||
} | ||
} | ||
} catch (error) { | ||
console.error(error); // 捕获并输出错误信息 | ||
} finally { | ||
clearTimeout(timeout); // 清除定时器 | ||
} | ||
|
||
const 订阅内容 = await ADD(newapi); | ||
// 返回处理后的结果 | ||
return [订阅内容, 订阅转换URLs]; | ||
if (!api || api.length === 0) { | ||
return []; | ||
} | ||
let newapi = ""; | ||
let 订阅转换URLs = ""; | ||
let 异常订阅 = ""; | ||
const controller = new AbortController(); // 创建一个AbortController实例,用于取消请求 | ||
const timeout = setTimeout(() => { | ||
controller.abort(); // 2秒后取消所有请求 | ||
}, 2000); | ||
|
||
try { | ||
// 使用Promise.allSettled等待所有API请求完成,无论成功或失败 | ||
const responses = await Promise.allSettled(api.map(apiUrl => getUrl(request, apiUrl, 追加UA, userAgentHeader).then(response => response.ok ? response.text() : Promise.reject(response)))); | ||
|
||
// 遍历所有响应 | ||
const modifiedResponses = responses.map((response, index) => { | ||
// 检查是否请求成功 | ||
if (response.status === 'rejected') { | ||
const reason = response.reason; | ||
if (reason && reason.name === 'AbortError') { | ||
return { | ||
status: '超时', | ||
value: null, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
} | ||
console.error(`请求失败: ${api[index]}, 错误信息: ${reason.status} ${reason.statusText}`); | ||
return { | ||
status: '请求失败', | ||
value: null, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
} | ||
return { | ||
status: response.status, | ||
value: response.value, | ||
apiUrl: api[index] // 将原始的apiUrl添加到返回对象中 | ||
}; | ||
}); | ||
|
||
console.log(modifiedResponses); // 输出修改后的响应数组 | ||
|
||
for (const response of modifiedResponses) { | ||
// 检查响应状态是否为'fulfilled' | ||
if (response.status === 'fulfilled') { | ||
const content = await response.value || 'null'; // 获取响应的内容 | ||
if (content.includes('proxies') && content.includes('proxy-groups')) { | ||
订阅转换URLs += "|" + response.apiUrl; // Clash 配置 | ||
} else if (content.includes('outbounds') && content.includes('inbounds')) { | ||
订阅转换URLs += "|" + response.apiUrl; // Singbox 配置 | ||
} else if (content.includes('://')) { | ||
newapi += content + '\n'; // 追加内容 | ||
} else if (isValidBase64(content)){ | ||
newapi += base64Decode(content) + '\n'; // 解码并追加内容 | ||
} else { | ||
const 异常订阅LINK = `trojan://[email protected]:8888?security=tls&allowInsecure=1&type=tcp&headerType=none#%E5%BC%82%E5%B8%B8%E8%AE%A2%E9%98%85%20${response.apiUrl.split('://')[1].split('/')[0]}`; | ||
console.log(异常订阅LINK); | ||
异常订阅 += `${异常订阅LINK}\n`; | ||
} | ||
} | ||
} | ||
} catch (error) { | ||
console.error(error); // 捕获并输出错误信息 | ||
} finally { | ||
clearTimeout(timeout); // 清除定时器 | ||
} | ||
|
||
const 订阅内容 = await ADD(newapi + 异常订阅); // 将处理后的内容转换为数组 | ||
// 返回处理后的结果 | ||
return [订阅内容, 订阅转换URLs]; | ||
} | ||
|
||
async function getUrl(request, targetUrl, 追加UA, userAgentHeader) { | ||
// 设置自定义 User-Agent | ||
const newHeaders = new Headers(request.headers); | ||
newHeaders.set("User-Agent", `v2rayN/${追加UA} cmliu/CF-Workers-SUB ${userAgentHeader}`); | ||
|
||
// 构建新的请求对象 | ||
const modifiedRequest = new Request(targetUrl, { | ||
method: request.method, | ||
headers: newHeaders, | ||
body: request.method === "GET" ? null : request.body, | ||
redirect: "follow" | ||
}); | ||
|
||
// 输出请求的详细信息 | ||
console.log(`请求URL: ${targetUrl}`); | ||
console.log(`请求头: ${JSON.stringify([...newHeaders])}`); | ||
console.log(`请求方法: ${request.method}`); | ||
console.log(`请求体: ${request.method === "GET" ? null : request.body}`); | ||
|
||
// 发送请求并返回响应 | ||
return fetch(modifiedRequest); | ||
// 设置自定义 User-Agent | ||
const newHeaders = new Headers(request.headers); | ||
newHeaders.set("User-Agent", `v2rayN/${追加UA} cmliu/CF-Workers-SUB ${userAgentHeader}`); | ||
|
||
// 构建新的请求对象 | ||
const modifiedRequest = new Request(targetUrl, { | ||
method: request.method, | ||
headers: newHeaders, | ||
body: request.method === "GET" ? null : request.body, | ||
redirect: "follow" | ||
}); | ||
|
||
// 输出请求的详细信息 | ||
console.log(`请求URL: ${targetUrl}`); | ||
console.log(`请求头: ${JSON.stringify([...newHeaders])}`); | ||
console.log(`请求方法: ${request.method}`); | ||
console.log(`请求体: ${request.method === "GET" ? null : request.body}`); | ||
|
||
// 发送请求并返回响应 | ||
return fetch(modifiedRequest); | ||
} | ||
|
||
function isValidBase64(str) { | ||
const base64Regex = /^[A-Za-z0-9+/=]+$/; | ||
return base64Regex.test(str); | ||
} |