diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/UrlContentRequest.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/UrlContentRequest.kt index 03fbaa31..c8440a4f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/UrlContentRequest.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/UrlContentRequest.kt @@ -6,5 +6,6 @@ data class UrlContentRequest( val httpPort: Int = 0, val proxyUsername: String? = null, val proxyPassword: String? = null, - val userAgent: String? = null + val userAgent: String? = null, + val requestHeaders: String? = null ) \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/SubscriptionItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/SubscriptionItem.kt index 021d51aa..8f62180f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/SubscriptionItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/SubscriptionItem.kt @@ -13,5 +13,6 @@ data class SubscriptionItem( var filter: String? = null, var allowInsecureUrl: Boolean = false, var userAgent: String? = null, + var requestHeaders: String? = null, ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt index 3a2e6a81..a42df526 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt @@ -557,6 +557,7 @@ object AngConfigManager { } LogUtil.i(AppConfig.TAG, url) val userAgent = it.subscription.userAgent + val requestHeaders = it.subscription.requestHeaders val proxyUsername = SettingsManager.getSocksUsername() val proxyPassword = SettingsManager.getSocksPassword() @@ -566,6 +567,7 @@ object AngConfigManager { UrlContentRequest( url = url, userAgent = userAgent, + requestHeaders = requestHeaders, timeout = 15000, httpPort = httpPort, proxyUsername = proxyUsername, @@ -581,7 +583,8 @@ object AngConfigManager { HttpUtil.getUrlContentWithUserAgent( UrlContentRequest( url = url, - userAgent = userAgent + userAgent = userAgent, + requestHeaders = requestHeaders ) ) } catch (e: Exception) { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt index 93fa9df7..cccbe2de 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt @@ -75,7 +75,6 @@ class SubEditActivity : BaseComponentActivity() { } private fun saveServer(subItem: SubscriptionItem): Boolean { - val intervalMinutes = subItem.updateInterval if (TextUtils.isEmpty(subItem.remarks)) { toast(R.string.sub_setting_remarks) @@ -94,6 +93,11 @@ class SubEditActivity : BaseComponentActivity() { } } + if (subItem.autoUpdate && subItem.updateInterval < AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) { + toast(R.string.toast_invalid_update_interval) + return false + } + MmkvManager.encodeSubscription(editSubId, subItem) SubscriptionUpdater.syncOne(subId = editSubId) SettingsChangeManager.makeSetupGroupTab() @@ -123,11 +127,11 @@ fun SubEditScreen( onSave: (SubscriptionItem) -> Boolean, onDelete: () -> Unit ) { - val context = LocalContext.current - + //val context = LocalContext.current var remarks by rememberSaveable { mutableStateOf(initial.remarks.orEmpty()) } var url by rememberSaveable { mutableStateOf(initial.url.orEmpty()) } var userAgent by rememberSaveable { mutableStateOf(initial.userAgent.orEmpty()) } + var requestHeaders by rememberSaveable { mutableStateOf(initial.requestHeaders.orEmpty()) } var filter by rememberSaveable { mutableStateOf(initial.filter ?: "") } var enabled by rememberSaveable { mutableStateOf(initial.enabled) } var autoUpdate by rememberSaveable { mutableStateOf(initial.autoUpdate) } @@ -145,25 +149,11 @@ fun SubEditScreen( subItem.remarks = remarks subItem.url = url subItem.userAgent = userAgent + subItem.requestHeaders = requestHeaders subItem.filter = filter subItem.enabled = enabled subItem.autoUpdate = autoUpdate - val intervalInput = updateInterval.trim() - val intervalMinutes = intervalInput.toLongOrNull() - if (autoUpdate) { - if (intervalMinutes == null) { - subItem.updateInterval = SubscriptionItem().updateInterval - } else if (intervalMinutes < AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) { - context.toast(context.getString(R.string.toast_invalid_update_interval)) - return null - } else { - subItem.updateInterval = intervalMinutes - } - } else { - if (intervalMinutes != null && intervalMinutes >= AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) { - subItem.updateInterval = intervalMinutes - } - } + subItem.updateInterval = updateInterval.toLong() subItem.prevProfile = prevProfile subItem.nextProfile = nextProfile subItem.allowInsecureUrl = allowInsecureUrl @@ -203,6 +193,7 @@ fun SubEditScreen( FormTextField(stringResource(R.string.sub_setting_remarks), remarks, { remarks = it }) FormTextField(stringResource(R.string.sub_setting_url), url, { url = it }) FormTextField(stringResource(R.string.sub_setting_user_agent), userAgent, { userAgent = it }) + FormTextField(stringResource(R.string.sub_setting_request_headers), requestHeaders, { requestHeaders = it }) FormTextField(stringResource(R.string.sub_setting_filter), filter, { filter = it }) SettingsSwitchItem( title = stringResource(R.string.sub_setting_enable), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt index 4282801d..92396909 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt @@ -164,6 +164,16 @@ object HttpUtil { applyEmbeddedBasicAuthHeader(currentUrl, requestBuilder) + + val headersMap = JsonUtil.parseHeadersToMap(request.requestHeaders) + for ((key, value) in headersMap) { + LogUtil.d(AppConfig.TAG, "Adding custom header: $key = $value") + try { + requestBuilder.header(key, value) + } catch (_: IllegalArgumentException) { + } + } + if (request.httpPort != 0 && !request.proxyUsername.isNullOrBlank() && !request.proxyPassword.isNullOrBlank()) { requestBuilder.header("Proxy-Authorization", Credentials.basic(request.proxyUsername, request.proxyPassword)) } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt index 88094475..cd657173 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/JsonUtil.kt @@ -92,4 +92,23 @@ object JsonUtil { return null } } + + fun parseHeadersToMap(headersJson: String?): Map { + val headerMap = mutableMapOf() + val jsonObject = parseString(headersJson) ?: return headerMap + try { + for ((key, jsonElement) in jsonObject.entrySet()) { + if (key.isNullOrBlank() || jsonElement == null || jsonElement.isJsonNull) { + continue + } + val value = if (jsonElement.isJsonPrimitive) jsonElement.asString else jsonElement.toString() + if (value.isNotBlank()) { + headerMap[key] = value + } + } + } catch (e: Exception) { + LogUtil.e(AppConfig.TAG, "Failed to parse headers JSON", e) + } + return headerMap + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index e005899e..0924d9a0 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -305,6 +305,7 @@ 备注 可选地址 (url) User Agent + 请求头(JSON 格式) 别名正则过滤 启用更新 启用自动更新 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 58c4d3c8..84ad9a65 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -304,6 +304,7 @@ 備註 可選位址(url) User Agent + 請求頭(JSON 格式) 別名正規過濾 啟用更新 啟用自動更新 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index fb2aaee1..264ab448 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -311,6 +311,7 @@ remarks Optional URL User Agent + Request Headers (JSON format) Remarks regular filter Enable update Enable automatic update