mirror of
https://github.com/bestnite/sub2sing-box.git
synced 2025-04-19 12:25:55 +08:00
Compare commits
No commits in common. "master" and "v0.0.9-beta.1" have entirely different histories.
master
...
v0.0.9-bet
@ -15,7 +15,7 @@ import (
|
|||||||
//go:embed static
|
//go:embed static
|
||||||
var staticFiles embed.FS
|
var staticFiles embed.FS
|
||||||
|
|
||||||
func RunServer(bind string, port uint16) {
|
func RunServer(port uint16) {
|
||||||
tpl, err := template.ParseFS(staticFiles, "static/*")
|
tpl, err := template.ParseFS(staticFiles, "static/*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err.Error())
|
println(err.Error())
|
||||||
@ -41,9 +41,8 @@ func RunServer(bind string, port uint16) {
|
|||||||
|
|
||||||
r.GET("/convert", handler.Convert)
|
r.GET("/convert", handler.Convert)
|
||||||
|
|
||||||
address := bind + ":" + strconv.Itoa(int(port))
|
fmt.Println("Server is running on port", port)
|
||||||
fmt.Println("Server is running on", address)
|
err = r.Run(":" + strconv.Itoa(int(port)))
|
||||||
err = r.Run(address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Run server failed: ", err)
|
fmt.Println("Run server failed: ", err)
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="mdui-theme-auto">
|
<html lang="zh-CN" class="mdui-theme-auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>sub2sing-box</title>
|
<title>sub2sing-box</title>
|
||||||
<!-- Include Material Design UI CSS -->
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.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" rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" 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>
|
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
|
||||||
<style>
|
<style>
|
||||||
/* Apply a uniform font across Chinese, Russian, and English */
|
|
||||||
body {
|
|
||||||
font-family: "Roboto", "Noto Sans SC", sans-serif;
|
|
||||||
}
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@ -26,351 +21,232 @@
|
|||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
background-color: rgb(var(--mdui-color-surface-container));
|
background-color: rgb(var(--mdui-color-surface-container));
|
||||||
}
|
}
|
||||||
.section > * {
|
|
||||||
|
.section>* {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdui-container {
|
.mdui-container {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 30px auto;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="mdui-container">
|
<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>
|
<h2>
|
||||||
<a href="https://github.com/nitezs/sub2sing-box" target="_blank">sub2sing-box</a>
|
<a href="https://github.com/nitezs/sub2sing-box" target="_blank">sub2sing-box</a>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="header-controls">
|
<mdui-dropdown trigger="hover">
|
||||||
<!-- Language switcher -->
|
<mdui-icon slot="trigger" name="dark_mode"></mdui-icon>
|
||||||
<mdui-dropdown trigger="hover">
|
<mdui-menu id="theme" selects="single" value="dark">
|
||||||
<mdui-icon slot="trigger" name="language"></mdui-icon>
|
<mdui-menu-item value="light">亮色</mdui-menu-item>
|
||||||
<mdui-menu id="language" selects="single">
|
<mdui-menu-item value="dark">暗色</mdui-menu-item>
|
||||||
<mdui-menu-item value="zh-CN">中文</mdui-menu-item>
|
<mdui-divider></mdui-divider>
|
||||||
<mdui-menu-item value="en">English</mdui-menu-item>
|
<mdui-menu-item value="auto">跟随系统</mdui-menu-item>
|
||||||
<mdui-menu-item value="ru">Русский</mdui-menu-item>
|
</mdui-menu>
|
||||||
</mdui-menu>
|
</mdui-dropdown>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Template Section -->
|
<!-- Template Section -->
|
||||||
<div class="section">
|
<div class=" section">
|
||||||
<h3>
|
<h3><a href="https://github.com/nitezs/sub2sing-box/tree/master/templates">模板</a></h3>
|
||||||
<a href="https://github.com/nitezs/sub2sing-box/tree/master/templates" data-i18n="template">Template</a>
|
<mdui-text-field label="模板" type="text" id="template" name="template"
|
||||||
</h3>
|
value="https://raw.githubusercontent.com/nitezs/sub2sing-box/refs/heads/master/templates/example-windows.json" />
|
||||||
<mdui-text-field data-i18n="template" label="Template" type="text" id="template" name="template"
|
|
||||||
value="https://raw.githubusercontent.com/nitezs/sub2sing-box/refs/heads/master/templates/example-windows.json">
|
|
||||||
</mdui-text-field>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="form">
|
<div id="form">
|
||||||
<!-- Nodes Section -->
|
<!-- Nodes Section -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3 data-i18n="nodes">Nodes</h3>
|
<h3>节点</h3>
|
||||||
<div>
|
<div>
|
||||||
<mdui-text-field autosize min-rows="3" max-rows="5" data-i18n="subscription-link"
|
<mdui-text-field label="订阅链接" autosize min-rows="3" max-rows="5" id="subscription" name="subscription"
|
||||||
data-i18n-placeholder="one-per-line"
|
placeholder="一行一个"></mdui-text-field>
|
||||||
label="Subscription Link" type="text" id="subscription" name="subscription"
|
|
||||||
placeholder="One per line">
|
|
||||||
</mdui-text-field>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mdui-text-field autosize min-rows="3" max-rows="5" data-i18n="proxy-link"
|
<mdui-text-field label="节点分享链接" autosize min-rows="3" max-rows="5" id="proxy" name="proxy"
|
||||||
data-i18n-placeholder="one-per-line"
|
placeholder="一行一个"></mdui-text-field>
|
||||||
label="Node Share Link" type="text" id="proxy" name="proxy"
|
|
||||||
placeholder="One per line">
|
|
||||||
</mdui-text-field>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mdui-text-field data-i18n="delete-node"
|
<mdui-text-field label="删除节点" type="text" id="delete" name="delete" placeholder="支持正则表达式" />
|
||||||
data-i18n-placeholder="supports-regex"
|
|
||||||
label="Delete Node" type="text" id="delete" name="delete"
|
|
||||||
placeholder="Supports regex">
|
|
||||||
</mdui-text-field>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mdui-list id="list">
|
<mdui-list id="list">
|
||||||
<mdui-list-item>
|
<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-item>
|
||||||
</mdui-list>
|
</mdui-list>
|
||||||
<div id="renameContainer"></div>
|
<div id="renameContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Policy Group Section -->
|
<!-- Policy Group Section -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3 data-i18n="policy-group">Policy Group</h3>
|
<h3>策略组</h3>
|
||||||
<div>
|
<div>
|
||||||
<mdui-text-field data-i18n="type" label="Type" type="text" id="group-type" name="group-type"
|
<mdui-text-field label="类型" type="text" id="group-type" name="group-type" value="selector" />
|
||||||
value="selector">
|
|
||||||
</mdui-text-field>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mdui-select data-i18n="sort-by" label="Sort By" name="sort" id="sort" value="tag">
|
<mdui-select label="排序依据" 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="tag" selected>节点名</mdui-menu-item>
|
||||||
<mdui-menu-item value="num" data-i18n="node-count">Node Count</mdui-menu-item>
|
<mdui-menu-item value="num">节点数量</mdui-menu-item>
|
||||||
</mdui-select>
|
</mdui-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mdui-select data-i18n="sort-order" label="Sort Order" name="sort-type" id="sort-type" value="asc">
|
<mdui-select label="排序方式" 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="asc" selected>升序</mdui-menu-item>
|
||||||
<mdui-menu-item value="desc" data-i18n="descending">Descending</mdui-menu-item>
|
<mdui-menu-item value="desc">降序</mdui-menu-item>
|
||||||
</mdui-select>
|
</mdui-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Result Section -->
|
<!-- Result Section -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<mdui-text-field data-i18n="generate-link" label="Generate Link" type="text" id="output" name="output">
|
<mdui-text-field label="生成链接" autosize min-rows="2" max-rows="5" id="output" name="output"></mdui-text-field>
|
||||||
<!-- 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// Translation object (v11 parameters)
|
init();
|
||||||
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": "支持正则表达式"
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"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": "Поддерживает рег. выражения"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
function init() {
|
||||||
setupLanguage();
|
theme();
|
||||||
setupTheme();
|
|
||||||
listenInput();
|
listenInput();
|
||||||
// Do not auto-generate link on page load to keep Generate Link empty
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function setupLanguage() {
|
function theme() {
|
||||||
const languageMenu = document.getElementById('language');
|
const theme = window.localStorage.getItem("theme");
|
||||||
if (languageMenu) {
|
if (theme) {
|
||||||
const userLang = navigator.language || navigator.userLanguage;
|
document.querySelector("html").className = "mdui-theme-" + theme;
|
||||||
const savedLang = localStorage.getItem('language') ||
|
document.getElementById("theme").value = theme;
|
||||||
(userLang.includes('zh') ? 'zh-CN' : userLang.includes('ru') ? 'ru' : 'en');
|
} else {
|
||||||
languageMenu.value = savedLang;
|
document.querySelector("html").className = "mdui-theme-auto";
|
||||||
changeLanguage(savedLang);
|
document.getElementById("theme").value = "auto";
|
||||||
|
window.localStorage.setItem("theme", "auto");
|
||||||
languageMenu.addEventListener('change', function() {
|
|
||||||
changeLanguage(languageMenu.value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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) {
|
function encodeBase64(str) {
|
||||||
localStorage.setItem('language', lang);
|
return btoa(
|
||||||
document.documentElement.setAttribute('lang', lang);
|
encodeURIComponent(str).replace(
|
||||||
|
/%([0-9A-F]{2})/g,
|
||||||
document.querySelectorAll('[data-i18n]').forEach(element => {
|
function (match, p1) {
|
||||||
const key = element.getAttribute('data-i18n');
|
return String.fromCharCode("0x" + p1);
|
||||||
const translation = translations[lang][key];
|
|
||||||
if (translation) {
|
|
||||||
if (element.tagName.toLowerCase() === 'mdui-text-field' ||
|
|
||||||
element.tagName.toLowerCase() === 'mdui-select') {
|
|
||||||
element.setAttribute('label', translation);
|
|
||||||
} else {
|
|
||||||
element.textContent = translation;
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
});
|
)
|
||||||
|
.replace(/\+/g, "-")
|
||||||
document.querySelectorAll('mdui-text-field').forEach(element => {
|
.replace(/\//g, "_");
|
||||||
const phKey = element.getAttribute('data-i18n-placeholder');
|
|
||||||
if (phKey && translations[lang][phKey]) {
|
|
||||||
element.setAttribute('placeholder', translations[lang][phKey]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupTheme() {
|
function decodeBase64(str) {
|
||||||
const themeMenu = document.getElementById('theme');
|
return decodeURIComponent(
|
||||||
const savedTheme = localStorage.getItem("theme") || "auto";
|
Array.prototype.map
|
||||||
document.documentElement.className = "mdui-theme-" + savedTheme;
|
.call(atob(str), function (c) {
|
||||||
themeMenu.value = savedTheme;
|
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
|
})
|
||||||
themeMenu.addEventListener("change", function() {
|
.join("")
|
||||||
const newTheme = themeMenu.value;
|
);
|
||||||
document.documentElement.className = "mdui-theme-" + newTheme;
|
|
||||||
localStorage.setItem("theme", newTheme);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenInput() {
|
function listenInput() {
|
||||||
const inputs = document.querySelectorAll("mdui-text-field, mdui-select, mdui-checkbox");
|
const inputs = document.querySelectorAll(
|
||||||
inputs.forEach(input => {
|
"mdui-checkbox, mdui-text-field"
|
||||||
|
);
|
||||||
|
for (let input of inputs) {
|
||||||
input.addEventListener("input", generateLink);
|
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");
|
const inputs = document.querySelectorAll("#form input, #form textarea");
|
||||||
inputs.forEach(input => {
|
for (let input of inputs) {
|
||||||
input.removeEventListener("input", generateLink);
|
input.removeEventListener("input", generateLink);
|
||||||
});
|
}
|
||||||
const selects = document.querySelectorAll("#form select");
|
const selects = document.querySelectorAll("#form select");
|
||||||
selects.forEach(select => {
|
for (let select of selects) {
|
||||||
select.removeEventListener("change", generateLink);
|
select.removeEventListener("change", generateLink);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename field parameters from v11 - fields are now wide (stacked)
|
|
||||||
function addRenameField() {
|
function addRenameField() {
|
||||||
cleanListeners();
|
cleanLisnter();
|
||||||
const container = document.getElementById("list");
|
const container = document.getElementById("list");
|
||||||
const currentLang = localStorage.getItem('language') || 'en';
|
const fieldHTML = `<mdui-list-item class="rename-group">
|
||||||
const fieldHTML = `
|
<mdui-text-field slot="icon" type="text" name="rename_from[]" placeholder="支持正则表达式" label="原字符"></mdui-text-field>
|
||||||
<mdui-list-item class="rename-group">
|
<mdui-text-field type="text" name="rename_to[]" label="替换字符"></mdui-text-field>
|
||||||
<mdui-text-field type="text" name="rename_from[]" data-i18n-placeholder="delete-node"
|
<mdui-button slot="end-icon" type="button" class="btn btn-danger" onclick="removeThisField(this)">-</mdui-button>
|
||||||
label="${translations[currentLang]['delete-node']}" data-i18n="rename-node">
|
</mdui-list-item>`;
|
||||||
</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>`;
|
|
||||||
container.insertAdjacentHTML("beforeend", fieldHTML);
|
container.insertAdjacentHTML("beforeend", fieldHTML);
|
||||||
listenInput();
|
listenInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeThisField(button) {
|
function removeThisField(button) {
|
||||||
cleanListeners();
|
cleanLisnter();
|
||||||
button.parentElement.remove();
|
button.parentElement.remove();
|
||||||
generateLink();
|
generateLink();
|
||||||
listenInput();
|
listenInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateLink() {
|
function generateLink() {
|
||||||
const subscription = document.getElementById("subscription").value
|
const subscription = document
|
||||||
.split("\n").filter(i => i.trim());
|
.getElementById("subscription")
|
||||||
const proxy = document.getElementById("proxy").value
|
.value.split("\n")
|
||||||
.split("\n").filter(i => i.trim());
|
.filter((i) => i);
|
||||||
|
const proxy = document
|
||||||
|
.getElementById("proxy")
|
||||||
|
.value.split("\n")
|
||||||
|
.filter((i) => i);
|
||||||
const deleteRule = document.getElementById("delete").value;
|
const deleteRule = document.getElementById("delete").value;
|
||||||
const template = document.getElementById("template").value;
|
const template = document.getElementById("template").value;
|
||||||
const renameFrom = Array.from(document.getElementsByName("rename_from[]")).map(input => input.value);
|
const renameFrom = Array.from(
|
||||||
const renameTo = Array.from(document.getElementsByName("rename_to[]")).map(input => input.value);
|
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 output = document.getElementById("output");
|
||||||
const groupType = document.getElementById("group-type").value;
|
const groupType = document.getElementById("group-type").value;
|
||||||
const sort = document.getElementById("sort").value;
|
const sort = document.getElementById("sort").value;
|
||||||
const sortType = document.getElementById("sort-type").value;
|
const sortType = document.getElementById("sort-type").value;
|
||||||
|
|
||||||
let rename = {};
|
let rename = {};
|
||||||
for (let i = 0; i < renameFrom.length; i++) {
|
for (let i = 0; i < renameFrom.length; i++) {
|
||||||
if (renameFrom[i] && renameTo[i]) {
|
if (renameFrom[i] && renameTo[i]) {
|
||||||
rename[renameFrom[i]] = renameTo[i];
|
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 = {
|
const data = {
|
||||||
subscription,
|
subscription,
|
||||||
proxy,
|
proxy,
|
||||||
@ -379,43 +255,14 @@
|
|||||||
rename,
|
rename,
|
||||||
"group-type": groupType,
|
"group-type": groupType,
|
||||||
sort,
|
sort,
|
||||||
"sort-type": sortType
|
"sort-type": sortType,
|
||||||
};
|
};
|
||||||
output.value = baseURL + "/convert?data=" + encodeBase64(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeBase64(str) {
|
output.value = `${window.location.origin}/convert?data=${encodeBase64(
|
||||||
return btoa(
|
JSON.stringify(data)
|
||||||
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
|
)}`;
|
||||||
return String.fromCharCode("0x" + p1);
|
|
||||||
})
|
|
||||||
).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" });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
@ -6,14 +6,10 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var port uint16
|
||||||
port uint16
|
|
||||||
bind string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runCmd.Flags().Uint16VarP(&port, "port", "p", 8080, "server port")
|
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)
|
RootCmd.AddCommand(runCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +18,6 @@ var runCmd = &cobra.Command{
|
|||||||
Long: "Run the server",
|
Long: "Run the server",
|
||||||
Short: "Run the server",
|
Short: "Run the server",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
api.RunServer(bind, port)
|
api.RunServer(port)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,6 @@ func Convert(
|
|||||||
result := ""
|
result := ""
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if groupType == "" {
|
|
||||||
groupType = C.TypeSelector
|
|
||||||
}
|
|
||||||
|
|
||||||
outbounds, err := ConvertSubscriptionsToSProxy(subscriptions)
|
outbounds, err := ConvertSubscriptionsToSProxy(subscriptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -120,15 +116,6 @@ func Convert(
|
|||||||
if template, err = J.UnmarshalExtendedContext[model.Options](globalCtx, []byte(templateData)); err != nil {
|
if template, err = J.UnmarshalExtendedContext[model.Options](globalCtx, []byte(templateData)); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for _, v := range template.Options.Outbounds {
|
|
||||||
template.Outbounds = append(template.Outbounds, (model.Outbound)(v))
|
|
||||||
}
|
|
||||||
for _, v := range template.Options.Inbounds {
|
|
||||||
template.Inbounds = append(template.Inbounds, (model.Inbound)(v))
|
|
||||||
}
|
|
||||||
for _, v := range template.Options.Endpoints {
|
|
||||||
template.Endpoints = append(template.Endpoints, (model.Endpoint)(v))
|
|
||||||
}
|
|
||||||
result, err = MergeTemplate(outbounds, &template)
|
result, err = MergeTemplate(outbounds, &template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -160,7 +147,7 @@ func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string,
|
|||||||
AppendOutbound(&group, p.Tag)
|
AppendOutbound(&group, p.Tag)
|
||||||
newGroup[country] = group
|
newGroup[country] = group
|
||||||
} else {
|
} else {
|
||||||
if groupType == C.TypeSelector {
|
if groupType == C.TypeSelector || groupType == "" {
|
||||||
newGroup[country] = model.Outbound{
|
newGroup[country] = model.Outbound{
|
||||||
Tag: country,
|
Tag: country,
|
||||||
Type: groupType,
|
Type: groupType,
|
||||||
@ -186,25 +173,23 @@ func AddCountryGroup(proxies []model.Outbound, groupType string, sortKey string,
|
|||||||
for _, p := range newGroup {
|
for _, p := range newGroup {
|
||||||
groups = append(groups, p)
|
groups = append(groups, p)
|
||||||
}
|
}
|
||||||
if sortType != "" {
|
if sortType == "asc" {
|
||||||
if sortType == "asc" {
|
switch sortKey {
|
||||||
switch sortKey {
|
case "tag":
|
||||||
case "tag":
|
sort.Sort(model.SortByTag(groups))
|
||||||
sort.Sort(model.SortByTag(groups))
|
case "num":
|
||||||
case "num":
|
sort.Sort(model.SortByNumber(groups))
|
||||||
sort.Sort(model.SortByNumber(groups))
|
default:
|
||||||
default:
|
sort.Sort(model.SortByTag(groups))
|
||||||
sort.Sort(model.SortByTag(groups))
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
switch sortKey {
|
||||||
switch sortKey {
|
case "tag":
|
||||||
case "tag":
|
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
||||||
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
case "num":
|
||||||
case "num":
|
sort.Sort(sort.Reverse(model.SortByNumber(groups)))
|
||||||
sort.Sort(sort.Reverse(model.SortByNumber(groups)))
|
default:
|
||||||
default:
|
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
||||||
sort.Sort(sort.Reverse(model.SortByTag(groups)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return append(proxies, groups...)
|
return append(proxies, groups...)
|
||||||
@ -251,8 +236,7 @@ func MergeTemplate(outbounds []model.Outbound, template *model.Options) (string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
reg := regexp.MustCompile("<[A-Za-z]{2}>")
|
reg := regexp.MustCompile("<[A-Za-z]{2}>")
|
||||||
for i, o := range template.Outbounds {
|
for i, outbound := range template.Outbounds {
|
||||||
outbound := (model.Outbound)(o)
|
|
||||||
if outbound.Type == C.TypeSelector || outbound.Type == C.TypeURLTest {
|
if outbound.Type == C.TypeSelector || outbound.Type == C.TypeURLTest {
|
||||||
var parsedOutbound []string = make([]string, 0)
|
var parsedOutbound []string = make([]string, 0)
|
||||||
for _, o := range GetOutbounds(&outbound) {
|
for _, o := range GetOutbounds(&outbound) {
|
||||||
@ -365,12 +349,6 @@ func SetOutbounds(outbound *model.Outbound, outbounds []string) {
|
|||||||
case option.URLTestOutboundOptions:
|
case option.URLTestOutboundOptions:
|
||||||
v.Outbounds = outbounds
|
v.Outbounds = outbounds
|
||||||
outbound.Options = v
|
outbound.Options = v
|
||||||
case *option.SelectorOutboundOptions:
|
|
||||||
v.Outbounds = outbounds
|
|
||||||
outbound.Options = v
|
|
||||||
case *option.URLTestOutboundOptions:
|
|
||||||
v.Outbounds = outbounds
|
|
||||||
outbound.Options = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,12 +360,6 @@ func AppendOutbound(outbound *model.Outbound, outboundTag string) {
|
|||||||
case option.URLTestOutboundOptions:
|
case option.URLTestOutboundOptions:
|
||||||
v.Outbounds = append(v.Outbounds, outboundTag)
|
v.Outbounds = append(v.Outbounds, outboundTag)
|
||||||
outbound.Options = v
|
outbound.Options = v
|
||||||
case *option.SelectorOutboundOptions:
|
|
||||||
v.Outbounds = append(v.Outbounds, outboundTag)
|
|
||||||
outbound.Options = v
|
|
||||||
case *option.URLTestOutboundOptions:
|
|
||||||
v.Outbounds = append(v.Outbounds, outboundTag)
|
|
||||||
outbound.Options = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,10 +369,6 @@ func GetOutbounds(outbound *model.Outbound) []string {
|
|||||||
return v.Outbounds
|
return v.Outbounds
|
||||||
case option.URLTestOutboundOptions:
|
case option.URLTestOutboundOptions:
|
||||||
return v.Outbounds
|
return v.Outbounds
|
||||||
case *option.SelectorOutboundOptions:
|
|
||||||
return v.Outbounds
|
|
||||||
case *option.URLTestOutboundOptions:
|
|
||||||
return v.Outbounds
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
sing-box merge -C ./templates/windows ./templates/example-windows.json
|
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/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
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -90,29 +90,30 @@
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "resolve",
|
"type": "logical",
|
||||||
"strategy": "prefer_ipv4"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "sniff",
|
"protocol": "dns"
|
||||||
"timeout": "1s"
|
},
|
||||||
},
|
{
|
||||||
{
|
"port": 53
|
||||||
"protocol": "dns",
|
}
|
||||||
"action": "hijack-dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": [
|
|
||||||
3478,
|
|
||||||
5348,
|
|
||||||
5349
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "dns"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
@ -131,19 +132,18 @@
|
|||||||
"outbound": "default"
|
"outbound": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"process_path_regex": [
|
"type": "logical",
|
||||||
"^.+/steam/compatibilitytools.d/.+",
|
"mode": "or",
|
||||||
"^.+/steamapps/common/.*"
|
"rules": [
|
||||||
],
|
{
|
||||||
"outbound": "direct"
|
"rule_set": [
|
||||||
},
|
"geoip-cn",
|
||||||
{
|
"geosite-geolocation-cn",
|
||||||
"rule_set": [
|
"geosite-cn",
|
||||||
"geoip-cn",
|
"geosite-category-games@cn",
|
||||||
"geosite-geolocation-cn",
|
"geosite-steam@cn"
|
||||||
"geosite-cn",
|
]
|
||||||
"geosite-category-games@cn",
|
}
|
||||||
"geosite-steam@cn"
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
@ -216,7 +216,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "./ui",
|
"external_ui": "./ui",
|
||||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
|
||||||
"external_ui_download_detour": "default",
|
"external_ui_download_detour": "default",
|
||||||
"default_mode": "Enhanced"
|
"default_mode": "Enhanced"
|
||||||
}
|
}
|
||||||
|
@ -68,15 +68,15 @@
|
|||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type": "tun",
|
"type": "tun",
|
||||||
"tag": "tun-in",
|
|
||||||
"address": [
|
"address": [
|
||||||
"192.168.135.1/30",
|
"172.16.0.1/30",
|
||||||
"fdfe:dcba:9876::1/126"
|
"fdfe:dcba:9876::1/126"
|
||||||
],
|
],
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"auto_redirect": true,
|
"auto_redirect": true,
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"route_exclude_address": "100.64.0.0/10"
|
"exclude_interface": "tailscale0",
|
||||||
|
"sniff": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@ -91,29 +91,30 @@
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "resolve",
|
"type": "logical",
|
||||||
"strategy": "prefer_ipv4"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "sniff",
|
"protocol": "dns"
|
||||||
"timeout": "1s"
|
},
|
||||||
},
|
{
|
||||||
{
|
"port": 53
|
||||||
"protocol": "dns",
|
}
|
||||||
"action": "hijack-dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": [
|
|
||||||
3478,
|
|
||||||
5348,
|
|
||||||
5349
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "dns"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
@ -132,23 +133,18 @@
|
|||||||
"outbound": "default"
|
"outbound": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-bahamut",
|
"type": "logical",
|
||||||
"outbound": "台湾(TW)"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"process_path_regex": [
|
"rule_set": [
|
||||||
"^.+/steam/compatibilitytools.d/.+",
|
"geoip-cn",
|
||||||
"^.+/steamapps/common/.*"
|
"geosite-geolocation-cn",
|
||||||
],
|
"geosite-cn",
|
||||||
"outbound": "direct"
|
"geosite-category-games@cn",
|
||||||
},
|
"geosite-steam@cn"
|
||||||
{
|
]
|
||||||
"rule_set": [
|
}
|
||||||
"geoip-cn",
|
|
||||||
"geosite-geolocation-cn",
|
|
||||||
"geosite-cn",
|
|
||||||
"geosite-category-games@cn",
|
|
||||||
"geosite-steam@cn"
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
@ -221,7 +217,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "./ui",
|
"external_ui": "./ui",
|
||||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
|
||||||
"external_ui_download_detour": "default",
|
"external_ui_download_detour": "default",
|
||||||
"default_mode": "Enhanced"
|
"default_mode": "Enhanced"
|
||||||
}
|
}
|
||||||
|
@ -90,29 +90,30 @@
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "resolve",
|
"type": "logical",
|
||||||
"strategy": "prefer_ipv4"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "sniff",
|
"protocol": "dns"
|
||||||
"timeout": "1s"
|
},
|
||||||
},
|
{
|
||||||
{
|
"port": 53
|
||||||
"protocol": "dns",
|
}
|
||||||
"action": "hijack-dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": [
|
|
||||||
3478,
|
|
||||||
5348,
|
|
||||||
5349
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "dns"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
@ -131,19 +132,18 @@
|
|||||||
"outbound": "default"
|
"outbound": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"process_path_regex": [
|
"type": "logical",
|
||||||
"^.+/steam/compatibilitytools.d/.+",
|
"mode": "or",
|
||||||
"^.+/steamapps/common/.*"
|
"rules": [
|
||||||
],
|
{
|
||||||
"outbound": "direct"
|
"rule_set": [
|
||||||
},
|
"geoip-cn",
|
||||||
{
|
"geosite-geolocation-cn",
|
||||||
"rule_set": [
|
"geosite-cn",
|
||||||
"geoip-cn",
|
"geosite-category-games@cn",
|
||||||
"geosite-geolocation-cn",
|
"geosite-steam@cn"
|
||||||
"geosite-cn",
|
]
|
||||||
"geosite-category-games@cn",
|
}
|
||||||
"geosite-steam@cn"
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
@ -216,7 +216,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "./ui",
|
"external_ui": "./ui",
|
||||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
|
||||||
"external_ui_download_detour": "default",
|
"external_ui_download_detour": "default",
|
||||||
"default_mode": "Enhanced"
|
"default_mode": "Enhanced"
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,10 @@
|
|||||||
"rule_set": "geosite-bahamut",
|
"rule_set": "geosite-bahamut",
|
||||||
"outbound": "台湾(TW)"
|
"outbound": "台湾(TW)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain_suffix": "online-fix.me",
|
||||||
|
"outbound": "香港(HK)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"process_path_regex": [
|
"process_path_regex": [
|
||||||
"^.+/steam/compatibilitytools.d/.+",
|
"^.+/steam/compatibilitytools.d/.+",
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "./ui",
|
"external_ui": "./ui",
|
||||||
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip",
|
|
||||||
"external_ui_download_detour": "default",
|
"external_ui_download_detour": "default",
|
||||||
"default_mode": "Enhanced"
|
"default_mode": "Enhanced"
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
{
|
{
|
||||||
"tag": "direct",
|
"tag": "direct",
|
||||||
"type": "direct"
|
"type": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "block",
|
||||||
|
"type": "block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "dns",
|
||||||
|
"type": "dns"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,17 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "resolve",
|
"type": "logical",
|
||||||
"strategy": "prefer_ipv4"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"action": "sniff",
|
"protocol": "dns"
|
||||||
"timeout": "1s"
|
},
|
||||||
},
|
{
|
||||||
{
|
"port": 53
|
||||||
"protocol": "dns",
|
}
|
||||||
"action": "hijack-dns"
|
],
|
||||||
},
|
"outbound": "dns"
|
||||||
{
|
|
||||||
"port": [3478, 5348, 5349],
|
|
||||||
"outbound": "direct"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
@ -34,23 +31,18 @@
|
|||||||
"outbound": "default"
|
"outbound": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-bahamut",
|
"type": "logical",
|
||||||
"outbound": "台湾(TW)"
|
"mode": "or",
|
||||||
},
|
"rules": [
|
||||||
{
|
{
|
||||||
"process_path_regex": [
|
"rule_set": [
|
||||||
"^.+/steam/compatibilitytools.d/.+",
|
"geoip-cn",
|
||||||
"^.+/steamapps/common/.*"
|
"geosite-geolocation-cn",
|
||||||
],
|
"geosite-cn",
|
||||||
"outbound": "direct"
|
"geosite-category-games@cn",
|
||||||
},
|
"geosite-steam@cn"
|
||||||
{
|
]
|
||||||
"rule_set": [
|
}
|
||||||
"geoip-cn",
|
|
||||||
"geosite-geolocation-cn",
|
|
||||||
"geosite-cn",
|
|
||||||
"geosite-category-games@cn",
|
|
||||||
"geosite-steam@cn"
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user