@@ -11,16 +11,17 @@ import features.proxy.server.usecase.withUpdatedSubscriptionServers
1111import features.subscription.usecase.toSubscriptionFetchOptions
1212import features.subscription.usecase.updateSubscriptions
1313
14- internal data class V2rayNgInstallConfig (
14+ internal data class SubscriptionInstallConfig (
1515 val name : String ,
1616 val url : String ,
17+ val userAgent : String ,
1718)
1819
1920internal class SubscriptionInstallConfigUseCase (
2021 private val stateStore : AndroidAppStateStore ,
2122 private val subscriptionFetchUseCase : SubscriptionFetchUseCase ,
2223) {
23- suspend fun install (config : V2rayNgInstallConfig ): ProxyServerListSubscriptionUpdateResult {
24+ suspend fun install (config : SubscriptionInstallConfig ): ProxyServerListSubscriptionUpdateResult {
2425 val group = stateStore.addSubscriptionGroup(config)
2526 val result = updateSubscriptions(
2627 groups = listOf (group),
@@ -39,29 +40,37 @@ internal class SubscriptionInstallConfigUseCase(
3940 }
4041}
4142
42- internal fun Intent.toV2rayNgInstallConfigOrNull (): V2rayNgInstallConfig ? {
43+ internal fun Intent.toSubscriptionInstallConfigOrNull (): SubscriptionInstallConfig ? {
4344 if (action != Intent .ACTION_VIEW ) return null
44- return data?.toV2rayNgInstallConfigOrNull ()
45+ return data?.toSubscriptionInstallConfigOrNull ()
4546}
4647
47- internal fun Uri.isV2rayNgInstallConfigUri (): Boolean {
48- return scheme.equals(V2rayNgScheme , ignoreCase = true ) &&
48+ internal fun String.toSubscriptionInstallConfigOrNull (): SubscriptionInstallConfig ? {
49+ val uri = runCatching { trim().toUri() }.getOrNull() ? : return null
50+ return uri.toSubscriptionInstallConfigOrNull()
51+ }
52+
53+ internal fun Uri.isSubscriptionInstallConfigUri (): Boolean {
54+ return installConfigSource() != null &&
4955 isHierarchical &&
50- host.equals(V2rayNgInstallConfigHost , ignoreCase = true )
56+ host.equals(InstallConfigHost , ignoreCase = true )
5157}
5258
53- private fun Uri.toV2rayNgInstallConfigOrNull (): V2rayNgInstallConfig ? {
54- if (! isV2rayNgInstallConfigUri()) return null
59+ internal fun Uri.toSubscriptionInstallConfigOrNull (): SubscriptionInstallConfig ? {
60+ val source = installConfigSource() ? : return null
61+ if (! isHierarchical || ! host.equals(InstallConfigHost , ignoreCase = true )) return null
5562 val name = getQueryParameter(" name" )?.trim().orEmpty()
63+ .ifBlank { source.defaultName.orEmpty() }
5664 val url = getQueryParameter(" url" )?.trim().orEmpty()
5765 if (name.isBlank() || ! url.isValidSubscriptionUrl()) return null
58- return V2rayNgInstallConfig (
66+ return SubscriptionInstallConfig (
5967 name = name,
6068 url = url,
69+ userAgent = source.userAgent,
6170 )
6271}
6372
64- private fun AndroidAppStateStore.addSubscriptionGroup (config : V2rayNgInstallConfig ): SubscriptionGroupState {
73+ private fun AndroidAppStateStore.addSubscriptionGroup (config : SubscriptionInstallConfig ): SubscriptionGroupState {
6574 var savedGroup: SubscriptionGroupState ? = null
6675 update { state ->
6776 val group = state.newSubscriptionGroup(config)
@@ -74,12 +83,12 @@ private fun AndroidAppStateStore.addSubscriptionGroup(config: V2rayNgInstallConf
7483 return checkNotNull(savedGroup)
7584}
7685
77- private fun AppState.newSubscriptionGroup (config : V2rayNgInstallConfig ): SubscriptionGroupState {
86+ private fun AppState.newSubscriptionGroup (config : SubscriptionInstallConfig ): SubscriptionGroupState {
7887 return SubscriptionGroupState (
7988 id = nextSubscriptionGroupId,
8089 name = config.name,
8190 url = config.url,
82- userAgent = DefaultSubscriptionUserAgent ,
91+ userAgent = config.userAgent ,
8392 updateInterval = " " ,
8493 updateViaProxy = false ,
8594 enabled = true ,
@@ -94,6 +103,26 @@ private fun String.isValidSubscriptionUrl(): Boolean {
94103 uri.host?.isNotBlank() == true
95104}
96105
97- private const val V2rayNgScheme = " v2rayng"
98- private const val V2rayNgInstallConfigHost = " install-config"
106+ private enum class InstallConfigSource (
107+ val scheme : String ,
108+ val userAgent : String ,
109+ val defaultName : String? = null ,
110+ ) {
111+ V2rayNg (scheme = " v2rayng" , userAgent = DefaultSubscriptionUserAgent ),
112+ ClashMeta (scheme = " clashmeta" , userAgent = ClashMetaSubscriptionUserAgent ),
113+ FlClashX (
114+ scheme = " flclashx" ,
115+ userAgent = FlClashXSubscriptionUserAgent ,
116+ defaultName = " clashsub" ,
117+ ),
118+ }
119+
120+ private fun Uri.installConfigSource (): InstallConfigSource ? {
121+ val uriScheme = scheme ? : return null
122+ return InstallConfigSource .entries.firstOrNull { source ->
123+ source.scheme.equals(uriScheme, ignoreCase = true )
124+ }
125+ }
126+
127+ private const val InstallConfigHost = " install-config"
99128private val SubscriptionUrlSchemes = setOf (" http" , " https" )
0 commit comments