diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml
index 799fc968..7d118e8d 100644
--- a/V2rayNG/app/src/main/AndroidManifest.xml
+++ b/V2rayNG/app/src/main/AndroidManifest.xml
@@ -109,8 +109,9 @@
-
+
+
+
diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/UrlSchemeActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/UrlSchemeActivity.kt
index 7f18a71e..793b3c29 100644
--- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/UrlSchemeActivity.kt
+++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/UrlSchemeActivity.kt
@@ -3,10 +3,12 @@ package com.v2ray.ang.ui
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
+import java.net.URLDecoder
class UrlSchemeActivity : BaseActivity() {
private lateinit var binding: ActivityLogcatBinding
@@ -17,34 +19,68 @@ class UrlSchemeActivity : BaseActivity() {
val view = binding.root
setContentView(view)
- var shareUrl: String = ""
try {
- intent?.apply {
- when (action) {
- Intent.ACTION_SEND -> {
- if ("text/plain" == type) {
- intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
- shareUrl = it
+ intent.apply {
+ if (action == Intent.ACTION_SEND) {
+ if ("text/plain" == type) {
+ intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
+ val uri = Uri.parse(it)
+ if (uri.scheme?.startsWith(AppConfig.HTTPS_PROTOCOL) == true || uri.scheme?.startsWith(
+ AppConfig.HTTP_PROTOCOL
+ ) == true
+ ) {
+ val name = uri.getQueryParameter("name") ?: "Subscription"
+ importSubscription(it, name)
+ } else {
+ importConfig(it)
}
}
}
- Intent.ACTION_VIEW -> {
- val uri: Uri? = intent.data
- shareUrl = uri?.getQueryParameter("url")!!
+ } else if (action == Intent.ACTION_VIEW) {
+ when (data?.host) {
+ "install-config" -> {
+ val uri: Uri? = intent.data
+ val shareUrl: String = uri?.getQueryParameter("url")!!
+ toast(shareUrl)
+ importConfig(shareUrl)
+ }
+
+ "install-sub" -> {
+ val uri: Uri? = intent.data
+ val url = uri?.getQueryParameter("url")!!
+ val name = uri.getQueryParameter("name") ?: "Subscription"
+ importSubscription(url, name)
+ }
+
+ else -> {
+ toast(R.string.toast_failure)
+ }
}
}
+
}
- toast(shareUrl)
- val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
- if (count > 0) {
- toast(R.string.toast_success)
- } else {
- toast(R.string.toast_failure)
- }
+
+
startActivity(Intent(this, MainActivity::class.java))
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}
+
+ private fun importSubscription(url: String, name: String) {
+ val decodedUrl = URLDecoder.decode(url, "UTF-8")
+
+ val check = AngConfigManager.importSubscription(name, decodedUrl)
+ if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
+ }
+
+ private fun importConfig(shareUrl: String) {
+ val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
+ if (count > 0) {
+ toast(R.string.toast_success)
+ } else {
+ toast(R.string.toast_failure)
+ }
+ }
}
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt
index eb87b934..7366a7c3 100644
--- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt
+++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt
@@ -20,11 +20,27 @@ import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import java.net.URI
import java.util.*
import com.v2ray.ang.extension.idnHost
+import com.v2ray.ang.extension.toast
object AngConfigManager {
- private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
- private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
- private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
+ private val mainStorage by lazy {
+ MMKV.mmkvWithID(
+ MmkvManager.ID_MAIN,
+ MMKV.MULTI_PROCESS_MODE
+ )
+ }
+ private val serverRawStorage by lazy {
+ MMKV.mmkvWithID(
+ MmkvManager.ID_SERVER_RAW,
+ MMKV.MULTI_PROCESS_MODE
+ )
+ }
+ private val settingsStorage by lazy {
+ MMKV.mmkvWithID(
+ MmkvManager.ID_SETTING,
+ MMKV.MULTI_PROCESS_MODE
+ )
+ }
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
/**
@@ -82,8 +98,14 @@ object AngConfigManager {
).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
}
- settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true))
- settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf()))
+ settingsStorage?.encode(
+ AppConfig.PREF_SNIFFING_ENABLED,
+ sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
+ )
+ settingsStorage?.encode(
+ AppConfig.PREF_PER_APP_PROXY_SET,
+ sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
+ )
}
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
@@ -125,7 +147,8 @@ object AngConfigManager {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null
} else {
- val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
+ val socksUsersBean =
+ V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean)
@@ -135,17 +158,27 @@ object AngConfigManager {
}
}
config.outboundBean?.streamSettings?.let { streamSetting ->
- val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType,
- vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path,
- vmessBean.headerType, vmessBean.path)
+ val sni = streamSetting.populateTransportSettings(
+ vmessBean.network,
+ vmessBean.headerType,
+ vmessBean.requestHost,
+ vmessBean.path,
+ vmessBean.path,
+ vmessBean.requestHost,
+ vmessBean.path,
+ vmessBean.headerType,
+ vmessBean.path
+ )
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
} else {
vmessBean.allowInsecure.toBoolean()
}
var fingerprint = streamSetting.tlsSettings?.fingerprint
- streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure,
- vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null)
+ streamSetting.populateTlsSettings(
+ vmessBean.streamSecurity, allowInsecure,
+ vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
+ )
}
}
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
@@ -168,14 +201,21 @@ object AngConfigManager {
/**
* import config form qrcode or...
*/
- private fun importConfig(str: String?, subid: String, removedSelectedServer: ServerConfig?): Int {
+ private fun importConfig(
+ str: String?,
+ subid: String,
+ removedSelectedServer: ServerConfig?
+ ): Int {
try {
if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data
}
//maybe sub
- if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL))) {
+ if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(
+ HTTPS_PROTOCOL
+ ))
+ ) {
MmkvManager.importUrlAsSubscription(str)
return 0
}
@@ -201,9 +241,9 @@ object AngConfigManager {
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add)
- || TextUtils.isEmpty(vmessQRCode.port)
- || TextUtils.isEmpty(vmessQRCode.id)
- || TextUtils.isEmpty(vmessQRCode.net)
+ || TextUtils.isEmpty(vmessQRCode.port)
+ || TextUtils.isEmpty(vmessQRCode.id)
+ || TextUtils.isEmpty(vmessQRCode.net)
) {
return R.string.toast_incorrect_protocol
}
@@ -213,16 +253,28 @@ object AngConfigManager {
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
- vnext.users[0].security = if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
+ vnext.users[0].security =
+ if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
- val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host,
- vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path)
+ val sni = streamSetting.populateTransportSettings(
+ vmessQRCode.net,
+ vmessQRCode.type,
+ vmessQRCode.host,
+ vmessQRCode.path,
+ vmessQRCode.path,
+ vmessQRCode.host,
+ vmessQRCode.path,
+ vmessQRCode.type,
+ vmessQRCode.path
+ )
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
- streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure,
- if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
- fingerprint, vmessQRCode.alpn, null, null, null)
+ streamSetting.populateTlsSettings(
+ vmessQRCode.tls, allowInsecure,
+ if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
+ fingerprint, vmessQRCode.alpn, null, null, null
+ )
}
}
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
@@ -232,7 +284,8 @@ object AngConfigManager {
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
- config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
+ config.remarks =
+ Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
@@ -243,13 +296,17 @@ object AngConfigManager {
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
- Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
+ Utils.decode(result.substring(0, indexS)) + result.substring(
+ indexS,
+ result.length
+ )
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
- val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
+ val match = legacyPattern.matchEntire(result)
+ ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
@@ -264,7 +321,8 @@ object AngConfigManager {
config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) {
try {
- config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
+ config.remarks =
+ Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
@@ -275,18 +333,23 @@ object AngConfigManager {
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
- result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
+ result = Utils.decode(result.substring(0, indexS)) + result.substring(
+ indexS,
+ result.length
+ )
} else {
result = Utils.decode(result)
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
- val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
+ val match =
+ legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
- val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
+ val socksUsersBean =
+ V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1].lowercase()
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
@@ -302,17 +365,29 @@ object AngConfigManager {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
- val sni = config.outboundBean?.streamSettings?.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"],
- queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"],
- queryParam["mode"], queryParam["serviceName"])
+ val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
+ queryParam["type"] ?: "tcp",
+ queryParam["headerType"],
+ queryParam["host"],
+ queryParam["path"],
+ queryParam["seed"],
+ queryParam["quicSecurity"],
+ queryParam["key"],
+ queryParam["mode"],
+ queryParam["serviceName"]
+ )
fingerprint = queryParam["fp"] ?: ""
- config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS,
- allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
- null, null, null)
+ config.outboundBean?.streamSettings?.populateTlsSettings(
+ queryParam["security"] ?: TLS,
+ allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
+ null, null, null
+ )
flow = queryParam["flow"] ?: ""
} else {
- config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "",
- fingerprint, null, null, null, null)
+ config.outboundBean?.streamSettings?.populateTlsSettings(
+ TLS, allowInsecure, "",
+ fingerprint, null, null, null, null
+ )
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
@@ -335,27 +410,41 @@ object AngConfigManager {
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
- vnext.users[0].flow =queryParam["flow"] ?: ""
+ vnext.users[0].flow = queryParam["flow"] ?: ""
}
- val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"],
- queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"],
- queryParam["mode"], queryParam["serviceName"])
+ val sni = streamSetting.populateTransportSettings(
+ queryParam["type"] ?: "tcp",
+ queryParam["headerType"],
+ queryParam["host"],
+ queryParam["path"],
+ queryParam["seed"],
+ queryParam["quicSecurity"],
+ queryParam["key"],
+ queryParam["mode"],
+ queryParam["serviceName"]
+ )
fingerprint = queryParam["fp"] ?: ""
val pbk = queryParam["pbk"] ?: ""
val sid = queryParam["sid"] ?: ""
- val spx = Utils.urlDecode(queryParam["spx"] ?: "")
- streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure,
- queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx)
+ val spx = Utils.urlDecode(queryParam["spx"] ?: "")
+ streamSetting.populateTlsSettings(
+ queryParam["security"] ?: "", allowInsecure,
+ queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx
+ )
}
- if (config == null){
+ if (config == null) {
return R.string.toast_incorrect_protocol
}
config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null &&
- config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() &&
- config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) {
+ config.getProxyOutbound()
+ ?.getServerAddress() == removedSelectedServer.getProxyOutbound()
+ ?.getServerAddress() &&
+ config.getProxyOutbound()
+ ?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
+ ) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
}
} catch (e: Exception) {
@@ -365,14 +454,18 @@ object AngConfigManager {
return 0
}
- private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean {
+ private fun tryParseNewVmess(
+ uriString: String,
+ config: ServerConfig,
+ allowInsecure: Boolean
+ ): Boolean {
return runCatching {
val uri = URI(uriString)
check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) =
- Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
- .matchEntire(uri.userInfo)?.groupValues
- ?: error("parse user info fail.")
+ Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
+ .matchEntire(uri.userInfo)?.groupValues
+ ?: error("parse user info fail.")
val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
@@ -387,12 +480,19 @@ object AngConfigManager {
vnext.users[0].alterId = alterId.toInt()
}
var fingerprint = streamSetting.tlsSettings?.fingerprint
- val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"],
- queryParam["host"]?.split("|")?.get(0) ?: "",
- queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"],
- queryParam["key"], queryParam["mode"], queryParam["serviceName"])
- streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
- null, null, null)
+ val sni = streamSetting.populateTransportSettings(protocol,
+ queryParam["type"],
+ queryParam["host"]?.split("|")?.get(0) ?: "",
+ queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
+ queryParam["seed"],
+ queryParam["security"],
+ queryParam["key"],
+ queryParam["mode"],
+ queryParam["serviceName"])
+ streamSetting.populateTlsSettings(
+ if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
+ null, null, null
+ )
true
}.getOrElse { false }
}
@@ -480,12 +580,16 @@ object AngConfigManager {
vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty()
- vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
- vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
+ vmessQRCode.aid =
+ outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
+ vmessQRCode.scy =
+ outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
- vmessQRCode.alpn = Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
+ vmessQRCode.alpn =
+ Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
+ .orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0]
@@ -495,25 +599,34 @@ object AngConfigManager {
val json = Gson().toJson(vmessQRCode)
Utils.encode(json)
}
+
EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
- val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
- val url = String.format("%s@%s:%s",
- pw,
- Utils.getIpv6Address(outbound.getServerAddress()!!),
- outbound.getServerPort())
- url + remark
- }
- EConfigType.SOCKS -> {
- val remark = "#" + Utils.urlEncode(config.remarks)
- val pw = Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
- val url = String.format("%s@%s:%s",
+ val pw =
+ Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
+ val url = String.format(
+ "%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()!!),
- outbound.getServerPort())
+ outbound.getServerPort()
+ )
url + remark
}
+
+ EConfigType.SOCKS -> {
+ val remark = "#" + Utils.urlEncode(config.remarks)
+ val pw =
+ Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
+ val url = String.format(
+ "%s@%s:%s",
+ pw,
+ Utils.getIpv6Address(outbound.getServerAddress()!!),
+ outbound.getServerPort()
+ )
+ url + remark
+ }
+
EConfigType.VLESS,
EConfigType.TROJAN -> {
val remark = "#" + Utils.urlEncode(config.remarks)
@@ -537,12 +650,14 @@ object AngConfigManager {
}
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
- (streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting ->
+ (streamSetting.tlsSettings
+ ?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
- dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
+ dicQuery["alpn"] =
+ Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!!
@@ -567,12 +682,14 @@ object AngConfigManager {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
+
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
+
"ws" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
@@ -581,6 +698,7 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
+
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
@@ -590,11 +708,13 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
+
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
+
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["serviceName"] = transportDetails[2]
@@ -602,13 +722,15 @@ object AngConfigManager {
}
}
val query = "?" + dicQuery.toList().joinToString(
- separator = "&",
- transform = { it.first + "=" + it.second })
+ separator = "&",
+ transform = { it.first + "=" + it.second })
- val url = String.format("%s@%s:%s",
- outbound.getPassword(),
- Utils.getIpv6Address(outbound.getServerAddress()!!),
- outbound.getServerPort())
+ val url = String.format(
+ "%s@%s:%s",
+ outbound.getPassword(),
+ Utils.getIpv6Address(outbound.getServerAddress()!!),
+ outbound.getServerPort()
+ )
url + query + remark
}
}
@@ -756,17 +878,34 @@ object AngConfigManager {
var count = 0
servers.lines()
- .reversed()
- .forEach {
- val resId = importConfig(it, subid, removedSelectedServer)
- if (resId == 0) {
- count++
- }
+ .reversed()
+ .forEach {
+ val resId = importConfig(it, subid, removedSelectedServer)
+ if (resId == 0) {
+ count++
}
+ }
return count
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
+
+ fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
+ val subId = Utils.getUuid()
+ val subItem = SubscriptionItem()
+
+
+ subItem.remarks = remark
+ subItem.url = url
+ subItem.enabled = enabled
+
+ if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
+ return false
+ }
+ subStorage?.encode(subId, Gson().toJson(subItem))
+
+ return true
+ }
}
diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml
index f3779193..5397da42 100644
--- a/V2rayNG/app/src/main/res/values-fa/strings.xml
+++ b/V2rayNG/app/src/main/res/values-fa/strings.xml
@@ -234,5 +234,7 @@
- VPN
- فقط پروکسی
+ اشتراک با موفقیت ذخیره شد
+ ذخیره اشتراک ناموفق بود
diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml
index 41663951..05ac1688 100644
--- a/V2rayNG/app/src/main/res/values-ru/strings.xml
+++ b/V2rayNG/app/src/main/res/values-ru/strings.xml
@@ -239,5 +239,7 @@
- VPN
- Только прокси
+ подписка успешно импортирована
+ не удалось импортировать подписку
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 738ace60..c86b8a7c 100644
--- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
+++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
@@ -234,5 +234,7 @@
- VPN
- 仅代理
+ 订阅导入成功
+ 导入订阅失败
diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml
index 6a539045..eb3c42ab 100644
--- a/V2rayNG/app/src/main/res/values/strings.xml
+++ b/V2rayNG/app/src/main/res/values/strings.xml
@@ -212,6 +212,9 @@
Connected, tap to check connection
Not connected
+ Subscription imported Successfully
+ Import subscription failed
+
- QRcode
- Export to clipboard