feature: import subscription from v2rayng://install-sub deeplink

This commit is contained in:
Hadi Norouzi committed 2023-05-11 20:42:15 +03:30
1 parent 2349805968
commit 4d4a4543c5
7 files changed
+265 -80

No files matched your search

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