feature: import subscription from v2rayng://install-sub deeplink
This commit is contained in:
1 parent
2349805968
commit
4d4a4543c5
7 files changed
+263
-78
No files matched your search
@@ -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 ->
|
||||||
@@ -338,15 +413,25 @@ object AngConfigManager {
|
|||||||
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
|
||||||
@@ -354,8 +439,12 @@ object AngConfigManager {
|
|||||||
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>
|
||||||
|
|||||||
Reference in new issue
Block a user