Add rule storage script.

This commit is contained in:
NobyDa 2024-02-24 09:46:43 +08:00
parent 952ba7689f
commit bff207400d
No known key found for this signature in database
GPG Key ID: E6B0AC788D373C5B
2 changed files with 165 additions and 0 deletions

View File

@ -0,0 +1,26 @@
DOMAIN-KEYWORD,.a
DOMAIN-KEYWORD,.b
DOMAIN-KEYWORD,.c
DOMAIN-KEYWORD,.d
DOMAIN-KEYWORD,.e
DOMAIN-KEYWORD,.f
DOMAIN-KEYWORD,.g
DOMAIN-KEYWORD,.h
DOMAIN-KEYWORD,.i
DOMAIN-KEYWORD,.j
DOMAIN-KEYWORD,.k
DOMAIN-KEYWORD,.l
DOMAIN-KEYWORD,.m
DOMAIN-KEYWORD,.n
DOMAIN-KEYWORD,.o
DOMAIN-KEYWORD,.p
DOMAIN-KEYWORD,.q
DOMAIN-KEYWORD,.r
DOMAIN-KEYWORD,.s
DOMAIN-KEYWORD,.t
DOMAIN-KEYWORD,.u
DOMAIN-KEYWORD,.v
DOMAIN-KEYWORD,.w
DOMAIN-KEYWORD,.x
DOMAIN-KEYWORD,.y
DOMAIN-KEYWORD,.z

View File

@ -0,0 +1,139 @@
/*
Surge规则自动生成脚本
更新时间2024/02/24
需按照博客内教程配合使用
https://nobyda.github.io/2024/02/24/Surge_Rule_Storage
*/
const args = argsList(typeof $argument == "string" && $argument || 'region=debug');
/*
When matching whitelist rules, skip generating suffix domain. Three ways to write:
Domain: example.com
Domain suffix: .example.com
Domain keyword: .example.
*/
args.whitelist = args.whitelist || `[".mwcname.com", ".akadns.", ".akamai.", ".cloud.", ".cdn.", ".yun."]`;
args.key = args.key || 'Rule-Storage';
(async () => {
const host = $request.hostname.toLowerCase();
if (['127.0.0.1', '0.0.0.0'].filter((v) => [...$request.dnsResult.v4Addresses].includes(v)).length) {
// DNS poisoning
args.matched = false;
args.region = 'global';
}
if (!/\d$|:/.test(host) && host.includes('.')) {
const data = JSON.parse($persistentStore.read(args.key) || '{}');
const saved_rules = $persistentStore.read(`${args.key}-${args.region}`);
if (!evalRules(host, saved_rules)) {
data[args.region] = saveDecision(host, data[args.region]);
if (data[args.region][host].quantity >= (args.quantity || 10)) {
data.eTLD = await eTLD(data.eTLD);
const suffix = shortenDomain(host, data.eTLD.public_suffix);
const domain = evalRules(host, JSON.parse(args.whitelist)) ? host : suffix;
const text = [...formatRules(saved_rules), ...formatRules(domain)].join('\n');
delete data[args.region][host];
$persistentStore.write(text, `${args.key}-${args.region}`)
}
}
return $persistentStore.write(JSON.stringify(data, null, 2), args.key)
}
})().catch((e) => $notification.post(args.key, ``, e.message || e))
.finally(() => $done({ matched: Boolean(args.matched) }));
function saveDecision(host_name, content = {}) {
for (const i in content) {
if (Date.now() - content[i].update_time > 86400000 * 30) {
delete content[i];
}
}
if (content[host_name]) {
if (Date.now() - content[host_name].update_time > ((args.interval || 30) * 1000)) {
content[host_name].update_time = Date.now();
content[host_name].quantity++;
}
} else {
content[host_name] = { update_time: Date.now(), quantity: 1 }
}
return content
}
function evalRules(host_name, rule_list) {
const host_suffix = host_name.split('.').reverse();
rule_list = typeof rule_list == 'object' ? rule_list : formatRules(rule_list, 1);
for (const i in rule_list) {
if (rule_list[i].startsWith('.') && !rule_list[i].endsWith('.')) {
const rule_host_suffix = rule_list[i].split('.').reverse().filter((v) => v);
if (rule_host_suffix.filter((v, i) => host_suffix[i] === v).length === rule_host_suffix.length) {
return true
}
} else if (rule_list[i].startsWith('.') && rule_list[i].endsWith('.')) {
if (host_name.includes(rule_list[i].slice(1, -1))) {
return true
}
} else if (rule_list[i] === host_name) {
return true
}
}
return false
}
function formatRules(list, type) {
return (list || '').replace(/\r|\ |(\/\/|#|;).*/g, '').split('\n').map((v) => {
if (v.startsWith('DOMAIN,')) { return type ? v.split(",")[1] : v }
if (v.startsWith('DOMAIN-SUFFIX,')) { return type ? `.${v.split(",")[1]}` : v }
if (v.startsWith('.')) { return type ? v : `DOMAIN-SUFFIX,${v.slice(1)}` }
if (v.includes('.')) { return type ? v : `DOMAIN,${v}` }
}).filter((v) => v);
}
async function eTLD(content = {}) {
if (!content.update_time || (Date.now() - content.update_time > 86400000 * 30)) {
await new Promise(resolve => {
$httpClient.get({
url: 'https://publicsuffix.org/list/public_suffix_list.dat'
}, (error, resp, body) => {
if (resp.status == 200 && !error && body) {
content.update_time = Date.now();
content.public_suffix = body.replace(/\r|.*(\/\/|#|;).*|\n(\!|\*\.)/g, '\n').split('\n').filter((t) => t);
resolve()
} else if (content.update_time) {
console.log(`Update eTLD list failed: ${error}`);
resolve()
} else {
throw new Error(`Download eTLD list failed: ${error}`)
}
})
})
}
return content
}
/*
Shorten multi level domain: non-eTLD, full eTLD, second level domain will return original
Basic logic: www.abc.com -> .abc.com
*/
function shortenDomain(host_name, eTLD_list) {
return host_name.split('.').reverse().reduce((t, v, i, c) => {
if (t === host_name || c.length == 2) { return host_name }
if (t.startsWith('.')) { return t }
const host_suffix = v + (t && `.${t}` || '');
for (const ix in eTLD_list) {
if (eTLD_list[ix] === host_suffix) {
return host_suffix
}
}
return !i && host_name || `.${host_suffix}`
}, '')
}
function argsList(data) {
return Array.from(
data.split("&")
.map((i) => i.split("="))
.map(([k, v]) => [k, decodeURIComponent(v)])
)
.reduce((a, [k, v]) => Object.assign(a, { [k]: v }), {})
}