From a3dff98071bc91703cc2a16d060b1d944722542a Mon Sep 17 00:00:00 2001 From: LinBeitsi Date: Thu, 4 Jun 2026 19:05:04 +0800 Subject: [PATCH 1/5] feat(profile): support age secret keys --- .../kr328/clash/ExternalControlActivity.kt | 4 +- .../github/kr328/clash/PropertiesActivity.kt | 4 +- core/src/foss/golang/go.mod | 1 + core/src/foss/golang/go.sum | 2 + core/src/main/cpp/main.c | 23 +++++-- core/src/main/golang/go.mod | 1 + core/src/main/golang/go.sum | 2 + core/src/main/golang/native/config.go | 13 ++++ .../java/com/github/kr328/clash/core/Clash.kt | 4 ++ .../github/kr328/clash/core/bridge/Bridge.kt | 2 + .../kr328/clash/design/PropertiesDesign.kt | 17 +++++ .../kr328/clash/design/util/Validator.kt | 4 ++ .../src/main/res/drawable/ic_baseline_key.xml | 3 + .../src/main/res/layout/design_properties.xml | 10 +++ design/src/main/res/values-ja-rJP/strings.xml | 3 + design/src/main/res/values-ko-rKR/strings.xml | 3 + design/src/main/res/values-ru/strings.xml | 3 + design/src/main/res/values-vi/strings.xml | 3 + design/src/main/res/values-zh-rHK/strings.xml | 3 + design/src/main/res/values-zh-rTW/strings.xml | 3 + design/src/main/res/values-zh/strings.xml | 3 + design/src/main/res/values/strings.xml | 3 + .../kr328/clash/service/ProfileManager.kt | 40 ++++++----- .../kr328/clash/service/ProfileProcessor.kt | 66 +++++++++---------- .../clash/module/ConfigurationModule.kt | 4 +- .../kr328/clash/service/data/Database.kt | 4 +- .../kr328/clash/service/data/Imported.kt | 1 + .../kr328/clash/service/data/Pending.kt | 1 + .../service/data/migrations/Migrations.kt | 14 +++- .../kr328/clash/service/document/Picker.kt | 3 +- .../kr328/clash/service/model/Profile.kt | 5 +- .../clash/service/remote/IProfileManager.kt | 6 +- 32 files changed, 187 insertions(+), 71 deletions(-) create mode 100644 design/src/main/res/drawable/ic_baseline_key.xml diff --git a/app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt b/app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt index ba80d03835..2f451ebf73 100644 --- a/app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt @@ -47,7 +47,7 @@ class ExternalControlActivity : Activity(), CoroutineScope by MainScope() { val intervalMs = java.util.concurrent.TimeUnit.MINUTES.toMillis(updateInterval) create(type, name).also { - patch(it, name, url, intervalMs) + patch(it, name, url, intervalMs, null) } } startActivity(PropertiesActivity::class.intent.setUUID(uuid)) @@ -103,4 +103,4 @@ class ExternalControlActivity : Activity(), CoroutineScope by MainScope() { @Suppress("DEPRECATION") overridePendingTransition(0, 0) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/kr328/clash/PropertiesActivity.kt b/app/src/main/java/com/github/kr328/clash/PropertiesActivity.kt index 62adc8bca3..a6fc07550a 100644 --- a/app/src/main/java/com/github/kr328/clash/PropertiesActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/PropertiesActivity.kt @@ -45,7 +45,7 @@ class PropertiesActivity : BaseActivity() { if (!canceled && profile != original) { withProfile { - patch(profile.uuid, profile.name, profile.source, profile.interval) + patch(profile.uuid, profile.name, profile.source, profile.interval, profile.ageSecretKey) } } } @@ -92,7 +92,7 @@ class PropertiesActivity : BaseActivity() { try { withProcessing { updateStatus -> withProfile { - patch(profile.uuid, profile.name, profile.source, profile.interval) + patch(profile.uuid, profile.name, profile.source, profile.interval, profile.ageSecretKey) coroutineScope { commit(profile.uuid) { diff --git a/core/src/foss/golang/go.mod b/core/src/foss/golang/go.mod index 4f3b5eedad..6558ae9155 100644 --- a/core/src/foss/golang/go.mod +++ b/core/src/foss/golang/go.mod @@ -44,6 +44,7 @@ require ( github.com/klauspost/reedsolomon v1.12.3 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect + github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 // indirect github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d // indirect github.com/metacubex/ascon v0.1.0 // indirect github.com/metacubex/bart v0.26.0 // indirect diff --git a/core/src/foss/golang/go.sum b/core/src/foss/golang/go.sum index 10f27a2e59..b524747070 100644 --- a/core/src/foss/golang/go.sum +++ b/core/src/foss/golang/go.sum @@ -86,6 +86,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 h1:LqWr0vb9zDNuQS+jJd4fnRYk/SEI7KJ7TDe/L4WFK48= +github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78/go.mod h1:BTBG/iVY7rg3qq5WdVCg0GFk58CSvCDSbjy8I7kEx/c= github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c= github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY= github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM= diff --git a/core/src/main/cpp/main.c b/core/src/main/cpp/main.c index 914bf7d9b6..20c5a46b83 100644 --- a/core/src/main/cpp/main.c +++ b/core/src/main/cpp/main.c @@ -226,9 +226,9 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeLoad(JNIEnv *env, jobject t JNIEXPORT void JNICALL Java_com_github_kr328_clash_core_bridge_Bridge_nativeFetchAndValid(JNIEnv *env, jobject thiz, - jobject callback, - jstring path, - jstring url, jboolean force) { + jobject callback, + jstring path, + jstring url, jboolean force) { TRACE_METHOD(); jobject _completable = new_global(callback); @@ -238,6 +238,21 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeFetchAndValid(JNIEnv *env, fetchAndValid(_completable, _path, _url, force); } +JNIEXPORT void JNICALL +Java_com_github_kr328_clash_core_bridge_Bridge_nativeSetAgeSecretKey(JNIEnv *env, jobject thiz, + jstring key) { + TRACE_METHOD(); + + if (key == NULL) { + setAgeSecretKey(NULL); + return; + } + + scoped_string _key = get_string(key); + + setAgeSecretKey(_key); +} + JNIEXPORT jstring JNICALL Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryProviders(JNIEnv *env, jobject thiz) { TRACE_METHOD(); @@ -526,4 +541,4 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeCoreVersion(JNIEnv *env, jo char* Version = make_String(GIT_VERSION); return new_string(Version); -} \ No newline at end of file +} diff --git a/core/src/main/golang/go.mod b/core/src/main/golang/go.mod index dbfb52c455..7ac5c97305 100644 --- a/core/src/main/golang/go.mod +++ b/core/src/main/golang/go.mod @@ -49,6 +49,7 @@ require ( github.com/klauspost/reedsolomon v1.12.3 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect + github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 // indirect github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d // indirect github.com/metacubex/ascon v0.1.0 // indirect github.com/metacubex/bart v0.26.0 // indirect diff --git a/core/src/main/golang/go.sum b/core/src/main/golang/go.sum index 10f27a2e59..b524747070 100644 --- a/core/src/main/golang/go.sum +++ b/core/src/main/golang/go.sum @@ -86,6 +86,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78 h1:LqWr0vb9zDNuQS+jJd4fnRYk/SEI7KJ7TDe/L4WFK48= +github.com/metacubex/age v0.0.0-20260603010618-28d156b4ea78/go.mod h1:BTBG/iVY7rg3qq5WdVCg0GFk58CSvCDSbjy8I7kEx/c= github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c= github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY= github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM= diff --git a/core/src/main/golang/native/config.go b/core/src/main/golang/native/config.go index f1e41606aa..8f864ceb63 100644 --- a/core/src/main/golang/native/config.go +++ b/core/src/main/golang/native/config.go @@ -8,6 +8,8 @@ import ( "unsafe" "cfa/native/config" + + "github.com/metacubex/mihomo/component/age" ) type remoteValidCallback struct { @@ -59,4 +61,15 @@ func writeOverride(slot C.int, content C.c_string) { //export clearOverride func clearOverride(slot C.int) { config.ClearOverride(config.OverrideSlot(slot)) +} + +//export setAgeSecretKey +func setAgeSecretKey(key C.c_string) { + if key == nil { + age.SetGlobalSecretKeys() + return + } + + k := C.GoString(key) + age.SetGlobalSecretKeys(k) } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index a3be5fa561..bc87822325 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -225,4 +225,8 @@ object Clash { }) } } + + fun setAgeSecretKey(key: String?) { + Bridge.nativeSetAgeSecretKey(key) + } } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt index 66df597d75..6007ff1d0d 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt @@ -50,6 +50,8 @@ object Bridge { external fun nativeSubscribeLogcat(callback: LogcatInterface) external fun nativeCoreVersion(): String + external fun nativeSetAgeSecretKey(key: String?) + private external fun nativeInit(home: String, versionName: String, sdkVersion: Int) init { diff --git a/design/src/main/java/com/github/kr328/clash/design/PropertiesDesign.kt b/design/src/main/java/com/github/kr328/clash/design/PropertiesDesign.kt index e18fafdf33..c3b5d56e82 100644 --- a/design/src/main/java/com/github/kr328/clash/design/PropertiesDesign.kt +++ b/design/src/main/java/com/github/kr328/clash/design/PropertiesDesign.kt @@ -121,6 +121,23 @@ class PropertiesDesign(context: Context) : Design(cont } } + fun inputAgeSecretKey() { + launch { + val ageSecretKey = context.requestModelTextInput( + initial = profile.ageSecretKey ?: "", + title = context.getText(R.string.age_secret_key), + hint = context.getText(R.string.age_secret_key_hint), + error = context.getText(R.string.age_secret_key_error), + validator = ValidatorAgeSecretKey + ) + + val newKey = ageSecretKey.ifBlank { null } + if (newKey != profile.ageSecretKey) { + profile = profile.copy(ageSecretKey = newKey) + } + } + } + fun inputInterval() { launch { var minutes = TimeUnit.MILLISECONDS.toMinutes(profile.interval) diff --git a/design/src/main/java/com/github/kr328/clash/design/util/Validator.kt b/design/src/main/java/com/github/kr328/clash/design/util/Validator.kt index f78f6e4225..8fa8723251 100644 --- a/design/src/main/java/com/github/kr328/clash/design/util/Validator.kt +++ b/design/src/main/java/com/github/kr328/clash/design/util/Validator.kt @@ -22,4 +22,8 @@ val ValidatorHttpUrl: Validator = { val ValidatorAutoUpdateInterval: Validator = { it.isEmpty() || (it.toLongOrNull() ?: 0) >= 15 +} + +val ValidatorAgeSecretKey: Validator = { + it.isEmpty() || it.startsWith("AGE-SECRET-KEY-", ignoreCase = true) } \ No newline at end of file diff --git a/design/src/main/res/drawable/ic_baseline_key.xml b/design/src/main/res/drawable/ic_baseline_key.xml new file mode 100644 index 0000000000..d2cd6f1138 --- /dev/null +++ b/design/src/main/res/drawable/ic_baseline_key.xml @@ -0,0 +1,3 @@ + + + diff --git a/design/src/main/res/layout/design_properties.xml b/design/src/main/res/layout/design_properties.xml index b037d95cc7..a234bba265 100644 --- a/design/src/main/res/layout/design_properties.xml +++ b/design/src/main/res/layout/design_properties.xml @@ -77,6 +77,16 @@ app:text="@{profile.source}" app:title="@string/url" /> + + http(s) のみを許可 15分以上か空白にしてください 空白にはできません + Age秘密鍵 + AGE-SECRET-KEY-…(任意) + フォーマットが無効です。鍵は AGE-SECRET-KEY- で始まる必要があります 詳細 更新 編集 diff --git a/design/src/main/res/values-ko-rKR/strings.xml b/design/src/main/res/values-ko-rKR/strings.xml index ab0bbf0a5e..5e858be606 100644 --- a/design/src/main/res/values-ko-rKR/strings.xml +++ b/design/src/main/res/values-ko-rKR/strings.xml @@ -47,6 +47,9 @@ http(s) 연결만 허용 최소 15분 이상 또는 공백 이 필드는 공백이 허용되지 않습니다. + Age 비밀 키 + AGE-SECRET-KEY-…(선택사항) + 잘못된 형식입니다. 키는 AGE-SECRET-KEY- 로 시작해야 합니다 자세히 업데이트 편집 diff --git a/design/src/main/res/values-ru/strings.xml b/design/src/main/res/values-ru/strings.xml index 342b639d28..f81525e3b7 100644 --- a/design/src/main/res/values-ru/strings.xml +++ b/design/src/main/res/values-ru/strings.xml @@ -57,6 +57,9 @@ Принимать только http(s) Не менее 15 минут или пустой Не должно быть пустым + Секретный ключ Age + AGE-SECRET-KEY-… (необязательно) + Неверный формат. Ключ должен начинаться с AGE-SECRET-KEY- Детали Обновить Изменить diff --git a/design/src/main/res/values-vi/strings.xml b/design/src/main/res/values-vi/strings.xml index 2bf8aacad4..fd337de68f 100644 --- a/design/src/main/res/values-vi/strings.xml +++ b/design/src/main/res/values-vi/strings.xml @@ -25,6 +25,9 @@ Các gói kiểm soát truy cập Định cấu hình quyền truy cập cho các ứng dụng Hồ sơ cần được lưu trước khi kích hoạt + Khóa bí mật Age + AGE-SECRET-KEY-… (tùy chọn) + Định dạng không hợp lệ. Khóa phải bắt đầu bằng AGE-SECRET-KEY- Cho phép tất cả các ứng dụng Cho phép bỏ qua Cho phép tất cả các ứng dụng bỏ qua kết nối VPN này diff --git a/design/src/main/res/values-zh-rHK/strings.xml b/design/src/main/res/values-zh-rHK/strings.xml index 537b33f3ee..9a83b6e048 100644 --- a/design/src/main/res/values-zh-rHK/strings.xml +++ b/design/src/main/res/values-zh-rHK/strings.xml @@ -78,6 +78,9 @@ 更新時間 應用包名稱 安裝時間 + Age 私鑰 + AGE-SECRET-KEY-…(可選) + 格式無效。密鑰應以 AGE-SECRET-KEY- 開頭 反饋 Github Issues Clash 配置文件(包含代理/規則)]]> diff --git a/design/src/main/res/values-zh-rTW/strings.xml b/design/src/main/res/values-zh-rTW/strings.xml index cf70cc2d41..feca4582c9 100644 --- a/design/src/main/res/values-zh-rTW/strings.xml +++ b/design/src/main/res/values-zh-rTW/strings.xml @@ -78,6 +78,9 @@ 更新時間 套件名稱 安裝時間 + Age 私鑰 + AGE-SECRET-KEY-…(選填) + 格式無效。金鑰應以 AGE-SECRET-KEY- 開頭 回饋 Github Issues Clash 設定檔 (包含Proxy /規則)]]> diff --git a/design/src/main/res/values-zh/strings.xml b/design/src/main/res/values-zh/strings.xml index 31c1371995..09de0724bd 100644 --- a/design/src/main/res/values-zh/strings.xml +++ b/design/src/main/res/values-zh/strings.xml @@ -78,6 +78,9 @@ VpnService 选项 选项在 Clash 运行时不可用 查找 + Age 私钥 + AGE-SECRET-KEY-…(可选) + 格式无效。密钥应以 AGE-SECRET-KEY- 开头 系统应用 更新时间 应用包名称 diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml index 0808ae4bd9..f4afaa29fe 100644 --- a/design/src/main/res/values/strings.xml +++ b/design/src/main/res/values/strings.xml @@ -62,6 +62,9 @@ Accept only http(s) At least 15 minutes or empty + Age Secret Key + AGE-SECRET-KEY-… (optional) + Invalid format. Key should start with AGE-SECRET-KEY- Should not be blank Detail Update diff --git a/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt b/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt index 848dd3d20e..b5f1e1e339 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt @@ -37,7 +37,7 @@ class ProfileManager(private val context: Context) : IProfileManager, } } - override suspend fun create(type: Profile.Type, name: String, source: String): UUID { + override suspend fun create(type: Profile.Type, name: String, source: String, ageSecretKey: String?): UUID { val uuid = generateProfileUUID() val pending = Pending( uuid = uuid, @@ -49,6 +49,7 @@ class ProfileManager(private val context: Context) : IProfileManager, total = 0, download = 0, expire = 0, + ageSecretKey = ageSecretKey, ) PendingDao().insert(pending) @@ -81,6 +82,7 @@ class ProfileManager(private val context: Context) : IProfileManager, total = imported.total, download = imported.download, expire = imported.expire, + ageSecretKey = imported.ageSecretKey ) cloneImportedFiles(uuid, newUUID) @@ -90,7 +92,7 @@ class ProfileManager(private val context: Context) : IProfileManager, return newUUID } - override suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) { + override suspend fun patch(uuid: UUID, name: String, source: String, interval: Long, ageSecretKey: String?) { val pending = PendingDao().queryByUUID(uuid) if (pending == null) { @@ -110,6 +112,7 @@ class ProfileManager(private val context: Context) : IProfileManager, total = 0, download = 0, expire = 0, + ageSecretKey = ageSecretKey, ) ) } else { @@ -121,6 +124,7 @@ class ProfileManager(private val context: Context) : IProfileManager, total = 0, download = 0, expire = 0, + ageSecretKey = ageSecretKey, ) PendingDao().update(newPending) @@ -188,7 +192,8 @@ class ProfileManager(private val context: Context) : IProfileManager, download, total, expire, - old?.createdAt ?: System.currentTimeMillis() + old?.createdAt ?: System.currentTimeMillis(), + ageSecretKey = old.ageSecretKey ) if (old != null) { @@ -266,19 +271,20 @@ class ProfileManager(private val context: Context) : IProfileManager, val expire = pending?.expire ?: imported?.expire ?: return null return Profile( - uuid, - name, - type, - source, - active != null && imported?.uuid == active, - interval, - upload, - download, - total, - expire, - resolveUpdatedAt(uuid), - imported != null, - pending != null + uuid = uuid, + name = name, + type = type, + source = source, + active = active != null && imported?.uuid == active, + interval = interval, + upload = upload, + download = download, + total = total, + expire = expire, + updatedAt = resolveUpdatedAt(uuid), + imported = imported != null, + pending = pending != null, + ageSecretKey = if (pending != null) pending.ageSecretKey else imported?.ageSecretKey, ) } @@ -309,4 +315,4 @@ class ProfileManager(private val context: Context) : IProfileManager, ProfileReceiver.scheduleNext(context, imported) } } -} \ No newline at end of file +} diff --git a/service/src/main/java/com/github/kr328/clash/service/ProfileProcessor.kt b/service/src/main/java/com/github/kr328/clash/service/ProfileProcessor.kt index 710f5d48c6..6ad6276954 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ProfileProcessor.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ProfileProcessor.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import java.math.BigDecimal -import java.net.URL import java.util.* import java.util.concurrent.TimeUnit @@ -34,8 +33,8 @@ object ProfileProcessor { withContext(NonCancellable) { processLock.withLock { val snapshot = profileLock.withLock { - val pending = PendingDao().queryByUUID(uuid) - ?: throw IllegalArgumentException("profile $uuid not found") + val pending = + PendingDao().queryByUUID(uuid) ?: throw IllegalArgumentException("profile $uuid not found") pending.enforceFieldValid() @@ -48,6 +47,8 @@ object ProfileProcessor { pending } + Clash.setAgeSecretKey(snapshot.ageSecretKey?.takeIf { it.isNotBlank() }) + val force = snapshot.type != Profile.Type.File var cb = callback @@ -63,10 +64,8 @@ object ProfileProcessor { profileLock.withLock { if (PendingDao().queryByUUID(snapshot.uuid) == snapshot) { - context.importedDir.resolve(snapshot.uuid.toString()) - .deleteRecursively() - context.processingDir - .copyRecursively(context.importedDir.resolve(snapshot.uuid.toString())) + context.importedDir.resolve(snapshot.uuid.toString()).deleteRecursively() + context.processingDir.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString())) val old = ImportedDao().queryByUUID(snapshot.uuid) var upload: Long = 0 @@ -77,11 +76,10 @@ object ProfileProcessor { if (snapshot?.type == Profile.Type.Url) { if (snapshot.source.startsWith("https://", true)) { val client = OkHttpClient() - val versionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName - val request = Request.Builder() - .url(snapshot.source) - .header("User-Agent", "ClashMetaForAndroid/$versionName") - .build() + val versionName = + context.packageManager.getPackageInfo(context.packageName, 0).versionName + val request = Request.Builder().url(snapshot.source) + .header("User-Agent", "ClashMetaForAndroid/$versionName").build() client.newCall(request).execute().use { response -> val userinfo = response.headers["subscription-userinfo"] @@ -99,7 +97,7 @@ object ProfileProcessor { info[0].contains("total") && info[1].isNotEmpty() -> total = BigDecimal(info[1].split('.').first()).longValueExact() - info[0].contains("expire") && info[1].isNotEmpty() -> expire = + info[0].contains("expire") && info[1].isNotEmpty() -> expire = (info[1].toDouble() * 1000).toLong() } } @@ -110,8 +108,8 @@ object ProfileProcessor { val intervalHours = updateIntervalHeader.toLongOrNull() if (intervalHours != null) { updateInterval = if (intervalHours > 0) { - java.util.concurrent.TimeUnit.HOURS.toMillis(intervalHours) - .coerceAtLeast(java.util.concurrent.TimeUnit.MINUTES.toMillis(15)) + TimeUnit.HOURS.toMillis(intervalHours) + .coerceAtLeast(TimeUnit.MINUTES.toMillis(15)) } else { 0L } @@ -129,7 +127,8 @@ object ProfileProcessor { download, total, expire, - old?.createdAt ?: System.currentTimeMillis() + old?.createdAt ?: System.currentTimeMillis(), + ageSecretKey = snapshot.ageSecretKey ) if (old != null) { ImportedDao().update(new) @@ -139,8 +138,7 @@ object ProfileProcessor { PendingDao().remove(snapshot.uuid) - context.pendingDir.resolve(snapshot.uuid.toString()) - .deleteRecursively() + context.pendingDir.resolve(snapshot.uuid.toString()).deleteRecursively() context.sendProfileChanged(snapshot.uuid) } else if (snapshot?.type == Profile.Type.File) { @@ -154,7 +152,8 @@ object ProfileProcessor { download, total, expire, - old?.createdAt ?: System.currentTimeMillis() + old?.createdAt ?: System.currentTimeMillis(), + ageSecretKey = snapshot.ageSecretKey ) if (old != null) { ImportedDao().update(new) @@ -164,8 +163,7 @@ object ProfileProcessor { PendingDao().remove(snapshot.uuid) - context.pendingDir.resolve(snapshot.uuid.toString()) - .deleteRecursively() + context.pendingDir.resolve(snapshot.uuid.toString()).deleteRecursively() context.sendProfileChanged(snapshot.uuid) } @@ -179,8 +177,8 @@ object ProfileProcessor { withContext(NonCancellable) { processLock.withLock { val snapshot = profileLock.withLock { - val imported = ImportedDao().queryByUUID(uuid) - ?: throw IllegalArgumentException("profile $uuid not found") + val imported = + ImportedDao().queryByUUID(uuid) ?: throw IllegalArgumentException("profile $uuid not found") context.processingDir.deleteRecursively() context.processingDir.mkdirs() @@ -191,6 +189,8 @@ object ProfileProcessor { imported } + Clash.setAgeSecretKey(snapshot.ageSecretKey?.takeIf { it.isNotBlank() }) + var cb = callback Clash.fetchAndValid(context.processingDir, snapshot.source, true) { @@ -206,8 +206,7 @@ object ProfileProcessor { profileLock.withLock { if (ImportedDao().exists(snapshot.uuid)) { context.importedDir.resolve(snapshot.uuid.toString()).deleteRecursively() - context.processingDir - .copyRecursively(context.importedDir.resolve(snapshot.uuid.toString())) + context.processingDir.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString())) context.sendProfileChanged(snapshot.uuid) } @@ -261,17 +260,16 @@ object ProfileProcessor { val scheme = Uri.parse(source)?.scheme?.lowercase(Locale.getDefault()) when { - name.isBlank() -> - throw IllegalArgumentException("Empty name") + name.isBlank() -> throw IllegalArgumentException("Empty name") - source.isEmpty() && type != Profile.Type.File -> - throw IllegalArgumentException("Invalid url") + source.isEmpty() && type != Profile.Type.File -> throw IllegalArgumentException("Invalid url") - source.isNotEmpty() && scheme != "https" && scheme != "http" && scheme != "content" -> - throw IllegalArgumentException("Unsupported url $source") + source.isNotEmpty() && scheme != "https" && scheme != "http" && scheme != "content" -> throw IllegalArgumentException( + "Unsupported url $source" + ) - interval != 0L && TimeUnit.MILLISECONDS.toMinutes(interval) < 15 -> - throw IllegalArgumentException("Invalid interval") + interval != 0L && TimeUnit.MILLISECONDS.toMinutes(interval) < 15 -> throw IllegalArgumentException("Invalid interval") } } -} \ No newline at end of file + +} diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/ConfigurationModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/ConfigurationModule.kt index 4e384b46d6..7de311aaa9 100644 --- a/service/src/main/java/com/github/kr328/clash/service/clash/module/ConfigurationModule.kt +++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/ConfigurationModule.kt @@ -55,6 +55,8 @@ class ConfigurationModule(service: Service) : Module = arrayOf() +private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE imported ADD COLUMN ageSecretKey TEXT") + database.execSQL("ALTER TABLE pending ADD COLUMN ageSecretKey TEXT") + } +} -val LEGACY_MIGRATION = ::migrationFromLegacy \ No newline at end of file +val MIGRATIONS: Array = arrayOf( + MIGRATION_1_2, +) + +val LEGACY_MIGRATION = ::migrationFromLegacy diff --git a/service/src/main/java/com/github/kr328/clash/service/document/Picker.kt b/service/src/main/java/com/github/kr328/clash/service/document/Picker.kt index aa08829ce4..7eaa9e2a4f 100644 --- a/service/src/main/java/com/github/kr328/clash/service/document/Picker.kt +++ b/service/src/main/java/com/github/kr328/clash/service/document/Picker.kt @@ -134,7 +134,8 @@ class Picker(private val context: Context) { imported.type, imported.source, imported.interval, - 0,0,0,0 + 0,0,0,0, + ageSecretKey = imported.ageSecretKey ) ) diff --git a/service/src/main/java/com/github/kr328/clash/service/model/Profile.kt b/service/src/main/java/com/github/kr328/clash/service/model/Profile.kt index 4c2b498016..436894ad0a 100644 --- a/service/src/main/java/com/github/kr328/clash/service/model/Profile.kt +++ b/service/src/main/java/com/github/kr328/clash/service/model/Profile.kt @@ -22,11 +22,10 @@ data class Profile( var download: Long, val total: Long, val expire: Long, - - val updatedAt: Long, val imported: Boolean, val pending: Boolean, + val ageSecretKey: String? = null, ) : Parcelable { enum class Type { File, Url, External @@ -49,4 +48,4 @@ data class Profile( return arrayOfNulls(size) } } -} \ No newline at end of file +} diff --git a/service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt b/service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt index f5915f0f17..0eace2ef7b 100644 --- a/service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt +++ b/service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt @@ -6,15 +6,15 @@ import java.util.* @BinderInterface interface IProfileManager { - suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID + suspend fun create(type: Profile.Type, name: String, source: String = "", ageSecretKey: String? = null): UUID suspend fun clone(uuid: UUID): UUID suspend fun commit(uuid: UUID, callback: IFetchObserver? = null) suspend fun release(uuid: UUID) suspend fun delete(uuid: UUID) - suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) + suspend fun patch(uuid: UUID, name: String, source: String, interval: Long, ageSecretKey: String?) suspend fun update(uuid: UUID) suspend fun queryByUUID(uuid: UUID): Profile? suspend fun queryAll(): List suspend fun queryActive(): Profile? suspend fun setActive(profile: Profile) -} \ No newline at end of file +} From 1644be4e75d03c1359ca29f353a9dbc553fbfa25 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Thu, 4 Jun 2026 20:51:39 +0800 Subject: [PATCH 2/5] Update golang binding --- core/src/main/golang/native/config.go | 8 +++----- core/src/main/golang/native/config/age.go | 7 +++++++ 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 core/src/main/golang/native/config/age.go diff --git a/core/src/main/golang/native/config.go b/core/src/main/golang/native/config.go index 8f864ceb63..6338bd138c 100644 --- a/core/src/main/golang/native/config.go +++ b/core/src/main/golang/native/config.go @@ -8,8 +8,6 @@ import ( "unsafe" "cfa/native/config" - - "github.com/metacubex/mihomo/component/age" ) type remoteValidCallback struct { @@ -66,10 +64,10 @@ func clearOverride(slot C.int) { //export setAgeSecretKey func setAgeSecretKey(key C.c_string) { if key == nil { - age.SetGlobalSecretKeys() + config.SetGlobalSecretKeys() return } k := C.GoString(key) - age.SetGlobalSecretKeys(k) -} \ No newline at end of file + config.SetGlobalSecretKeys(k) +} diff --git a/core/src/main/golang/native/config/age.go b/core/src/main/golang/native/config/age.go new file mode 100644 index 0000000000..5348dd8370 --- /dev/null +++ b/core/src/main/golang/native/config/age.go @@ -0,0 +1,7 @@ +package config + +import "github.com/metacubex/mihomo/component/age" + +func SetGlobalSecretKeys(secretKeys ...string) { + age.SetGlobalSecretKeys(secretKeys...) +} From 087d6addc2bf60a761e06a291e3d240f628da881 Mon Sep 17 00:00:00 2001 From: LinBeitsi <58358419+NevadaCities@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:28:52 +0800 Subject: [PATCH 3/5] Update ic_baseline_key.xml --- design/src/main/res/drawable/ic_baseline_key.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/design/src/main/res/drawable/ic_baseline_key.xml b/design/src/main/res/drawable/ic_baseline_key.xml index d2cd6f1138..c3ea24bd84 100644 --- a/design/src/main/res/drawable/ic_baseline_key.xml +++ b/design/src/main/res/drawable/ic_baseline_key.xml @@ -1,3 +1,10 @@ - - + + From 0dc616cb32856b79ddbcc9075414002537a54011 Mon Sep 17 00:00:00 2001 From: LinBeitsi Date: Thu, 4 Jun 2026 22:30:01 +0800 Subject: [PATCH 4/5] fix(app): reduce shortcut icon scale --- app/src/main/res/drawable/ic_toggle_all.xml | 42 +++++++++++++++++--- app/src/main/res/drawable/ic_toggle_off.xml | 44 +++++++++++++++++---- app/src/main/res/drawable/ic_toggle_on.xml | 30 ++++++++++---- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/drawable/ic_toggle_all.xml b/app/src/main/res/drawable/ic_toggle_all.xml index 29ce98e9c9..f6ad6e2ba9 100644 --- a/app/src/main/res/drawable/ic_toggle_all.xml +++ b/app/src/main/res/drawable/ic_toggle_all.xml @@ -1,9 +1,41 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + + + + + + + diff --git a/app/src/main/res/drawable/ic_toggle_off.xml b/app/src/main/res/drawable/ic_toggle_off.xml index 6bbc6d155c..89931b20a4 100644 --- a/app/src/main/res/drawable/ic_toggle_off.xml +++ b/app/src/main/res/drawable/ic_toggle_off.xml @@ -1,11 +1,41 @@ - - + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + + + + + diff --git a/app/src/main/res/drawable/ic_toggle_on.xml b/app/src/main/res/drawable/ic_toggle_on.xml index 1162f274a5..354b34eca7 100644 --- a/app/src/main/res/drawable/ic_toggle_on.xml +++ b/app/src/main/res/drawable/ic_toggle_on.xml @@ -1,11 +1,27 @@ - - + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + + + From 74bf1b8420ecb7e259b1131dd3719565c5d310e9 Mon Sep 17 00:00:00 2001 From: LinBeitsi Date: Thu, 4 Jun 2026 23:28:45 +0800 Subject: [PATCH 5/5] Refine age secret key translations --- design/src/main/res/values-ja-rJP/strings.xml | 8 ++++---- design/src/main/res/values-ko-rKR/strings.xml | 6 +++--- design/src/main/res/values-ru/strings.xml | 2 +- design/src/main/res/values-zh-rHK/strings.xml | 4 ++-- design/src/main/res/values-zh-rTW/strings.xml | 2 +- design/src/main/res/values-zh/strings.xml | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/design/src/main/res/values-ja-rJP/strings.xml b/design/src/main/res/values-ja-rJP/strings.xml index 280c20abc0..92fed35d72 100644 --- a/design/src/main/res/values-ja-rJP/strings.xml +++ b/design/src/main/res/values-ja-rJP/strings.xml @@ -47,9 +47,9 @@ http(s) のみを許可 15分以上か空白にしてください 空白にはできません - Age秘密鍵 - AGE-SECRET-KEY-…(任意) - フォーマットが無効です。鍵は AGE-SECRET-KEY- で始まる必要があります + Age 秘密鍵 + AGE-SECRET-KEY-… (任意) + 形式が無効です。キーは AGE-SECRET-KEY- で始まる必要があります 詳細 更新 編集 @@ -256,4 +256,4 @@ Override Destination カメラのアクセスが制限されています。設定から有効にしてください。 システムで予期しない例外が発生しました。 - \ No newline at end of file + diff --git a/design/src/main/res/values-ko-rKR/strings.xml b/design/src/main/res/values-ko-rKR/strings.xml index 5e858be606..a42b6481f4 100644 --- a/design/src/main/res/values-ko-rKR/strings.xml +++ b/design/src/main/res/values-ko-rKR/strings.xml @@ -48,8 +48,8 @@ 최소 15분 이상 또는 공백 이 필드는 공백이 허용되지 않습니다. Age 비밀 키 - AGE-SECRET-KEY-…(선택사항) - 잘못된 형식입니다. 키는 AGE-SECRET-KEY- 로 시작해야 합니다 + AGE-SECRET-KEY-… (선택 사항) + 형식이 올바르지 않습니다. 키는 AGE-SECRET-KEY-로 시작해야 합니다 자세히 업데이트 편집 @@ -256,4 +256,4 @@ Override Destination 카메라 접근이 제한되었습니다. 설정에서 허용해 주세요. 처리되지 않은 시스템 예외가 발생했습니다. - \ No newline at end of file + diff --git a/design/src/main/res/values-ru/strings.xml b/design/src/main/res/values-ru/strings.xml index f81525e3b7..e96db42ce8 100644 --- a/design/src/main/res/values-ru/strings.xml +++ b/design/src/main/res/values-ru/strings.xml @@ -59,7 +59,7 @@ Не должно быть пустым Секретный ключ Age AGE-SECRET-KEY-… (необязательно) - Неверный формат. Ключ должен начинаться с AGE-SECRET-KEY- + Недопустимый формат. Ключ должен начинаться с AGE-SECRET-KEY- Детали Обновить Изменить diff --git a/design/src/main/res/values-zh-rHK/strings.xml b/design/src/main/res/values-zh-rHK/strings.xml index 9a83b6e048..21f1a2cfd1 100644 --- a/design/src/main/res/values-zh-rHK/strings.xml +++ b/design/src/main/res/values-zh-rHK/strings.xml @@ -80,7 +80,7 @@ 安裝時間 Age 私鑰 AGE-SECRET-KEY-…(可選) - 格式無效。密鑰應以 AGE-SECRET-KEY- 開頭 + 無效格式。密鑰應以 AGE-SECRET-KEY- 開頭 反饋 Github Issues Clash 配置文件(包含代理/規則)]]> @@ -253,4 +253,4 @@ Override Destination 相機權限受限,請前往設定開啟。 發生系統未知異常,操作失敗。 - \ No newline at end of file + diff --git a/design/src/main/res/values-zh-rTW/strings.xml b/design/src/main/res/values-zh-rTW/strings.xml index feca4582c9..aae2c144b2 100644 --- a/design/src/main/res/values-zh-rTW/strings.xml +++ b/design/src/main/res/values-zh-rTW/strings.xml @@ -80,7 +80,7 @@ 安裝時間 Age 私鑰 AGE-SECRET-KEY-…(選填) - 格式無效。金鑰應以 AGE-SECRET-KEY- 開頭 + 無效格式。金鑰應以 AGE-SECRET-KEY- 開頭 回饋 Github Issues Clash 設定檔 (包含Proxy /規則)]]> diff --git a/design/src/main/res/values-zh/strings.xml b/design/src/main/res/values-zh/strings.xml index 09de0724bd..253ab81b98 100644 --- a/design/src/main/res/values-zh/strings.xml +++ b/design/src/main/res/values-zh/strings.xml @@ -80,7 +80,7 @@ 查找 Age 私钥 AGE-SECRET-KEY-…(可选) - 格式无效。密钥应以 AGE-SECRET-KEY- 开头 + 无效格式。密钥应以 AGE-SECRET-KEY- 开头 系统应用 更新时间 应用包名称