<!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" /> <title>sub2clash</title> <!-- Bootstrap CSS --> <link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" rel="stylesheet" /> <!-- Bootstrap JS --> <script crossorigin="anonymous" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" ></script> <!-- Axios --> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <style> .container { max-width: 800px; } .btn-xs { padding: 2px 2px; /* 调整内边距以减小按钮大小 */ font-size: 10px; /* 设置字体大小 */ line-height: 1.2; /* 调整行高 */ border-radius: 3px; /* 可选的边框半径调整 */ height: 25px; width: 25px; } </style> </head> <body class="bg-light"> <div class="container mt-5"> <div class="mb-4"> <h2>sub2clash</h2> <span class="text-muted fst-italic" >通用订阅链接转 Clash(Meta) 配置工具 <a href="https://github.com/nitezs/sub2clash#clash-meta" target="_blank" >使用文档</a ></span > </div> <form id="apiForm"> <!-- API Endpoint --> <div class="form-group mb-3"> <label for="endpoint">客户端类型:</label> <select class="form-control" id="endpoint" name="endpoint"> <option value="clash">Clash</option> <option value="meta">Clash.Meta</option> </select> </div> <!-- Subscription Link --> <div class="form-group mb-3"> <label for="sub">订阅链接:</label> <textarea class="form-control" id="sub" name="sub" placeholder="每行输入一个订阅链接" rows="5" ></textarea> </div> <!-- Proxy Link --> <div class="form-group mb-3"> <label for="proxy">节点分享链接:</label> <textarea class="form-control" id="proxy" name="proxy" placeholder="每行输入一个节点分享链接" rows="5" ></textarea> </div> <!-- Refresh --> <div class="form-check mb-3"> <input class="form-check-input" id="refresh" name="refresh" type="checkbox" /> <label class="form-check-label" for="refresh">强制刷新配置</label> </div> <!-- Template --> <div class="form-group mb-3"> <label for="template">模板链接或名称(可选):</label> <input class="form-control" id="template" name="template" placeholder="输入外部模板链接或内部模板名称" type="text" /> </div> <!-- Rule Provider --> <div class="form-group mb-3" id="ruleProviderGroup"> <label>Rule Provider:</label> <button class="btn btn-primary mb-1 btn-xs" onclick="addRuleProvider()" type="button" > + </button> </div> <!-- Rule --> <div class="form-group mb-3" id="ruleGroup"> <label>规则:</label> <button class="btn btn-primary mb-1 btn-xs" onclick="addRule()" type="button" > + </button> </div> <!-- Auto Test --> <div class="form-check mb-3"> <input class="form-check-input" id="autoTest" name="autoTest" type="checkbox" /> <label class="form-check-label" for="autoTest" >国家策略组自动测速</label > </div> <!-- Lazy --> <div class="form-check mb-3"> <input class="form-check-input" id="lazy" name="lazy" type="checkbox" /> <label class="form-check-label" for="lazy">自动测速启用 lazy</label> </div> <!-- Sort --> <div class="form-group mb-3"> <label for="sort">国家策略组排序策略:</label> <select class="form-control" id="sort" name="sort"> <option value="nameasc">名称(升序)</option> <option value="namedesc">名称(降序)</option> <option value="sizeasc">节点数量(升序)</option> <option value="sizedesc">节点数量(降序)</option> </select> </div> <!-- Remove --> <div class="form-group mb-3"> <label for="remove">删除节点:</label> <input class="form-control" type="text" name="remove" id="remove" placeholder="正则表达式" /> </div> <!-- Rename --> <div class="form-group mb-3" id="replaceGroup"> <label>节点名称替换:</label> <button class="btn btn-primary mb-1 btn-xs" onclick="addReplace()" type="button" > + </button> </div> </form> <!-- Display the API Link --> <div class="form-group mb-5"> <label for="apiLink">配置链接:</label> <div class="input-group mb-2"> <input class="form-control" id="apiLink" readonly type="text" /> <button class="btn btn-primary" onclick="generateURL()" type="button"> 生成链接 </button> <button class="btn btn-primary" onclick="copyToClipboard('apiLink',this)" type="button" > 复制链接 </button> </div> <div class="input-group"> <input class="form-control" id="apiShortLink" readonly type="text" /> <input class="form-control" id="password" type="text" placeholder="密码" /> <button class="btn btn-primary" onclick="generateShortLink()" type="button" > 生成短链 </button> <button class="btn btn-primary" onclick="copyToClipboard('apiShortLink',this)" type="button" > 复制短链 </button> </div> </div> <!-- footer--> <footer> <p class="text-center"> Powered by <a class="link-primary" href="https://github.com/nitezs/sub2clash" >sub2clash</a > </p> <p class="text-center">Version {{.Version}}</p> </footer> </div> <script> async function copyToClipboard(elem, e) { const apiLinkInput = document.querySelector(`#${elem}`).value; try { await navigator.clipboard.writeText(apiLinkInput); let text = e.textContent; e.addEventListener("mouseout", function () { e.textContent = text; }); e.textContent = "复制成功"; } catch (err) { console.error("复制到剪贴板失败:", err); } } function createRuleProvider() { const div = document.createElement("div"); div.classList.add("input-group", "mb-2"); div.innerHTML = ` <input type="text" class="form-control" name="ruleProvider" placeholder="Behavior"> <input type="text" class="form-control" name="ruleProvider" placeholder="Url"> <input type="text" class="form-control" name="ruleProvider" placeholder="Group"> <input type="text" class="form-control" name="ruleProvider" placeholder="Prepend"> <input type="text" class="form-control" name="ruleProvider" placeholder="Name"> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> `; return div; } function createReplace() { const div = document.createElement("div"); div.classList.add("input-group", "mb-2"); div.innerHTML = ` <input type="text" class="form-control" name="replace" placeholder="原字符串(正则表达式)"> <input type="text" class="form-control" name="replace" placeholder="替换为(可为空)"> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> `; return div; } function createRule() { const div = document.createElement("div"); div.classList.add("input-group", "mb-2"); div.innerHTML = ` <input type="text" class="form-control" name="rule" placeholder="Rule"> <input type="text" class="form-control" name="rule" placeholder="Prepend"> <input type="text" class="form-control" name="rule" placeholder="Group"> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> `; return div; } function addRuleProvider() { const div = createRuleProvider(); document.getElementById("ruleProviderGroup").appendChild(div); } function addRule() { const div = createRule(); document.getElementById("ruleGroup").appendChild(div); } function addReplace() { const div = createReplace(); document.getElementById("replaceGroup").appendChild(div); } function removeElement(button) { button.parentElement.remove(); } function generateURI() { const queryParams = []; // 获取 API Endpoint const endpoint = document.getElementById("endpoint").value; // 获取并组合订阅链接 let subLines = document .getElementById("sub") .value.split("\n") .filter((line) => line.trim() !== ""); let noSub = false; // 去除 subLines 中空元素 subLines = subLines.map((item) => { if (item !== "") { return item; } }); if (subLines.length > 0) { queryParams.push(`sub=${encodeURIComponent(subLines.join(","))}`); } else { noSub = true; } // 获取并组合节点分享链接 let proxyLines = document .getElementById("proxy") .value.split("\n") .filter((line) => line.trim() !== ""); let noProxy = false; // 去除 proxyLines 中空元素 proxyLines = proxyLines.map((item) => { if (item !== "") { return item; } }); if (proxyLines.length > 0) { queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(","))}`); } else { noProxy = true; } if (noSub && noProxy) { alert("订阅链接和节点分享链接不能同时为空!"); return ""; } // 获取复选框的值 const refresh = document.getElementById("refresh").checked; queryParams.push(`refresh=${refresh ? "true" : "false"}`); const autoTest = document.getElementById("autoTest").checked; queryParams.push(`autoTest=${autoTest ? "true" : "false"}`); const lazy = document.getElementById("lazy").checked; queryParams.push(`lazy=${lazy ? "true" : "false"}`); // 获取模板链接或名称(如果存在) const template = document.getElementById("template").value; if (template.trim() !== "") { queryParams.push(`template=${encodeURIComponent(template)}`); } // 获取Rule Provider和规则 const ruleProviders = document.getElementsByName("ruleProvider"); const rules = document.getElementsByName("rule"); let providers = []; for (let i = 0; i < ruleProviders.length / 5; i++) { let baseIndex = i * 5; let behavior = ruleProviders[baseIndex].value; let url = ruleProviders[baseIndex + 1].value; let group = ruleProviders[baseIndex + 2].value; let prepend = ruleProviders[baseIndex + 3].value; let name = ruleProviders[baseIndex + 4].value; // 是否存在空值 if ( behavior.trim() === "" || url.trim() === "" || group.trim() === "" || prepend.trim() === "" || name.trim() === "" ) { alert("Rule Provider 中存在空值,请检查后重试!"); return ""; } providers.push(`[${behavior},${url},${group},${prepend},${name}]`); } queryParams.push( `ruleProvider=${encodeURIComponent(providers.join(","))}`, ); let ruleList = []; for (let i = 0; i < rules.length / 3; i++) { if (rules[i * 3].value.trim() !== "") { let rule = rules[i * 3].value; let prepend = rules[i * 3 + 1].value; let group = rules[i * 3 + 2].value; // 是否存在空值 if ( rule.trim() === "" || prepend.trim() === "" || group.trim() === "" ) { alert("Rule 中存在空值,请检查后重试!"); return ""; } ruleList.push(`[${rule},${prepend},${group}]`); } } queryParams.push(`rule=${encodeURIComponent(ruleList.join(","))}`); // 获取排序策略 const sort = document.getElementById("sort").value; queryParams.push(`sort=${sort}`); // 获取删除节点的正则表达式 const remove = document.getElementById("remove").value; if (remove.trim() !== "") { queryParams.push(`remove=${encodeURIComponent(remove)}`); } // 获取替换节点名称的正则表达式 let replaceList = []; const replaces = document.getElementsByName("replace"); for (let i = 0; i < replaces.length / 2; i++) { let replaceStr = `<${replaces[i * 2].value}>`; let replaceTo = `<${replaces[i * 2 + 1].value}>`; if (replaceStr.trim() === "") { alert("重命名设置中存在空值,请检查后重试!"); return ""; } replaceList.push(`[${replaceStr},${replaceTo}]`); } queryParams.push( `replace=${encodeURIComponent(replaceList.join(","))}`, ); return `${endpoint}?${queryParams.join("&")}`; } function generateURL() { const apiLink = document.getElementById("apiLink"); let uri = generateURI(); if (uri === "") { return; } apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`; } function generateShortLink() { const apiShortLink = document.getElementById("apiShortLink"); const password = document.getElementById("password"); let uri = generateURI(); if (uri === "") { return; } axios .post( "./short", { url: uri, password: password.value.trim(), }, { headers: { "Content-Type": "application/json", }, }, ) .then((response) => { apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`; }) .catch((error) => { console.log(error); alert("生成短链失败,请重试!"); }); } </script> </body> </html>