mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-18 22:20:06 +08:00
dev custom rule config
This commit is contained in:
parent
0127c60906
commit
7c46729c83
@ -0,0 +1,373 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "3d3db9106a89d6f20ef3fde6e81dbaa9",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "proxy_groups",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userOrder",
|
||||||
|
"columnName": "userOrder",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ungrouped",
|
||||||
|
"columnName": "ungrouped",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subscription",
|
||||||
|
"columnName": "subscription",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "order",
|
||||||
|
"columnName": "order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isSelector",
|
||||||
|
"columnName": "isSelector",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "frontProxy",
|
||||||
|
"columnName": "frontProxy",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "landingProxy",
|
||||||
|
"columnName": "landingProxy",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_entities",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "groupId",
|
||||||
|
"columnName": "groupId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userOrder",
|
||||||
|
"columnName": "userOrder",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tx",
|
||||||
|
"columnName": "tx",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rx",
|
||||||
|
"columnName": "rx",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "status",
|
||||||
|
"columnName": "status",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ping",
|
||||||
|
"columnName": "ping",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uuid",
|
||||||
|
"columnName": "uuid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "error",
|
||||||
|
"columnName": "error",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socksBean",
|
||||||
|
"columnName": "socksBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpBean",
|
||||||
|
"columnName": "httpBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ssBean",
|
||||||
|
"columnName": "ssBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "vmessBean",
|
||||||
|
"columnName": "vmessBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trojanBean",
|
||||||
|
"columnName": "trojanBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trojanGoBean",
|
||||||
|
"columnName": "trojanGoBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mieruBean",
|
||||||
|
"columnName": "mieruBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naiveBean",
|
||||||
|
"columnName": "naiveBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hysteriaBean",
|
||||||
|
"columnName": "hysteriaBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tuicBean",
|
||||||
|
"columnName": "tuicBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sshBean",
|
||||||
|
"columnName": "sshBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wgBean",
|
||||||
|
"columnName": "wgBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shadowTLSBean",
|
||||||
|
"columnName": "shadowTLSBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "anyTLSBean",
|
||||||
|
"columnName": "anyTLSBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "chainBean",
|
||||||
|
"columnName": "chainBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "nekoBean",
|
||||||
|
"columnName": "nekoBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "configBean",
|
||||||
|
"columnName": "configBean",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "groupId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "rules",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `config` TEXT NOT NULL DEFAULT '', `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "config",
|
||||||
|
"columnName": "config",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userOrder",
|
||||||
|
"columnName": "userOrder",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enabled",
|
||||||
|
"columnName": "enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "domains",
|
||||||
|
"columnName": "domains",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ip",
|
||||||
|
"columnName": "ip",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "port",
|
||||||
|
"columnName": "port",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sourcePort",
|
||||||
|
"columnName": "sourcePort",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "network",
|
||||||
|
"columnName": "network",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "source",
|
||||||
|
"columnName": "source",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "protocol",
|
||||||
|
"columnName": "protocol",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "outbound",
|
||||||
|
"columnName": "outbound",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "packages",
|
||||||
|
"columnName": "packages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d3db9106a89d6f20ef3fde6e81dbaa9')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,8 @@ import kotlinx.parcelize.Parcelize
|
|||||||
data class RuleEntity(
|
data class RuleEntity(
|
||||||
@PrimaryKey(autoGenerate = true) var id: Long = 0L,
|
@PrimaryKey(autoGenerate = true) var id: Long = 0L,
|
||||||
var name: String = "",
|
var name: String = "",
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
var config: String = "",
|
||||||
var userOrder: Long = 0L,
|
var userOrder: Long = 0L,
|
||||||
var enabled: Boolean = false,
|
var enabled: Boolean = false,
|
||||||
var domains: String = "",
|
var domains: String = "",
|
||||||
@ -31,11 +33,12 @@ data class RuleEntity(
|
|||||||
|
|
||||||
fun mkSummary(): String {
|
fun mkSummary(): String {
|
||||||
var summary = ""
|
var summary = ""
|
||||||
|
if (config.isNotBlank()) summary += "[config]\n"
|
||||||
if (domains.isNotBlank()) summary += "$domains\n"
|
if (domains.isNotBlank()) summary += "$domains\n"
|
||||||
if (ip.isNotBlank()) summary += "$ip\n"
|
if (ip.isNotBlank()) summary += "$ip\n"
|
||||||
if (source.isNotBlank()) summary += "source: $source\n"
|
if (source.isNotBlank()) summary += "src ip: $source\n"
|
||||||
if (sourcePort.isNotBlank()) summary += "sourcePort: $sourcePort\n"
|
if (sourcePort.isNotBlank()) summary += "src port: $sourcePort\n"
|
||||||
if (port.isNotBlank()) summary += "port: $port\n"
|
if (port.isNotBlank()) summary += "dst port: $port\n"
|
||||||
if (network.isNotBlank()) summary += "network: $network\n"
|
if (network.isNotBlank()) summary += "network: $network\n"
|
||||||
if (protocol.isNotBlank()) summary += "protocol: $protocol\n"
|
if (protocol.isNotBlank()) summary += "protocol: $protocol\n"
|
||||||
if (packages.isNotEmpty()) summary += app.getString(
|
if (packages.isNotEmpty()) summary += app.getString(
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class],
|
entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class],
|
||||||
version = 5,
|
version = 6,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(from = 3, to = 4),
|
AutoMigration(from = 3, to = 4),
|
||||||
AutoMigration(from = 4, to = 5)
|
AutoMigration(from = 4, to = 5)
|
||||||
|
|||||||
@ -35,7 +35,6 @@ import moe.matsuri.nb4a.proxy.config.ConfigBean
|
|||||||
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
|
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
|
||||||
import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
|
import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
|
||||||
import moe.matsuri.nb4a.utils.JavaUtil.gson
|
import moe.matsuri.nb4a.utils.JavaUtil.gson
|
||||||
import moe.matsuri.nb4a.utils.Util
|
|
||||||
import moe.matsuri.nb4a.utils.listByLineOrComma
|
import moe.matsuri.nb4a.utils.listByLineOrComma
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
@ -82,7 +81,6 @@ fun buildConfig(
|
|||||||
val globalOutbounds = HashMap<Long, String>()
|
val globalOutbounds = HashMap<Long, String>()
|
||||||
val selectorNames = ArrayList<String>()
|
val selectorNames = ArrayList<String>()
|
||||||
val group = SagerDatabase.groupDao.getById(proxy.groupId)
|
val group = SagerDatabase.groupDao.getById(proxy.groupId)
|
||||||
val optionsToMerge = proxy.requireBean().customConfigJson ?: ""
|
|
||||||
|
|
||||||
fun ProxyEntity.resolveChainInternal(): MutableList<ProxyEntity> {
|
fun ProxyEntity.resolveChainInternal(): MutableList<ProxyEntity> {
|
||||||
val bean = requireBean()
|
val bean = requireBean()
|
||||||
@ -255,13 +253,13 @@ fun buildConfig(
|
|||||||
add(entity)
|
add(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentOutbound = mutableMapOf<String, Any>()
|
var currentOutbound: SingBoxOption
|
||||||
lateinit var pastOutbound: MutableMap<String, Any>
|
lateinit var pastOutbound: SingBoxOption
|
||||||
lateinit var pastInboundTag: String
|
lateinit var pastInboundTag: String
|
||||||
var pastEntity: ProxyEntity? = null
|
var pastEntity: ProxyEntity? = null
|
||||||
val externalChainMap = LinkedHashMap<Int, ProxyEntity>()
|
val externalChainMap = LinkedHashMap<Int, ProxyEntity>()
|
||||||
externalIndexMap.add(IndexEntity(externalChainMap))
|
externalIndexMap.add(IndexEntity(externalChainMap))
|
||||||
val chainOutbounds = ArrayList<MutableMap<String, Any>>()
|
val chainOutbounds = ArrayList<SingBoxOption>()
|
||||||
|
|
||||||
// chainTagOut: v2ray outbound tag for this chain
|
// chainTagOut: v2ray outbound tag for this chain
|
||||||
var chainTagOut = ""
|
var chainTagOut = ""
|
||||||
@ -309,7 +307,7 @@ fun buildConfig(
|
|||||||
outbound = tagOut
|
outbound = tagOut
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
pastOutbound["detour"] = tagOut
|
pastOutbound._hack_config_map["detour"] = tagOut
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// index == 0 means last profile in chain / not chain
|
// index == 0 means last profile in chain / not chain
|
||||||
@ -332,53 +330,51 @@ fun buildConfig(
|
|||||||
type = "socks"
|
type = "socks"
|
||||||
server = LOCALHOST
|
server = LOCALHOST
|
||||||
server_port = localPort
|
server_port = localPort
|
||||||
}.asMap()
|
}
|
||||||
} else { // internal outbound
|
} else {
|
||||||
|
// internal outbound
|
||||||
|
|
||||||
currentOutbound = when (bean) {
|
currentOutbound = when (bean) {
|
||||||
is ConfigBean ->
|
is ConfigBean -> SingBoxOption().apply {
|
||||||
gson.fromJson(bean.config, currentOutbound.javaClass)
|
_hack_custom_config = bean.config
|
||||||
|
}
|
||||||
|
|
||||||
is ShadowTLSBean -> // before StandardV2RayBean
|
is ShadowTLSBean -> // before StandardV2RayBean
|
||||||
buildSingBoxOutboundShadowTLSBean(bean).asMap()
|
buildSingBoxOutboundShadowTLSBean(bean)
|
||||||
|
|
||||||
is StandardV2RayBean -> // http/trojan/vmess/vless
|
is StandardV2RayBean -> // http/trojan/vmess/vless
|
||||||
buildSingBoxOutboundStandardV2RayBean(bean).asMap()
|
buildSingBoxOutboundStandardV2RayBean(bean)
|
||||||
|
|
||||||
is HysteriaBean ->
|
is HysteriaBean ->
|
||||||
buildSingBoxOutboundHysteriaBean(bean)
|
buildSingBoxOutboundHysteriaBean(bean)
|
||||||
|
|
||||||
is TuicBean ->
|
is TuicBean ->
|
||||||
buildSingBoxOutboundTuicBean(bean).asMap()
|
buildSingBoxOutboundTuicBean(bean)
|
||||||
|
|
||||||
is SOCKSBean ->
|
is SOCKSBean ->
|
||||||
buildSingBoxOutboundSocksBean(bean).asMap()
|
buildSingBoxOutboundSocksBean(bean)
|
||||||
|
|
||||||
is ShadowsocksBean ->
|
is ShadowsocksBean ->
|
||||||
buildSingBoxOutboundShadowsocksBean(bean).asMap()
|
buildSingBoxOutboundShadowsocksBean(bean)
|
||||||
|
|
||||||
is WireGuardBean ->
|
is WireGuardBean ->
|
||||||
buildSingBoxOutboundWireguardBean(bean).asMap()
|
buildSingBoxOutboundWireguardBean(bean)
|
||||||
|
|
||||||
is SSHBean ->
|
is SSHBean ->
|
||||||
buildSingBoxOutboundSSHBean(bean).asMap()
|
buildSingBoxOutboundSSHBean(bean)
|
||||||
|
|
||||||
is AnyTLSBean ->
|
is AnyTLSBean ->
|
||||||
buildSingBoxOutboundAnyTLSBean(bean).asMap()
|
buildSingBoxOutboundAnyTLSBean(bean)
|
||||||
|
|
||||||
else -> throw IllegalStateException("can't reach")
|
else -> throw IllegalStateException("can't reach")
|
||||||
}
|
} as SingBoxOption
|
||||||
|
|
||||||
currentOutbound.apply {
|
// internal mux
|
||||||
// TODO nb4a keepAliveInterval?
|
if (!muxApplied) {
|
||||||
// val keepAliveInterval = DataStore.tcpKeepAliveInterval
|
val muxObj = proxyEntity.singMux()
|
||||||
// val needKeepAliveInterval = keepAliveInterval !in intArrayOf(0, 15)
|
if (muxObj != null && muxObj.enabled) {
|
||||||
|
muxApplied = true
|
||||||
if (!muxApplied) {
|
currentOutbound._hack_config_map["multiplex"] = muxObj.asMap()
|
||||||
val muxObj = proxyEntity.singMux()
|
|
||||||
if (muxObj != null && muxObj.enabled) {
|
|
||||||
muxApplied = true
|
|
||||||
currentOutbound["multiplex"] = muxObj.asMap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -388,8 +384,8 @@ fun buildConfig(
|
|||||||
// udp over tcp
|
// udp over tcp
|
||||||
try {
|
try {
|
||||||
val sUoT = bean.javaClass.getField("sUoT").get(bean)
|
val sUoT = bean.javaClass.getField("sUoT").get(bean)
|
||||||
if (sUoT is Boolean && sUoT == true) {
|
if (sUoT is Boolean && sUoT) {
|
||||||
currentOutbound["udp_over_tcp"] = true
|
_hack_config_map["udp_over_tcp"] = true
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
@ -401,19 +397,13 @@ fun buildConfig(
|
|||||||
domainListDNSDirectForce.add("full:$serverAddress")
|
domainListDNSDirectForce.add("full:$serverAddress")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentOutbound["domain_strategy"] =
|
_hack_config_map["domain_strategy"] =
|
||||||
if (forTest) "" else defaultServerDomainStrategy
|
if (forTest) "" else defaultServerDomainStrategy
|
||||||
|
|
||||||
// custom JSON merge
|
_hack_config_map["tag"] = tagOut
|
||||||
if (bean.customOutboundJson.isNotBlank()) {
|
|
||||||
Util.mergeJSON(
|
|
||||||
bean.customOutboundJson,
|
|
||||||
currentOutbound as MutableMap<String, Any?>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentOutbound["tag"] = tagOut
|
_hack_custom_config = bean.customOutboundJson
|
||||||
|
}
|
||||||
|
|
||||||
// External proxy need a dokodemo-door inbound to forward the traffic
|
// External proxy need a dokodemo-door inbound to forward the traffic
|
||||||
// For external proxy software, their traffic must goes to v2ray-core to use protected fd.
|
// For external proxy software, their traffic must goes to v2ray-core to use protected fd.
|
||||||
@ -472,8 +462,8 @@ fun buildConfig(
|
|||||||
|
|
||||||
// build outbounds
|
// build outbounds
|
||||||
if (buildSelector) {
|
if (buildSelector) {
|
||||||
val list = group?.id?.let { SagerDatabase.proxyDao.getByGroup(it) }
|
val list = group.id.let { SagerDatabase.proxyDao.getByGroup(it) }
|
||||||
list?.forEach {
|
list.forEach {
|
||||||
tagMap[it.id] = buildChain(it.id, it)
|
tagMap[it.id] = buildChain(it.id, it)
|
||||||
}
|
}
|
||||||
outbounds.add(0, Outbound_SelectorOptions().apply {
|
outbounds.add(0, Outbound_SelectorOptions().apply {
|
||||||
@ -481,7 +471,7 @@ fun buildConfig(
|
|||||||
tag = TAG_PROXY
|
tag = TAG_PROXY
|
||||||
default_ = tagMap[proxy.id]
|
default_ = tagMap[proxy.id]
|
||||||
outbounds = tagMap.values.toList()
|
outbounds = tagMap.values.toList()
|
||||||
}.asMap())
|
})
|
||||||
} else {
|
} else {
|
||||||
buildChain(0, proxy)
|
buildChain(0, proxy)
|
||||||
}
|
}
|
||||||
@ -591,6 +581,8 @@ fun buildConfig(
|
|||||||
-2L -> TAG_BLOCK
|
-2L -> TAG_BLOCK
|
||||||
else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId] ?: ""
|
else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId] ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_hack_custom_config = rule.config
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ruleObj.checkEmpty()) {
|
if (!ruleObj.checkEmpty()) {
|
||||||
@ -620,7 +612,7 @@ fun buildConfig(
|
|||||||
for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply {
|
for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply {
|
||||||
tag = freedom
|
tag = freedom
|
||||||
type = "direct"
|
type = "direct"
|
||||||
}.asMap())
|
})
|
||||||
|
|
||||||
// Bypass Lookup for the first profile
|
// Bypass Lookup for the first profile
|
||||||
bypassDNSBeans.forEach {
|
bypassDNSBeans.forEach {
|
||||||
@ -746,16 +738,16 @@ fun buildConfig(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_hack_custom_config = proxy.requireBean().customConfigJson
|
||||||
}.let {
|
}.let {
|
||||||
ConfigBuildResult(
|
ConfigBuildResult(
|
||||||
gson.toJson(it.asMap().apply {
|
gson.toJson(it.asMap()),
|
||||||
Util.mergeJSON(optionsToMerge, this)
|
|
||||||
}),
|
|
||||||
externalIndexMap,
|
externalIndexMap,
|
||||||
proxy.id,
|
proxy.id,
|
||||||
trafficMap,
|
trafficMap,
|
||||||
tagMap,
|
tagMap,
|
||||||
if (buildSelector) group!!.id else -1L
|
if (buildSelector) group.id else -1L
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -272,7 +272,7 @@ fun HysteriaBean.canUseSingBox(): Boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): MutableMap<String, Any> {
|
fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.SingBoxOption {
|
||||||
return when (bean.protocolVersion) {
|
return when (bean.protocolVersion) {
|
||||||
1 -> SingBoxOptions.Outbound_HysteriaOptions().apply {
|
1 -> SingBoxOptions.Outbound_HysteriaOptions().apply {
|
||||||
type = "hysteria"
|
type = "hysteria"
|
||||||
@ -311,7 +311,7 @@ fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): MutableMap<String, Any
|
|||||||
insecure = bean.allowInsecure || DataStore.globalAllowInsecure
|
insecure = bean.allowInsecure || DataStore.globalAllowInsecure
|
||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
}.asMap()
|
}
|
||||||
|
|
||||||
2 -> SingBoxOptions.Outbound_Hysteria2Options().apply {
|
2 -> SingBoxOptions.Outbound_Hysteria2Options().apply {
|
||||||
type = "hysteria2"
|
type = "hysteria2"
|
||||||
@ -350,9 +350,9 @@ fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): MutableMap<String, Any
|
|||||||
insecure = bean.allowInsecure || DataStore.globalAllowInsecure
|
insecure = bean.allowInsecure || DataStore.globalAllowInsecure
|
||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
}.asMap()
|
}
|
||||||
|
|
||||||
else -> mutableMapOf("error_version" to bean.protocolVersion)
|
else -> error("error_version $bean.protocolVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import io.nekohasekai.sagernet.widget.AppListPreference
|
|||||||
import io.nekohasekai.sagernet.widget.ListListener
|
import io.nekohasekai.sagernet.widget.ListListener
|
||||||
import io.nekohasekai.sagernet.widget.OutboundPreference
|
import io.nekohasekai.sagernet.widget.OutboundPreference
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import moe.matsuri.nb4a.ui.EditConfigPreference
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class RouteSettingsActivity(
|
class RouteSettingsActivity(
|
||||||
@ -57,6 +58,7 @@ class RouteSettingsActivity(
|
|||||||
|
|
||||||
fun RuleEntity.init() {
|
fun RuleEntity.init() {
|
||||||
DataStore.routeName = name
|
DataStore.routeName = name
|
||||||
|
DataStore.serverConfig = config
|
||||||
DataStore.routeDomain = domains
|
DataStore.routeDomain = domains
|
||||||
DataStore.routeIP = ip
|
DataStore.routeIP = ip
|
||||||
DataStore.routePort = port
|
DataStore.routePort = port
|
||||||
@ -76,6 +78,7 @@ class RouteSettingsActivity(
|
|||||||
|
|
||||||
fun RuleEntity.serialize() {
|
fun RuleEntity.serialize() {
|
||||||
name = DataStore.routeName
|
name = DataStore.routeName
|
||||||
|
config = DataStore.serverConfig
|
||||||
domains = DataStore.routeDomain
|
domains = DataStore.routeDomain
|
||||||
ip = DataStore.routeIP
|
ip = DataStore.routeIP
|
||||||
port = DataStore.routePort
|
port = DataStore.routePort
|
||||||
@ -96,12 +99,10 @@ class RouteSettingsActivity(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var editConfigPreference: EditConfigPreference
|
||||||
|
|
||||||
fun needSave(): Boolean {
|
fun needSave(): Boolean {
|
||||||
if (!DataStore.dirty) return false
|
return DataStore.dirty
|
||||||
if (DataStore.routePackages.isBlank() && DataStore.routeDomain.isBlank() && DataStore.routeIP.isBlank() && DataStore.routePort.isBlank() && DataStore.routeSourcePort.isBlank() && DataStore.routeNetwork.isBlank() && DataStore.routeSource.isBlank() && DataStore.routeProtocol.isBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PreferenceFragmentCompat.createPreferences(
|
fun PreferenceFragmentCompat.createPreferences(
|
||||||
@ -109,6 +110,16 @@ class RouteSettingsActivity(
|
|||||||
rootKey: String?,
|
rootKey: String?,
|
||||||
) {
|
) {
|
||||||
addPreferencesFromResource(R.xml.route_preferences)
|
addPreferencesFromResource(R.xml.route_preferences)
|
||||||
|
|
||||||
|
editConfigPreference = findPreference(Key.SERVER_CONFIG)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if (::editConfigPreference.isInitialized) {
|
||||||
|
editConfigPreference.notifyChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val selectProfileForAdd = registerForActivityResult(
|
val selectProfileForAdd = registerForActivityResult(
|
||||||
@ -163,7 +174,7 @@ class RouteSettingsActivity(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PreferenceFragmentCompat.displayPreferenceDialog(preference: Preference): Boolean {
|
fun displayPreferenceDialog(preference: Preference): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,74 @@
|
|||||||
package moe.matsuri.nb4a;
|
package moe.matsuri.nb4a;
|
||||||
|
|
||||||
import static moe.matsuri.nb4a.utils.JavaUtil.gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.google.gson.ToNumberPolicy;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import moe.matsuri.nb4a.utils.Util;
|
||||||
|
|
||||||
public class SingBoxOptions {
|
public class SingBoxOptions {
|
||||||
|
|
||||||
// base
|
// base
|
||||||
|
|
||||||
|
private static final Gson gsonSingbox = new GsonBuilder()
|
||||||
|
.registerTypeHierarchyAdapter(SingBoxOption.class, new SingBoxOptionSerializer())
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
|
||||||
|
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
|
||||||
|
.setLenient()
|
||||||
|
.disableHtmlEscaping()
|
||||||
|
.create();
|
||||||
|
|
||||||
public static class SingBoxOption {
|
public static class SingBoxOption {
|
||||||
|
|
||||||
|
public transient Map<String, Object> _hack_config_map; // 仍然用普通json方式合并,所以Object内不要使用 _hack
|
||||||
|
|
||||||
|
public transient String _hack_custom_config;
|
||||||
|
|
||||||
|
public SingBoxOption() {
|
||||||
|
_hack_config_map = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Object> asMap() {
|
public Map<String, Object> asMap() {
|
||||||
return gson.fromJson(gson.toJson(this), Map.class);
|
return gsonSingbox.fromJson(gsonSingbox.toJson(this), Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义序列化器
|
||||||
|
public static class SingBoxOptionSerializer implements JsonSerializer<SingBoxOption> {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(SingBoxOption src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
// 拿到原始的 delegate(默认序列化器)
|
||||||
|
TypeAdapter<?> delegate = gsonSingbox.getDelegateAdapter(
|
||||||
|
new TypeAdapterFactory() {
|
||||||
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
|
return null; // 返回 null,表示只作为“跳过当前自定义”的 marker
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TypeToken.get(src.getClass())
|
||||||
|
);
|
||||||
|
Map<String, Object> map = gsonSingbox.fromJson(((TypeAdapter<SingBoxOption>) delegate).toJson(src), Map.class);
|
||||||
|
if (src._hack_config_map != null && !src._hack_config_map.isEmpty()) {
|
||||||
|
Util.INSTANCE.mergeMap(map, src._hack_config_map);
|
||||||
|
}
|
||||||
|
if (src._hack_custom_config != null && !src._hack_custom_config.isBlank()) {
|
||||||
|
Util.INSTANCE.mergeJSON(map, src._hack_custom_config);
|
||||||
|
}
|
||||||
|
return gsonSingbox.toJsonTree(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +88,7 @@ public class SingBoxOptions {
|
|||||||
|
|
||||||
public List<Inbound> inbounds;
|
public List<Inbound> inbounds;
|
||||||
|
|
||||||
public List<Map<String, Object>> outbounds;
|
public List<SingBoxOption> outbounds;
|
||||||
|
|
||||||
public RouteOptions route;
|
public RouteOptions route;
|
||||||
|
|
||||||
|
|||||||
@ -152,7 +152,7 @@ object Util {
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mergeJSON(j: String, dst: MutableMap<String, Any?>) {
|
fun mergeJSON(dst: MutableMap<String, Any?>, j: String) {
|
||||||
if (j.isBlank()) return
|
if (j.isBlank()) return
|
||||||
val src = JavaUtil.gson.fromJson(j, dst.javaClass)
|
val src = JavaUtil.gson.fromJson(j, dst.javaClass)
|
||||||
mergeMap(dst, src)
|
mergeMap(dst, src)
|
||||||
|
|||||||
@ -6,11 +6,16 @@
|
|||||||
app:key="routeName"
|
app:key="routeName"
|
||||||
app:title="@string/route_name"
|
app:title="@string/route_name"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<io.nekohasekai.sagernet.widget.AppListPreference
|
<moe.matsuri.nb4a.ui.EditConfigPreference
|
||||||
app:icon="@drawable/ic_baseline_legend_toggle_24"
|
app:icon="@drawable/ic_baseline_layers_24"
|
||||||
app:key="routePackages"
|
app:key="serverConfig"
|
||||||
app:title="@string/apps" />
|
app:title="@string/custom_config"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
<PreferenceCategory app:title="@string/cag_route">
|
<PreferenceCategory app:title="@string/cag_route">
|
||||||
|
<io.nekohasekai.sagernet.widget.AppListPreference
|
||||||
|
app:icon="@drawable/ic_baseline_legend_toggle_24"
|
||||||
|
app:key="routePackages"
|
||||||
|
app:title="@string/apps" />
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:icon="@drawable/ic_baseline_domain_24"
|
app:icon="@drawable/ic_baseline_domain_24"
|
||||||
app:key="routeDomain"
|
app:key="routeDomain"
|
||||||
@ -19,19 +24,24 @@
|
|||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:icon="@drawable/ic_baseline_add_road_24"
|
app:icon="@drawable/ic_baseline_add_road_24"
|
||||||
app:key="routeIP"
|
app:key="routeIP"
|
||||||
app:title="ip"
|
app:title="dst ip"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:icon="@drawable/ic_maps_directions_boat"
|
app:icon="@drawable/ic_maps_directions_boat"
|
||||||
app:key="routePort"
|
app:key="routePort"
|
||||||
app:title="port"
|
app:title="dst port"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<EditTextPreference
|
||||||
|
app:icon="@drawable/ic_baseline_local_bar_24"
|
||||||
|
app:key="routeSource"
|
||||||
|
app:title="src ip"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:icon="@drawable/ic_baseline_home_24"
|
app:icon="@drawable/ic_baseline_home_24"
|
||||||
app:key="routeSourcePort"
|
app:key="routeSourcePort"
|
||||||
app:title="sourcePort"
|
app:title="src port"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<moe.matsuri.nb4a.ui.SimpleMenuPreference
|
<moe.matsuri.nb4a.ui.SimpleMenuPreference
|
||||||
app:entries="@array/route_protocol_entry"
|
app:entries="@array/route_protocol_entry"
|
||||||
@ -40,11 +50,6 @@
|
|||||||
app:key="routeNetwork"
|
app:key="routeNetwork"
|
||||||
app:title="network"
|
app:title="network"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<EditTextPreference
|
|
||||||
app:icon="@drawable/ic_baseline_local_bar_24"
|
|
||||||
app:key="routeSource"
|
|
||||||
app:title="source"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:icon="@drawable/ic_baseline_layers_24"
|
app:icon="@drawable/ic_baseline_layers_24"
|
||||||
app:key="routeProtocol"
|
app:key="routeProtocol"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user