Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60925ed8eb | ||
|
|
f15c7a93eb | ||
|
|
3c0fb800ff | ||
|
|
1637856af3 | ||
|
|
6b3f554103 | ||
|
|
f72b0dae3c | ||
|
|
d3f67f9e8e | ||
|
|
84eff4ed5f | ||
|
|
6a19c1decd | ||
|
|
c4b813d91e | ||
|
|
ca34c6cef3 | ||
|
|
a21d0ad7da | ||
|
|
46f16c9fb8 | ||
|
|
90e9c3e555 | ||
|
|
9079aaf15e | ||
|
|
3f3b097740 | ||
|
|
533a66002b | ||
|
|
e908f1cfcf | ||
|
|
1a3bc924ce | ||
|
|
340899681c | ||
|
|
4e349b057e | ||
|
|
dc0e041463 | ||
|
|
cdf537fb8a | ||
|
|
58c3d7a6c3 | ||
|
|
da48ff0ee0 | ||
|
|
e590b0f695 | ||
|
|
bc48ced7ba | ||
|
|
b302a6a35e | ||
|
|
b48ab05e58 | ||
|
|
8aee784d7f | ||
|
|
4a65083c3f | ||
|
|
0d0cf419ab | ||
|
|
d5025262b4 | ||
|
|
8ee1825ff1 | ||
|
|
a97f61a3ad | ||
|
|
b604225b2b | ||
|
|
2923a9b194 | ||
|
|
5cbaaf956c | ||
|
|
7cefec8fc2 | ||
|
|
830364e196 | ||
|
|
82f5b64a3a | ||
|
|
944aa9f109 | ||
|
|
d8b5995329 | ||
|
|
3252a79750 | ||
|
|
460706182f | ||
|
|
502fa4ef1f | ||
|
|
e85b2cb977 | ||
|
|
9c9da3e5ad | ||
|
|
8d03ef5a0f | ||
|
|
647e83ac5b | ||
|
|
768e76160c | ||
|
|
c434a152cb | ||
|
|
54db90a86b | ||
|
|
f063a6881f | ||
|
|
a4dbc9ed5f | ||
|
|
51f35dc544 | ||
|
|
268f75884d | ||
|
|
8128a01bb7 | ||
|
|
f4d8bcd246 | ||
|
|
1ebc52e08a | ||
|
|
9e3b7dcd32 | ||
|
|
34aeabd3ca | ||
|
|
058b301ee6 | ||
|
|
831328db98 | ||
|
|
c1094723e5 | ||
|
|
d77c86ba0f | ||
|
|
389328cf5a | ||
|
|
2db579df8a | ||
|
|
665fa2229d | ||
|
|
13e62404fe | ||
|
|
89753e40ff | ||
|
|
24400c4c45 | ||
|
|
89b03eb3ad | ||
|
|
93cfb60a8e | ||
|
|
90bbe99388 | ||
|
|
9ae86618bd | ||
|
|
09fd6aa15b | ||
|
|
0c073de0e5 | ||
|
|
934a5bb2f1 | ||
|
|
0f51f8b37f | ||
|
|
0cb0ce785e | ||
|
|
b26f69702f | ||
|
|
d02823f80f | ||
|
|
5ea70d5c61 | ||
|
|
d663250d77 | ||
|
|
aa8cc2769a | ||
|
|
699efffd40 | ||
|
|
955c5d5e4b | ||
|
|
b27530a020 | ||
|
|
0c0912a3f2 | ||
|
|
c5bea6e6ba | ||
|
|
e41da7eb4f | ||
|
|
9f11961836 | ||
|
|
f4e3aaeab1 | ||
|
|
c09505e304 | ||
|
|
7230deb89c | ||
|
|
062de6a9cf | ||
|
|
128842e1ca | ||
|
|
cb5c48e4ac | ||
|
|
e839494a5d | ||
|
|
cd3c9204b4 | ||
|
|
e46f7e94a7 | ||
|
|
76376f9874 | ||
|
|
bf4008c26e | ||
|
|
24afe91239 | ||
|
|
1dfa449b71 | ||
|
|
a926dbf152 | ||
|
|
ec1c7d7f32 | ||
|
|
7f18ef56bd | ||
|
|
cfb12aa8e3 | ||
|
|
6a397d1330 | ||
|
|
c9f2d71216 | ||
|
|
c7680a99cc | ||
|
|
c58df86736 | ||
|
|
b92ad83725 | ||
|
|
f1ea409491 | ||
|
|
7bee9b51db | ||
|
|
e93d6f65c6 | ||
|
|
3a340a4737 | ||
|
|
c835aa65a8 | ||
|
|
868725fbf8 | ||
|
|
8290d424cc | ||
|
|
11de6b2bbd | ||
|
|
1f21e55df7 | ||
|
|
2c6a0754c6 | ||
|
|
99c610d62b | ||
|
|
602f715795 | ||
|
|
de3f8c39a8 | ||
|
|
06bf3d4eec | ||
|
|
3ca154680b | ||
|
|
29f6cae182 | ||
|
|
6b011294a5 | ||
|
|
df1442fce0 | ||
|
|
ab0d0e5423 | ||
|
|
c7d5904b4e | ||
|
|
b25e7fcfb7 | ||
|
|
bcd98604b1 | ||
|
|
2122de2912 | ||
|
|
e2fe02353e | ||
|
|
903b57fac1 | ||
|
|
b5194bb768 | ||
|
|
2e6d3f3fc9 | ||
|
|
cdb4aeac4a | ||
|
|
609debcacb | ||
|
|
53d43f22b0 | ||
|
|
fc76349933 | ||
|
|
6726efd9ee | ||
|
|
57c7f07ea5 | ||
|
|
58e764aef7 | ||
|
|
01c2eadbd1 | ||
|
|
a4edd595c4 | ||
|
|
a9d6138d36 | ||
|
|
c8191da3c5 | ||
|
|
5de189cfc1 | ||
|
|
fce8ef8a83 | ||
|
|
fc1d30536b | ||
|
|
dd6fad269b | ||
|
|
70498fcf88 | ||
|
|
0e472ffcf6 | ||
|
|
fdfaf7e4c5 | ||
|
|
9093d30507 | ||
|
|
47bcdad9d8 | ||
|
|
7148769cf7 | ||
|
|
5f3f1de5b2 | ||
|
|
b38825bb44 | ||
|
|
8b543e13f2 | ||
|
|
dbda8e3c73 | ||
|
|
81b2ac5b3e | ||
|
|
b009c3ef19 | ||
|
|
5cdb1a411c | ||
|
|
c8127dde15 | ||
|
|
6ebcb21bd2 | ||
|
|
9bd39a17fd | ||
|
|
79534b9653 | ||
|
|
32688eb7fd | ||
|
|
6354152686 | ||
|
|
27b102a27f | ||
|
|
dd5163be32 | ||
|
|
27b4fcf7e2 | ||
|
|
067053a226 | ||
|
|
eab8bb3311 | ||
|
|
7281461add | ||
|
|
62fca2c69c | ||
|
|
1971c56c4c | ||
|
|
e660a839bd | ||
|
|
57638f9d2b | ||
|
|
08524cf87b | ||
|
|
978e4feaae | ||
|
|
0b3b856c39 | ||
|
|
dfe0c82a75 | ||
|
|
92aea6592d | ||
|
|
14f8065fea | ||
|
|
06b647974d | ||
|
|
7889fb390f | ||
|
|
48ca436207 | ||
|
|
770a6167d9 | ||
|
|
6414580b58 | ||
|
|
a411bd82fa | ||
|
|
35f5d7669f | ||
|
|
f8038b38e5 | ||
|
|
9ba44b80d9 | ||
|
|
c292629677 | ||
|
|
c7b514590b | ||
|
|
fad239874c | ||
|
|
5321b846a5 | ||
|
|
319d67d7fe | ||
|
|
7112144499 | ||
|
|
ba4f58bb7f | ||
|
|
ab5d241e90 | ||
|
|
ad706d9c11 | ||
|
|
78a587aeef | ||
|
|
838a2d88dd | ||
|
|
635bcb095c | ||
|
|
47c716c112 | ||
|
|
b519f37efc | ||
|
|
3e029afb71 | ||
|
|
e1d02248c0 | ||
|
|
cce6ab1f8f | ||
|
|
cc3e087aaf | ||
|
|
c70f1a6b08 | ||
|
|
94ccb171ea | ||
|
|
6e09711ec1 | ||
|
|
897f8823e5 | ||
|
|
67762ba695 | ||
|
|
93496eec2b | ||
|
|
f02d757fa2 | ||
|
|
a83e0d0229 | ||
|
|
bf4c38af9c | ||
|
|
3be7654f81 | ||
|
|
d6f4f41c80 | ||
|
|
86ffe68b6d | ||
|
|
77fb3a0e61 | ||
|
|
4c93d54601 | ||
|
|
b37d3d979b | ||
|
|
c3d2a6d357 | ||
|
|
a0be2524ef | ||
|
|
11f7d9109a | ||
|
|
eb45d33204 | ||
|
|
3171e0474b | ||
|
|
a40710b8d5 | ||
|
|
a64665cf3b | ||
|
|
c07f563f39 | ||
|
|
f326d712cd | ||
|
|
60a6091db5 | ||
|
|
82498ce45e | ||
|
|
e4dab2c993 | ||
|
|
49cb695d1f | ||
|
|
a06b63a81c | ||
|
|
9391902934 | ||
|
|
dd07797b11 | ||
|
|
aa87f219f3 | ||
|
|
3ae6d4f68a | ||
|
|
745f912475 | ||
|
|
315146571a | ||
|
|
392f052478 | ||
|
|
a391e527da | ||
|
|
a27c6438df | ||
|
|
c0f277ba46 | ||
|
|
dc9e856564 | ||
|
|
1cf27d8914 | ||
|
|
0b8d1d4663 | ||
|
|
cd28e1f29e | ||
|
|
f17715093c | ||
|
|
e3ed4dfb4f | ||
|
|
7dd3431916 | ||
|
|
66939b5eb9 | ||
|
|
a95b5a9c1f | ||
|
|
db3ae9d0a6 | ||
|
|
aa1eb1072f | ||
|
|
f8a5d9470c | ||
|
|
728b97c405 | ||
|
|
c6f6dcb3e9 | ||
|
|
c3a217fb20 | ||
|
|
819d2303ab | ||
|
|
01d5e810e7 | ||
|
|
905fbd0ff5 | ||
|
|
50b0cd47e6 | ||
|
|
7ca3977bb0 | ||
|
|
edd9bc9252 | ||
|
|
e45e979f4e | ||
|
|
8c627ea05b | ||
|
|
f2f1b1f708 | ||
|
|
316fb81f1e | ||
|
|
afaa5efad8 | ||
|
|
ad0f198f18 | ||
|
|
2797d03ede | ||
|
|
ab4626f2db | ||
|
|
9fe412cb4a | ||
|
|
2b6f2b3366 | ||
|
|
6c01c4807c | ||
|
|
6456674ab9 | ||
|
|
b3d82d8fbc | ||
|
|
2490ac85bb | ||
|
|
558da59ac6 | ||
|
|
4b176e83d3 | ||
|
|
77c98b4b15 | ||
|
|
0bc222697c | ||
|
|
cbdf386f37 | ||
|
|
789843330c | ||
|
|
d85093aad9 | ||
|
|
3c119bce00 | ||
|
|
76d1419e24 | ||
|
|
fb4295f423 | ||
|
|
6c0987fca9 | ||
|
|
c6649ba9f3 | ||
|
|
4604db1e91 | ||
|
|
0821a9fd3b | ||
|
|
e2bb9e6fca | ||
|
|
69a58eefe9 | ||
|
|
9b767927f3 | ||
|
|
f4c823a2b0 | ||
|
|
04897cb8e3 | ||
|
|
bdbbd1da6c | ||
|
|
651a17fc6e | ||
|
|
e39f5570b5 | ||
|
|
1a76e0d38b | ||
|
|
487361e13f | ||
|
|
6ddab4b5fc | ||
|
|
1f7cc824dd | ||
|
|
281d033eb7 | ||
|
|
60c2df8d09 | ||
|
|
f2d3488baa | ||
|
|
f2724b90ec | ||
|
|
fbaa94ffb1 | ||
|
|
373e0aac94 | ||
|
|
d048756989 | ||
|
|
7984fab79d | ||
|
|
8dd895f783 | ||
|
|
0eae6c952a | ||
|
|
e9ddf7e514 | ||
|
|
6a3f2c8b6d | ||
|
|
a39df14b6e | ||
|
|
6dfe760a9f | ||
|
|
f2eb7405d3 | ||
|
|
7ae7161045 | ||
|
|
fc1b861763 | ||
|
|
f7166a158f | ||
|
|
4cd5611f33 | ||
|
|
68045dd393 | ||
|
|
794c6a4aab | ||
|
|
b66097d25d | ||
|
|
9edec1c833 | ||
|
|
a8d39678d5 | ||
|
|
f7004addfe |
No files matched your search
@@ -0,0 +1,106 @@
|
|||||||
|
name: The Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- script
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Set up Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
- name: Run test suite
|
||||||
|
run: bun test META.test.js workflow.test.js
|
||||||
|
|
||||||
|
run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: ci
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Set up Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
- name: Validate runnable scenarios
|
||||||
|
run: bun run run
|
||||||
|
|
||||||
|
purge-dns-cache:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: run
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Purge CDN cache
|
||||||
|
run: |
|
||||||
|
for file in $(ls); do
|
||||||
|
curl -i "https://purge.jsdelivr.net/gh/${{ github.repository }}@script/${file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
generate-config:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: run
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Set up Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
- name: Generate YAML configuration
|
||||||
|
run: |
|
||||||
|
mkdir -p publish
|
||||||
|
bun run generate
|
||||||
|
cat > publish/META.yaml << 'EOL'
|
||||||
|
proxy-providers-defaults: &ppd
|
||||||
|
type: http
|
||||||
|
interval: 86400
|
||||||
|
health-check:
|
||||||
|
enable: true
|
||||||
|
url: "https://www.gstatic.com/generate_204"
|
||||||
|
interval: 300
|
||||||
|
filter: "^(?!.*(群|邀请|返利|循环|官网|客服|网站|网址|获取|订阅|流量|到期|机场|下次|版本|官址|备用|过期|已用|联系|邮箱|工单|贩卖|通知|倒卖|防止|国内|地址|频道|无法|说明|使用|提示|特别|访问|支持|教程|关注|更新|作者|加入|USE|USED|TOTAL|EXPIRE|EMAIL|Panel|Channel|Author))"
|
||||||
|
proxy-providers:
|
||||||
|
Provider A:
|
||||||
|
<<: *ppd
|
||||||
|
url: "https://the.url.of/A.yaml"
|
||||||
|
override:
|
||||||
|
additional-prefix: "[A] "
|
||||||
|
Provider B:
|
||||||
|
<<: *ppd
|
||||||
|
url: "https://the.url.of/B.yaml"
|
||||||
|
override:
|
||||||
|
additional-prefix: "[B] "
|
||||||
|
Provider C:
|
||||||
|
<<: *ppd
|
||||||
|
url: "https://the.url.of/C.yaml"
|
||||||
|
override:
|
||||||
|
additional-prefix: "[C] "
|
||||||
|
EOL
|
||||||
|
cat META.yaml >> publish/META.yaml
|
||||||
|
- name: Push to config branch
|
||||||
|
run: |
|
||||||
|
cd publish
|
||||||
|
git init
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git checkout -b config
|
||||||
|
git add .
|
||||||
|
git commit -m "Update YAML configuration"
|
||||||
|
git remote add origin "https://github-actions[bot]:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
|
||||||
|
git push -f origin config
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
META.yaml
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const vm = require("node:vm");
|
||||||
|
const yaml = require("yaml");
|
||||||
|
|
||||||
|
function loadMain() {
|
||||||
|
const content = fs.readFileSync(path.join(__dirname, "META.js"), "utf8");
|
||||||
|
const wrapped = `${content}
|
||||||
|
|
||||||
|
module.exports = { main };`;
|
||||||
|
const context = {
|
||||||
|
module: { exports: {} },
|
||||||
|
console: {
|
||||||
|
log() {},
|
||||||
|
warn() {},
|
||||||
|
error() {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runInNewContext(wrapped, context, { filename: "META.js" });
|
||||||
|
return context.module.exports.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const generateConfig = loadMain();
|
||||||
|
const config = generateConfig({}, { skipValidation: true });
|
||||||
|
|
||||||
|
if (config.error) {
|
||||||
|
throw new Error(config.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(__dirname, "META.yaml"), yaml.stringify(config));
|
||||||
|
console.log("Generated META.yaml");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
main();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,791 @@
|
|||||||
|
// from https://github.com/xixu-me/META
|
||||||
|
|
||||||
|
// ################################################### this section can be flexibly customized ###################################################
|
||||||
|
|
||||||
|
// Number of STATIC proxy groups (for IP-sensitive services)
|
||||||
|
const STATIC_PROXY_COUNT = 4;
|
||||||
|
|
||||||
|
// Routing rules are matched in order from top to bottom, with the rule at the top of the list taking precedence over the rules below it.
|
||||||
|
const services = [
|
||||||
|
// Media & Content
|
||||||
|
{ name: "bilibili", tld: "com" },
|
||||||
|
{ name: "Netflix", tld: "com" },
|
||||||
|
{ name: "YouTube", tld: "com" },
|
||||||
|
|
||||||
|
// AI/LLM Services
|
||||||
|
{ name: "Anthropic", tld: "com" },
|
||||||
|
{ name: "Arco", domain: "www.youware.com" },
|
||||||
|
{ name: "Gemini", tld: "google", alias: "Google-Gemini" },
|
||||||
|
{ name: "OpenAI", tld: "com" },
|
||||||
|
{ name: "xAI", tld: "ai", sld: "x" },
|
||||||
|
|
||||||
|
// Social Media & Communication
|
||||||
|
{ name: "Douyin", domain: "douyin.com", alias: "Douyin" },
|
||||||
|
{ name: "rednote", domain: "xiaohongshu.com", alias: "Xiaohongshu" },
|
||||||
|
{ name: "Telegram", tld: "org" },
|
||||||
|
{
|
||||||
|
name: "Truth Social",
|
||||||
|
tld: "com",
|
||||||
|
sld: "truthsocial",
|
||||||
|
alias: "TruthSocial",
|
||||||
|
},
|
||||||
|
{ name: "X", tld: "com" },
|
||||||
|
|
||||||
|
// Financial Services
|
||||||
|
{ name: "Binance", tld: "com" },
|
||||||
|
{ name: "Bybit", tld: "com" },
|
||||||
|
{ name: "OKX", tld: "com" },
|
||||||
|
|
||||||
|
// Technology Companies
|
||||||
|
{ name: "Apple", tld: "com" },
|
||||||
|
{ name: "Google", tld: "google", sld: "about" },
|
||||||
|
{ name: "Microsoft", tld: "com" },
|
||||||
|
|
||||||
|
// Infrastructure
|
||||||
|
{ name: "Speedtest", tld: "net" },
|
||||||
|
{ name: "Xget", domain: "xuc.xi-xu.me" },
|
||||||
|
{ name: "Cloudflare", tld: "com" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOCATION_ICON_SET_URL = "https://img.icons8.com/color/144/";
|
||||||
|
|
||||||
|
const locations = [
|
||||||
|
{
|
||||||
|
name: "Argentina 🇦🇷",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}argentina`,
|
||||||
|
keywords: ["阿根廷", "Argentina"],
|
||||||
|
codes: ["ARG", "AR"],
|
||||||
|
emoji: ["🇦🇷"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Canada 🇨🇦",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}canada`,
|
||||||
|
keywords: ["加拿大", "Canada"],
|
||||||
|
codes: ["CAN", "CA"],
|
||||||
|
emoji: ["🇨🇦"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Finland 🇫🇮",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}finland`,
|
||||||
|
keywords: ["芬兰", "Finland"],
|
||||||
|
codes: ["FIN", "FI"],
|
||||||
|
emoji: ["🇫🇮"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "France 🇫🇷",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}france`,
|
||||||
|
keywords: ["法国", "France"],
|
||||||
|
codes: ["FR"],
|
||||||
|
emoji: ["🇫🇷"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Germany 🇩🇪",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}germany`,
|
||||||
|
keywords: ["德国", "Germany"],
|
||||||
|
codes: ["GER", "DE"],
|
||||||
|
emoji: ["🇩🇪"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Hong Kong, China 🇭🇰",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}hongkong-circular`,
|
||||||
|
keywords: ["香港", "Hong Kong"],
|
||||||
|
codes: ["HK"],
|
||||||
|
emoji: ["🇭🇰"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "India 🇮🇳",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}india`,
|
||||||
|
keywords: ["印度", "India"],
|
||||||
|
codes: ["IND", "IN"],
|
||||||
|
emoji: ["🇮🇳"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Iraq 🇮🇶",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}iraq`,
|
||||||
|
keywords: ["伊拉克", "Iraq"],
|
||||||
|
codes: ["IQ"],
|
||||||
|
emoji: ["🇮🇶"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Japan 🇯🇵",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}japan`,
|
||||||
|
keywords: ["日本", "Japan"],
|
||||||
|
codes: ["JP"],
|
||||||
|
emoji: ["🇯🇵"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Korea 🇰🇷",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}south-korea`,
|
||||||
|
keywords: ["韩国", "Korea"],
|
||||||
|
codes: ["KR"],
|
||||||
|
emoji: ["🇰🇷"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Russia 🇷🇺",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}russian-federation`,
|
||||||
|
keywords: ["俄罗斯", "Russia Federation", "Russia"],
|
||||||
|
codes: ["RU"],
|
||||||
|
emoji: ["🇷🇺"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Singapore 🇸🇬",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}singapore`,
|
||||||
|
keywords: ["新加坡", "Singapore"],
|
||||||
|
codes: ["SG"],
|
||||||
|
emoji: ["🇸🇬"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Taiwan, China 🇨🇳",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}china-circular`,
|
||||||
|
keywords: ["台湾", "Taiwan"],
|
||||||
|
codes: ["TW"],
|
||||||
|
emoji: ["🇹🇼"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Thailand 🇹🇭",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}thailand`,
|
||||||
|
keywords: ["泰国", "Thailand"],
|
||||||
|
codes: ["TH"],
|
||||||
|
emoji: ["🇹🇭"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Türkiye 🇹🇷",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}turkey`,
|
||||||
|
keywords: ["土耳其", "Türkiye", "Turkey"],
|
||||||
|
codes: ["TUR", "TR"],
|
||||||
|
emoji: ["🇹🇷"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ukraine 🇺🇦",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}ukraine`,
|
||||||
|
keywords: ["乌克兰", "Ukraine"],
|
||||||
|
codes: ["UKR", "UA"],
|
||||||
|
emoji: ["🇺🇦"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "United Kingdom 🇬🇧",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}great-britain`,
|
||||||
|
keywords: ["英国", "United Kingdom", "Great Britain"],
|
||||||
|
codes: ["UK", "GB"],
|
||||||
|
emoji: ["🇬🇧"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "United States 🇺🇸",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}usa`,
|
||||||
|
keywords: ["美国", "United States of America", "United States"],
|
||||||
|
codes: ["USA", "US"],
|
||||||
|
emoji: ["🇺🇸"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Global 🌐",
|
||||||
|
icon: "https://img.icons8.com/?size=144&id=3685&format=png&color=7bbbe9",
|
||||||
|
keywords: ["Global", "Cloudflare"],
|
||||||
|
codes: ["GL", "CF"],
|
||||||
|
emoji: ["🌐"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ####################################### DO NOT MODIFY THE CODE BELOW UNLESS YOU KNOW WHAT YOU ARE DOING #######################################
|
||||||
|
|
||||||
|
function extractFavicon(domain) {
|
||||||
|
return `https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://${domain}&size=128`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRegex(text) {
|
||||||
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLocationFilter({ keywords = [], codes = [], emoji = [] }) {
|
||||||
|
const patterns = [
|
||||||
|
...keywords.map((keyword) => escapeRegex(keyword)),
|
||||||
|
...codes.map((code) => `\\b${escapeRegex(code)}\\b`),
|
||||||
|
...emoji.map((symbol) => escapeRegex(symbol)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return `(?i)${patterns.join("|")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
locations.forEach((location) => {
|
||||||
|
location.filter = buildLocationFilter(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
// General Configuration
|
||||||
|
|
||||||
|
const generalConfig = {
|
||||||
|
"mixed-port": 7890,
|
||||||
|
"allow-lan": false,
|
||||||
|
mode: "rule",
|
||||||
|
"log-level": "info",
|
||||||
|
ipv6: true,
|
||||||
|
"find-process-mode": "strict",
|
||||||
|
profile: {
|
||||||
|
"store-selected": true,
|
||||||
|
"store-fake-ip": true,
|
||||||
|
},
|
||||||
|
"unified-delay": true,
|
||||||
|
"tcp-concurrent": true,
|
||||||
|
"global-ua": "\u0063\u006c\u0061\u0073\u0068.\u006D\u0065\u0074\u0061",
|
||||||
|
"etag-support": true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// DNS
|
||||||
|
|
||||||
|
const chineseNameservers = [
|
||||||
|
"https://dns.alidns.com/dns-query",
|
||||||
|
"https://doh.pub/dns-query",
|
||||||
|
];
|
||||||
|
|
||||||
|
const internationalNameservers = [
|
||||||
|
"https://cloudflare-dns.com/dns-query",
|
||||||
|
"https://dns.google/dns-query",
|
||||||
|
// "https://dns.mullvad.net/dns-query",
|
||||||
|
// "https://wikimedia-dns.org/dns-query",
|
||||||
|
// "https://doh.dns.sb/dns-query",
|
||||||
|
];
|
||||||
|
|
||||||
|
const dns = {
|
||||||
|
enable: true,
|
||||||
|
"cache-algorithm": "arc",
|
||||||
|
"prefer-h3": false,
|
||||||
|
"use-hosts": true,
|
||||||
|
"use-system-hosts": false,
|
||||||
|
"respect-rules": true,
|
||||||
|
ipv6: true,
|
||||||
|
"enhanced-mode": "fake-ip",
|
||||||
|
"fake-ip-range": "198.18.0.1/16",
|
||||||
|
"fake-ip-range6": "fd00::/18",
|
||||||
|
"fake-ip-filter-mode": "blacklist",
|
||||||
|
"fake-ip-filter": [
|
||||||
|
"rule-set:fake-ip-filter",
|
||||||
|
"rule-set:direct",
|
||||||
|
"rule-set:private",
|
||||||
|
"rule-set:connectivity-check",
|
||||||
|
],
|
||||||
|
"default-nameserver": [
|
||||||
|
"tls://223.5.5.5",
|
||||||
|
"tls://223.6.6.6",
|
||||||
|
"tls://119.29.29.29",
|
||||||
|
],
|
||||||
|
"proxy-server-nameserver": chineseNameservers,
|
||||||
|
"nameserver-policy": {
|
||||||
|
"rule-set:private": ["system://"],
|
||||||
|
"rule-set:direct,geolocation-cn": chineseNameservers,
|
||||||
|
},
|
||||||
|
nameserver: internationalNameservers,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hosts
|
||||||
|
|
||||||
|
const hosts = {};
|
||||||
|
|
||||||
|
// Domain Sniffing
|
||||||
|
|
||||||
|
const sniffer = {
|
||||||
|
enable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TUN
|
||||||
|
|
||||||
|
const tun = {
|
||||||
|
enable: true,
|
||||||
|
stack: "mixed",
|
||||||
|
"auto-route": true,
|
||||||
|
"auto-detect-interface": true,
|
||||||
|
"strict-route": true,
|
||||||
|
"dns-hijack": ["any:53", "tcp://any:53"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Proxy Groups
|
||||||
|
|
||||||
|
const proxyGroupDefaults = {
|
||||||
|
url: "https://www.gstatic.com/generate_204",
|
||||||
|
interval: 300,
|
||||||
|
lazy: true,
|
||||||
|
timeout: 5000,
|
||||||
|
"max-failed-times": 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate STATIC proxy group names
|
||||||
|
function generateStaticProxyNames() {
|
||||||
|
if (STATIC_PROXY_COUNT === 1) {
|
||||||
|
return ["STATIC"];
|
||||||
|
}
|
||||||
|
return Array.from(
|
||||||
|
{ length: STATIC_PROXY_COUNT },
|
||||||
|
(_, i) => `STATIC-${i + 1}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticProxyNames = generateStaticProxyNames();
|
||||||
|
|
||||||
|
const serviceProxyGroupProxies = [
|
||||||
|
"PROXY",
|
||||||
|
...staticProxyNames,
|
||||||
|
"AUTO",
|
||||||
|
"DIRECT",
|
||||||
|
"Chinese Mainland 🇨🇳",
|
||||||
|
...locations.map(({ name }) => name),
|
||||||
|
];
|
||||||
|
|
||||||
|
const locationPolicyProxyGroupDefaults = {
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
proxies: ["REJECT"],
|
||||||
|
"include-all": true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SERVICE_ICON_SET_URL =
|
||||||
|
"https://cdn.jsdelivr.net/gh/xixu-me/favicons@assets/favicons/";
|
||||||
|
|
||||||
|
function getServiceIcon(service) {
|
||||||
|
if (service.icon) return service.icon;
|
||||||
|
if (service.domain) return extractFavicon(service.domain);
|
||||||
|
if (service.tld) {
|
||||||
|
const secondLevelDomain = service.sld || service.name.toLowerCase();
|
||||||
|
return `${SERVICE_ICON_SET_URL}${secondLevelDomain}.${service.tld}.png`;
|
||||||
|
}
|
||||||
|
console.warn(`No icon configuration for service: ${service.name}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
services.forEach((service) => {
|
||||||
|
service.icon = getServiceIcon(service);
|
||||||
|
});
|
||||||
|
|
||||||
|
function generateServiceProxyGroups(items, defaultConfig) {
|
||||||
|
return items.map(({ name, icon }) => ({
|
||||||
|
...defaultConfig,
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
type: "select",
|
||||||
|
proxies: serviceProxyGroupProxies,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLocationPolicyProxyGroups(
|
||||||
|
items,
|
||||||
|
defaultConfig,
|
||||||
|
type,
|
||||||
|
extraProps = {},
|
||||||
|
) {
|
||||||
|
const getStrategyName = (type, strategy) => {
|
||||||
|
switch (type) {
|
||||||
|
case "url-test":
|
||||||
|
return "AUTO";
|
||||||
|
case "fallback":
|
||||||
|
return "FALLBACK";
|
||||||
|
case "load-balance":
|
||||||
|
switch (strategy) {
|
||||||
|
case "consistent-hashing":
|
||||||
|
return "LOAD BALANCING (consistent hashing)";
|
||||||
|
case "round-robin":
|
||||||
|
return "LOAD BALANCING (round-robin)";
|
||||||
|
case "sticky-sessions":
|
||||||
|
return "LOAD BALANCING (sticky sessions)";
|
||||||
|
default:
|
||||||
|
return "LOAD BALANCING";
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return type.toUpperCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return items.map(({ name, icon, filter }) => {
|
||||||
|
const strategyName = getStrategyName(type, extraProps.strategy);
|
||||||
|
const emoji = name.split(" ").pop();
|
||||||
|
const newName = `${strategyName} ${emoji}`;
|
||||||
|
return {
|
||||||
|
...defaultConfig,
|
||||||
|
name: newName,
|
||||||
|
type,
|
||||||
|
icon,
|
||||||
|
filter,
|
||||||
|
hidden: true,
|
||||||
|
...extraProps,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLocationSelectProxyGroups() {
|
||||||
|
return locations.map(({ name, icon }) => ({
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name,
|
||||||
|
type: "select",
|
||||||
|
icon,
|
||||||
|
proxies: [
|
||||||
|
`LOAD BALANCING (consistent hashing) ${name.split(" ").pop()}`,
|
||||||
|
`LOAD BALANCING (round-robin) ${name.split(" ").pop()}`,
|
||||||
|
`LOAD BALANCING (sticky sessions) ${name.split(" ").pop()}`,
|
||||||
|
`FALLBACK ${name.split(" ").pop()}`,
|
||||||
|
`AUTO ${name.split(" ").pop()}`,
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE_ICON_SET_URL =
|
||||||
|
"https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/";
|
||||||
|
|
||||||
|
// Generate STATIC proxy groups
|
||||||
|
function generateStaticProxyGroups(defaultConfig) {
|
||||||
|
return staticProxyNames.map((name, index) => ({
|
||||||
|
...defaultConfig,
|
||||||
|
name,
|
||||||
|
type: "select",
|
||||||
|
"include-all": true,
|
||||||
|
icon: `${BASE_ICON_SET_URL}Static.png`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyGroups = [
|
||||||
|
{
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name: "PROXY",
|
||||||
|
type: "select",
|
||||||
|
proxies: [
|
||||||
|
...staticProxyNames,
|
||||||
|
"AUTO",
|
||||||
|
"DIRECT",
|
||||||
|
"Chinese Mainland 🇨🇳",
|
||||||
|
...locations.map(({ name }) => name),
|
||||||
|
],
|
||||||
|
icon: `${BASE_ICON_SET_URL}Proxy.png`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name: "AUTO",
|
||||||
|
type: "url-test",
|
||||||
|
tolerance: 50,
|
||||||
|
"include-all": true,
|
||||||
|
icon: `${BASE_ICON_SET_URL}Auto.png`,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
...generateStaticProxyGroups(proxyGroupDefaults),
|
||||||
|
...generateServiceProxyGroups(services, proxyGroupDefaults),
|
||||||
|
{
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name: "Domestic",
|
||||||
|
type: "select",
|
||||||
|
proxies: [
|
||||||
|
"DIRECT",
|
||||||
|
"PROXY",
|
||||||
|
...staticProxyNames,
|
||||||
|
"AUTO",
|
||||||
|
"Chinese Mainland 🇨🇳",
|
||||||
|
...locations.map(({ name }) => name),
|
||||||
|
],
|
||||||
|
icon: `${BASE_ICON_SET_URL}Domestic.png`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name: "Others",
|
||||||
|
type: "select",
|
||||||
|
proxies: [...serviceProxyGroupProxies],
|
||||||
|
icon: `${BASE_ICON_SET_URL}Final.png`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...proxyGroupDefaults,
|
||||||
|
name: "Advertising",
|
||||||
|
type: "select",
|
||||||
|
proxies: ["REJECT", "DIRECT", ...staticProxyNames],
|
||||||
|
icon: `${BASE_ICON_SET_URL}Advertising.png`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...locationPolicyProxyGroupDefaults,
|
||||||
|
name: "Chinese Mainland 🇨🇳",
|
||||||
|
type: "select",
|
||||||
|
icon: `${LOCATION_ICON_SET_URL}china`,
|
||||||
|
proxies: ["REJECT"],
|
||||||
|
"include-all": true,
|
||||||
|
filter:
|
||||||
|
"(?i)\u5927\u9646|\u4e2d\u56fd|\u7535\u4fe1|\u79fb\u52a8|\u8054\u901a|Mainland|China|CN|mainland|china|cn|🇨🇳",
|
||||||
|
},
|
||||||
|
...generateLocationPolicyProxyGroups(
|
||||||
|
locations,
|
||||||
|
locationPolicyProxyGroupDefaults,
|
||||||
|
"url-test",
|
||||||
|
{
|
||||||
|
tolerance: 50,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...generateLocationPolicyProxyGroups(
|
||||||
|
locations,
|
||||||
|
locationPolicyProxyGroupDefaults,
|
||||||
|
"fallback",
|
||||||
|
),
|
||||||
|
...generateLocationPolicyProxyGroups(
|
||||||
|
locations,
|
||||||
|
locationPolicyProxyGroupDefaults,
|
||||||
|
"load-balance",
|
||||||
|
{
|
||||||
|
strategy: "round-robin",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...generateLocationPolicyProxyGroups(
|
||||||
|
locations,
|
||||||
|
locationPolicyProxyGroupDefaults,
|
||||||
|
"load-balance",
|
||||||
|
{
|
||||||
|
strategy: "consistent-hashing",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...generateLocationPolicyProxyGroups(
|
||||||
|
locations,
|
||||||
|
locationPolicyProxyGroupDefaults,
|
||||||
|
"load-balance",
|
||||||
|
{
|
||||||
|
strategy: "sticky-sessions",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...generateLocationSelectProxyGroups(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Routing Rules
|
||||||
|
|
||||||
|
function generateServiceRules(services) {
|
||||||
|
return services.map(
|
||||||
|
({ name }) => `RULE-SET,${name.toLowerCase().replace(/\s+/g, "")},${name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = [
|
||||||
|
"RULE-SET,applications,DIRECT",
|
||||||
|
"RULE-SET,win-phonelink,DIRECT",
|
||||||
|
"RULE-SET,lancidr,DIRECT,no-resolve",
|
||||||
|
"RULE-SET,private,DIRECT",
|
||||||
|
...generateServiceRules(services),
|
||||||
|
"RULE-SET,reject,Advertising",
|
||||||
|
"RULE-SET,win-spy,Advertising",
|
||||||
|
"RULE-SET,direct,Domestic",
|
||||||
|
"RULE-SET,proxy,PROXY",
|
||||||
|
"RULE-SET,telegramcidr,Telegram",
|
||||||
|
"RULE-SET,xcidr,X",
|
||||||
|
"RULE-SET,googlecidr,Google",
|
||||||
|
"RULE-SET,cloudflarecidr,Cloudflare",
|
||||||
|
"RULE-SET,cncidr,Domestic",
|
||||||
|
"MATCH,Others",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Rule Providers
|
||||||
|
|
||||||
|
const ruleProviderDefaults = {
|
||||||
|
type: "http",
|
||||||
|
interval: 14400,
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateServiceRuleProviders(services, defaultConfig) {
|
||||||
|
return services.reduce((acc, { name, alias }) => {
|
||||||
|
const fileName = (alias || name).toLowerCase();
|
||||||
|
const ruleName = name.toLowerCase().replace(/\s+/g, "");
|
||||||
|
acc[ruleName] = {
|
||||||
|
...defaultConfig,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: `https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/${fileName}.mrs`,
|
||||||
|
path: `./rulesets/${ruleName}.mrs`,
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleProviders = {
|
||||||
|
"fake-ip-filter": {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/fake-ip-filter.mrs",
|
||||||
|
path: "./rulesets/fake-ip-filter.mrs",
|
||||||
|
},
|
||||||
|
"connectivity-check": {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/connectivity-check.mrs",
|
||||||
|
path: "./rulesets/connectivity-check.mrs",
|
||||||
|
},
|
||||||
|
applications: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "yaml",
|
||||||
|
behavior: "classical",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/applications.yaml",
|
||||||
|
path: "./rulesets/applications.yaml",
|
||||||
|
},
|
||||||
|
"win-phonelink": {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "yaml",
|
||||||
|
behavior: "classical",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@fixed/win-phonelink.yaml",
|
||||||
|
path: "./rulesets/win-phonelink.yaml",
|
||||||
|
},
|
||||||
|
lancidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/lancidr.mrs",
|
||||||
|
path: "./rulesets/lancidr.mrs",
|
||||||
|
},
|
||||||
|
cncidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/cncidr.mrs",
|
||||||
|
path: "./rulesets/cncidr.mrs",
|
||||||
|
},
|
||||||
|
cloudflarecidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/cloudflarecidr.mrs",
|
||||||
|
path: "./rulesets/cloudflarecidr.mrs",
|
||||||
|
},
|
||||||
|
googlecidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/googlecidr.mrs",
|
||||||
|
path: "./rulesets/googlecidr.mrs",
|
||||||
|
},
|
||||||
|
telegramcidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/telegramcidr.mrs",
|
||||||
|
path: "./rulesets/telegramcidr.mrs",
|
||||||
|
},
|
||||||
|
xcidr: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "ipcidr",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/xcidr.mrs",
|
||||||
|
path: "./rulesets/xcidr.mrs",
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/private.mrs",
|
||||||
|
path: "./rulesets/private.mrs",
|
||||||
|
},
|
||||||
|
direct: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/direct.mrs",
|
||||||
|
path: "./rulesets/direct.mrs",
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/proxy.mrs",
|
||||||
|
path: "./rulesets/proxy.mrs",
|
||||||
|
},
|
||||||
|
"geolocation-cn": {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/geolocation-cn.mrs",
|
||||||
|
path: "./rulesets/geolocation-cn.mrs",
|
||||||
|
},
|
||||||
|
reject: {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/reject.mrs",
|
||||||
|
path: "./rulesets/reject.mrs",
|
||||||
|
},
|
||||||
|
"win-spy": {
|
||||||
|
...ruleProviderDefaults,
|
||||||
|
format: "mrs",
|
||||||
|
behavior: "domain",
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/win-spy.mrs",
|
||||||
|
path: "./rulesets/win-spy.mrs",
|
||||||
|
},
|
||||||
|
...generateServiceRuleProviders(services, ruleProviderDefaults),
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate configuration using the above settings
|
||||||
|
|
||||||
|
function validateOriginalConfig(config) {
|
||||||
|
if (!config)
|
||||||
|
throw new Error("Configuration object cannot be null or undefined");
|
||||||
|
const proxyCount = Array.isArray(config.proxies) ? config.proxies.length : 0;
|
||||||
|
const proxyProviderCount = Object.keys(
|
||||||
|
config["proxy-providers"] || {},
|
||||||
|
).length;
|
||||||
|
if (proxyCount === 0 && proxyProviderCount === 0)
|
||||||
|
throw new Error(
|
||||||
|
"The original configuration must contain a non-empty proxies array (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/) or a proxy-providers object with at least one property (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxy-providers/)",
|
||||||
|
);
|
||||||
|
if (proxyCount > 0) {
|
||||||
|
const proxiesWithoutRemoteEndpoint = new Set([
|
||||||
|
"direct",
|
||||||
|
"dns",
|
||||||
|
"pass",
|
||||||
|
"reject",
|
||||||
|
]);
|
||||||
|
|
||||||
|
config.proxies.forEach((proxy, index) => {
|
||||||
|
if (!proxy.name || !proxy.type) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid proxy number ${
|
||||||
|
index + 1
|
||||||
|
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!proxiesWithoutRemoteEndpoint.has(String(proxy.type).toLowerCase()) &&
|
||||||
|
(!proxy.server || proxy.port == null)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid proxy number ${
|
||||||
|
index + 1
|
||||||
|
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`The original configuration contains ${proxyCount} proxies, which will be preserved`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (proxyProviderCount > 0) {
|
||||||
|
Object.entries(config["proxy-providers"]).forEach(
|
||||||
|
([name, provider], index) => {
|
||||||
|
if (!provider.type || (provider.type === "http" && !provider.url)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid proxy provider number ${
|
||||||
|
index + 1
|
||||||
|
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxy-providers/)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`The original configuration contains ${proxyProviderCount} proxy providers, which will be preserved`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(config, options = {}) {
|
||||||
|
try {
|
||||||
|
if (!options.skipValidation) {
|
||||||
|
validateOriginalConfig(config);
|
||||||
|
}
|
||||||
|
Object.assign(config, generalConfig, {
|
||||||
|
dns,
|
||||||
|
hosts,
|
||||||
|
sniffer,
|
||||||
|
tun,
|
||||||
|
"proxy-groups": proxyGroups,
|
||||||
|
rules,
|
||||||
|
"rule-providers": ruleProviders,
|
||||||
|
});
|
||||||
|
console.log("The generated configuration is as follows");
|
||||||
|
console.log(config);
|
||||||
|
return config;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`An error occurred during configuration generation: ${error.message}`,
|
||||||
|
);
|
||||||
|
return { error: error.message, originalConfig: config };
|
||||||
|
}
|
||||||
|
}
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const vm = require("node:vm");
|
||||||
|
|
||||||
|
function loadMetaModule() {
|
||||||
|
const content = fs.readFileSync(path.join(__dirname, "META.js"), "utf8");
|
||||||
|
const wrapped = `${content}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
main,
|
||||||
|
validateOriginalConfig,
|
||||||
|
};`;
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
module: { exports: {} },
|
||||||
|
console: {
|
||||||
|
log() {},
|
||||||
|
warn() {},
|
||||||
|
error() {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runInNewContext(wrapped, context, { filename: "META.js" });
|
||||||
|
return context.module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runScenario(name, input, options) {
|
||||||
|
const { main } = loadMetaModule();
|
||||||
|
const config = main(structuredClone(input), options);
|
||||||
|
|
||||||
|
assert(!config.error, `${name}: ${config.error}`);
|
||||||
|
assert(Array.isArray(config.rules), `${name}: rules should be an array`);
|
||||||
|
assert(
|
||||||
|
Array.isArray(config["proxy-groups"]),
|
||||||
|
`${name}: proxy-groups should be an array`
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
config["rule-providers"] && typeof config["rule-providers"] === "object",
|
||||||
|
`${name}: rule-providers should be present`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
runScenario(
|
||||||
|
"proxy-based config",
|
||||||
|
{
|
||||||
|
proxies: [
|
||||||
|
{
|
||||||
|
name: "sample",
|
||||||
|
type: "socks5",
|
||||||
|
server: "127.0.0.1",
|
||||||
|
port: 1080,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
runScenario(
|
||||||
|
"provider-based config",
|
||||||
|
{
|
||||||
|
"proxy-providers": {
|
||||||
|
provider: {
|
||||||
|
type: "http",
|
||||||
|
url: "https://example.com/provider.yaml",
|
||||||
|
path: "./providers/provider.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
runScenario("publish generation", {}, { skipValidation: true });
|
||||||
|
|
||||||
|
console.log("Validated META.js against 3 scenarios.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
main();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const vm = require("node:vm");
|
||||||
|
|
||||||
|
const repoRoot = __dirname;
|
||||||
|
|
||||||
|
function loadMetaModule() {
|
||||||
|
const content = fs.readFileSync(path.join(repoRoot, "META.js"), "utf8");
|
||||||
|
const wrapped = `${content}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
locations,
|
||||||
|
generalConfig,
|
||||||
|
tun,
|
||||||
|
main,
|
||||||
|
validateOriginalConfig,
|
||||||
|
};`;
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
module: { exports: {} },
|
||||||
|
console: {
|
||||||
|
log() {},
|
||||||
|
warn() {},
|
||||||
|
error() {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runInNewContext(wrapped, context, { filename: "META.js" });
|
||||||
|
return context.module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mihomoRegex(source) {
|
||||||
|
return new RegExp(source.replace(/^\(\?i\)/, ""), "i");
|
||||||
|
}
|
||||||
|
|
||||||
|
test("validateOriginalConfig accepts mihomo dns outbound proxies", () => {
|
||||||
|
const { validateOriginalConfig } = loadMetaModule();
|
||||||
|
|
||||||
|
assert.doesNotThrow(() =>
|
||||||
|
validateOriginalConfig({
|
||||||
|
proxies: [{ name: "dns-out", type: "dns" }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generated config does not force deprecated global fingerprinting", () => {
|
||||||
|
const { main } = loadMetaModule();
|
||||||
|
|
||||||
|
const config = main({
|
||||||
|
proxies: [
|
||||||
|
{
|
||||||
|
name: "sample",
|
||||||
|
type: "socks5",
|
||||||
|
server: "127.0.0.1",
|
||||||
|
port: 1080,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(config["global-client-fingerprint"], undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("main can skip source validation for publish-time generation", () => {
|
||||||
|
const { main } = loadMetaModule();
|
||||||
|
|
||||||
|
const config = main({}, { skipValidation: true });
|
||||||
|
|
||||||
|
assert.equal(config.error, undefined);
|
||||||
|
assert.ok(Array.isArray(config.rules));
|
||||||
|
assert.ok(Array.isArray(config["proxy-groups"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generated tun config fully replaces the source tun settings", () => {
|
||||||
|
const { main } = loadMetaModule();
|
||||||
|
|
||||||
|
const config = main({
|
||||||
|
proxies: [
|
||||||
|
{
|
||||||
|
name: "sample",
|
||||||
|
type: "socks5",
|
||||||
|
server: "127.0.0.1",
|
||||||
|
port: 1080,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tun: {
|
||||||
|
enable: false,
|
||||||
|
"auto-route": false,
|
||||||
|
"auto-redirect": true,
|
||||||
|
mtu: 9000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(JSON.parse(JSON.stringify(config.tun)), {
|
||||||
|
enable: true,
|
||||||
|
stack: "mixed",
|
||||||
|
"auto-route": true,
|
||||||
|
"auto-detect-interface": true,
|
||||||
|
"strict-route": true,
|
||||||
|
"dns-hijack": ["any:53", "tcp://any:53"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("location filters avoid matching unrelated country names", () => {
|
||||||
|
const { locations } = loadMetaModule();
|
||||||
|
|
||||||
|
const india = locations.find((location) => location.name === "India 🇮🇳");
|
||||||
|
const canada = locations.find((location) => location.name === "Canada 🇨🇦");
|
||||||
|
|
||||||
|
assert.ok(india, "India location should exist");
|
||||||
|
assert.ok(canada, "Canada location should exist");
|
||||||
|
|
||||||
|
assert.equal(mihomoRegex(india.filter).test("Argentina 01"), false);
|
||||||
|
assert.equal(mihomoRegex(india.filter).test("Finland-IEPL"), false);
|
||||||
|
assert.equal(mihomoRegex(canada.filter).test("Singapore-Anycast"), false);
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "meta",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.8.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"yaml": ["yaml@2.8.3", "", { "bin": "bin.mjs" }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[install]
|
||||||
|
auto = "disable"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"generate": "bun META.generate.js",
|
||||||
|
"test": "bun test META.test.js workflow.test.js",
|
||||||
|
"run": "bun META.run.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const childProcess = require("node:child_process");
|
||||||
|
|
||||||
|
const repoRoot = __dirname;
|
||||||
|
const workflowPath = path.join(repoRoot, ".github", "workflows", "run.yml");
|
||||||
|
const runScriptPath = path.join(repoRoot, "META.run.js");
|
||||||
|
|
||||||
|
function runBun(args) {
|
||||||
|
return childProcess.spawnSync("bun", args, {
|
||||||
|
cwd: repoRoot,
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDependenciesInstalled() {
|
||||||
|
const result = runBun(["install", "--frozen-lockfile"]);
|
||||||
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("workflow runs CI before run and publish jobs", () => {
|
||||||
|
const workflow = fs.readFileSync(workflowPath, "utf8");
|
||||||
|
|
||||||
|
assert.match(workflow, /^ ci:\s*$/m, "workflow should define a ci job");
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/uses:\s+oven-sh\/setup-bun@v2/,
|
||||||
|
"workflow should install Bun via the official setup action"
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/^ run:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+ci\s*$/m,
|
||||||
|
"run job should depend on ci"
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/^ purge-dns-cache:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+run\s*$/m,
|
||||||
|
"purge-dns-cache should depend on run"
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/^ generate-config:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+run\s*$/m,
|
||||||
|
"generate-config should depend on run"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("workflow installs dependencies and runs tests in ci job", () => {
|
||||||
|
const workflow = fs.readFileSync(workflowPath, "utf8");
|
||||||
|
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/bun install --frozen-lockfile/,
|
||||||
|
"ci job should install dependencies with Bun"
|
||||||
|
);
|
||||||
|
assert.match(workflow, /bun test/, "ci job should run the test suite with Bun");
|
||||||
|
assert.match(workflow, /bun run run/, "run job should use Bun scripts");
|
||||||
|
assert.match(
|
||||||
|
workflow,
|
||||||
|
/bun run generate/,
|
||||||
|
"generate-config job should use Bun scripts"
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
workflow,
|
||||||
|
/sed -i .*validateOriginalConfig/,
|
||||||
|
"workflow should not mutate META.js to bypass validation"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("META.run.js executes the generator against sample inputs", () => {
|
||||||
|
const result = runBun([runScriptPath]);
|
||||||
|
|
||||||
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||||
|
assert.match(
|
||||||
|
result.stdout,
|
||||||
|
/validated/i,
|
||||||
|
"run script should report successful validation"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("META.generate.js creates YAML output without mutating source", () => {
|
||||||
|
const outputPath = path.join(repoRoot, "META.yaml");
|
||||||
|
fs.rmSync(outputPath, { force: true });
|
||||||
|
ensureDependenciesInstalled();
|
||||||
|
|
||||||
|
const result = runBun(["META.generate.js"]);
|
||||||
|
|
||||||
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||||
|
assert.ok(fs.existsSync(outputPath), "generate script should create META.yaml");
|
||||||
|
assert.match(
|
||||||
|
fs.readFileSync(outputPath, "utf8"),
|
||||||
|
/mixed-port:\s*7890/,
|
||||||
|
"generated YAML should include core config values"
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.rmSync(outputPath, { force: true });
|
||||||
|
});
|
||||||
Reference in new issue
Block a user