Skip to content

Commit

Permalink
Merge pull request 7Sageer#41 from Wikeolf/domainkeyword
Browse files Browse the repository at this point in the history
feat: add domain keyword custom rules array and allow user pin all custom rules
  • Loading branch information
7Sageer authored Sep 28, 2024
2 parents 52ba4f6 + 4e09014 commit 8b12280
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 17 deletions.
7 changes: 5 additions & 2 deletions doc/API-doc-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ In addition to using predefined rule sets, you can provide a list of custom rule
- `sites`: Array of domain rules
- `ips`: Array of IP rules
- `domain_suffix`: Array of domain suffix rules
- `domain_keyword`: Array of domain keyword rules
- `ip_cidr`: Array of IP CIDR rules
- `outbound`: Outbound name

Expand All @@ -123,11 +124,13 @@ Example:
"sites": ["google", "anthropic"],
"ips": ["private", "cn"],
"domain_suffix": [".com", ".org"],
"domain_keyword": ["Mijia Cloud", "push.apple"],
"ip_cidr": ["192.168.0.0/16", "10.0.0.0/8"],
"outbound": "🤪 MyCustomRule"
}
]
```
You can also use the `pin` parameter to place a custom rule on top of a predefined rule in order for the custom rule to take effect.

## Error Handling

Expand All @@ -151,9 +154,9 @@ The API will return appropriate HTTP status codes and error messages when proble
/singbox?config=vmess%3A%2F%2Fexample&selectedRules=balanced
```

2. Generate a Clash configuration with custom rules:
2. Generate a Clash configuration with custom rules of the top:
```
/clash?config=vless%3A%2F%2Fexample&customRules=%5B%7B%22sites%22%3A%5B%22example.com%22%5D%2C%22ips%22%3A%5B%22192.168.1.1%22%5D%2C%22domain_suffix%22%3A%5B%22.com%22%5D%2C%22ip_cidr%22%3A%5B%2210.0.0.0%2F8%22%5D%2C%22outbound%22%3A%22MyCustomRule%22%7D%5D
/clash?config=vless%3A%2F%2Fexample&customRules=%5B%7B%22sites%22%3A%5B%22example.com%22%5D%2C%22ips%22%3A%5B%22192.168.1.1%22%5D%2C%22domain_suffix%22%3A%5B%22.com%22%5D%2C%22domain_keyword%22%3A%5B%22Mijia%20Cloud%22%5D%2C%22ip_cidr%22%3A%5B%2210.0.0.0%2F8%22%5D%2C%22outbound%22%3A%22MyCustomRule%22%7D%5D&pin=true
```

3. Shorten a URL:
Expand Down
7 changes: 5 additions & 2 deletions doc/API-doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Singbox 的规则集来自 [https://github.com/lyc8503/sing-box-rules](https://g
- `sites`: 域名规则数组
- `ips`: IP 规则数组
- `domain_suffix`: 域名后缀规则数组
- `domain_keyword`: 域名关键词规则数组
- `ip_cidr`: IP CIDR 规则数组
- `outbound`: 出站名称

Expand All @@ -123,11 +124,13 @@ Singbox 的规则集来自 [https://github.com/lyc8503/sing-box-rules](https://g
"sites": ["google", "anthropic"],
"ips": ["private", "cn"],
"domain_suffix": [".com", ".org"],
"domain_keyword": ["Mijia Cloud", "push.apple"],
"ip_cidr": ["192.168.0.0/16", "10.0.0.0/8"],
"outbound": "🤪 MyCustomRule"
}
]
```
您还可以使用 `pin` 参数将自定义规则置于预定义规则之上,以便自定义规则生效。

## 错误处理

Expand All @@ -151,9 +154,9 @@ API 在出现问题时将返回适当的 HTTP 状态码和错误消息:
/singbox?config=vmess%3A%2F%2Fexample&selectedRules=balanced
```

2. 生成带有自定义规则的 Clash 配置:
2. 生成带有置顶自定义规则的 Clash 配置:
```
/clash?config=vless%3A%2F%2Fexample&customRules=%5B%7B%22sites%22%3A%5B%22example.com%22%5D%2C%22ips%22%3A%5B%22192.168.1.1%22%5D%2C%22domain_suffix%22%3A%5B%22.com%22%5D%2C%22ip_cidr%22%3A%5B%2210.0.0.0%2F8%22%5D%2C%22outbound%22%3A%22MyCustomRule%22%7D%5D
/clash?config=vless%3A%2F%2Fexample&customRules=%5B%7B%22sites%22%3A%5B%22example.com%22%5D%2C%22ips%22%3A%5B%22192.168.1.1%22%5D%2C%22domain_suffix%22%3A%5B%22.com%22%5D%2C%22domain_keyword%22%3A%5B%22Mijia%20Cloud%22%5D%2C%22ip_cidr%22%3A%5B%2210.0.0.0%2F8%22%5D%2C%22outbound%22%3A%22MyCustomRule%22%7D%5D&pin=true
```

3. 缩短 URL:
Expand Down
8 changes: 5 additions & 3 deletions src/ClashConfigBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { BaseConfigBuilder } from './BaseConfigBuilder.js';
import { DeepCopy } from './utils.js';

