Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18f0fe47ff | ||
|
|
cccd6139fc | ||
|
|
1fadca8524 | ||
|
|
af01e2ac06 | ||
|
|
de22e16cd4 | ||
|
|
e073b19343 | ||
|
|
a81a05cd45 | ||
|
|
fc67281d2a | ||
|
|
ece35b9a1c | ||
|
|
ed8fb7fa82 | ||
|
|
3688dd4634 | ||
|
|
7ff445ef55 |
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 597
|
||||
versionName = "1.9.4"
|
||||
versionCode = 598
|
||||
versionName = "1.9.5"
|
||||
multiDexEnabled = true
|
||||
splits {
|
||||
abi {
|
||||
|
||||
@@ -150,6 +150,7 @@ object AppConfig {
|
||||
const val WIREGUARD = "wireguard://"
|
||||
const val TUIC = "tuic://"
|
||||
const val HYSTERIA2 = "hysteria2://"
|
||||
const val HY2 = "hy2://"
|
||||
|
||||
/** Give a good name to this, IDK*/
|
||||
const val VPN = "VPN"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class ConfigResult (
|
||||
var status: Boolean,
|
||||
var guid: String? = null,
|
||||
var content: String = "",
|
||||
var domainPort: String? = null,
|
||||
)
|
||||
|
||||
@@ -4,9 +4,20 @@ data class Hysteria2Bean(
|
||||
val server: String?,
|
||||
val auth: String?,
|
||||
val lazy: Boolean? = true,
|
||||
val socks5: Socks5Bean?,
|
||||
val tls: TlsBean?
|
||||
val obfs: ObfsBean? = null,
|
||||
val socks5: Socks5Bean? = null,
|
||||
val http: Socks5Bean? = null,
|
||||
val tls: TlsBean? = null,
|
||||
) {
|
||||
data class ObfsBean(
|
||||
val type: String?,
|
||||
val salamander: SalamanderBean?
|
||||
) {
|
||||
data class SalamanderBean(
|
||||
val password: String?,
|
||||
)
|
||||
}
|
||||
|
||||
data class Socks5Bean(
|
||||
val listen: String?,
|
||||
)
|
||||
@@ -15,4 +26,4 @@ data class Hysteria2Bean(
|
||||
val sni: String?,
|
||||
val insecure: Boolean?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,8 @@ data class V2rayConfig(
|
||||
var secretKey: String? = null,
|
||||
val peers: List<WireGuardBean>? = null,
|
||||
var reserved: List<Int>? = null,
|
||||
var mtu: Int? = null
|
||||
var mtu: Int? = null,
|
||||
var obfsPassword: String? = null,
|
||||
) {
|
||||
|
||||
data class VnextBean(
|
||||
|
||||
@@ -5,10 +5,12 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import org.json.JSONObject
|
||||
import java.io.Serializable
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
|
||||
@@ -81,4 +83,14 @@ fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Uni
|
||||
addDataScheme("package")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
|
||||
else -> @Suppress("DEPRECATION") getSerializable(key) as? T
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Intent.serializable(key: String): T? = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
|
||||
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
|
||||
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
@@ -40,8 +41,8 @@ object SubscriptionUpdater {
|
||||
|
||||
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
|
||||
|
||||
for (i in subs) {
|
||||
val subscription = i.second
|
||||
for (sub in subs) {
|
||||
val subItem = sub.second
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
|
||||
@@ -56,11 +57,10 @@ object SubscriptionUpdater {
|
||||
notificationManager.notify(3, notification.build())
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"subscription automatic update: ---${subscription.remarks}"
|
||||
"subscription automatic update: ---${subItem.remarks}"
|
||||
)
|
||||
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
||||
AngConfigManager.importBatchConfig(configs, i.first, false)
|
||||
notification.setContentText("Updating ${subscription.remarks}")
|
||||
updateConfigViaSub(Pair(sub.first, subItem))
|
||||
notification.setContentText("Updating ${subItem.remarks}")
|
||||
}
|
||||
notificationManager.cancel(3)
|
||||
return Result.success()
|
||||
|
||||
@@ -164,7 +164,7 @@ object V2RayServiceManager {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||
showNotification()
|
||||
|
||||
PluginUtil.runPlugin(service, config)
|
||||
PluginUtil.runPlugin(service, config, result.domainPort)
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||
cancelNotification()
|
||||
|
||||
@@ -3,12 +3,18 @@ package com.v2ray.ang.service
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
|
||||
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.serializable
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.PluginUtil
|
||||
import com.v2ray.ang.util.SpeedtestUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import go.Seq
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -30,10 +36,10 @@ class V2RayTestService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
MSG_MEASURE_CONFIG -> {
|
||||
val contentPair = intent.getSerializableExtra("content") as Pair<String, String>
|
||||
val guid = intent.serializable<String>("content") ?: ""
|
||||
realTestScope.launch {
|
||||
val result = SpeedtestUtil.realPing(contentPair.second)
|
||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
||||
val result = startRealPing(guid)
|
||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(guid, result))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +53,29 @@ class V2RayTestService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun startRealPing(guid: String): Long {
|
||||
val retFailure = -1L
|
||||
|
||||
val server = MmkvManager.decodeServerConfig(guid) ?: return retFailure
|
||||
if (server.getProxyOutbound()?.protocol?.equals(EConfigType.HYSTERIA2.name, true) == true) {
|
||||
val socksPort = Utils.findFreePort(listOf(0))
|
||||
PluginUtil.runPlugin(this, server, "0:${socksPort}")
|
||||
Thread.sleep(1000L)
|
||||
|
||||
var delay = SpeedtestUtil.testConnection(this, socksPort)
|
||||
if (delay.first < 0) {
|
||||
Thread.sleep(10L)
|
||||
delay = SpeedtestUtil.testConnection(this, socksPort)
|
||||
}
|
||||
PluginUtil.stopPlugin()
|
||||
return delay.first
|
||||
} else {
|
||||
val config = V2rayConfigUtil.getV2rayConfig(this, guid)
|
||||
if (!config.status) {
|
||||
return retFailure
|
||||
}
|
||||
return SpeedtestUtil.realPing(config.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
private fun runTun2socks() {
|
||||
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
val cmd = arrayListOf(
|
||||
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
||||
|
||||
@@ -10,12 +10,14 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
@@ -108,6 +110,44 @@ class RoutingSettingActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_rulesets_from_clipboard -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
try {
|
||||
val clipboard = Utils.getClipboard(this)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val ret = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
|
||||
launch(Dispatchers.Main) {
|
||||
if (ret) {
|
||||
refreshData()
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.export_rulesets_to_clipboard -> {
|
||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||
if (rulesetList.isNullOrEmpty()) {
|
||||
toast(R.string.toast_failure)
|
||||
} else {
|
||||
Utils.setClipboard(this, JsonUtil.toJson(rulesetList))
|
||||
toast(R.string.toast_success)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
|
||||
)
|
||||
}
|
||||
|
||||
holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked ->
|
||||
holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
||||
if( !it.isPressed) return@setOnCheckedChangeListener
|
||||
ruleset.enabled = isChecked
|
||||
SettingsManager.saveRoutingRuleset(position, ruleset)
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ class ServerActivity : BaseActivity() {
|
||||
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
|
||||
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
|
||||
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
|
||||
private val et_obfs_password: EditText? by lazy { findViewById(R.id.et_obfs_password) }
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -292,7 +293,10 @@ class ServerActivity : BaseActivity() {
|
||||
} else {
|
||||
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
|
||||
}
|
||||
} else if (config.configType == EConfigType.HYSTERIA2) {
|
||||
et_obfs_password?.text = Utils.getEditable(outbound.settings?.obfsPassword)
|
||||
}
|
||||
|
||||
val securityEncryptions =
|
||||
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||
val security =
|
||||
@@ -455,6 +459,9 @@ class ServerActivity : BaseActivity() {
|
||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
config.subscriptionId = subscriptionId.orEmpty()
|
||||
}
|
||||
if (config.configType == EConfigType.HYSTERIA2) {
|
||||
config.outboundBean?.settings?.obfsPassword = et_obfs_password?.text?.toString()
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
toast(R.string.toast_success)
|
||||
|
||||
@@ -8,13 +8,14 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
@@ -78,7 +79,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val v2rayConfig = try {
|
||||
Gson().fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
|
||||
JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||
|
||||
@@ -44,7 +44,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
)
|
||||
}
|
||||
|
||||
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked ->
|
||||
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
||||
if( !it.isPressed) return@setOnCheckedChangeListener
|
||||
subItem.enabled = isChecked
|
||||
MmkvManager.encodeSubscription(subId, subItem)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.v2ray.ang.extension.toTrafficString
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.SettingsManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -176,7 +177,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
.show()
|
||||
toast(R.string.msg_downloading_content)
|
||||
|
||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@ import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.HY2
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.*
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.fmt.Hysteria2Fmt
|
||||
import com.v2ray.ang.util.fmt.ShadowsocksFmt
|
||||
import com.v2ray.ang.util.fmt.SocksFmt
|
||||
@@ -52,7 +52,7 @@ object AngConfigManager {
|
||||
VlessFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||
WireguardFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme)) {
|
||||
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme) || str.startsWith(HY2)) {
|
||||
Hysteria2Fmt.parse(str)
|
||||
} else {
|
||||
null
|
||||
@@ -278,34 +278,21 @@ object AngConfigManager {
|
||||
&& server.contains("routing")
|
||||
) {
|
||||
try {
|
||||
//val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
|
||||
object : TypeToken<Double>() {}.type,
|
||||
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
|
||||
JsonPrimitive(
|
||||
src?.toInt()
|
||||
)
|
||||
}
|
||||
)
|
||||
.create()
|
||||
val serverList: Array<Any> =
|
||||
Gson().fromJson(server, Array<Any>::class.java)
|
||||
JsonUtil.fromJson(server, Array<Any>::class.java)
|
||||
|
||||
if (serverList.isNotEmpty()) {
|
||||
var count = 0
|
||||
for (srv in serverList.reversed()) {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.fullConfig =
|
||||
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
||||
JsonUtil.fromJson(JsonUtil.toJson(srv), V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks
|
||||
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
||||
.toString())
|
||||
config.subscriptionId = subid
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, gson.toJson(srv))
|
||||
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
@@ -318,7 +305,7 @@ object AngConfigManager {
|
||||
// For compatibility
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.subscriptionId = subid
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
@@ -373,19 +360,17 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
Log.d(AppConfig.ANG_PACKAGE, url)
|
||||
|
||||
var configText = try {
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
if (configText.isEmpty()) {
|
||||
configText = try {
|
||||
val httpPort = Utils.parseInt(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||
AppConfig.PORT_HTTP.toInt()
|
||||
)
|
||||
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
|
||||
37
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/JsonUtil.kt
Normal file
37
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/JsonUtil.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.lang.reflect.Type
|
||||
|
||||
object JsonUtil {
|
||||
private var gson = Gson()
|
||||
|
||||
fun toJson(src: Any?): String {
|
||||
return gson.toJson(src)
|
||||
}
|
||||
|
||||
fun <T> fromJson(json: String, cls: Class<T>): T {
|
||||
return gson.fromJson(json, cls)
|
||||
}
|
||||
|
||||
fun toJsonPretty(src: Any?): String {
|
||||
val gsonPre = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
|
||||
object : TypeToken<Double>() {}.type,
|
||||
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
|
||||
JsonPrimitive(
|
||||
src?.toInt()
|
||||
)
|
||||
}
|
||||
)
|
||||
.create()
|
||||
return gsonPre.toJson(src)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig.PREF_IS_BOOTED
|
||||
import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET
|
||||
@@ -49,7 +49,7 @@ object MmkvManager {
|
||||
}
|
||||
|
||||
fun encodeServerList(serverList: MutableList<String>) {
|
||||
mainStorage.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
mainStorage.encode(KEY_ANG_CONFIGS, JsonUtil.toJson(serverList))
|
||||
}
|
||||
|
||||
fun decodeServerList(): MutableList<String> {
|
||||
@@ -57,7 +57,7 @@ object MmkvManager {
|
||||
return if (json.isNullOrBlank()) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
Gson().fromJson(json, Array<String>::class.java).toMutableList()
|
||||
JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ object MmkvManager {
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerConfig::class.java)
|
||||
return JsonUtil.fromJson(json, ServerConfig::class.java)
|
||||
}
|
||||
|
||||
fun decodeProfileConfig(guid: String): ProfileItem? {
|
||||
@@ -80,12 +80,12 @@ object MmkvManager {
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ProfileItem::class.java)
|
||||
return JsonUtil.fromJson(json, ProfileItem::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||
val key = guid.ifBlank { Utils.getUuid() }
|
||||
serverStorage.encode(key, Gson().toJson(config))
|
||||
serverStorage.encode(key, JsonUtil.toJson(config))
|
||||
val serverList = decodeServerList()
|
||||
if (!serverList.contains(key)) {
|
||||
serverList.add(0, key)
|
||||
@@ -101,7 +101,7 @@ object MmkvManager {
|
||||
server = config.getProxyOutbound()?.getServerAddress(),
|
||||
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||
)
|
||||
profileStorage.encode(key, Gson().toJson(profile))
|
||||
profileStorage.encode(key, JsonUtil.toJson(profile))
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ object MmkvManager {
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
|
||||
return JsonUtil.fromJson(json, ServerAffiliationInfo::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
|
||||
@@ -150,14 +150,14 @@ object MmkvManager {
|
||||
}
|
||||
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
|
||||
aff.testDelayMillis = testResult
|
||||
serverAffStorage.encode(guid, Gson().toJson(aff))
|
||||
serverAffStorage.encode(guid, JsonUtil.toJson(aff))
|
||||
}
|
||||
|
||||
fun clearAllTestDelayResults(keys: List<String>?) {
|
||||
keys?.forEach { key ->
|
||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||
aff.testDelayMillis = 0
|
||||
serverAffStorage.encode(key, Gson().toJson(aff))
|
||||
serverAffStorage.encode(key, JsonUtil.toJson(aff))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ object MmkvManager {
|
||||
decodeSubsList().forEach { key ->
|
||||
val json = subStorage.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||
subscriptions.add(Pair(key, JsonUtil.fromJson(json, SubscriptionItem::class.java)))
|
||||
}
|
||||
}
|
||||
return subscriptions
|
||||
@@ -234,7 +234,7 @@ object MmkvManager {
|
||||
|
||||
fun encodeSubscription(guid: String, subItem: SubscriptionItem) {
|
||||
val key = guid.ifBlank { Utils.getUuid() }
|
||||
subStorage.encode(key, Gson().toJson(subItem))
|
||||
subStorage.encode(key, JsonUtil.toJson(subItem))
|
||||
|
||||
val subsList = decodeSubsList()
|
||||
if (!subsList.contains(key)) {
|
||||
@@ -245,11 +245,11 @@ object MmkvManager {
|
||||
|
||||
fun decodeSubscription(subscriptionId: String): SubscriptionItem? {
|
||||
val json = subStorage.decodeString(subscriptionId) ?: return null
|
||||
return Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
return JsonUtil.fromJson(json, SubscriptionItem::class.java)
|
||||
}
|
||||
|
||||
fun encodeSubsList(subsList: MutableList<String>) {
|
||||
mainStorage.encode(KEY_SUB_IDS, Gson().toJson(subsList))
|
||||
mainStorage.encode(KEY_SUB_IDS, JsonUtil.toJson(subsList))
|
||||
}
|
||||
|
||||
fun decodeSubsList(): MutableList<String> {
|
||||
@@ -257,7 +257,7 @@ object MmkvManager {
|
||||
return if (json.isNullOrBlank()) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
Gson().fromJson(json, Array<String>::class.java).toMutableList()
|
||||
JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ object MmkvManager {
|
||||
assetStorage.allKeys()?.forEach { key ->
|
||||
val json = assetStorage.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
||||
assetUrlItems.add(Pair(key, JsonUtil.fromJson(json, AssetUrlItem::class.java)))
|
||||
}
|
||||
}
|
||||
return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
@@ -282,12 +282,12 @@ object MmkvManager {
|
||||
|
||||
fun encodeAsset(assetid: String, assetItem: AssetUrlItem) {
|
||||
val key = assetid.ifBlank { Utils.getUuid() }
|
||||
assetStorage.encode(key, Gson().toJson(assetItem))
|
||||
assetStorage.encode(key, JsonUtil.toJson(assetItem))
|
||||
}
|
||||
|
||||
fun decodeAsset(assetid: String): AssetUrlItem? {
|
||||
val json = assetStorage.decodeString(assetid) ?: return null
|
||||
return Gson().fromJson(json, AssetUrlItem::class.java)
|
||||
return JsonUtil.fromJson(json, AssetUrlItem::class.java)
|
||||
}
|
||||
|
||||
//endregion
|
||||
@@ -297,14 +297,14 @@ object MmkvManager {
|
||||
fun decodeRoutingRulesets(): MutableList<RulesetItem>? {
|
||||
val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET)
|
||||
if (ruleset.isNullOrEmpty()) return null
|
||||
return Gson().fromJson(ruleset, Array<RulesetItem>::class.java).toMutableList()
|
||||
return JsonUtil.fromJson(ruleset, Array<RulesetItem>::class.java).toMutableList()
|
||||
}
|
||||
|
||||
fun encodeRoutingRulesets(rulesetList: MutableList<RulesetItem>?) {
|
||||
if (rulesetList.isNullOrEmpty())
|
||||
settingsStorage.encode(PREF_ROUTING_RULESET, "")
|
||||
else
|
||||
settingsStorage.encode(PREF_ROUTING_RULESET, Gson().toJson(rulesetList))
|
||||
settingsStorage.encode(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList))
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
@@ -3,12 +3,10 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AppConfig
|
||||
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.fmt.Hysteria2Fmt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -25,22 +23,23 @@ object PluginUtil {
|
||||
// return PluginManager.init(name)!!
|
||||
// }
|
||||
|
||||
fun runPlugin(context: Context, config: ServerConfig?) {
|
||||
fun runPlugin(context: Context, config: ServerConfig?, domainPort: String?) {
|
||||
Log.d(packageName, "runPlugin")
|
||||
|
||||
val outbound = config?.getProxyOutbound() ?: return
|
||||
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
|
||||
Log.d(packageName, "runPlugin $HYSTERIA2")
|
||||
|
||||
val socksPort = 100 + Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
val socksPort = domainPort?.split(":")?.last()
|
||||
.let { if (it.isNullOrEmpty()) return else it.toInt() }
|
||||
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return
|
||||
|
||||
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
|
||||
Log.d(packageName, "runPlugin ${configFile.absolutePath}")
|
||||
|
||||
configFile.parentFile?.mkdirs()
|
||||
configFile.writeText(Gson().toJson(hy2Config))
|
||||
Log.d(packageName, Gson().toJson(hy2Config))
|
||||
configFile.writeText(JsonUtil.toJson(hy2Config))
|
||||
Log.d(packageName, JsonUtil.toJson(hy2Config))
|
||||
|
||||
runHy2(context, configFile)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.v2ray.ang.util
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeServerConfig
|
||||
import com.v2ray.ang.util.MmkvManager.decodeServerList
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils.parseInt
|
||||
import java.util.Collections
|
||||
|
||||
object SettingsManager {
|
||||
@@ -32,10 +35,34 @@ object SettingsManager {
|
||||
return null
|
||||
}
|
||||
|
||||
return Gson().fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
|
||||
return JsonUtil.fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
|
||||
}
|
||||
|
||||
fun resetRoutingRulesets(context: Context, index: Int) {
|
||||
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
|
||||
resetRoutingRulesetsCommon(rulesetList)
|
||||
}
|
||||
|
||||
fun resetRoutingRulesetsFromClipboard(content: String?): Boolean {
|
||||
if (content.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
val rulesetList = JsonUtil.fromJson(content, Array<RulesetItem>::class.java).toMutableList()
|
||||
if (rulesetList.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
resetRoutingRulesetsCommon(rulesetList)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetRoutingRulesetsCommon(rulesetList: MutableList<RulesetItem>) {
|
||||
val rulesetNew: MutableList<RulesetItem> = mutableListOf()
|
||||
MmkvManager.decodeRoutingRulesets()?.forEach { key ->
|
||||
if (key.looked == true) {
|
||||
@@ -43,7 +70,6 @@ object SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
|
||||
rulesetNew.addAll(rulesetList)
|
||||
MmkvManager.encodeRoutingRulesets(rulesetNew)
|
||||
}
|
||||
@@ -117,4 +143,12 @@ object SettingsManager {
|
||||
return null
|
||||
}
|
||||
|
||||
fun getSocksPort(): Int {
|
||||
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
}
|
||||
|
||||
fun getHttpPort(): Int {
|
||||
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ object SpeedtestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun testConnection(context: Context, port: Int): String {
|
||||
// return V2RayVpnService.measureV2rayDelay()
|
||||
fun testConnection(context: Context, port: Int): Pair<Long, String> {
|
||||
var result: String
|
||||
var elapsed = -1L
|
||||
var conn: HttpURLConnection? = null
|
||||
|
||||
try {
|
||||
@@ -120,7 +120,7 @@ object SpeedtestUtil {
|
||||
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
val code = conn.responseCode
|
||||
val elapsed = SystemClock.elapsedRealtime() - start
|
||||
elapsed = SystemClock.elapsedRealtime() - start
|
||||
|
||||
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
||||
result = context.getString(R.string.connection_test_available, elapsed)
|
||||
@@ -134,10 +134,7 @@ object SpeedtestUtil {
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// network exception
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"testConnection IOException: " + Log.getStackTraceString(e)
|
||||
)
|
||||
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
|
||||
result = context.getString(R.string.connection_test_error, e.message)
|
||||
} catch (e: Exception) {
|
||||
// library exception, eg sumsung
|
||||
@@ -147,7 +144,7 @@ object SpeedtestUtil {
|
||||
conn?.disconnect()
|
||||
}
|
||||
|
||||
return result
|
||||
return Pair(elapsed, result)
|
||||
}
|
||||
|
||||
fun getLibVersion(): String {
|
||||
|
||||
@@ -453,6 +453,17 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
fun findFreePort(ports: List<Int>): Int {
|
||||
for (port in ports) {
|
||||
try {
|
||||
return ServerSocket(port).use { it.localPort }
|
||||
} catch (ex: IOException) {
|
||||
continue // try next port
|
||||
}
|
||||
}
|
||||
|
||||
// if the program gets here, no port in the range was found
|
||||
throw IOException("no free port found")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
@@ -14,6 +14,7 @@ import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||
import com.v2ray.ang.dto.ConfigResult
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
@@ -25,34 +26,32 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
|
||||
object V2rayConfigUtil {
|
||||
|
||||
data class Result(var status: Boolean, var content: String = "", var domainPort: String? = null)
|
||||
|
||||
fun getV2rayConfig(context: Context, guid: String): Result {
|
||||
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
|
||||
try {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false)
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val raw = MmkvManager.decodeServerRaw(guid)
|
||||
val customConfig = if (raw.isNullOrBlank()) {
|
||||
config.fullConfig?.toPrettyPrinting() ?: return Result(false)
|
||||
config.fullConfig?.toPrettyPrinting() ?: return ConfigResult(false)
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
val domainPort = config.getProxyOutbound()?.getServerAddressAndPort()
|
||||
return Result(true, customConfig, domainPort)
|
||||
return ConfigResult(true, guid, customConfig, domainPort)
|
||||
}
|
||||
|
||||
val result = getV2rayNonCustomConfig(context, config)
|
||||
//Log.d(ANG_PACKAGE, result.content)
|
||||
Log.d(ANG_PACKAGE, result.domainPort?:"")
|
||||
result.guid = guid
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Result(false)
|
||||
return ConfigResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): Result {
|
||||
val result = Result(false)
|
||||
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): ConfigResult {
|
||||
val result = ConfigResult(false)
|
||||
|
||||
val outbound = config.getProxyOutbound() ?: return result
|
||||
val address = outbound.getServerAddress() ?: return result
|
||||
@@ -68,7 +67,7 @@ object V2rayConfigUtil {
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
v2rayConfig.remarks = config.remarks
|
||||
|
||||
@@ -101,14 +100,8 @@ object V2rayConfigUtil {
|
||||
|
||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val socksPort = Utils.parseInt(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT),
|
||||
AppConfig.PORT_SOCKS.toInt()
|
||||
)
|
||||
val httpPort = Utils.parseInt(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||
AppConfig.PORT_HTTP.toInt()
|
||||
)
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
|
||||
v2rayConfig.inbounds.forEach { curInbound ->
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
||||
@@ -149,7 +142,7 @@ object V2rayConfigUtil {
|
||||
|
||||
private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean, isPlugin: Boolean): Pair<Boolean, String> {
|
||||
if (isPlugin) {
|
||||
val socksPort = 100 + Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
|
||||
val outboundNew = V2rayConfig.OutboundBean(
|
||||
mux = null,
|
||||
protocol = EConfigType.SOCKS.name.lowercase(),
|
||||
@@ -213,7 +206,7 @@ object V2rayConfigUtil {
|
||||
return
|
||||
}
|
||||
|
||||
val rule = Gson().fromJson(Gson().toJson(item), RulesBean::class.java) ?: return
|
||||
val rule = JsonUtil.fromJson(JsonUtil.toJson(item), RulesBean::class.java) ?: return
|
||||
|
||||
v2rayConfig.routing.rules.add(rule)
|
||||
|
||||
@@ -447,7 +440,7 @@ object V2rayConfigUtil {
|
||||
val requestString: String by lazy {
|
||||
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
|
||||
}
|
||||
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(
|
||||
outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
|
||||
requestString,
|
||||
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ object Hysteria2Fmt {
|
||||
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
V2rayConfig.TLS,
|
||||
if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure,
|
||||
if ((queryParam["insecure"].orEmpty()) == "1") true else allowInsecure,
|
||||
queryParam["sni"] ?: uri.idnHost,
|
||||
null,
|
||||
queryParam["alpn"],
|
||||
@@ -40,6 +40,10 @@ object Hysteria2Fmt {
|
||||
server.port = uri.port
|
||||
server.password = uri.userInfo
|
||||
}
|
||||
if (!queryParam["obfs-password"].isNullOrEmpty()) {
|
||||
config.outboundBean?.settings?.obfsPassword = queryParam["obfs-password"]
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -59,6 +63,10 @@ object Hysteria2Fmt {
|
||||
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
|
||||
}
|
||||
}
|
||||
if (!outbound.settings?.obfsPassword.isNullOrEmpty()) {
|
||||
dicQuery["obfs"] = "salamander"
|
||||
dicQuery["obfs-password"] = outbound.settings?.obfsPassword ?: ""
|
||||
}
|
||||
|
||||
val query = "?" + dicQuery.toList().joinToString(
|
||||
separator = "&",
|
||||
@@ -76,12 +84,24 @@ object Hysteria2Fmt {
|
||||
fun toNativeConfig(config: ServerConfig, socksPort: Int): Hysteria2Bean? {
|
||||
val outbound = config.getProxyOutbound() ?: return null
|
||||
val tls = outbound.streamSettings?.tlsSettings
|
||||
val obfs = if (outbound.settings?.obfsPassword.isNullOrEmpty()) null else
|
||||
Hysteria2Bean.ObfsBean(
|
||||
type = "salamander",
|
||||
salamander = Hysteria2Bean.ObfsBean.SalamanderBean(
|
||||
password = outbound.settings?.obfsPassword
|
||||
)
|
||||
)
|
||||
|
||||
val bean = Hysteria2Bean(
|
||||
server = outbound.getServerAddressAndPort(),
|
||||
auth = outbound.getPassword(),
|
||||
obfs = obfs,
|
||||
socks5 = Hysteria2Bean.Socks5Bean(
|
||||
listen = "$LOOPBACK:${socksPort}",
|
||||
),
|
||||
http = Hysteria2Bean.Socks5Bean(
|
||||
listen = "$LOOPBACK:${socksPort}",
|
||||
),
|
||||
tls = Hysteria2Bean.TlsBean(
|
||||
sni = tls?.serverName ?: outbound.getServerAddress(),
|
||||
insecure = tls?.allowInsecure
|
||||
|
||||
@@ -2,13 +2,14 @@ package com.v2ray.ang.util.fmt
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.VmessQRCode
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
@@ -29,7 +30,7 @@ object VmessFmt {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
|
||||
return null
|
||||
}
|
||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||
val vmessQRCode = JsonUtil.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)
|
||||
@@ -100,7 +101,7 @@ object VmessFmt {
|
||||
vmessQRCode.host = transportDetails[1]
|
||||
vmessQRCode.path = transportDetails[2]
|
||||
}
|
||||
val json = Gson().toJson(vmessQRCode)
|
||||
val json = JsonUtil.toJson(vmessQRCode)
|
||||
return Utils.encode(json)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
@@ -21,14 +20,15 @@ import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.ServersCache
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.serializable
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.SpeedtestUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -98,7 +98,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
try {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.subscriptionId = subscriptionId
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, server)
|
||||
@@ -211,14 +211,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
|
||||
for (item in serversCopy) {
|
||||
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid)
|
||||
if (config.status) {
|
||||
MessageUtil.sendMsg2TestService(
|
||||
getApplication(),
|
||||
AppConfig.MSG_MEASURE_CONFIG,
|
||||
Pair(item.guid, config.content)
|
||||
)
|
||||
}
|
||||
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,11 +387,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||
val resultPair: Pair<String, Long> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getSerializableExtra("content", Pair::class.java) as Pair<String, Long>
|
||||
} else {
|
||||
intent.getSerializableExtra("content") as Pair<String, Long>
|
||||
}
|
||||
val resultPair = intent.serializable<Pair<String, Long>>("content") ?: return
|
||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||
updateListAction.value = getPosition(resultPair.first)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginBottom="24dp">
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
|
||||
@@ -13,6 +13,25 @@
|
||||
|
||||
<include layout="@layout/layout_address_port" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_obfs_password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_obfs_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -32,7 +51,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<include layout="@layout/layout_tls_hysteria2" />
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -14,5 +14,13 @@
|
||||
android:id="@+id/import_rulesets"
|
||||
android:title="@string/routing_settings_import_rulesets"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/import_rulesets_from_clipboard"
|
||||
android:title="@string/routing_settings_import_rulesets_from_clipboard"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/export_rulesets_to_clipboard"
|
||||
android:title="@string/routing_settings_export_rulesets_to_clipboard"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
@@ -112,6 +112,7 @@
|
||||
<string name="msg_file_not_found">الملف غير موجود</string>
|
||||
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
|
||||
<string name="toast_action_not_allowed">الإجراء غير مسموح به</string>
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">جار التحميل</string>
|
||||
@@ -260,6 +261,8 @@
|
||||
<string name="routing_settings_add_rule">Add rule</string>
|
||||
<string name="routing_settings_import_rulesets">Import ruleset</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
|
||||
<string name="connection_test_pending">التحقق من الاتصال</string>
|
||||
|
||||
@@ -111,6 +111,8 @@
|
||||
<string name="msg_file_not_found">ফাইল খুঁজে পাওয়া যায়নি</string>
|
||||
<string name="msg_remark_is_duplicate">মন্তব্য ইতিমধ্যে বিদ্যমান</string>
|
||||
<string name="toast_action_not_allowed">অ্যাকশন অনুমোদিত নয়</string>
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">লোড হচ্ছে</string>
|
||||
<string name="menu_item_search">অনুসন্ধান করুন</string>
|
||||
@@ -257,6 +259,8 @@
|
||||
<string name="routing_settings_add_rule">Add rule</string>
|
||||
<string name="routing_settings_import_rulesets">Import ruleset</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
|
||||
<string name="connection_test_pending">সংযোগ পরীক্ষা করুন</string>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">دانلود فایلها</string>
|
||||
<string name="toast_action_not_allowed">این عمل ممنوع است</string>
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">URL را اضافه کنید</string>
|
||||
@@ -256,6 +257,8 @@
|
||||
<string name="routing_settings_add_rule">Add rule</string>
|
||||
<string name="routing_settings_import_rulesets">Import ruleset</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
|
||||
<string name="connection_test_pending">اتصال را بررسی کنید</string>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<string name="msg_file_not_found">Файл не найден</string>
|
||||
<string name="msg_remark_is_duplicate">Название уже существует</string>
|
||||
<string name="toast_action_not_allowed">Это действие запрещено</string>
|
||||
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">Загрузка…</string>
|
||||
@@ -130,8 +130,8 @@
|
||||
<string name="title_vpn_settings">Настройки VPN</string>
|
||||
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string>
|
||||
<string name="summary_pref_per_app_proxy">Основной: выделенное приложение соединяется через прокси, не выделенное — напрямую;\nРежим обхода: выделенное приложение соединяется напрямую, не выделенное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string>
|
||||
<string name="title_pref_is_booted">Auto connect at startup</string>
|
||||
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
|
||||
<string name="title_pref_is_booted">Автоподключение при запуске</string>
|
||||
<string name="summary_pref_is_booted">Автоматически подключаться к выбранному серверу при запуске приложения (может оказаться неудачным)</string>
|
||||
|
||||
<string name="title_mux_settings">Настройки мультиплексирования</string>
|
||||
<string name="title_pref_mux_enabled">Использовать мультиплексирование</string>
|
||||
@@ -235,10 +235,10 @@
|
||||
<string name="sub_setting_url">URL (необязательно)</string>
|
||||
<string name="sub_setting_filter">Название фильтра</string>
|
||||
<string name="sub_setting_enable">Использовать обновление</string>
|
||||
<string name="sub_auto_update">Использовать автоматическое обновление</string>
|
||||
<string name="sub_auto_update">Использовать автообновление</string>
|
||||
<string name="sub_setting_pre_profile">Название предыдущего прокси</string>
|
||||
<string name="sub_setting_next_profile">Название следующего прокси</string>
|
||||
<string name="sub_setting_pre_profile_tip">Убедитесь, что название существует и является уникальным</string>
|
||||
<string name="sub_setting_pre_profile_tip">Название должно существовать и быть уникальным</string>
|
||||
<string name="title_sub_update">Обновить подписку группы</string>
|
||||
<string name="title_ping_all_server">Проверка профилей группы</string>
|
||||
<string name="title_real_ping_all_server">Время отклика профилей группы</string>
|
||||
@@ -260,7 +260,9 @@
|
||||
<string name="routing_settings_add_rule">Добавить правило</string>
|
||||
<string name="routing_settings_import_rulesets">Импорт правил</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Существующие правила будут удалены. Продолжить?</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Постоянное (сохранится при импорте правил)</string>
|
||||
<string name="routing_settings_domain">Домен</string>
|
||||
<string name="routing_settings_ip">IP</string>
|
||||
<string name="routing_settings_port">Порт</string>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
||||
<string name="toast_action_not_allowed">Hành động này bị cấm!</string>
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">Thêm URL nội dung</string>
|
||||
@@ -259,6 +260,8 @@
|
||||
<string name="routing_settings_add_rule">Add rule</string>
|
||||
<string name="routing_settings_import_rulesets">Import ruleset</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
|
||||
<string name="connection_test_pending">Kiểm tra kết nối</string>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">下载文件</string>
|
||||
<string name="toast_action_not_allowed">禁止此项操作</string>
|
||||
|
||||
<string name="server_obfs_password">混淆密码</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">添加资产网址</string>
|
||||
@@ -257,6 +257,8 @@
|
||||
<string name="routing_settings_add_rule">添加规则</string>
|
||||
<string name="routing_settings_import_rulesets">导入预设规则集</string>
|
||||
<string name="routing_settings_import_rulesets_tip">将删除现有的规则集,是否确定继续?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">从剪贴板导入规则集</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">导出规则集至剪贴板</string>
|
||||
<string name="routing_settings_locked">锁定中,导入预设时不删除此规则</string>
|
||||
|
||||
<string name="connection_test_pending">"检查网络连接"</string>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">下載檔案</string>
|
||||
<string name="toast_action_not_allowed">禁止此項操作</string>
|
||||
<string name="server_obfs_password">混淆密碼</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">新增資產網址</string>
|
||||
@@ -258,6 +259,8 @@
|
||||
<string name="routing_settings_add_rule">新增規則</string>
|
||||
<string name="routing_settings_import_rulesets">匯入預設規則集</string>
|
||||
<string name="routing_settings_import_rulesets_tip">將刪除現有的規則集,是否確定繼續? </string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">從剪貼簿匯入規則集</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">匯出規則集至剪貼簿</string>
|
||||
<string name="routing_settings_locked">鎖定中,匯入預設時不刪除此規則</string>
|
||||
|
||||
<string name="connection_test_pending">"測試連線能力"</string>
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<string name="msg_file_not_found">File not found</string>
|
||||
<string name="msg_remark_is_duplicate">The remarks already exists</string>
|
||||
<string name="toast_action_not_allowed">Action not allowed</string>
|
||||
|
||||
<string name="server_obfs_password">Obfs password</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">Loading</string>
|
||||
@@ -263,6 +263,8 @@
|
||||
<string name="routing_settings_add_rule">Add rule</string>
|
||||
<string name="routing_settings_import_rulesets">Import ruleset</string>
|
||||
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
|
||||
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
|
||||
<string name="routing_settings_domain" translatable="false">domain</string>
|
||||
<string name="routing_settings_ip" translatable="false">ip</string>
|
||||
|
||||
Reference in New Issue
Block a user