Skip to content

Commit

Permalink
Merge branch 'cmliu:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
jjwjs authored Nov 24, 2024
2 parents 81cfd93 + 312c109 commit 5592fd8
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 99 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/sync.yml
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ vmess://ew0KICAidiI6ICIyIiwNCiAgInBzIjogIuWKoOWFpeaIkeeahOmikemBk3QubWUvQ01MaXVz
## 变量说明
| 变量名 | 示例 | 备注 |
|--------|---------|-----|
| LINK | vless://b7a39... vmess://ew0K... https://sub... | 可同时放入多个节点链接与多个订阅链接, 链接之间用换行做间隔 |
| TOKEN | auto | 快速订阅内置节点的订阅路径地址 /auto |
| TGTOKEN | 6894123456:XXXXXXXXXX0qExVsBPUhHDAbXXXXXqWXgBA | 发送TG通知的机器人token |
| TGID | 6946912345 | 接收TG通知的账户数字ID |
| SUBAPI | subapi.fxxk.dedyn.io | clash、singbox等 订阅转换后端 |
| LINK | `vless://b7a39...` `vmess://ew0K...` `https://sub...` | 可同时放入多个节点链接与多个订阅链接, 链接之间用换行做间隔 |
| TOKEN | `auto` | 快速订阅内置节点的订阅路径地址 /auto |
| TGTOKEN | `6894123456:XXXXXXXXXX0qExVsBPUhHDAbXXXXXqWXgBA` | 发送TG通知的机器人token |
| TGID | `6946912345` | 接收TG通知的账户数字ID |
| SUBNAME | `CF-Workers-SUB` | 订阅名称 |
| SUBAPI | `subapi.fxxk.dedyn.io` | clash、singbox等 订阅转换后端 |
| SUBCONFIG | [https://raw.github.../ACL4SSR_Online_MultiCountry.ini](https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_MultiCountry.ini) | clash、singbox等 订阅转换配置文件 |


Expand Down
204 changes: 110 additions & 94 deletions _worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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 ;
Expand All @@ -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(请求订阅响应内容);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
}

0 comments on commit 5592fd8

Please sign in to comment.