export class ClashConfigBuilder extends BaseConfigBuilder {
constructor(inputString, selectedRules, customRules) {
constructor(inputString, selectedRules, customRules, pin) {
super(inputString, CLASH_CONFIG);
this.selectedRules = selectedRules;
this.customRules = customRules;
this.pin = pin;
}

addCustomItems(customItems) {
Expand Down Expand Up @@ -75,14 +76,15 @@ export class ClashConfigBuilder extends BaseConfigBuilder {
});
}
formatConfig() {
const rules = generateRules(this.selectedRules, this.customRules);
const rules = generateRules(this.selectedRules, this.customRules, this.pin);

this.config.rules = rules.flatMap(rule => {
const siteRules = rule.site_rules[0] !== '' ? rule.site_rules.map(site => `GEOSITE,${site},${rule.outbound}`) : [];
const ipRules = rule.ip_rules[0] !== '' ? rule.ip_rules.map(ip => `GEOIP,${ip},${rule.outbound}`) : [];
const domainSuffixRules = rule.domain_suffix ? rule.domain_suffix.map(suffix => `DOMAIN-SUFFIX,${suffix},${rule.outbound}`) : [];
const domainKeywordRules = rule.domain_keyword ? rule.domain_keyword.map(keyword => `DOMAIN-KEYWORD,${keyword},${rule.outbound}`) : [];
const ipCidrRules = rule.ip_cidr ? rule.ip_cidr.map(cidr => `IP-CIDR,${cidr},${rule.outbound}`) : [];
return [...siteRules, ...ipRules, ...domainSuffixRules, ...ipCidrRules];
return [...siteRules, ...ipRules, ...domainSuffixRules, ...domainKeywordRules, ...ipCidrRules];
});

// Add the final catch-all rule
Expand Down
6 changes: 4 additions & 2 deletions src/SingboxConfigBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { BaseConfigBuilder } from './BaseConfigBuilder.js';
import { DeepCopy } from './utils.js';

export class ConfigBuilder extends BaseConfigBuilder {
constructor(inputString, selectedRules, customRules) {
constructor(inputString, selectedRules, customRules, pin) {
super(inputString, SING_BOX_CONFIG);
this.selectedRules = selectedRules;
this.customRules = customRules;
this.pin = pin;
}

addCustomItems(customItems) {
Expand Down Expand Up @@ -69,7 +70,7 @@ export class ConfigBuilder extends BaseConfigBuilder {
}

formatConfig() {
const rules = generateRules(this.selectedRules, this.customRules);
const rules = generateRules(this.selectedRules, this.customRules, this.pin);
const { site_rule_sets, ip_rule_sets } = generateRuleSets(this.selectedRules,this.customRules);

this.config.route.rule_set = [...site_rule_sets, ...ip_rule_sets];
Expand All @@ -80,6 +81,7 @@ export class ConfigBuilder extends BaseConfigBuilder {
...(rule.ip_rules.filter(ip => ip.trim() !== '').map(ip => `${ip}-ip`))
],
domain_suffix: rule.domain_suffix,
domain_keyword: rule.domain_keyword,
ip_cidr: rule.ip_cidr,
outbound: rule.outbound
}));
Expand Down
18 changes: 16 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function getOutbounds(selectedRuleNames) {
}

// Helper function to generate rules based on selected rule names
export function generateRules(selectedRules = [], customRules = []) {
export function generateRules(selectedRules = [], customRules = [], pin) {
if (typeof selectedRules === 'string' && PREDEFINED_RULE_SETS[selectedRules]) {
selectedRules = PREDEFINED_RULE_SETS[selectedRules];
}
Expand All @@ -174,17 +174,31 @@ export function generateRules(selectedRules = [], customRules = []) {
}
});

if (customRules && customRules.length > 0) {
if (customRules && customRules.length > 0 && pin !== "true") {
customRules.forEach((rule) => {
rules.push({
site_rules: rule.site.split(','),
ip_rules: rule.ip.split(','),
domain_suffix: rule.domain_suffix ? rule.domain_suffix.split(',') : [],
domain_keyword: rule.domain_keyword ? rule.domain_keyword.split(',') : [],
ip_cidr: rule.ip_cidr ? rule.ip_cidr.split(',') : [],
outbound: rule.name
});
});
}
else if (customRules && customRules.length > 0 && pin === "true") {
customRules.reverse();
customRules.forEach((rule) => {
rules.unshift({
site_rules: rule.site.split(','),
ip_rules: rule.ip.split(','),
domain_suffix: rule.domain_suffix ? rule.domain_suffix.split(',') : [],
domain_keyword: rule.domain_keyword ? rule.domain_keyword.split(',') : [],
ip_cidr: rule.ip_cidr ? rule.ip_cidr.split(',') : [],
outbound: rule.name
});
});
}

return rules;
}
Expand Down
17 changes: 15 additions & 2 deletions src/htmlBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,10 @@ const generateRuleSetSelection = () => `
</div>
<div class="mt-4">
<h4>Custom Rules</h4>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="crpinToggle">
<label class="form-check-label" for="crpinToggle">Pin Custom Rules</label>
</div>
<div id="customRules">
<!-- Custom rules will be dynamically added here -->
</div>
Expand Down Expand Up @@ -708,6 +712,7 @@ const submitFormFunction = () => `
// Save form data to localStorage
localStorage.setItem('inputTextarea', inputString);
localStorage.setItem('advancedToggle', document.getElementById('advancedToggle').checked);
localStorage.setItem('crpinToggle', document.getElementById('crpinToggle').checked);
saveSelectedRules();
let selectedRules;
Expand All @@ -718,18 +723,21 @@ const submitFormFunction = () => `
selectedRules = Array.from(document.querySelectorAll('input[name="selectedRules"]:checked'))
.map(checkbox => checkbox.value);
}
let pin = document.getElementById('crpinToggle').checked;
const customRules = Array.from(document.querySelectorAll('.custom-rule')).map(rule => ({
site: rule.querySelector('input[name="customRuleSite[]"]').value,
ip: rule.querySelector('input[name="customRuleIP[]"]').value,
name: rule.querySelector('input[name="customRuleName[]"]').value,
domain_suffix: rule.querySelector('input[name="customRuleDomainSuffix[]"]').value,
domain_keyword: rule.querySelector('input[name="customRuleDomainKeyword[]"]').value,
ip_cidr: rule.querySelector('input[name="customRuleIPCIDR[]"]').value
}));
const xrayUrl = \`\${window.location.origin}/xray?config=\${encodeURIComponent(inputString)}\`;
const singboxUrl = \`\${window.location.origin}/singbox?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}\`;
const clashUrl = \`\${window.location.origin}/clash?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}\`;
const singboxUrl = \`\${window.location.origin}/singbox?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\`;
const clashUrl = \`\${window.location.origin}/clash?config=\${encodeURIComponent(inputString)}&selectedRules=\${encodeURIComponent(JSON.stringify(selectedRules))}&customRules=\${encodeURIComponent(JSON.stringify(customRules))}&pin=\${pin}\`;
document.getElementById('xrayLink').value = xrayUrl;
document.getElementById('singboxLink').value = singboxUrl;
Expand Down Expand Up @@ -796,6 +804,7 @@ const submitFormFunction = () => `
document.getElementById('advancedOptions').classList.remove('show');
document.querySelectorAll('input[name="selectedRules"]').forEach(checkbox => checkbox.checked = false);
document.getElementById('predefinedRules').value = 'custom';
document.getElementById('crpinToggle').checked = false;
const subscribeLinksContainer = document.getElementById('subscribeLinksContainer');
subscribeLinksContainer.classList.remove('show');
Expand Down Expand Up @@ -855,6 +864,10 @@ const customRuleFunctions = `
<label class="form-label">Domain Suffix</label>
<input type="text" class="form-control mb-2" name="customRuleDomainSuffix[]" placeholder="Domain Suffix (comma separated)">
</div>
<div class="mb-2">
<label class="form-label">Domain Keyword</label>
<input type="text" class="form-control mb-2" name="customRuleDomainKeyword[]" placeholder="Domain Keyword (comma separated)">
</div>
<div class="mb-2">
<label class="form-label">IP CIDR</label>
<input type="text" class="form-control mb-2" name="customRuleIPCIDR[]" placeholder="IP CIDR (comma separated)">
Expand Down
11 changes: 7 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ async function handleRequest(request) {
ips: customRuleIPs[index].split(',').map(ip => ip.trim()),
outbound: customRuleNames[index]
}));
const pin = formData.get('pin');

if (!inputString) {
return new Response('Missing input parameter', { status: 400 });
Expand All @@ -38,8 +39,8 @@ 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))}`;
const clashUrl = `${url.origin}/clash?config=${encodeURIComponent(inputString)}&selectedRules=${encodeURIComponent(JSON.stringify(rulesToUse))}&customRules=${encodeURIComponent(JSON.stringify(customRules))}`;
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}`;

return new Response(generateHtml(xrayUrl, singboxUrl, clashUrl), {
headers: { 'Content-Type': 'text/html' }
Expand All @@ -48,6 +49,7 @@ async function handleRequest(request) {
const inputString = url.searchParams.get('config');
let selectedRules = url.searchParams.get('selectedRules');
let customRules = url.searchParams.get('customRules');
let pin = url.searchParams.get('pin');

if (!inputString) {
return new Response('Missing config parameter', { status: 400 });
Expand All @@ -73,11 +75,12 @@ async function handleRequest(request) {
customRules = [];
}

// Env pin is use to pin customRules to top
let configBuilder;
if (url.pathname.startsWith('/singbox')) {
configBuilder = new ConfigBuilder(inputString, selectedRules, customRules);
configBuilder = new ConfigBuilder(inputString, selectedRules, customRules, pin);
} else {
configBuilder = new ClashConfigBuilder(inputString, selectedRules, customRules);
configBuilder = new ClashConfigBuilder(inputString, selectedRules, customRules, pin);
}

const config = await configBuilder.build();
Expand Down

0 comments on commit 8b12280

Please sign in to comment.