mirror of
https://github.com/bestnite/sub2sing-box.git
synced 2025-05-14 13:21:50 +08:00
Compare commits
No commits in common. "master" and "v0.0.9-beta.2" have entirely different histories.
master
...
v0.0.9-bet
@ -39,13 +39,6 @@ func Convert(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
groupRules := make(map[string][]string)
|
||||
err = json.Unmarshal([]byte(data.GroupRules), &groupRules)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
result, err := common.Convert(
|
||||
data.Subscriptions,
|
||||
data.Proxies,
|
||||
@ -56,7 +49,6 @@ func Convert(c *gin.Context) {
|
||||
data.GroupType,
|
||||
data.SortKey,
|
||||
data.SortType,
|
||||
groupRules,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
func RunServer(bind string, port uint16) {
|
||||
func RunServer(port uint16) {
|
||||
tpl, err := template.ParseFS(staticFiles, "static/*")
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
@ -41,9 +41,8 @@ func RunServer(bind string, port uint16) {
|
||||
|
||||
r.GET("/convert", handler.Convert)
|
||||
|
||||
address := bind + ":" + strconv.Itoa(int(port))
|
||||
fmt.Println("Server is running on", address)
|
||||
err = r.Run(address)
|
||||
fmt.Println("Server is running on port", port)
|
||||
err = r.Run(":" + strconv.Itoa(int(port)))
|
||||
if err != nil {
|
||||
fmt.Println("Run server failed: ", err)
|
||||
}
|
||||
|
@ -1,23 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="mdui-theme-auto">
|
||||
<html lang="zh-CN" class="mdui-theme-auto">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>sub2sing-box</title>
|
||||
<!-- Include Material Design UI CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css" />
|
||||
<!-- Include Google Fonts with Chinese support -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto|Noto+Sans+SC&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet" />
|
||||
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
|
||||
<style>
|
||||
/* Apply a uniform font across Chinese, Russian, and English */
|
||||
body {
|
||||
font-family: "Roboto", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -39,336 +31,215 @@
|
||||
max-width: 1200px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Rename fields: now stacked in a wide layout */
|
||||
.rename-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="mdui-container">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
">
|
||||
<h2>
|
||||
<a href="https://github.com/bestnite/sub2sing-box" target="_blank">sub2sing-box</a>
|
||||
<a href="https://github.com/nitezs/sub2sing-box" target="_blank">sub2sing-box</a>
|
||||
</h2>
|
||||
<div class="header-controls">
|
||||
<!-- Language switcher -->
|
||||
<mdui-dropdown trigger="hover">
|
||||
<mdui-icon slot="trigger" name="language"></mdui-icon>
|
||||
<mdui-menu id="language" selects="single">
|
||||
<mdui-menu-item value="zh-CN">中文</mdui-menu-item>
|
||||
<mdui-menu-item value="en">English</mdui-menu-item>
|
||||
<mdui-menu-item value="ru">Русский</mdui-menu-item>
|
||||
</mdui-menu>
|
||||
</mdui-dropdown>
|
||||
<!-- Theme switcher -->
|
||||
<mdui-dropdown trigger="hover">
|
||||
<mdui-icon slot="trigger" name="dark_mode"></mdui-icon>
|
||||
<mdui-menu id="theme" selects="single" value="dark">
|
||||
<mdui-menu-item value="light" data-i18n="theme-light">Light</mdui-menu-item>
|
||||
<mdui-menu-item value="dark" data-i18n="theme-dark">Dark</mdui-menu-item>
|
||||
<mdui-divider></mdui-divider>
|
||||
<mdui-menu-item value="auto" data-i18n="theme-auto">Auto</mdui-menu-item>
|
||||
</mdui-menu>
|
||||
</mdui-dropdown>
|
||||
</div>
|
||||
<mdui-dropdown trigger="hover">
|
||||
<mdui-icon slot="trigger" name="dark_mode"></mdui-icon>
|
||||
<mdui-menu id="theme" selects="single" value="dark">
|
||||
<mdui-menu-item value="light">亮色</mdui-menu-item>
|
||||
<mdui-menu-item value="dark">暗色</mdui-menu-item>
|
||||
<mdui-divider></mdui-divider>
|
||||
<mdui-menu-item value="auto">跟随系统</mdui-menu-item>
|
||||
</mdui-menu>
|
||||
</mdui-dropdown>
|
||||
</div>
|
||||
<!-- Template Section -->
|
||||
<div class="section">
|
||||
<h3>
|
||||
<a href="https://github.com/bestnite/sub2sing-box/tree/master/templates" data-i18n="template">Template</a>
|
||||
</h3>
|
||||
<mdui-text-field data-i18n="template" label="Template" type="text" id="template" name="template"
|
||||
value="https://raw.githubusercontent.com/bestnite/sub2sing-box/refs/heads/master/templates/example-windows.json">
|
||||
</mdui-text-field>
|
||||
<div class=" section">
|
||||
<h3><a href="https://github.com/nitezs/sub2sing-box/tree/master/templates">模板</a></h3>
|
||||
<mdui-text-field label="模板" type="text" id="template" name="template"
|
||||
value="https://raw.githubusercontent.com/nitezs/sub2sing-box/refs/heads/master/templates/example-windows.json" />
|
||||
</div>
|
||||
|
||||
<div id="form">
|
||||
<!-- Nodes Section -->
|
||||
<div class="section">
|
||||
<h3 data-i18n="nodes">Nodes</h3>
|
||||
<h3>节点</h3>
|
||||
<div>
|
||||
<mdui-text-field autosize min-rows="3" max-rows="5" data-i18n="subscription-link"
|
||||
data-i18n-placeholder="one-per-line" label="Subscription Link" type="text" id="subscription"
|
||||
name="subscription" placeholder="One per line">
|
||||
</mdui-text-field>
|
||||
<mdui-text-field label="订阅链接" autosize min-rows="3" max-rows="5" id="subscription" name="subscription"
|
||||
placeholder="一行一个"></mdui-text-field>
|
||||
</div>
|
||||
<div>
|
||||
<mdui-text-field autosize min-rows="3" max-rows="5" data-i18n="proxy-link"
|
||||
data-i18n-placeholder="one-per-line" label="Node Share Link" type="text" id="proxy" name="proxy"
|
||||
placeholder="One per line">
|
||||
</mdui-text-field>
|
||||
<mdui-text-field label="节点分享链接" autosize min-rows="3" max-rows="5" id="proxy" name="proxy"
|
||||
placeholder="一行一个"></mdui-text-field>
|
||||
</div>
|
||||
<div>
|
||||
<mdui-text-field data-i18n="delete-node" data-i18n-placeholder="supports-regex" label="Delete Node"
|
||||
type="text" id="delete" name="delete" placeholder="Supports regex">
|
||||
</mdui-text-field>
|
||||
<mdui-text-field label="删除节点" type="text" id="delete" name="delete" placeholder="支持正则表达式" />
|
||||
</div>
|
||||
<div>
|
||||
<mdui-list id="list">
|
||||
<mdui-list-item>
|
||||
<span data-i18n="rename-node">Rename Node</span>
|
||||
<mdui-button slot="end-icon" type="button" onclick="addRenameField()">+</mdui-button>
|
||||
重命名节点
|
||||
<mdui-button slot="end-icon" type="button" onclick="addRenameField()">+
|
||||
</mdui-button>
|
||||
</mdui-list-item>
|
||||
</mdui-list>
|
||||
<div id="renameContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Policy Group Section -->
|
||||
<div class="section">
|
||||
<h3 data-i18n="policy-group">Policy Group</h3>
|
||||
<h3>策略组</h3>
|
||||
<div>
|
||||
<mdui-checkbox data-i18n="group" label="Group" name="group" id="group"
|
||||
value="true">placeholder</mdui-checkbox>
|
||||
<mdui-text-field label="类型" type="text" id="group-type" name="group-type" value="selector" />
|
||||
</div>
|
||||
<div>
|
||||
<mdui-text-field data-i18n="type" label="Type" type="text" id="group-type" name="group-type" value="selector">
|
||||
</mdui-text-field>
|
||||
</div>
|
||||
<div>
|
||||
<mdui-select data-i18n="sort-by" label="Sort By" name="sort" id="sort" value="tag">
|
||||
<mdui-menu-item value="tag" selected data-i18n="node-name">Node Name</mdui-menu-item>
|
||||
<mdui-menu-item value="num" data-i18n="node-count">Node Count</mdui-menu-item>
|
||||
<mdui-select label="排序依据" name="sort" id="sort" value="tag">
|
||||
<mdui-menu-item value="tag" selected>节点名</mdui-menu-item>
|
||||
<mdui-menu-item value="num">节点数量</mdui-menu-item>
|
||||
</mdui-select>
|
||||
</div>
|
||||
<div>
|
||||
<mdui-select data-i18n="sort-order" label="Sort Order" name="sort-type" id="sort-type" value="asc">
|
||||
<mdui-menu-item value="asc" selected data-i18n="ascending">Ascending</mdui-menu-item>
|
||||
<mdui-menu-item value="desc" data-i18n="descending">Descending</mdui-menu-item>
|
||||
<mdui-select label="排序方式" name="sort-type" id="sort-type" value="asc">
|
||||
<mdui-menu-item value="asc" selected>升序</mdui-menu-item>
|
||||
<mdui-menu-item value="desc">降序</mdui-menu-item>
|
||||
</mdui-select>
|
||||
</div>
|
||||
<div>
|
||||
<mdui-text-field autosize data-i18n="group-rules" label="Group Rules" type="text" id="group-rules"
|
||||
name="group-rules" min-rows="3" max-rows="5" data-i18n-placeholder="group-rules-placeholder">
|
||||
</mdui-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Section -->
|
||||
<div class="section">
|
||||
<mdui-text-field data-i18n="generate-link" label="Generate Link" type="text" id="output" name="output">
|
||||
<!-- Integrated copy icon inside the Generate Link text field -->
|
||||
<mdui-icon slot="end-icon" onclick="copyLink()" style="cursor:pointer;">content_copy</mdui-icon>
|
||||
</mdui-text-field>
|
||||
<mdui-button color="primary" onclick="openProfileLink()">
|
||||
<span data-i18n="import-profile">Import Profile</span>
|
||||
</mdui-button>
|
||||
<mdui-text-field label="生成链接" autosize min-rows="2" max-rows="5" id="output" name="output"></mdui-text-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Translation object (v11 parameters)
|
||||
const translations = {
|
||||
"zh-CN": {
|
||||
"template": "模板",
|
||||
"nodes": "节点",
|
||||
"subscription-link": "订阅链接",
|
||||
"proxy-link": "节点分享链接",
|
||||
"delete-node": "删除节点",
|
||||
"rename-node": "重命名节点",
|
||||
"policy-group": "策略组",
|
||||
"type": "类型",
|
||||
"sort-by": "排序依据",
|
||||
"node-name": "节点名",
|
||||
"node-count": "节点数量",
|
||||
"sort-order": "排序方式",
|
||||
"ascending": "升序",
|
||||
"descending": "降序",
|
||||
"generate-link": "生成链接",
|
||||
"theme-light": "亮色",
|
||||
"theme-dark": "暗色",
|
||||
"theme-auto": "跟随系统",
|
||||
"import-profile": "导入配置",
|
||||
"one-per-line": "一行一个",
|
||||
"supports-regex": "支持正则表达式",
|
||||
"group-rules": "策略组规则",
|
||||
"group-rules-placeholder": "Json格式: {\"US\": [\"US-CA\", \"US-TX\"], \"CN\": [\"CN-BJ\", \"CN-SH\"]}, 支持正则表达式",
|
||||
"group": "启用策略组"
|
||||
},
|
||||
"en": {
|
||||
"template": "Template",
|
||||
"nodes": "Nodes",
|
||||
"subscription-link": "Subscription Link",
|
||||
"proxy-link": "Node Share Link",
|
||||
"delete-node": "Delete Node",
|
||||
"rename-node": "Rename Node",
|
||||
"policy-group": "Policy Group",
|
||||
"type": "Type",
|
||||
"sort-by": "Sort By",
|
||||
"node-name": "Node Name",
|
||||
"node-count": "Node Count",
|
||||
"sort-order": "Sort Order",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"generate-link": "Generate Link",
|
||||
"theme-light": "Light",
|
||||
"theme-dark": "Dark",
|
||||
"theme-auto": "Auto",
|
||||
"import-profile": "Import Profile",
|
||||
"one-per-line": "One per line",
|
||||
"supports-regex": "Supports regex",
|
||||
"group-rules": "Group Rules",
|
||||
"group-rules-placeholder": "Json format: {\"US\": [\"US-CA\", \"US-TX\"], \"CN\": [\"CN-BJ\", \"CN-SH\"]}, support regex",
|
||||
"group": "Enable Policy Group"
|
||||
},
|
||||
"ru": {
|
||||
"template": "Шаблон",
|
||||
"nodes": "Узлы",
|
||||
"subscription-link": "Ссылка подписки",
|
||||
"proxy-link": "Ссылка на узел",
|
||||
"delete-node": "Удалить узел",
|
||||
"rename-node": "Переименовать узел",
|
||||
"policy-group": "Группа политик",
|
||||
"type": "Тип",
|
||||
"sort-by": "Сортировать по",
|
||||
"node-name": "Имя узла",
|
||||
"node-count": "Количество узлов",
|
||||
"sort-order": "Порядок сортировки",
|
||||
"ascending": "По возрастанию",
|
||||
"descending": "По убыванию",
|
||||
"generate-link": "Создать ссылку",
|
||||
"theme-light": "Светлая",
|
||||
"theme-dark": "Темная",
|
||||
"theme-auto": "Системная",
|
||||
"import-profile": "Импортировать профиль",
|
||||
"one-per-line": "Одна на строку",
|
||||
"supports-regex": "Поддерживает рег. выражения",
|
||||
"group-rules": "Группа правил",
|
||||
"group-rules-placeholder": "Формат JSON: {\"US\": [\"US-CA\", \"US-TX\"], \"CN\": [\"CN-BJ\", \"CN-SH\"]}, поддержка регулярных выражений",
|
||||
"group": "Включить группу политик"
|
||||
}
|
||||
};
|
||||
init();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
setupLanguage();
|
||||
setupTheme();
|
||||
function init() {
|
||||
theme();
|
||||
listenInput();
|
||||
// Do not auto-generate link on page load to keep Generate Link empty
|
||||
});
|
||||
}
|
||||
|
||||
function setupLanguage() {
|
||||
const languageMenu = document.getElementById('language');
|
||||
if (languageMenu) {
|
||||
const userLang = navigator.language || navigator.userLanguage;
|
||||
const savedLang = localStorage.getItem('language') ||
|
||||
(userLang.includes('zh') ? 'zh-CN' : userLang.includes('ru') ? 'ru' : 'en');
|
||||
languageMenu.value = savedLang;
|
||||
changeLanguage(savedLang);
|
||||
|
||||
languageMenu.addEventListener('change', function () {
|
||||
changeLanguage(languageMenu.value);
|
||||
});
|
||||
function theme() {
|
||||
const theme = window.localStorage.getItem("theme");
|
||||
if (theme) {
|
||||
document.querySelector("html").className = "mdui-theme-" + theme;
|
||||
document.getElementById("theme").value = theme;
|
||||
} else {
|
||||
document.querySelector("html").className = "mdui-theme-auto";
|
||||
document.getElementById("theme").value = "auto";
|
||||
window.localStorage.setItem("theme", "auto");
|
||||
}
|
||||
const icon = document.getElementById("theme");
|
||||
const html = document.querySelector("html");
|
||||
icon.addEventListener("change", function (e) {
|
||||
switch (icon.value) {
|
||||
case "light":
|
||||
html.className = "mdui-theme-light";
|
||||
window.localStorage.setItem("theme", "light");
|
||||
break;
|
||||
case "dark":
|
||||
html.className = "mdui-theme-dark";
|
||||
window.localStorage.setItem("theme", "dark");
|
||||
break;
|
||||
case "auto":
|
||||
html.className = "mdui-theme-auto";
|
||||
window.localStorage.setItem("theme", "auto");
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function changeLanguage(lang) {
|
||||
localStorage.setItem('language', lang);
|
||||
document.documentElement.setAttribute('lang', lang);
|
||||
|
||||
document.querySelectorAll('[data-i18n]').forEach(element => {
|
||||
const key = element.getAttribute('data-i18n');
|
||||
const translation = translations[lang][key];
|
||||
if (translation) {
|
||||
if (element.tagName.toLowerCase() === 'mdui-text-field' ||
|
||||
element.tagName.toLowerCase() === 'mdui-select') {
|
||||
element.setAttribute('label', translation);
|
||||
} else if (element.tagName.toLowerCase() === 'mdui-checkbox') {
|
||||
element.innerHTML = translation;
|
||||
} else {
|
||||
element.textContent = translation;
|
||||
function encodeBase64(str) {
|
||||
return btoa(
|
||||
encodeURIComponent(str).replace(
|
||||
/%([0-9A-F]{2})/g,
|
||||
function (match, p1) {
|
||||
return String.fromCharCode("0x" + p1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('mdui-text-field').forEach(element => {
|
||||
const phKey = element.getAttribute('data-i18n-placeholder');
|
||||
if (phKey && translations[lang][phKey]) {
|
||||
element.setAttribute('placeholder', translations[lang][phKey]);
|
||||
}
|
||||
});
|
||||
)
|
||||
)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_");
|
||||
}
|
||||
|
||||
function setupTheme() {
|
||||
const themeMenu = document.getElementById('theme');
|
||||
const savedTheme = localStorage.getItem("theme") || "auto";
|
||||
document.documentElement.className = "mdui-theme-" + savedTheme;
|
||||
themeMenu.value = savedTheme;
|
||||
|
||||
themeMenu.addEventListener("change", function () {
|
||||
const newTheme = themeMenu.value;
|
||||
document.documentElement.className = "mdui-theme-" + newTheme;
|
||||
localStorage.setItem("theme", newTheme);
|
||||
});
|
||||
function decodeBase64(str) {
|
||||
return decodeURIComponent(
|
||||
Array.prototype.map
|
||||
.call(atob(str), function (c) {
|
||||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
}
|
||||
|
||||
function listenInput() {
|
||||
const inputs = document.querySelectorAll("mdui-text-field, mdui-select, mdui-checkbox");
|
||||
inputs.forEach(input => {
|
||||
const inputs = document.querySelectorAll(
|
||||
"mdui-checkbox, mdui-text-field"
|
||||
);
|
||||
for (let input of inputs) {
|
||||
input.addEventListener("input", generateLink);
|
||||
input.addEventListener("change", generateLink);
|
||||
});
|
||||
}
|
||||
const selects = document.querySelectorAll("mdui-select");
|
||||
for (let select of selects) {
|
||||
select.addEventListener("change", generateLink);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanListeners() {
|
||||
function cleanLisnter() {
|
||||
const inputs = document.querySelectorAll("#form input, #form textarea");
|
||||
inputs.forEach(input => {
|
||||
for (let input of inputs) {
|
||||
input.removeEventListener("input", generateLink);
|
||||
});
|
||||
}
|
||||
const selects = document.querySelectorAll("#form select");
|
||||
selects.forEach(select => {
|
||||
for (let select of selects) {
|
||||
select.removeEventListener("change", generateLink);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Rename field parameters from v11 - fields are now wide (stacked)
|
||||
function addRenameField() {
|
||||
cleanListeners();
|
||||
cleanLisnter();
|
||||
const container = document.getElementById("list");
|
||||
const currentLang = localStorage.getItem('language') || 'en';
|
||||
const fieldHTML = `
|
||||
<mdui-list-item class="rename-group">
|
||||
<mdui-text-field type="text" name="rename_from[]" data-i18n-placeholder="delete-node"
|
||||
label="${translations[currentLang]['delete-node']}" data-i18n="rename-node">
|
||||
</mdui-text-field>
|
||||
<mdui-text-field type="text" name="rename_to[]"
|
||||
label="${translations[currentLang]['rename-node']}" data-i18n="rename-node">
|
||||
</mdui-text-field>
|
||||
<mdui-button slot="end-icon" type="button" onclick="removeThisField(this)">-</mdui-button>
|
||||
</mdui-list-item>`;
|
||||
const fieldHTML = `<mdui-list-item class="rename-group">
|
||||
<mdui-text-field slot="icon" type="text" name="rename_from[]" placeholder="支持正则表达式" label="原字符"></mdui-text-field>
|
||||
<mdui-text-field type="text" name="rename_to[]" label="替换字符"></mdui-text-field>
|
||||
<mdui-button slot="end-icon" type="button" class="btn btn-danger" onclick="removeThisField(this)">-</mdui-button>
|
||||
</mdui-list-item>`;
|
||||
container.insertAdjacentHTML("beforeend", fieldHTML);
|
||||
listenInput();
|
||||
}
|
||||
|
||||
function removeThisField(button) {
|
||||
cleanListeners();
|
||||
cleanLisnter();
|
||||
button.parentElement.remove();
|
||||
generateLink();
|
||||
listenInput();
|
||||
}
|
||||
|
||||
function generateLink() {
|
||||
const subscription = document.getElementById("subscription").value
|
||||
.split("\n").filter(i => i.trim());
|
||||
const proxy = document.getElementById("proxy").value
|
||||
.split("\n").filter(i => i.trim());
|
||||
const subscription = document
|
||||
.getElementById("subscription")
|
||||
.value.split("\n")
|
||||
.filter((i) => i);
|
||||
const proxy = document
|
||||
.getElementById("proxy")
|
||||
.value.split("\n")
|
||||
.filter((i) => i);
|
||||
const deleteRule = document.getElementById("delete").value;
|
||||
const template = document.getElementById("template").value;
|
||||
const renameFrom = Array.from(document.getElementsByName("rename_from[]")).map(input => input.value);
|
||||
const renameTo = Array.from(document.getElementsByName("rename_to[]")).map(input => input.value);
|
||||
const renameFrom = Array.from(
|
||||
document.getElementsByName("rename_from[]")
|
||||
).map((input) => input.value);
|
||||
const renameTo = Array.from(
|
||||
document.getElementsByName("rename_to[]")
|
||||
).map((input) => input.value);
|
||||
const output = document.getElementById("output");
|
||||
const groupType = document.getElementById("group-type").value;
|
||||
const sort = document.getElementById("sort").value;
|
||||
const sortType = document.getElementById("sort-type").value;
|
||||
const groupRules = document.getElementById("group-rules").value;
|
||||
const group = document.getElementById("group").value;
|
||||
|
||||
let rename = {};
|
||||
for (let i = 0; i < renameFrom.length; i++) {
|
||||
@ -376,26 +247,6 @@
|
||||
rename[renameFrom[i]] = renameTo[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Only generate link if there is some user input (subscription, proxy, delete, or rename)
|
||||
if (
|
||||
subscription.length === 0 &&
|
||||
proxy.length === 0 &&
|
||||
deleteRule.trim() === "" &&
|
||||
Object.keys(rename).length === 0
|
||||
) {
|
||||
output.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine base URL using window.location.origin and window.location.pathname
|
||||
let basePath = window.location.pathname;
|
||||
// Remove trailing slash if exists
|
||||
if (basePath.endsWith('/')) {
|
||||
basePath = basePath.slice(0, -1);
|
||||
}
|
||||
const baseURL = window.location.origin + basePath;
|
||||
|
||||
const data = {
|
||||
subscription,
|
||||
proxy,
|
||||
@ -405,35 +256,11 @@
|
||||
"group-type": groupType,
|
||||
sort,
|
||||
"sort-type": sortType,
|
||||
"group-rules": groupRules,
|
||||
group: group === "true"
|
||||
};
|
||||
output.value = baseURL + "/convert?data=" + btoa(unescape(encodeURIComponent(JSON.stringify(data)))).replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
function openProfileLink() {
|
||||
const output = document.getElementById("output").value;
|
||||
if (output) {
|
||||
window.location.href = "sing-box://import-remote-profile?url=" + encodeURIComponent(output);
|
||||
} else {
|
||||
alert("Output link is empty");
|
||||
}
|
||||
}
|
||||
|
||||
// Integrated copy function for the Generate Link field
|
||||
function copyLink() {
|
||||
const output = document.getElementById("output").value;
|
||||
if (output) {
|
||||
navigator.clipboard.writeText(output)
|
||||
.then(() => {
|
||||
mdui.snackbar({ message: "Link copied!", position: "top" });
|
||||
})
|
||||
.catch(err => {
|
||||
mdui.snackbar({ message: "Failed to copy link!", position: "top" });
|
||||
});
|
||||
} else {
|
||||
mdui.snackbar({ message: "Output link is empty", position: "top" });
|
||||
}
|
||||
output.value = `${window.location.origin}/convert?data=${encodeBase64(
|
||||
JSON.stringify(data)
|
||||
)}`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
@ -24,7 +24,6 @@ var (
|
||||
sortKey string
|
||||
sortType string
|
||||
config string
|
||||
groupRules string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -39,7 +38,6 @@ func init() {
|
||||
convertCmd.Flags().StringVarP(&sortKey, "sort", "S", "", "sort key, tag or num")
|
||||
convertCmd.Flags().StringVarP(&sortType, "sort-type", "T", "", "sort type, asc or desc")
|
||||
convertCmd.Flags().StringVarP(&config, "config", "c", "", "configuration file path")
|
||||
convertCmd.Flags().StringVarP(&groupRules, "group-rules", "R", "", "group rules")
|
||||
RootCmd.AddCommand(convertCmd)
|
||||
}
|
||||
|
||||
@ -52,14 +50,6 @@ var convertCmd = &cobra.Command{
|
||||
|
||||
func convertRun(cmd *cobra.Command, args []string) {
|
||||
loadConfig()
|
||||
groupRulesMap := make(map[string][]string)
|
||||
if groupRules != "" {
|
||||
err := json.Unmarshal([]byte(groupRules), &groupRulesMap)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing group rules:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
result, err := common.Convert(
|
||||
subscriptions,
|
||||
proxies,
|
||||
@ -70,7 +60,6 @@ func convertRun(cmd *cobra.Command, args []string) {
|
||||
groupType,
|
||||
sortKey,
|
||||
sortType,
|
||||
groupRulesMap,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println("Conversion error:", err)
|
||||
|
@ -6,14 +6,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
port uint16
|
||||
bind string
|
||||
)
|
||||
var port uint16
|
||||
|
||||
func init() {
|
||||
runCmd.Flags().Uint16VarP(&port, "port", "p", 8080, "server port")
|
||||
runCmd.Flags().StringVarP(&bind, "bind", "b", "0.0.0.0", "bind address (e.g., 0.0.0.0, 127.0.0.1, localhost)")
|
||||
RootCmd.AddCommand(runCmd)
|
||||
}
|
||||
|
||||
@ -22,6 +18,6 @@ var runCmd = &cobra.Command{
|
||||
Long: "Run the server",
|
||||
Short: "Run the server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
api.RunServer(bind, port)
|
||||
api.RunServer(port)
|
||||
},
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@ -35,7 +34,6 @@ func Convert(
|
||||
groupType string,
|
||||
sortKey string,
|
||||
sortType string,
|
||||
groupRules map[string][]string,
|
||||
) (string, error) {
|
||||
result := ""
|
||||
var err error
|
||||
@ -101,7 +99,7 @@ func Convert(
|
||||
}
|
||||
|
||||
if enableGroup {
|
||||
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType, groupRules)
|
||||
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType)
|
||||
}
|
||||
if templatePath != "" {
|
||||
templateData, err := ReadTemplate(templatePath)
|
||||
@ -116,7 +114,7 @@ func Convert(
|
||||
}
|
||||
}
|
||||
if !enableGroup && (reg.MatchString(templateData) || strings.Contains(templateData, constant.AllCountryTags) || group) {
|
||||
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType, groupRules)
|
||||
outbounds = AddCountryGroup(outbounds, groupType, sortKey, sortType)
|
||||
}
|
||||
var template model.Options
|
||||
if template, err = J.UnmarshalExtendedContext[model.Options](globalCtx, []byte(templateData)); err != nil {
|
||||
@ -131,7 +129,7 @@ func Convert(
|
||||
for _, v := range template.Options.Endpoints {
|
||||
template.Endpoints = append(template.Endpoints, (model.Endpoint)(v))
|
||||
}
|
||||
result, err = MergeTemplate(outbounds, &template, groupRules)
|
||||
result, err = MergeTemplate(outbounds, &template)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -153,25 +151,11 @@ func Convert(
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string, sortType string, groupRules map[string][]string) []model.Outbound {
|
||||
func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string, sortType string) []model.Outbound {
|
||||
newGroup := make(map[string]model.Outbound)
|
||||
groupRulesRegexps := make(map[string][]*regexp.Regexp)
|
||||
for k, v := range groupRules {
|
||||
for _, rule := range v {
|
||||
groupRulesRegexps[k] = append(groupRulesRegexps[k], regexp.MustCompile(rule))
|
||||
}
|
||||
}
|
||||
for _, p := range proxies {
|
||||
if p.Type != C.TypeSelector && p.Type != C.TypeURLTest {
|
||||
country := model.GetContryName(p.Tag)
|
||||
for k, rules := range groupRulesRegexps {
|
||||
for _, rule := range rules {
|
||||
if rule.MatchString(p.Tag) {
|
||||
country = k
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if group, ok := newGroup[country]; ok {
|
||||
AppendOutbound(&group, p.Tag)
|
||||
newGroup[country] = group
|
||||
@ -251,17 +235,13 @@ func ReadTemplate(template string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func MergeTemplate(outbounds []model.Outbound, template *model.Options, groupRules map[string][]string) (string, error) {
|
||||
func MergeTemplate(outbounds []model.Outbound, template *model.Options) (string, error) {
|
||||
var err error
|
||||
proxyTags := make([]string, 0)
|
||||
groupTags := make([]string, 0)
|
||||
groups := make(map[string]model.Outbound)
|
||||
rulesKeys := make([]string, 0)
|
||||
for k := range groupRules {
|
||||
rulesKeys = append(rulesKeys, k)
|
||||
}
|
||||
for _, p := range outbounds {
|
||||
if slices.Contains(rulesKeys, p.Tag) || model.IsCountryGroup(p.Tag) {
|
||||
if model.IsCountryGroup(p.Tag) {
|
||||
groupTags = append(groupTags, p.Tag)
|
||||
reg := regexp.MustCompile("[A-Za-z]{2}")
|
||||
country := reg.FindString(p.Tag)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
sing-box merge -C ./templates/windows ./templates/example-windows.json
|
||||
sing-box merge -C ./templates/linux ./templates/example-linux.json
|
||||
sing-box merge -C ./templates/windows ./templates/example-android.json
|
||||
sing-box merge -C ./templates/android ./templates/example-android.json
|
||||
|
@ -11,5 +11,4 @@ type ConvertRequest struct {
|
||||
SortKey string `form:"sort" json:"sort"`
|
||||
SortType string `form:"sort-type" json:"sort-type"`
|
||||
Output string `json:"output"`
|
||||
GroupRules string `form:"group-rules" json:"group-rules"`
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package model
|
||||
|
||||
type VmessJson struct {
|
||||
V any `json:"v"`
|
||||
V string `json:"v"`
|
||||
Ps string `json:"ps"`
|
||||
Add string `json:"add"`
|
||||
Port any `json:"port"`
|
||||
|
60
templates/android/dns.json
Executable file
60
templates/android/dns.json
Executable file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "https://8.8.8.8/dns-query",
|
||||
"detour": "default"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "h3://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "refused",
|
||||
"address": "rcode://refused"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-category-ads-all",
|
||||
"server": "refused"
|
||||
},
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "google",
|
||||
"client_subnet": "114.114.114.114/24"
|
||||
}
|
||||
],
|
||||
"final": "google",
|
||||
"disable_cache": false,
|
||||
"disable_expire": false
|
||||
}
|
||||
}
|
13
templates/android/experimental.json
Executable file
13
templates/android/experimental.json
Executable file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true
|
||||
},
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_detour": "default",
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
}
|
||||
}
|
12
templates/android/inbounds.json
Executable file
12
templates/android/inbounds.json
Executable file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"sniff": true,
|
||||
"address": ["172.16.0.1/30", "fdfe:dcba:9876::1/126"],
|
||||
"exclude_interface": ["tailscale0"]
|
||||
}
|
||||
]
|
||||
}
|
6
templates/android/log.json
Executable file
6
templates/android/log.json
Executable file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info",
|
||||
"timestamp": true
|
||||
}
|
||||
}
|
8
templates/android/ntp.json
Executable file
8
templates/android/ntp.json
Executable file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"ntp": {
|
||||
"enabled": true,
|
||||
"server": "ntp.aliyun.com",
|
||||
"server_port": 123,
|
||||
"interval": "30m"
|
||||
}
|
||||
}
|
21
templates/android/outbounds.json
Executable file
21
templates/android/outbounds.json
Executable file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"outbounds": ["<all-proxy-tags>", "direct"],
|
||||
"tag": "default",
|
||||
"type": "selector"
|
||||
},
|
||||
{
|
||||
"tag": "direct",
|
||||
"type": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "block",
|
||||
"type": "block"
|
||||
},
|
||||
{
|
||||
"tag": "dns",
|
||||
"type": "dns"
|
||||
}
|
||||
]
|
||||
}
|
53
templates/android/route.json
Executable file
53
templates/android/route.json
Executable file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-google",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"final": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
62
templates/android/route_rule_set.json
Executable file
62
templates/android/route_rule_set.json
Executable file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-category-ads-all",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-category-games@cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-games@cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-steam@cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-steam@cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
|
||||
"download_detour": "default"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-google",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-google.srs",
|
||||
"download_detour": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -68,7 +68,10 @@
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": ["172.16.0.1/30", "fdfe:dcba:9876::1/126"],
|
||||
"address": [
|
||||
"172.16.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"exclude_interface": "tailscale0",
|
||||
@ -81,32 +84,36 @@
|
||||
"tag": "default",
|
||||
"outbounds": [
|
||||
"\u003call-proxy-tags\u003e",
|
||||
"\u003call-country-tags\u003e",
|
||||
"direct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "resolve",
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
{
|
||||
"action": "sniff",
|
||||
"timeout": "1s"
|
||||
},
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"port": [3478, 5348, 5349],
|
||||
"outbound": "direct"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
@ -125,19 +132,18 @@
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"process_path_regex": [
|
||||
"^.+/steam/compatibilitytools.d/.+",
|
||||
"^.+/steamapps/common/.*"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
@ -210,7 +216,6 @@
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
||||
"external_ui_download_detour": "default",
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
|
@ -68,12 +68,15 @@
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"address": ["192.168.135.1/30", "fdfe:dcba:9876::1/126"],
|
||||
"address": [
|
||||
"172.16.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"auto_route": true,
|
||||
"auto_redirect": true,
|
||||
"strict_route": true,
|
||||
"route_exclude_address": "100.64.0.0/10"
|
||||
"exclude_interface": "tailscale0",
|
||||
"sniff": true
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@ -82,32 +85,36 @@
|
||||
"tag": "default",
|
||||
"outbounds": [
|
||||
"\u003call-proxy-tags\u003e",
|
||||
"\u003call-country-tags\u003e",
|
||||
"direct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "resolve",
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
{
|
||||
"action": "sniff",
|
||||
"timeout": "1s"
|
||||
},
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"port": [3478, 5348, 5349],
|
||||
"outbound": "direct"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
@ -126,23 +133,18 @@
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-bahamut",
|
||||
"outbound": "台湾(TW)"
|
||||
},
|
||||
{
|
||||
"process_path_regex": [
|
||||
"^.+/steam/compatibilitytools.d/.+",
|
||||
"^.+/steamapps/common/.*"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
@ -215,7 +217,6 @@
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
||||
"external_ui_download_detour": "default",
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
|
@ -68,7 +68,10 @@
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": ["172.16.0.1/30", "fdfe:dcba:9876::1/126"],
|
||||
"address": [
|
||||
"172.16.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"exclude_interface": "tailscale0",
|
||||
@ -81,32 +84,36 @@
|
||||
"tag": "default",
|
||||
"outbounds": [
|
||||
"\u003call-proxy-tags\u003e",
|
||||
"\u003call-country-tags\u003e",
|
||||
"direct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "resolve",
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
{
|
||||
"action": "sniff",
|
||||
"timeout": "1s"
|
||||
},
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"port": [3478, 5348, 5349],
|
||||
"outbound": "direct"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
@ -125,19 +132,18 @@
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"process_path_regex": [
|
||||
"^.+/steam/compatibilitytools.d/.+",
|
||||
"^.+/steamapps/common/.*"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
@ -210,7 +216,6 @@
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
||||
"external_ui_download_detour": "default",
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
|
@ -37,6 +37,10 @@
|
||||
"rule_set": "geosite-bahamut",
|
||||
"outbound": "台湾(TW)"
|
||||
},
|
||||
{
|
||||
"domain_suffix": "online-fix.me",
|
||||
"outbound": "香港(HK)"
|
||||
},
|
||||
{
|
||||
"process_path_regex": [
|
||||
"^.+/steam/compatibilitytools.d/.+",
|
||||
|
@ -6,7 +6,6 @@
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "./ui",
|
||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
||||
"external_ui_download_detour": "default",
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
|
@ -8,6 +8,14 @@
|
||||
{
|
||||
"tag": "direct",
|
||||
"type": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "block",
|
||||
"type": "block"
|
||||
},
|
||||
{
|
||||
"tag": "dns",
|
||||
"type": "dns"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,20 +2,17 @@
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "resolve",
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
{
|
||||
"action": "sniff",
|
||||
"timeout": "1s"
|
||||
},
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"port": [3478, 5348, 5349],
|
||||
"outbound": "direct"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
@ -34,23 +31,18 @@
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-bahamut",
|
||||
"outbound": "台湾(TW)"
|
||||
},
|
||||
{
|
||||
"process_path_regex": [
|
||||
"^.+/steam/compatibilitytools.d/.+",
|
||||
"^.+/steamapps/common/.*"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn",
|
||||
"geosite-cn",
|
||||
"geosite-category-games@cn",
|
||||
"geosite-steam@cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user