From 3ba7e43bc30c8f5ec5fff89e6ff6dd0c93fa56c0 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 25 Apr 2026 22:46:35 +0800 Subject: [PATCH] feat: add URL Schemes for Quick Import buttons (Android/iOS/Desktop) - Android: clash://install-config (Clash Meta for Android) - iOS: shadowrocket://add/sub/ (Shadowrocket) - Desktop: clash-verge://install-config (Clash Verge) - Extended API to return subEnable/subUrl for standard subscription --- config/version | 2 +- .../2026-04-25-user-panel-url-schemes.md | 28 +++++++++++++++ web/controller/inbound.go | 18 ++++++++++ web/html/user.html | 36 +++++++++++++++++-- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 docs/Tasktracking/2026-04-25-user-panel-url-schemes.md diff --git a/config/version b/config/version index 1a83aff2..6a0d714f 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -v1.7.2.5 \ No newline at end of file +v1.7.2.6 diff --git a/docs/Tasktracking/2026-04-25-user-panel-url-schemes.md b/docs/Tasktracking/2026-04-25-user-panel-url-schemes.md new file mode 100644 index 00000000..27c4931b --- /dev/null +++ b/docs/Tasktracking/2026-04-25-user-panel-url-schemes.md @@ -0,0 +1,28 @@ +# 2026-04-25 — User Panel: Quick Import URL Schemes + +## Summary +Wired up the 3 Quick Import dropdown buttons (Android/iOS/Desktop) with deep link URL schemes to launch proxy client apps directly from the user panel. + +## Changes + +### Backend (`web/controller/inbound.go`) +- Extended `getUserSubscriptions` API to also return `subEnable` and `subUrl` (standard subscription URL) +- Previously only returned `subClashEnable` and `subClashUrl` + +### Frontend (`web/html/user.html`) +- Added `subEnable` and `subUrl` data fields +- Updated `loadSubscriptions()` to save the new fields +- Added 3 URL scheme methods: + - **Android** → `clash://install-config?url=` (Clash Meta for Android) + - **iOS** → `shadowrocket://add/sub/?remark=` (Shadowrocket) + - **Desktop** → `clash-verge://install-config?url=&name=` (Clash Verge) +- Added `@click` handlers on the 3 dropdown menu items +- Each method validates subscription availability before opening the URL scheme + +### URL Scheme Priority +- Android/Desktop: prefers Clash URL (`subClashUrl`), falls back to standard URL (`subUrl`) +- iOS (Shadowrocket): prefers standard URL (`subUrl`), falls back to Clash URL + +## Files Modified +- `web/controller/inbound.go` — extended API response with subEnable/subUrl +- `web/html/user.html` — added URL scheme methods and click handlers diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 901c5b1e..ea42ffde 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -508,6 +508,22 @@ func (a *InboundController) getUserSubscriptions(c *gin.Context) { return } + subEnable := false + if v, ok := settings["subEnable"]; ok { + if b, ok2 := v.(bool); ok2 { + subEnable = b + } + } + + subUrl := "" + if subEnable { + if uri, ok := settings["subURI"]; ok { + if s, ok2 := uri.(string); ok2 && s != "" { + subUrl = s + subId + } + } + } + subClashEnable := false if v, ok := settings["subClashEnable"]; ok { if b, ok2 := v.(bool); ok2 { @@ -526,6 +542,8 @@ func (a *InboundController) getUserSubscriptions(c *gin.Context) { jsonObj(c, gin.H{ "subId": subId, + "subEnable": subEnable, + "subUrl": subUrl, "subClashEnable": subClashEnable, "subClashUrl": subClashUrl, }, nil) diff --git a/web/html/user.html b/web/html/user.html index ca07c6db..ccfd492d 100644 --- a/web/html/user.html +++ b/web/html/user.html @@ -136,15 +136,15 @@ {{ i18n "pages.user.quickImport" }} - + {{ i18n "pages.user.android" }} - + {{ i18n "pages.user.ios" }} - + {{ i18n "pages.user.desktop" }} @@ -177,6 +177,8 @@ username: '', traffic: null, lang: '', + subEnable: false, + subUrl: '', subClashEnable: false, subClashUrl: '', }, @@ -201,6 +203,8 @@ try { const msg = await HttpUtil.get('/panel/api/inbounds/userSubscriptions'); if (msg.success && msg.obj) { + this.subEnable = msg.obj.subEnable || false; + this.subUrl = msg.obj.subUrl || ''; this.subClashEnable = msg.obj.subClashEnable || false; this.subClashUrl = msg.obj.subClashUrl || ''; } @@ -214,6 +218,32 @@ this.$message[messageType](ok ? '{{ i18n "pages.user.copied" }}' : 'Copy failed'); }); }, + quickImportAndroid() { + const url = this.subClashUrl || this.subUrl; + if (!url) { + this.$message.warning('{{ i18n "pages.user.noSubscription" }}'); + return; + } + window.location.href = 'clash://install-config?url=' + encodeURIComponent(url); + }, + quickImportIOS() { + const url = this.subUrl || this.subClashUrl; + if (!url) { + this.$message.warning('{{ i18n "pages.user.noSubscription" }}'); + return; + } + const base64Url = btoa(url); + const remark = encodeURIComponent(this.username || 'Subscription'); + window.location.href = 'shadowrocket://add/sub/' + base64Url + '?remark=' + remark; + }, + quickImportDesktop() { + const url = this.subClashUrl || this.subUrl; + if (!url) { + this.$message.warning('{{ i18n "pages.user.noSubscription" }}'); + return; + } + window.location.href = 'clash-verge://install-config?url=' + encodeURIComponent(url) + '&name=' + encodeURIComponent(this.username || 'Subscription'); + }, formatExpiryTime(timestamp) { if (timestamp <= 0) return '{{ i18n "unlimited" }}'; const date = new Date(timestamp);