diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index 3a9cc1e..b53343d 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -30,11 +30,11 @@ import io.nekohasekai.sagernet.fmt.tuic.toUri import io.nekohasekai.sagernet.fmt.v2ray.* import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.app -import io.nekohasekai.sagernet.ktx.applyDefaultValues import io.nekohasekai.sagernet.ui.profile.* import moe.matsuri.nb4a.SingBoxOptions.MultiplexOptions import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity +import moe.matsuri.nb4a.proxy.anytls.toUri import moe.matsuri.nb4a.proxy.config.ConfigBean import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity import moe.matsuri.nb4a.proxy.neko.* @@ -98,10 +98,8 @@ data class ProxyEntity( val chainName by lazy { app.getString(R.string.proxy_chain) } - private val placeHolderBean = SOCKSBean().applyDefaultValues() - @JvmField - val CREATOR = object : Serializable.CREATOR() { + val CREATOR = object : CREATOR() { override fun newInstance(): ProxyEntity { return ProxyEntity() @@ -241,7 +239,6 @@ data class ProxyEntity( is SSHBean -> false is WireGuardBean -> false is ShadowTLSBean -> false - is AnyTLSBean -> false is NekoBean -> nekoBean!!.haveStandardLink() is ConfigBean -> false else -> true @@ -259,6 +256,7 @@ data class ProxyEntity( is NaiveBean -> toUri() is HysteriaBean -> toUri() is TuicBean -> toUri() + is AnyTLSBean -> toUri() is NekoBean -> shareLink() else -> toUniversalLink() } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt index 8f7a9b4..3fc8573 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -15,6 +15,7 @@ import io.nekohasekai.sagernet.fmt.tuic.parseTuic import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo import io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.proxy.anytls.parseAnytls import moe.matsuri.nb4a.proxy.neko.NekoJSInterface import moe.matsuri.nb4a.proxy.neko.parseShareLink import moe.matsuri.nb4a.utils.JavaUtil.gson @@ -203,6 +204,13 @@ suspend fun parseProxies(text: String): List { }.onFailure { Logs.w(it) } + } else if (startsWith("anytls://")) { + Logs.d("Try parse anytls link: $this") + runCatching { + entities.add(parseAnytls(this)) + }.onFailure { + Logs.w(it) + } } else { // Neko Plugins NekoPluginManager.getProtocols().forEach { obj -> obj.protocolConfig.optJSONArray("links")?.forEach { _, any -> @@ -228,12 +236,12 @@ suspend fun parseProxies(text: String): List { for (link in linksByLine) { link.parseLink(entitiesByLine) } - var isBadLink = false +// var isBadLink = false if (entities.onEach { it.initializeDefaultValues() }.size == entitiesByLine.onEach { it.initializeDefaultValues() }.size) run test@{ entities.forEachIndexed { index, bean -> val lineBean = entitiesByLine[index] if (bean == lineBean && bean.displayName() != lineBean.displayName()) { - isBadLink = true +// isBadLink = true return@test } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index f9090d3..f82edad 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -1661,8 +1661,8 @@ class ConfigurationFragment @JvmOverloads constructor( try { currentName = entity.displayName()!! when (item.itemId) { - R.id.action_standard_qr -> showCode(entity.toStdLink()!!) - R.id.action_standard_clipboard -> export(entity.toStdLink()!!) + R.id.action_standard_qr -> showCode(entity.toStdLink()) + R.id.action_standard_clipboard -> export(entity.toStdLink()) R.id.action_universal_qr -> showCode(entity.requireBean().toUniversalLink()) R.id.action_universal_clipboard -> export( entity.requireBean().toUniversalLink() diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt index 06100d1..2fb7b45 100644 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt @@ -1,8 +1,12 @@ package moe.matsuri.nb4a.proxy.anytls import io.nekohasekai.sagernet.ktx.blankAsNull +import io.nekohasekai.sagernet.ktx.linkBuilder +import io.nekohasekai.sagernet.ktx.toLink +import io.nekohasekai.sagernet.ktx.urlSafe import moe.matsuri.nb4a.SingBoxOptions import moe.matsuri.nb4a.utils.listByLineOrComma +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull fun buildSingBoxOutboundAnyTLSBean(bean: AnyTLSBean): SingBoxOptions.Outbound_AnyTLSOptions { return SingBoxOptions.Outbound_AnyTLSOptions().apply { @@ -34,4 +38,44 @@ fun buildSingBoxOutboundAnyTLSBean(bean: AnyTLSBean): SingBoxOptions.Outbound_An } } } -} \ No newline at end of file +} + +fun AnyTLSBean.toUri(): String { + val builder = linkBuilder() + .host(serverAddress) + .port(serverPort) + .username(password) + if (!name.isNullOrBlank()) { + builder.encodedFragment(name.urlSafe()) + } + if (allowInsecure) { + builder.addQueryParameter("insecure", "1") + } + if (!sni.isNullOrBlank()) { + builder.addQueryParameter("sni", sni) + } + if (!utlsFingerprint.isNullOrBlank()) { + builder.addQueryParameter("fp", utlsFingerprint) + } + return builder.toLink("anytls") +} + +fun parseAnytls(url: String): AnyTLSBean { + // https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md + val link = url.replace("anytls://", "https://").toHttpUrlOrNull() ?: error( + "invalid anytls link $url" + ) + return AnyTLSBean().apply { + serverAddress = link.host + serverPort = link.port + name = link.fragment + password = link.username + sni = link.queryParameter("sni") ?: "" + link.queryParameter("insecure")?.also { + allowInsecure = it == "1" || it == "true" + } + link.queryParameter("fp")?.let { + utlsFingerprint = it + } + } +}