From d580086361036f87af843d0f7386bdc54736720a Mon Sep 17 00:00:00 2001 From: zhuzn Date: Mon, 20 Apr 2026 04:26:13 +0800 Subject: feat add clash yaml convert (#3916) * docs(agents): add AI agent guidance documentation * feat(sub): add Clash/Mihomo YAML subscription service Add SubClashService to convert subscription links to Clash/Mihomo YAML format for direct client compatibility. Co-Authored-By: Claude Sonnet 4.6 * feat(sub): integrate Clash YAML endpoint into subscription system - Add Clash route handler in SUBController - Update BuildURLs to include Clash URL - Pass Clash settings through subscription pipeline Co-Authored-By: Claude Sonnet 4.6 * feat(web): add Clash settings to entity and service - Add SubClashEnable, SubClashPath, SubClashURI fields - Add getter methods for Clash configuration - Set default Clash path to /clash/ and enable by default Co-Authored-By: Claude Sonnet 4.6 * feat(ui): add Clash settings to subscription panels - Add Clash enable switch in general subscription settings - Add Clash path/URI configuration in formats panel - Display Clash QR code on subscription page - Rename JSON tab to "Formats" for clarity Co-Authored-By: Claude Sonnet 4.6 * feat(js): add Clash support to frontend models - Add subClashEnable, subClashPath, subClashURI to AllSetting - Generate and display Clash QR code on subscription page - Handle Clash URL in subscription data binding Co-Authored-By: Claude Sonnet 4.6 * fix --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Sanaei --- web/assets/js/model/setting.js | 3 + web/assets/js/subscription.js | 7 ++ web/entity/entity.go | 10 +++ web/html/settings.html | 4 +- web/html/settings/panel/subscription/general.html | 86 ++++++++++++++++------- web/html/settings/panel/subscription/json.html | 26 +++++-- web/html/settings/panel/subscription/subpage.html | 17 ++++- web/service/setting.go | 39 ++++++++-- 8 files changed, 152 insertions(+), 40 deletions(-) (limited to 'web') diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index af80a63e..d61d4b8e 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -38,6 +38,8 @@ class AllSetting { this.subPort = 2096; this.subPath = "/sub/"; this.subJsonPath = "/json/"; + this.subClashEnable = true; + this.subClashPath = "/clash/"; this.subDomain = ""; this.externalTrafficInformEnable = false; this.externalTrafficInformURI = ""; @@ -48,6 +50,7 @@ class AllSetting { this.subShowInfo = true; this.subURI = ""; this.subJsonURI = ""; + this.subClashURI = ""; this.subJsonFragment = ""; this.subJsonNoises = ""; this.subJsonMux = ""; diff --git a/web/assets/js/subscription.js b/web/assets/js/subscription.js index 228dcfa0..d08bfd28 100644 --- a/web/assets/js/subscription.js +++ b/web/assets/js/subscription.js @@ -9,6 +9,7 @@ sId: el.getAttribute('data-sid') || '', subUrl: el.getAttribute('data-sub-url') || '', subJsonUrl: el.getAttribute('data-subjson-url') || '', + subClashUrl: el.getAttribute('data-subclash-url') || '', download: el.getAttribute('data-download') || '', upload: el.getAttribute('data-upload') || '', used: el.getAttribute('data-used') || '', @@ -98,13 +99,19 @@ this.lang = LanguageManager.getLanguage(); const tpl = document.getElementById('subscription-data'); const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; + const sc = tpl ? tpl.getAttribute('data-subclash-url') : ''; if (sj) this.app.subJsonUrl = sj; + if (sc) this.app.subClashUrl = sc; drawQR(this.app.subUrl); try { const elJson = document.getElementById('qrcode-subjson'); if (elJson && this.app.subJsonUrl) { new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 }); } + const elClash = document.getElementById('qrcode-subclash'); + if (elClash && this.app.subClashUrl) { + new QRious({ element: elClash, value: this.app.subClashUrl, size: 220 }); + } } catch (e) { /* ignore */ } this._onResize = () => { this.viewportWidth = window.innerWidth; }; window.addEventListener('resize', this._onResize); diff --git a/web/entity/entity.go b/web/entity/entity.go index 40294925..14353cf0 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -76,6 +76,9 @@ type AllSetting struct { SubURI string `json:"subURI" form:"subURI"` // Subscription server URI SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI + SubClashEnable bool `json:"subClashEnable" form:"subClashEnable"` // Enable Clash/Mihomo subscription endpoint + SubClashPath string `json:"subClashPath" form:"subClashPath"` // Path for Clash/Mihomo subscription endpoint + SubClashURI string `json:"subClashURI" form:"subClashURI"` // Clash/Mihomo subscription server URI SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration @@ -168,6 +171,13 @@ func (s *AllSetting) CheckValid() error { s.SubJsonPath += "/" } + if !strings.HasPrefix(s.SubClashPath, "/") { + s.SubClashPath = "/" + s.SubClashPath + } + if !strings.HasSuffix(s.SubClashPath, "/") { + s.SubClashPath += "/" + } + _, err := time.LoadLocation(s.TimeLocation) if err != nil { return common.NewError("time location not exist:", s.TimeLocation) diff --git a/web/html/settings.html b/web/html/settings.html index 48aad524..441e62de 100644 --- a/web/html/settings.html +++ b/web/html/settings.html @@ -79,10 +79,10 @@ {{ template "settings/panel/subscription/general" . }} - + {{ template "settings/panel/subscription/json" . }} diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html index 5d83aa37..725a9359 100644 --- a/web/html/settings/panel/subscription/general.html +++ b/web/html/settings/panel/subscription/general.html @@ -3,43 +3,58 @@ - + - + + + + + + - + - + - + - +