diff --git a/README.md b/README.md index b09c60a..b132245 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ sing-box / universal proxy toolchain for Android. **Google Play 版本自 2024 年 5 月起已被第三方控制,为非开源版本,请不要下载。** -**The Google Play version has been controlled by a third party since May 2024 and is a non-open source version. Please do not download it.** +**The Google Play version has been controlled by a third party since May 2024 and is a non-open +source version. Please do not download it.** ## 更新日志 & Telegram 发布频道 / Changelog & Telegram Channel @@ -33,23 +34,33 @@ https://matsuridayo.github.io * SSH * Shadowsocks * VMess -* VLESS -* WireGuard * Trojan +* VLESS +* AnyTLS +* ShadowTLS +* TUIC +* Hysteria 1/2 +* WireGuard * Trojan-Go (trojan-go-plugin) * NaïveProxy (naive-plugin) -* Hysteria (hysteria-plugin) * Mieru (mieru-plugin) -* TUIC -请到[这里](https://matsuridayo.github.io/m-plugin/)下载插件以获得完整的代理支持. +请到[这里](https://matsuridayo.github.io/nb4a-plugin/)下载插件以获得完整的代理支持. -Please visit [here](https://matsuridayo.github.io/m-plugin/) to download plugins for full proxy supports. +Please visit [here](https://matsuridayo.github.io/nb4a-plugin/) to download plugins for full proxy +supports. ## 支持的订阅格式 / Supported Subscription Format -* 原始格式: 一些广泛使用的格式 (如 Shadowsocks, Clash 和 v2rayN) -* Raw: some widely used formats (like Shadowsocks, Clash and v2rayN) +* 一些广泛使用的格式 (如 Shadowsocks, ClashMeta 和 v2rayN) +* sing-box 出站 + +仅支持解析出站,即节点。分流规则等信息会被忽略。 + +* Some widely used formats (like Shadowsocks, ClashMeta and v2rayN) +* sing-box outbound + +Only resolving outbound, i.e. nodes, is supported. Information such as diversion rules are ignored. ## 捐助 / Donate @@ -57,9 +68,12 @@ Please visit [here](https://matsuridayo.github.io/m-plugin/) to download plugins 如果这个项目对您有帮助, 可以通过捐赠的方式帮助我们维持这个项目. -捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里, 欢迎联系我们补充. +捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里, +欢迎联系我们补充. -Donations of 50 USD or more can display your avatar on the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please contact us to add it. +Donations of 50 USD or more can display your avatar on +the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please +contact us to add it. USDT TRC20 @@ -74,13 +88,14 @@ XMR ## Credits Core: + - [SagerNet/sing-box](https://github.com/SagerNet/sing-box) -- [Matsuridayo/sing-box-extra](https://github.com/MatsuriDayo/sing-box-extra) Android GUI: + - [shadowsocks/shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) - [SagerNet/SagerNet](https://github.com/SagerNet/SagerNet) -- [Matsuridayo/Matsuri](https://github.com/MatsuriDayo/Matsuri) Web Dashboard: + - [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta) diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt index 59adcb7..c8a6143 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt @@ -1,7 +1,6 @@ package io.nekohasekai.sagernet.group import android.annotation.SuppressLint -import android.net.Uri import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.database.* import io.nekohasekai.sagernet.fmt.AbstractBean @@ -33,6 +32,7 @@ import org.yaml.snakeyaml.TypeDescription import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.error.YAMLException import java.io.StringReader +import androidx.core.net.toUri @Suppress("EXPERIMENTAL_API_USAGE") object RawUpdater : GroupUpdater() { @@ -48,7 +48,7 @@ object RawUpdater : GroupUpdater() { val link = subscription.link var proxies: List if (link.startsWith("content://")) { - val contentText = app.contentResolver.openInputStream(Uri.parse(link)) + val contentText = app.contentResolver.openInputStream(link.toUri()) ?.bufferedReader() ?.readText() @@ -174,11 +174,12 @@ object RawUpdater : GroupUpdater() { } } else { changed++ - SagerDatabase.proxyDao.addProxy(ProxyEntity( - groupId = proxyGroup.id, userOrder = userOrder - ).apply { - putBean(bean) - }) + SagerDatabase.proxyDao.addProxy( + ProxyEntity( + groupId = proxyGroup.id, userOrder = userOrder + ).apply { + putBean(bean) + }) added.add(name) Logs.d("Inserted profile: $name") } @@ -289,226 +290,182 @@ object RawUpdater : GroupUpdater() { }) } - "vmess", "vless" -> { - var isHttpUpgrade = false - val isVLESS = proxy["type"].toString() == "vless" - val bean = VMessBean().apply { - if (isVLESS) { + "vmess", "vless", "trojan" -> { + val bean = when (proxy["type"] as String) { + "vmess" -> VMessBean() + "vless" -> VMessBean().apply { alterId = -1 // make it VLESS packetEncoding = 2 // clash meta default XUDP + security = "tls" } + + "trojan" -> TrojanBean().apply { + security = "tls" + } + + else -> error("impossible") } + + bean.serverAddress = proxy["server"]?.toString() ?: continue + bean.serverPort = proxy["port"]?.toString()?.toIntOrNull() ?: continue + for (opt in proxy) { - if (opt.value == null) continue - when (opt.key.replace("_", "-")) { - "name" -> bean.name = opt.value.toString() - "server" -> bean.serverAddress = opt.value as String - "port" -> bean.serverPort = opt.value.toString().toInt() - "uuid" -> bean.uuid = opt.value as String + when (opt.key) { + "name" -> bean.name = opt.value?.toString() + "password" -> if (bean is TrojanBean) bean.password = + opt.value?.toString() - "alterId" -> if (!isVLESS) bean.alterId = - opt.value.toString().toInt() + "uuid" -> if (bean is VMessBean) bean.uuid = + opt.value?.toString() - "cipher" -> if (!isVLESS) bean.encryption = opt.value as String + "alterId" -> if (bean is VMessBean && !bean.isVLESS) bean.alterId = + opt.value?.toString()?.toIntOrNull() - "flow" -> if (isVLESS) bean.encryption = opt.value as String + "cipher" -> if (bean is VMessBean && !bean.isVLESS) bean.encryption = + (opt.value as? String) - "packet-addr" -> if (opt.value.toString() == "true") { - bean.packetEncoding = 1 + "flow" -> if (bean is VMessBean && bean.isVLESS) { + (opt.value as? String)?.let { + if (it.contains("xtls-rprx-vision")) { + bean.encryption = "xtls-rprx-vision" + } + } } - "xudp" -> if (opt.value.toString() == "true") { - bean.packetEncoding = 2 + "packet-encoding" -> if (bean is VMessBean) { + bean.packetEncoding = when ((opt.value as? String)) { + "packetaddr" -> 1 + "xudp" -> 2 + else -> 0 + } + } + + "tls" -> if (bean is VMessBean) { + bean.security = + if (opt.value as? Boolean == true) "tls" else "" + } + + "servername", "sni" -> bean.sni = opt.value?.toString() + + "alpn" -> bean.alpn = + (opt.value as? List)?.joinToString("\n") + + "skip-cert-verify" -> bean.allowInsecure = + opt.value as? Boolean == true + + "client-fingerprint" -> bean.utlsFingerprint = + opt.value as String + + "reality-opts" -> (opt.value as? Map)?.also { + for (realityOpt in it) { + bean.security = "tls" + + when (realityOpt.key) { + "public-key" -> bean.realityPubKey = + realityOpt.value?.toString() + + "short-id" -> bean.realityShortId = + realityOpt.value?.toString() + } + } } "network" -> { - bean.type = opt.value as String - // Clash "network" fix - when (bean.type) { - "h2" -> bean.type = "http" + when (opt.value) { + "h2", "http" -> bean.type = "http" + "ws", "grpc" -> bean.type = opt.value as String } } - "client-fingerprint" -> bean.utlsFingerprint = - opt.value as String - - "tls" -> bean.security = - if (opt.value.toString() == "true") "tls" else "" - - "servername" -> bean.sni = opt.value.toString() - - "skip-cert-verify" -> bean.allowInsecure = - opt.value.toString() == "true" - - "alpn" -> { - val alpn = (opt.value as? (List)) - bean.alpn = alpn?.joinToString("\n") - } - - "ws-path" -> bean.path = opt.value.toString() - "ws-headers" -> for (wsHeader in (opt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() - } - } - - "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { - when (wsOpt.key.lowercase()) { - "headers" -> for (wsHeader in (wsOpt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() + "ws-opts" -> (opt.value as? Map)?.also { + for (wsOpt in it) { + when (wsOpt.key) { + "headers" -> (wsOpt.value as? Map)?.forEach { (key, value) -> + when (key.toString().lowercase()) { + "host" -> { + bean.host = value?.toString() + } + } } - } - "path" -> { - bean.path = wsOpt.value.toString() - } + "path" -> { + bean.path = wsOpt.value?.toString() + } - "max-early-data" -> { - bean.wsMaxEarlyData = wsOpt.value.toString().toInt() - } + "max-early-data" -> { + bean.wsMaxEarlyData = + wsOpt.value?.toString()?.toIntOrNull() + } - "early-data-header-name" -> { - bean.earlyDataHeaderName = wsOpt.value.toString() - } + "early-data-header-name" -> { + bean.earlyDataHeaderName = + wsOpt.value?.toString() + } - "v2ray-http-upgrade" -> { - isHttpUpgrade = true - } - } - } - - // The format of the VMessBean is wrong, so the `host` `path` has some strange transformations here. - "h2-opts", "h2-opt" -> for (h2Opt in (opt.value as Map)) { - when (h2Opt.key.lowercase()) { - "host" -> bean.host = - (h2Opt.value as List).first() - - "path" -> bean.path = h2Opt.value.toString() - } - } - - "http-opts", "http-opt" -> for (httpOpt in (opt.value as Map)) { - when (httpOpt.key.lowercase()) { - "path" -> bean.path = - (httpOpt.value as List).first() - - "headers" -> for (hdr in (httpOpt.value as Map)) { - when (hdr.key.lowercase()) { - "host" -> bean.host = - (hdr.value as List).first() + "v2ray-http-upgrade" -> { + if (wsOpt.value as? Boolean == true) { + bean.type = "httpupgrade" + } } } } } - "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { - when (grpcOpt.key.lowercase()) { - "grpc-service-name" -> bean.path = - grpcOpt.value.toString() + "h2-opts" -> (opt.value as? Map)?.also { + for (h2Opt in it) { + when (h2Opt.key) { + "host" -> bean.host = + (h2Opt.value as? List)?.joinToString("\n") + + "path" -> bean.path = h2Opt.value?.toString() + } } } - "reality-opts" -> for (realityOpt in (opt.value as Map)) { - when (realityOpt.key.lowercase()) { - "public-key" -> bean.realityPubKey = - realityOpt.value.toString() + "http-opts" -> (opt.value as? Map)?.also { + for (httpOpt in it) { + when (httpOpt.key) { + "path" -> bean.path = + (httpOpt.value as? List)?.joinToString("\n") - "short-id" -> bean.realityShortId = - realityOpt.value.toString() - } - } - - "smux" -> for (smuxOpt in (opt.value as Map)) { - when (smuxOpt.key.lowercase()) { - "enabled" -> bean.enableMux = - smuxOpt.value.toString() == "true" - - "max-streams" -> bean.muxConcurrency = - smuxOpt.value.toString().toInt() - - "padding" -> bean.muxPadding = - smuxOpt.value.toString() == "true" - } - } - - } - } - if (isHttpUpgrade) { - bean.type = "httpupgrade" - } - proxies.add(bean) - } - - "trojan" -> { - var isHttpUpgrade = false - val bean = TrojanBean() - bean.security = "tls" - for (opt in proxy) { - if (opt.value == null) continue - when (opt.key.replace("_", "-")) { - "name" -> bean.name = opt.value.toString() - "server" -> bean.serverAddress = opt.value as String - "port" -> bean.serverPort = opt.value.toString().toInt() - "password" -> bean.password = opt.value.toString() - "client-fingerprint" -> bean.utlsFingerprint = - opt.value as String - - "sni" -> bean.sni = opt.value.toString() - "skip-cert-verify" -> bean.allowInsecure = - opt.value.toString() == "true" - - "alpn" -> { - val alpn = (opt.value as? (List)) - bean.alpn = alpn?.joinToString("\n") - } - - "network" -> when (opt.value) { - "ws", "grpc" -> bean.type = opt.value.toString() - } - - "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { - when (wsOpt.key.lowercase()) { - "headers" -> for (wsHeader in (wsOpt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() + "headers" -> { + (httpOpt.value as? Map>)?.forEach { (key, value) -> + when (key.toString().lowercase()) { + "host" -> { + bean.host = value.joinToString("\n") + } + } + } } } + } + } - "path" -> { - bean.path = wsOpt.value.toString() - } - - "v2ray-http-upgrade" -> { - isHttpUpgrade = true + "grpc-opts" -> (opt.value as? Map)?.also { + for (grpcOpt in it) { + when (grpcOpt.key) { + "grpc-service-name" -> bean.path = + grpcOpt.value?.toString() } } } - "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { - when (grpcOpt.key.lowercase()) { - "grpc-service-name" -> bean.path = - grpcOpt.value.toString() - } - } + "smux" -> (opt.value as? Map)?.also { + for (smuxOpt in it) { + when (smuxOpt.key) { + "enabled" -> bean.enableMux = + smuxOpt.value.toString() == "true" - "smux" -> for (smuxOpt in (opt.value as Map)) { - when (smuxOpt.key.lowercase()) { - "enabled" -> bean.enableMux = - smuxOpt.value.toString() == "true" + "max-streams" -> bean.muxConcurrency = + smuxOpt.value.toString().toInt() - "max-streams" -> bean.muxConcurrency = - smuxOpt.value.toString().toInt() - - "padding" -> bean.muxPadding = - smuxOpt.value.toString() == "true" + "padding" -> bean.muxPadding = + smuxOpt.value.toString() == "true" + } } } } } - if (isHttpUpgrade) { - bean.type = "httpupgrade" - } proxies.add(bean) }