From 855d875779a0ac6b9ad1947ac8047b77cd40c9c6 Mon Sep 17 00:00:00 2001 From: Wikeolf Date: Sun, 22 Dec 2024 15:59:52 +0800 Subject: [PATCH 1/2] feat: add keyword filters for node subscription --- src/BaseConfigBuilder.js | 11 ++++++----- src/ClashConfigBuilder.js | 7 +++++-- src/SingboxConfigBuilder.js | 9 ++++++--- src/htmlBuilder.js | 22 +++++++++++++++++++--- src/index.js | 10 ++++++---- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/BaseConfigBuilder.js b/src/BaseConfigBuilder.js index 0c087e14..7d0a4d6f 100644 --- a/src/BaseConfigBuilder.js +++ b/src/BaseConfigBuilder.js @@ -2,20 +2,21 @@ import { ProxyParser } from './ProxyParsers.js'; import { DeepCopy } from './utils.js'; export class BaseConfigBuilder { - constructor(inputString, baseConfig) { + constructor(inputString, filters, baseConfig) { this.inputString = inputString; this.config = DeepCopy(baseConfig); this.customRules = []; + this.filters = filters; } async build() { - const customItems = await this.parseCustomItems(); + const customItems = await this.parseCustomItems(this.filters); this.addCustomItems(customItems); this.addSelectors(); return this.formatConfig(); } - async parseCustomItems() { + async parseCustomItems(filters) { const urls = this.inputString.split('\n').filter(url => url.trim() !== ''); const parsedItems = []; @@ -24,11 +25,11 @@ export class BaseConfigBuilder { if (Array.isArray(result)) { for (const subUrl of result) { const subResult = await ProxyParser.parse(subUrl); - if (subResult) { + if (subResult && !filters.some(filter => subResult.tag.includes(filter))) { parsedItems.push(subResult); } } - } else if (result) { + } else if (result && !filters.some(filter => result.tag.includes(filter))) { parsedItems.push(result); } } diff --git a/src/ClashConfigBuilder.js b/src/ClashConfigBuilder.js index 67036b05..0c5d2934 100644 --- a/src/ClashConfigBuilder.js +++ b/src/ClashConfigBuilder.js @@ -4,11 +4,14 @@ import { BaseConfigBuilder } from './BaseConfigBuilder.js'; import { DeepCopy } from './utils.js'; export class ClashConfigBuilder extends BaseConfigBuilder { - constructor(inputString, selectedRules, customRules, pin, baseConfig) { + constructor(inputString, filters, selectedRules, customRules, pin, baseConfig) { if (!baseConfig) { baseConfig = CLASH_CONFIG } - super(inputString, baseConfig); + if (!filters) { + filters = []; + } + super(inputString, filters ,baseConfig); this.selectedRules = selectedRules; this.customRules = customRules; this.pin = pin; diff --git a/src/SingboxConfigBuilder.js b/src/SingboxConfigBuilder.js index a4f2ce55..ee3c8147 100644 --- a/src/SingboxConfigBuilder.js +++ b/src/SingboxConfigBuilder.js @@ -3,11 +3,14 @@ import { BaseConfigBuilder } from './BaseConfigBuilder.js'; import { DeepCopy } from './utils.js'; export class ConfigBuilder extends BaseConfigBuilder { - constructor(inputString, selectedRules, customRules, pin, baseConfig) { + constructor(inputString, filters, selectedRules, customRules, pin, baseConfig) { if (baseConfig === undefined) { - baseConfig = SING_BOX_CONFIG + baseConfig = SING_BOX_CONFIG; } - super(inputString, baseConfig); + if (!filters) { + filters = []; + } + super(inputString, filters ,baseConfig); this.selectedRules = selectedRules; this.customRules = customRules; this.pin = pin; diff --git a/src/htmlBuilder.js b/src/htmlBuilder.js index 7d084820..3069ff26 100644 --- a/src/htmlBuilder.js +++ b/src/htmlBuilder.js @@ -78,6 +78,8 @@ const generateForm = () => `
Share URLs
+
Filter Keywords
+
@@ -468,9 +470,11 @@ const submitFormFunction = () => ` const form = event.target; const formData = new FormData(form); const inputString = formData.get('input'); + const filters = formData.get('filters') ? formData.get('filters').split(',') : ''; // Save form data to localStorage localStorage.setItem('inputTextarea', inputString); + localStorage.setItem('inputFilter', filters); localStorage.setItem('advancedToggle', document.getElementById('advancedToggle').checked); localStorage.setItem('crpinToggle', document.getElementById('crpinToggle').checked); @@ -503,9 +507,14 @@ const submitFormFunction = () => ` const configParam = configId ? \`&configId=\${configId}\` : ''; const xrayUrl = \`\${window.location.origin}/xray?config=\${encodeURIComponent(inputString)}\${configParam}\`; - const singboxUrl = \`\${window.location.origin}/singbox?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; - const clashUrl = \`\${window.location.origin}/clash?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; - + let singboxUrl, clashUrl; + if (filters.length > 0) { + singboxUrl = \`\${window.location.origin}/singbox?config=\${encodeURIComponent(inputString)}&filters=\${encodeURIComponent(JSON.stringify(filters))}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; + clashUrl = \`\${window.location.origin}/clash?config=\${encodeURIComponent(inputString)}&filters=\${encodeURIComponent(JSON.stringify(filters))}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; + } else { + singboxUrl = \`\${window.location.origin}/singbox?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; + clashUrl = \`\${window.location.origin}/clash?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\${configParam}\`; + } document.getElementById('xrayLink').value = xrayUrl; document.getElementById('singboxLink').value = singboxUrl; document.getElementById('clashLink').value = clashUrl; @@ -524,6 +533,11 @@ const submitFormFunction = () => ` if (savedInput) { document.getElementById('inputTextarea').value = savedInput; } + + const savedFilter = localStorage.getItem('inputFilter'); + if (savedFilter) { + document.getElementById('inputFilter').value = savedFilter; + } const advancedToggle = localStorage.getItem('advancedToggle'); if (advancedToggle) { @@ -579,6 +593,7 @@ const submitFormFunction = () => ` function clearFormData() { localStorage.removeItem('inputTextarea'); + localStorage.removeItem('inputFilter'); localStorage.removeItem('advancedToggle'); localStorage.removeItem('selectedRules'); localStorage.removeItem('predefinedRules'); @@ -586,6 +601,7 @@ const submitFormFunction = () => ` localStorage.removeItem('configType'); // 添加清除 configType document.getElementById('inputTextarea').value = ''; + document.getElementById('inputFilter').value = ''; document.getElementById('advancedToggle').checked = false; document.getElementById('advancedOptions').classList.remove('show'); document.getElementById('configEditor').value = ''; diff --git a/src/index.js b/src/index.js index 6a52dd6a..a8f3fccf 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ async function handleRequest(request) { } else if (request.method === 'POST' && url.pathname === '/') { const formData = await request.formData(); const inputString = formData.get('input'); + const filters = formData.getAll('filters[]'); const selectedRules = formData.getAll('selectedRules'); const customRuleDomains = formData.getAll('customRuleSite[]'); const customRuleIPs = formData.getAll('customRuleIP[]'); @@ -40,14 +41,15 @@ async function handleRequest(request) { const rulesToUse = selectedRules.length > 0 ? selectedRules : ['广告拦截', '谷歌服务', '国外媒体', '电报消息']; const xrayUrl = `${url.origin}/xray?config=${encodeURIComponent(inputString)}`; - const singboxUrl = `${url.origin}/singbox?config=${encodeURIComponent(inputString)}&selectedRules=${encodeURIComponent(JSON.stringify(rulesToUse))}&customRules=${encodeURIComponent(JSON.stringify(customRules))}pin=${pin}`; - const clashUrl = `${url.origin}/clash?config=${encodeURIComponent(inputString)}&selectedRules=${encodeURIComponent(JSON.stringify(rulesToUse))}&customRules=${encodeURIComponent(JSON.stringify(customRules))}pin=${pin}`; + const singboxUrl = `${url.origin}/singbox?config=${encodeURIComponent(inputString)}&filters=${encodeURIComponent(JSON.stringify(filters))}&selectedRules=${encodeURIComponent(JSON.stringify(rulesToUse))}&customRules=${encodeURIComponent(JSON.stringify(customRules))}pin=${pin}`; + const clashUrl = `${url.origin}/clash?config=${encodeURIComponent(inputString)}&filters=${encodeURIComponent(JSON.stringify(filters))}&selectedRules=${encodeURIComponent(JSON.stringify(rulesToUse))}&customRules=${encodeURIComponent(JSON.stringify(customRules))}pin=${pin}`; return new Response(generateHtml(xrayUrl, singboxUrl, clashUrl), { headers: { 'Content-Type': 'text/html' } }); } else if (url.pathname.startsWith('/singbox') || url.pathname.startsWith('/clash')) { const inputString = url.searchParams.get('config'); + let filters = JSON.parse(url.searchParams.get('filters')); let selectedRules = url.searchParams.get('selectedRules'); let customRules = url.searchParams.get('customRules'); let pin = url.searchParams.get('pin'); @@ -89,9 +91,9 @@ async function handleRequest(request) { // Env pin is use to pin customRules to top let configBuilder; if (url.pathname.startsWith('/singbox')) { - configBuilder = new ConfigBuilder(inputString, selectedRules, customRules, pin, baseConfig); + configBuilder = new ConfigBuilder(inputString, filters, selectedRules, customRules, pin, baseConfig); } else { - configBuilder = new ClashConfigBuilder(inputString, selectedRules, customRules, pin, baseConfig); + configBuilder = new ClashConfigBuilder(inputString, filters, selectedRules, customRules, pin, baseConfig); } const config = await configBuilder.build(); From 71f53517202c20728a5fd18f82020237b80b54da Mon Sep 17 00:00:00 2001 From: Wikeolf Date: Sun, 22 Dec 2024 16:00:54 +0800 Subject: [PATCH 2/2] doc: add a note about the filters field --- docs/API-doc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/API-doc.md b/docs/API-doc.md index da8e8617..cc9d9319 100644 --- a/docs/API-doc.md +++ b/docs/API-doc.md @@ -24,6 +24,7 @@ https://your-worker-domain.workers.dev - **方法**: GET - **参数**: - `config` (必需): URL 编码的字符串,包含一个或多个代理配置 + - `filters` (可选): 一个或多个关键词,用逗号分隔,用于过滤包含关键词的节点,不支持正则表达式 - `selectedRules` (可选): 预定义规则集名称或自定义规则的 JSON 数组 - `customRules` (可选): 自定义规则的 JSON 数组 - `pin` (可选): 布尔值,是否将自定义规则置于预定义规则之上