Skip to content

Commit 863c6bd

Browse files
committed
refactor: align mihomo subscription import behavior
1 parent 715282b commit 863c6bd

26 files changed

Lines changed: 347 additions & 123 deletions

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@
4949
<category android:name="android.intent.category.DEFAULT" />
5050
<category android:name="android.intent.category.BROWSABLE" />
5151

52+
<data android:scheme="clash" />
53+
<data android:host="install-config" />
54+
</intent-filter>
55+
<intent-filter>
56+
<action android:name="android.intent.action.VIEW" />
57+
58+
<category android:name="android.intent.category.DEFAULT" />
59+
<category android:name="android.intent.category.BROWSABLE" />
60+
5261
<data android:scheme="clashmeta" />
5362
<data android:host="install-config" />
5463
</intent-filter>

app/src/main/kotlin/features/proxy/server/editor/ProxyServerHysteria2Editor.kt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,6 @@ internal fun LazyListScope.hysteria2ProxyServer(hy2Edit: Hysteria2) {
184184
exit = ExitTransition.None,
185185
) {
186186
Column {
187-
val allowInsecureOptions = remember { listOf("false", "true") }
188-
val allowInsecure = remember { mutableIntStateOf(hy2Edit.insecure) }
189-
190187
SmallTitle(text = stringResource(R.string.proxy_editor_tls_settings))
191188
TextField(
192189
label = "SNI",
@@ -201,18 +198,6 @@ internal fun LazyListScope.hysteria2ProxyServer(hy2Edit: Hysteria2) {
201198
onKeyboardAction = { focusManager.clearFocus() },
202199
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
203200
)
204-
OverlayDropdownPreference(
205-
title = stringResource(R.string.proxy_editor_allow_insecure),
206-
items = allowInsecureOptions,
207-
selectedIndex = allowInsecure.intValue,
208-
modifier = Modifier
209-
.padding(horizontal = 12.dp)
210-
.padding(bottom = 12.dp),
211-
onSelectedIndexChange = { newOption ->
212-
allowInsecure.intValue = newOption
213-
hy2Edit.insecure = newOption
214-
},
215-
)
216201
TextField(
217202
label = stringResource(R.string.proxy_editor_certificate_fingerprint),
218203
state = rememberTextFieldState(initialText = hy2Edit.pinSHA256),

app/src/main/kotlin/features/proxy/server/list/ProxyServerListTopBar.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ import features.proxy.server.usecase.withImportedProxyServers
3131
import features.proxy.server.usecase.withUpdatedSubscriptionServers
3232
import features.subscription.DefaultSubscriptionGroupId
3333
import features.subscription.SubscriptionInstallConfigUseCase
34+
import features.subscription.runtime.AndroidSubscriptionFetchOptions
3435
import features.subscription.runtime.AndroidSubscriptionFetcher
3536
import features.subscription.usecase.subscriptionUpdateMessage
3637
import features.subscription.usecase.toSubscriptionFetchOptions
3738
import features.subscription.usecase.updateSubscriptions
38-
import features.subscription.toRawHttpsSubscriptionInstallConfigOrNull
39+
import features.subscription.toSubscriptionInstallConfigOrNull
3940
import kotlinx.coroutines.CoroutineScope
4041
import kotlinx.coroutines.launch
4142
import top.yukonga.miuix.kmp.basic.ScrollBehavior
@@ -173,6 +174,7 @@ private fun handleProxyServerListAddAction(
173174
text = scanText,
174175
source = ProxyServerImportSource.QrCode,
175176
groupState = groupState,
177+
subscriptionFetcher = subscriptionFetcher,
176178
updateAppState = updateAppState,
177179
tipNotifier = tipNotifier,
178180
messages = messages,
@@ -200,6 +202,7 @@ private fun handleProxyServerListAddAction(
200202
text = text,
201203
source = ProxyServerImportSource.Clipboard,
202204
groupState = groupState,
205+
subscriptionFetcher = subscriptionFetcher,
203206
updateAppState = updateAppState,
204207
tipNotifier = tipNotifier,
205208
messages = messages,
@@ -226,6 +229,7 @@ private fun handleProxyServerListAddAction(
226229
text = text,
227230
source = ProxyServerImportSource.File,
228231
groupState = groupState,
232+
subscriptionFetcher = subscriptionFetcher,
229233
updateAppState = updateAppState,
230234
tipNotifier = tipNotifier,
231235
messages = messages,
@@ -262,7 +266,7 @@ private suspend fun installSubscriptionFromText(
262266
tipNotifier: AndroidToastTipNotifier,
263267
messages: ProxyServerListMessages,
264268
): Boolean {
265-
val config = text.toRawHttpsSubscriptionInstallConfigOrNull() ?: return false
269+
val config = text.toSubscriptionInstallConfigOrNull() ?: return false
266270
runCatching {
267271
SubscriptionInstallConfigUseCase(
268272
stateStore = stateStore,
@@ -286,6 +290,7 @@ private suspend fun importProxyServers(
286290
text: String,
287291
source: ProxyServerImportSource,
288292
groupState: ProxyServerListGroups,
293+
subscriptionFetcher: AndroidSubscriptionFetcher,
289294
updateAppState: ((AppState) -> AppState) -> Unit,
290295
tipNotifier: AndroidToastTipNotifier,
291296
messages: ProxyServerListMessages,
@@ -298,6 +303,13 @@ private suspend fun importProxyServers(
298303
val importResult = importProxyServersFromText(
299304
text = text,
300305
source = source,
306+
providerUrlFetcher = { providerUrl ->
307+
subscriptionFetcher.fetch(
308+
url = providerUrl,
309+
userAgent = "",
310+
options = AndroidSubscriptionFetchOptions(),
311+
)
312+
},
301313
)
302314
if (importResult.servers.isNotEmpty()) {
303315
updateAppState { state -> state.withImportedProxyServers(importResult, targetGroupId) }

app/src/main/kotlin/features/proxy/server/model/Hysteria2.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ data class Hysteria2(
2424
var obfs: String = "",
2525
var obfsPassword: String = "",
2626
var sni: String = "",
27-
var insecure: Int = 0,
2827
var pinSHA256: String = "",
2928
//v2rayNg
3029
var mport: String = "",
@@ -56,9 +55,6 @@ data class Hysteria2(
5655
putJsonObject("tlsSettings") {
5756
putIfNotBlank("serverName", sni)
5857
putIfNotBlank("pinnedPeerCertSha256", pinSHA256)
59-
if (insecure == 1) {
60-
put("allowInsecure", true)
61-
}
6258
}
6359
val finalMask = toXrayFinalMask()
6460
if (finalMask.isNotEmpty()) {
@@ -77,7 +73,9 @@ data class Hysteria2(
7773
this.obfs = url.parameters["obfs"] ?: ""
7874
this.obfsPassword = url.parameters["obfs-password"] ?: ""
7975
this.sni = url.parameters["sni"] ?: ""
80-
this.insecure = url.parameters["insecure"]?.toIntOrNull() ?: 0
76+
if (url.parameters["insecure"].isEnabledFlag()) {
77+
throw IllegalArgumentException("Hysteria2 insecure is not supported")
78+
}
8179
this.pinSHA256 = url.parameters["pinSHA256"] ?: ""
8280
//v2rayNg
8381
this.mport = url.parameters["mport"] ?: url.parameters["ports"] ?: ""
@@ -116,7 +114,6 @@ data class Hysteria2(
116114
}
117115
if (this@Hysteria2.security.isNotBlank() && this@Hysteria2.security != "none") {
118116
parameters.append("security", this@Hysteria2.security)
119-
parameters.append("insecure", this@Hysteria2.insecure.toString())
120117
}
121118

122119
fragment = this@Hysteria2.remarks
@@ -135,7 +132,6 @@ data class Hysteria2(
135132
obfs = other.obfs
136133
obfsPassword = other.obfsPassword
137134
sni = other.sni
138-
insecure = other.insecure
139135
pinSHA256 = other.pinSHA256
140136
mport = other.mport
141137
mportHopInt = other.mportHopInt
@@ -159,13 +155,17 @@ data class Hysteria2(
159155
validateOptionalBandwidth(up, "up bandwidth")
160156
validateOptionalBandwidth(down, "down bandwidth")
161157
validateAllowed(security.ifBlank { "none" }, "transport security", setOf("none", "tls"))
162-
if (insecure !in 0..1) {
163-
proxyValidationError(ProxyServerValidationError.BooleanRequired)
164-
}
165158
validateOptionalSha256(pinSHA256, "certificate fingerprint")
166159
}
167160
}
168161

162+
private fun String?.isEnabledFlag(): Boolean {
163+
return when (this?.trim()?.lowercase()) {
164+
"1", "true", "yes", "on" -> true
165+
else -> false
166+
}
167+
}
168+
169169
private fun Hysteria2.toXrayFinalMask(): JsonObject {
170170
return buildJsonObject {
171171
if (up.isNotBlank() || down.isNotBlank() || mport.isNotBlank()) {

app/src/main/kotlin/features/proxy/server/model/ProxyServer.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ data class V2RayParameters(
159159
var security: String = "none",
160160
var path: String? = null,
161161
var host: String? = null,
162+
var headers: String? = null,
162163
var mtu: String? = null,
163164
var tti: String? = null,
164165
var serviceName: String? = null,
@@ -200,11 +201,13 @@ data class V2RayParameters(
200201
this.type = "websocket"
201202
this.path = url.parameters["path"]
202203
this.host = url.parameters["host"]
204+
this.headers = url.parameters["headers"]
203205
}
204206

205207
"httpupgrade" -> {
206208
this.path = url.parameters["path"]
207209
this.host = url.parameters["host"]
210+
this.headers = url.parameters["headers"]
208211
}
209212

210213
"grpc" -> {
@@ -269,6 +272,7 @@ data class V2RayParameters(
269272
"ws", "websocket", "httpupgrade" -> {
270273
appendIfNotBlank("path", this@V2RayParameters.path)
271274
appendIfNotBlank("host", this@V2RayParameters.host)
275+
appendIfNotBlank("headers", this@V2RayParameters.headers)
272276
}
273277

274278
"grpc" -> {

app/src/main/kotlin/features/proxy/server/model/ProxyServerValidation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ internal fun validateV2RayParameters(params: V2RayParameters) {
132132
"ws", "websocket", "httpupgrade", "xhttp", "splithttp" -> validateOptionalPath(params.path, "$type path")
133133
"grpc" -> validateAllowed(params.mode ?: "gun", "gRPC mode", setOf("gun", "multi"))
134134
}
135+
if (type in setOf("ws", "websocket", "httpupgrade")) {
136+
validateOptionalJsonObject(params.headers, "$type headers")
137+
}
135138
if (type in setOf("xhttp", "splithttp")) {
136139
validateAllowed(params.mode ?: "auto", "XHTTP mode", setOf("auto", "packet-up", "stream-up", "stream-one"))
137140
validateOptionalJsonObject(params.extra, "XHTTP Extra")

app/src/main/kotlin/features/proxy/server/model/ProxyServerValidationError.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ enum class ProxyServerValidationError {
4747
ChainProxyMemberCountInvalid,
4848
HysteriaObfsTypeRequired,
4949
HysteriaObfsUnsupported,
50-
BooleanRequired,
5150
PortHoppingIntervalNumberRequired,
5251
PortHoppingIntervalTooSmall,
5352
Shadowsocks2022KeyBase64Invalid,

app/src/main/kotlin/features/proxy/server/model/VMess.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ data class LegacyVMess(
9999
}
100100

101101
fun convertToAEAD(): VMess {
102+
val alterId = this.alterId.trim().toIntOrNull() ?: 0
103+
if (alterId != 0) {
104+
throw IllegalArgumentException("VMess legacy alterId is not supported")
105+
}
102106
return VMess(
103107
remarks = this.remarks.ifBlank { "none" },
104108
id = this.id,

app/src/main/kotlin/features/proxy/server/model/XrayOutbound.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ private fun JsonObjectBuilder.putWsSettings(params: V2RayParameters) {
145145
putJsonObject("wsSettings") {
146146
putIfNotBlank("path", params.path)
147147
putIfNotBlank("host", params.host)
148+
val headers = params.headers.toXrayJsonObjectOrNull("WebSocket headers")
149+
if (headers != null) {
150+
put("headers", headers)
151+
}
148152
}
149153
}
150154

@@ -162,6 +166,10 @@ private fun JsonObjectBuilder.putHttpUpgradeSettings(params: V2RayParameters) {
162166
putJsonObject("httpupgradeSettings") {
163167
putIfNotBlank("path", params.path)
164168
putIfNotBlank("host", params.host)
169+
val headers = params.headers.toXrayJsonObjectOrNull("HTTPUpgrade headers")
170+
if (headers != null) {
171+
put("headers", headers)
172+
}
165173
}
166174
}
167175

app/src/main/kotlin/features/proxy/server/usecase/ProxyServerImportModels.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,21 @@ internal enum class ProxyServerImportSource(
1818
File(logName = "file", decodeBase64 = true),
1919
QrCode(logName = "qr_code", decodeBase64 = false),
2020
SubscriptionUrl(logName = "subscription_url", decodeBase64 = true),
21+
MihomoProxyProviderUrl(logName = "mihomo_proxy_provider_url", decodeBase64 = true),
2122
}
2223

23-
internal typealias ProxyServerPayloadParser = (String, ProxyServerImportSource) -> ProxyServerImportResult
24+
internal fun interface ProxyServerProviderUrlFetcher {
25+
suspend fun fetch(url: String): String
26+
}
27+
28+
internal data class ProxyServerImportContext(
29+
val source: ProxyServerImportSource,
30+
val providerUrlFetcher: ProxyServerProviderUrlFetcher? = null,
31+
val providerDepth: Int = 0,
32+
val fetchedProviderUrls: Set<String> = emptySet(),
33+
)
34+
35+
internal typealias ProxyServerPayloadParser = suspend (String, ProxyServerImportContext) -> ProxyServerImportResult
2436

2537
internal val EmptyProxyServerImportResult = ProxyServerImportResult(
2638
urlCount = 0,

0 commit comments

Comments
 (0)