添加自动切换节点

This commit is contained in:
cyh 2025-07-03 16:30:34 +08:00
parent 7c964af900
commit 38f5963797
21 changed files with 842 additions and 365 deletions

View File

@ -29,6 +29,7 @@ import com.yingmao.clash.R
import com.yingmao.clash.base.BaseActivity
import com.cncat.vpn.common.constants.Intents
import com.cncat.vpn.common.log.Log
import com.cncat.vpn.service.ProfileManager
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.ClashNodebBean
@ -38,8 +39,11 @@ import com.yingmao.clash.remote.Resource
import com.cncat.vpn.service.model.Profile
import com.cncat.vpn.service.remote.IProfileManager
import com.cncat.vpn.service.remote.IRemoteService
import com.cncat.vpn.service.remote.wrap
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.fragment.home.HomeFragment
import com.yingmao.clash.util.CommonUtils
import com.yingmao.clash.util.Utils
import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.adapter.NodeListAdapter
@ -100,6 +104,8 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
private fun initData() {
// clash = MainApplication.getApp()?.let { ClashManager(it) }
// profileBinder =
// MainApplication.getApp()?.let { ProfileManager(it).wrap() as IProfileManager }
cacheNodeStr = MMKV.defaultMMKV().decodeString(BZYConstants.NODE_All_ITEM_NAME_KEY, "");
val intentFilter = IntentFilter(Intents.ACTION_NODE_LIST)
@ -233,14 +239,21 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
var nowSubUrl: String = ""
private fun updateProfile(isDelete: Boolean) {
// showLoading("正在获取...", this@ChangeNodeActivity)
try {
launch(Dispatchers.IO) {
UIMainActivity.profileBinder?.let { profileManager ->
var mProfile = profileManager.queryAll().find {
it.name == Conf.getClashConfName(this@ChangeNodeActivity.applicationContext)
// var mProfile = profileManager.queryAll().find {
// it.name == Conf.getClashConfName(this@ChangeNodeActivity.applicationContext)
// }
var mProfile: Profile? = null
Log.d("queryAll().size ==>${profileManager.queryAll().size}")
profileManager.queryAll().forEach { it_pf ->
var confName =
Conf.getClashConfName(this@ChangeNodeActivity.applicationContext)
Log.d(" it_pf.name ==>${it_pf.name}, confName==>${confName}")
if (confName == it_pf.name) {
mProfile = it_pf
}
}
if (mProfile == null) {
@ -256,16 +269,16 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
Log.d("ChangeNode22 uuid==>${profile.uuid}")
if (isDelete) {
Log.d("gogogogogo isDelete")
profileManager.delete(profile.uuid)
// profileManager.delete(profile.uuid)
MMKV.defaultMMKV()
.removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
setSub2(profileManager, nowSubUrl)
} else {
Log.d("gogogogogo update")
profileManager.setActive(profile)
delay(500)
Log.d("updateProfile pro.source==>${profile.source}, url==>${nowSubUrl}")
if (!TextUtils.isEmpty(profile.source)) {
// Log.d("updateProfile pro.source==>${profile.source}, url==>${nowSubUrl}")
if (profile.source != nowSubUrl) {
Log.d("updateProfile patch")
profileManager.patch(
@ -280,6 +293,17 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
}
Log.d("updateProfile patch 2")
}
} else {
profileManager.patch(
profile.uuid,
profile.name,
nowSubUrl,
1800000L
)
Log.d("updateProfile patch new 1")
profileManager.commit(profile.uuid) { status ->
Log.d("updateProfile new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress)
}
}
Log.d("updateProfile go update")
profileManager.update(profile.uuid)
@ -318,29 +342,35 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
UIMainActivity.subUrl?.let {
nowSubUrl = it
}
doProfile()
doProfile(false)
}
fun doProfile() {
fun doProfile(isDelete: Boolean) {
Log.d("doProfile....")
if (TextUtils.isEmpty(cacheNodeStr)) {
}
showLoading("正在获取...", this@ChangeNodeActivity)
startTimer()
updateProfile(false)
// updateProfile(isDelete)
launch(Dispatchers.IO) {
UIMainActivity.setSub2(nowSubUrl)
}
}
private fun changeSubUrl(originalUrl: String, newUrl: String) {
if (!originalUrl.isNullOrEmpty() && !newUrl.isNullOrEmpty()) {
nowSubUrl = originalUrl.replace(Regex("https://[^/]+/"), "$newUrl/")
UIMainActivity.subUrl = nowSubUrl
MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl)
}
}
private fun updateTestBut(str: String) {
nodeItemTest.text=str
nodeItemTest.text = str
}
val reloadLock = Semaphore(10)
@ -393,7 +423,6 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
}
}
private fun setNewData(nodes: MutableList<ClashNodebBean>?) {
runOnUiThread {
if (nodes != null) {
@ -449,13 +478,25 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
}
private fun createProfile() {
cleanProfile()
launch(Dispatchers.IO) {
UIMainActivity.profileBinder?.let { pro ->
uuid?.let { uu ->
pro.delete(uu)
delay(500)
setSub2(pro, UIMainActivity.subUrl)
createMyProfile(pro, nowSubUrl)
}
}
}
private fun cleanProfile() {
launch(Dispatchers.IO) {
UIMainActivity.profileBinder?.let { pm ->
pm.queryAll().forEach { profile ->
Log.d("changeNode cleanProfile go delete name==>${profile.name}, uuid==>${profile.uuid}")
pm.delete(profile.uuid)
}
} ?: let {
Log.d("changeNode profileBinder is null")
}
}
}
@ -466,10 +507,6 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
com.cncat.vpn.common.log.Log.d("setSub2 url isEmpty")
return
}
com.cncat.vpn.common.log.Log.d("setSub2 2")
com.cncat.vpn.common.log.Log.d("setSub2 3")
val pro = pm.queryAll().find {
it.name == Conf.getClashConfName(this@ChangeNodeActivity.applicationContext)
@ -502,11 +539,40 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
// }
}
}
}
suspend fun createMyProfile(pm: IProfileManager, url: String) {
var uuid: UUID = pm.create(
Profile.Type.Url, Conf.getClashConfName(this@ChangeNodeActivity.applicationContext)
)
delay(500)
pm.queryByUUID(uuid)?.let { qPro ->
Log.d("createMyProfile url==>${url}")
pm.setActive(qPro)
pm.patch(
qPro.uuid, qPro.name,
// "https://mojie0201.xn--8stx8olrwkucjq3b.com/api/v1/client/subscribe?token=169110d2674c49604b7e5b5cea341579",
// "https://www.otcopusapp.cc/lx3af288h5i8pz380/api/v1/client/subscribe?token=d6bc98c7ea53099fa5bd1a3ea415ebb6",
// "https://reg.bazhuayujiasu.cc/api/v1/client/subscribe?token=7f0f025ab341bcec4822d2c90aadf8b0",
url, 1800000L
)
// coroutineScope {
pm.commit(qPro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
// UIMainActivity.profileBinder = pm
// }
}
}
var isCreate: Boolean = false
private var timerJob: Job? = null
private fun startTimer() {
@ -514,15 +580,42 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
timerJob = launch {
repeat(16) {
Log.d("GetNodeTimeOutNum==>$GetNodeTimeOutNum")
if (GetNodeTimeOutNum == 0) {
hideLoading()
Log.d("stopTimer...")
RxToast.error("获取节点失败...请尝试切换其他地址")
showDomainList()
RxToast.error("获取节点失败...正在切换其他地址")
// showDomainList()
// var mmkv = MMKV.defaultMMKV()
// var currentHost = mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "")
if (setNewData()) {
timerJob?.cancel()
// cleanProfile()
MMKV.defaultMMKV()
.removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
// createProfile()
saveSubUrl()
MainApplication.restartApp(this@ChangeNodeActivity, object :
MainApplication.OnRestartButtonListener {
override fun enterButton() {
}
override fun cancelButton() {
doProfile(false)
}
})
// doProfile(false)
// isCreate=true
} else {
timerJob?.cancel()
RxToast.error("获取全部线路失败...请切换网络重试")
}
// delay(1000)
// this@ChangeNodeActivity.finish()
}
GetNodeTimeOutNum--
delay(1000)
@ -545,7 +638,7 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
DialogDoMainChoose.OnTryButtonListener {
override fun tryButton() {
saveSubUrl()
doProfile()
doProfile(false)
dialogDoMainChoose?.dismiss()
}
@ -567,7 +660,7 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
}
override fun cancelButton() {
doProfile()
doProfile(false)
dialogDoMainChoose?.dismiss()
}
@ -585,10 +678,90 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener {
}
}
private fun setNewData(): Boolean {
var mmkv = MMKV.defaultMMKV()
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
var newUrlString = originalUrl
var isChange = false
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
var switchFinshSet = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_Switch_Finsh_Set, HashSet())
routerHost?.let {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// if (!isNetworkAvailable(this@BaseActivity)){
// return isChange
// }
// }
if (it.isEmpty()) {
android.util.Log.d(Conf.TAG, "routerHost isEmpty")
return isChange
}
com.cncat.vpn.common.log.Log.d("switchFinshSet.size==>${switchFinshSet?.size!!} , routerHost.size==>${routerHost.size}")
//次数已满
if (switchFinshSet?.size!! >= routerHost.size) {
return isChange
}
android.util.Log.d(Conf.TAG, "routerHost not empty")
for (url in it) {
// com.cncat.vpn.common.log.Log.d("url==>$url")
if (url == originalUrl) {
switchFinshSet?.add(url)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinshSet)
continue
}
if (switchFinshSet?.contains(url) == true) {
continue
} else {
isChange = true
android.util.Log.e("Retry", "切换到新url:$url")
newUrlString = url
switchFinshSet?.add(newUrlString)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinshSet)
// mmkv.encode(BZYConstants.MMKV_KEY_OLD_CURRENT_HOST, newUrlString)
break
}
}
// val iterator = it.iterator()
// while (iterator.hasNext()) {
// val newUrl = iterator.next()
// android.util.Log.e(
// "Data",
// "遍历备用的url:$newUrl, contains==>${switchFinshSet?.contains(newUrl)}"
// )
// if (newUrl == originalUrl) {
// switchFinshSet?.add(newUrl)
// iterator.remove()
// continue
// }
// if (switchFinshSet?.contains(newUrl) == true) {
// iterator.remove()
// } else {
// android.util.Log.e("Retry", "切换到新url:$newUrl")
// newUrlString = newUrl
// iterator.remove()
// if (originalUrl != null) {
// switchFinshSet?.add(originalUrl)
// mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinshSet)
// mmkv.encode(BZYConstants.MMKV_KEY_OLD_CURRENT_HOST, originalUrl)
// }
// break
// }
// }
// mmkv.encode(BZYConstants.MMKV_KEY_ROUTER_HOST, routerHost)
mmkv.encode(BZYConstants.MMKV_KEY_CURRENT_HOST, newUrlString)
}
return isChange
}
private fun saveSubUrl() {
var currentHost = MMKV.defaultMMKV()
.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "")
// com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}")
com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}")
var oldUrl = nowSubUrl
currentHost?.let { changeSubUrl(oldUrl, it) }
}

View File

@ -362,7 +362,19 @@ class MemberRechargeActivity : BaseActivity() {
override fun httpFailure(httpStatus: HttpStatus.Failure) {
when (httpStatus.reqType) {
HttpReqType.GET_RECHARGE_INFO -> {
showDomainList()
// showDomainList()
if (super.switchServer2()) {
val username =
MMKV.defaultMMKV()
.decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
val phToken =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")!!
val userId = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_USER_ID)!!
viewModel.getPhRechargeInfo(username, phToken, userId)
} else {
// super.switchFinshSet?.clear()
}
Logger.d("获取充值续费信息异常${httpStatus.msg}")
swipeLayout.isRefreshing = false
RxToast.error(resources.getString(R.string.DataError))

View File

@ -128,8 +128,8 @@ class NewMainActivity : BaseActivity() {
// private var profile: ProfileManager? = null
// private var clashManager: ClashManager? = null
var clashBinder: IClashManager? = null
var profileBinder: IProfileManager? = null
private var clashBinder: IClashManager? = null
private var profileBinder: IProfileManager? = null
var newMainActGlo: Boolean = false
var isUpload: Boolean = false
var hostsMem: ArrayList<String> = ArrayList()
@ -1187,6 +1187,7 @@ class NewMainActivity : BaseActivity() {
suspend fun createMyProfile(pm: IProfileManager, url: String) {
Log.d("createMyProfile")
var uuid: UUID = pm.create(
Profile.Type.Url, Conf.getClashConfName(this@NewMainActivity.applicationContext)
)

View File

@ -110,7 +110,7 @@ class SettingActivity : BaseActivity() {
showLoading(resources.getString(R.string.UnderRepair), this@SettingActivity)
launch(Dispatchers.IO) {
NewMainActivity.profileBinder?.let { pm ->
UIMainActivity.profileBinder?.let { pm ->
pm.queryAll().forEach { profile ->
Log.d("setting profile delete ==>${profile.name}")
pm.delete(profile.uuid)

View File

@ -21,9 +21,9 @@ class SplashHelper(private val splashAtyVM: PublicViewMode) {
third: (AsyncCallback) -> Unit,
callback: (Boolean) -> Unit
) {
first(createCallback("getHost method success", "getHostByGithub method failed", callback) {
first(createCallback("getHost method success", "getHost method failed", callback) {
second(createCallback("getHostByAWS method success", "getHostByAWS method failed", callback) {
third(createCallback("getHost method success", "getHost methods failed", callback) {
third(createCallback("getHostByGithub method success", "getHostByGithub methods failed", callback) {
callback(false) // 所有方法都失败
})
})

View File

@ -239,6 +239,11 @@ class SplashActivity : BaseActivity() {
com.cncat.vpn.common.log.Log.d("si==>${si}, ss1==>${ss1}, ss2==>${ss2}")
MainApplication.KEY2 = ss1
MainApplication.IV2 = ss2
// val byteArrayToHexStr =
// AESUtil.byteArrayToHexStr(AESUtil.encrypt("https://api.kuaifei.net+https://api.wfvpnpro.com+https://wf.jimao.xyz+https://api.msljsqpro.com+https://api.mashanglianjsq.com"))
// android.util.Log.i("AAAAAAAAAAAAAAAA","data::"+byteArrayToHexStr+"::")
// 太子
// com.cncat.vpn.common.log.Log.d("tz encrypt:"+ AESUtil.byteArrayToHexStr(AESUtil.encrypt("")))
// 八爪鱼 com.cncat.vpn.common.log.Log.d("bzy encrypt:"+ AESUtil.byteArrayToHexStr(AESUtil.encrypt("")))
@ -817,6 +822,7 @@ class SplashActivity : BaseActivity() {
val expired = checkLoginRsp.data?.expired
val isPerfect = checkLoginRsp.data?.isPerfect
val userCommand = checkLoginRsp.data?.userCommand
val logSwitch = checkLoginRsp.data?.logSwitch
val mmkv = MMKV.defaultMMKV()
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
mmkv.encode(
@ -825,11 +831,8 @@ class SplashActivity : BaseActivity() {
)
mmkv.encode(BZYConstants.MMKV_KEY_IS_PERFECT, isPerfect ?: true)
mmkv.encode(BZYConstants.MMKV_KEY_VPN_UserCommand, userCommand ?: "")
mmkv.encode(BZYConstants.MMKV_KEY_VPN_LogSwitch, logSwitch ?: "")
toMainPage()
} else {
toLoginPage()
}
@ -903,56 +906,56 @@ class SplashActivity : BaseActivity() {
rlPb.visibility = View.GONE
pb_web?.visibility = View.INVISIBLE
com.cncat.vpn.common.log.Log.d("httpFailure HttpReqType.REGISTER")
try {
RxToast.error("检测到网络异常,请尝试切换其他地址.")
if (dialogDoMainChoose == null) {
dialogDoMainChoose = DialogDoMainChoose(this@SplashActivity)
}
if (dialogDoMainChoose?.isShowing == false) {
dialogDoMainChoose?.setOnTryButtonListener(object :
DialogDoMainChoose.OnTryButtonListener {
override fun tryButton() {
var currentHost = MMKV.defaultMMKV()
.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "")
// com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}")
doRegisterByEncryption()
}
override fun cancel() {
com.cncat.vpn.common.log.Log.d("tryButton REGISTER cancel")
dialogDoMainChoose?.dismiss()
dialogDoMainChoose = null
}
})
dialogDoMainChoose?.setOnListItemListener(object :
DialogDoMainChoose.OnListItemListener {
override fun listItemClick() {
doRegisterByEncryption()
dialogDoMainChoose?.dismiss()
}
})
dialogDoMainChoose?.show()
}
} catch (e: Exception) {
e.printStackTrace()
}
// if (super.switchServer()) {//切换服务器并直接重试
//// doRegister()
// doRegisterByEncryption()
// } else {
// MaterialDialog(this@SplashActivity).show {
// message(text = initErrMsg)
// positiveButton(R.string.retry) {
//// doRegister()
// try {
// RxToast.error("检测到网络异常,请尝试切换其他地址.")
// if (dialogDoMainChoose == null) {
// dialogDoMainChoose = DialogDoMainChoose(this@SplashActivity)
// }
// if (dialogDoMainChoose?.isShowing == false) {
// dialogDoMainChoose?.setOnTryButtonListener(object :
// DialogDoMainChoose.OnTryButtonListener {
// override fun tryButton() {
// var currentHost = MMKV.defaultMMKV()
// .decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "")
//// com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}")
// doRegisterByEncryption()
// }
// negativeButton(R.string.cancel) {
// it.dismiss()
// finish()
//
// override fun cancel() {
// com.cncat.vpn.common.log.Log.d("tryButton REGISTER cancel")
// dialogDoMainChoose?.dismiss()
// dialogDoMainChoose = null
// }
// })
// dialogDoMainChoose?.setOnListItemListener(object :
// DialogDoMainChoose.OnListItemListener {
// override fun listItemClick() {
// doRegisterByEncryption()
// dialogDoMainChoose?.dismiss()
// }
//
// })
// dialogDoMainChoose?.show()
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
if (super.switchServer2()) {//切换服务器并直接重试
// doRegister()
doRegisterByEncryption()
} else {
MaterialDialog(this@SplashActivity).show {
message(text = initErrMsg)
positiveButton(R.string.retry) {
// doRegister()
doRegisterByEncryption()
}
negativeButton(R.string.cancel) {
it.dismiss()
finish()
}
}
}
}
HttpReqType.LOGIN -> {

View File

@ -92,8 +92,9 @@ class UIMainActivity : BaseActivity() {
private var timer: Timer? = null
private var uTimerTask: TimerTask? = null
private var newMainActGlo: Boolean = false
private var homeFragment:HomeFragment?=null
private var settingFragment:SettingFragment?=null
private var homeFragment: HomeFragment? = null
private var settingFragment: SettingFragment? = null
@SuppressLint("SimpleDateFormat")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
private var activityDialog: ActivityViewDialog? = null
@ -113,6 +114,69 @@ class UIMainActivity : BaseActivity() {
var hostsMem: ArrayList<String> = ArrayList()
var isSubed = false
var newMainActGlo: Boolean = false
public suspend fun setSub2(url: String) {
profileBinder?.let { pm ->
pm.queryAll().forEach { itemPro ->
Log.d("uimain setSub2 itemPro.name==${itemPro.name}")
if (itemPro.name != Conf.getClashConfName(MainApplication.getApp()?.applicationContext)) {
Log.d("setSub2 delete ==>${itemPro.name}")
pm.delete(itemPro.uuid)
}
}
var mProfile = pm.queryAll().find {
it.name == Conf.getClashConfName(MainApplication.getApp()?.applicationContext)
}
mProfile?.let {
var actname = it.name
Log.d("new main actname==>" + actname)
Log.d("setSub2 pro 已存在 ==>")
pm.setActive(it)
delay(500)
//判断配置文件source
if (!TextUtils.isEmpty(mProfile.source)) {
Log.d("setSub2 pro.source==>${mProfile.source}, url==>${url}")
//订阅链接不同就更换
if (mProfile.source != url) {
pm.patch(
mProfile.uuid, mProfile.name,
url, 1800000L
)
pm.commit(mProfile.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
}
}
pm.update(it.uuid)
newUuid = mProfile.uuid.toString()
isSubbed = true
} ?: run {
Log.d("setSub2 go create")
val uuid: UUID = pm.create(
Profile.Type.Url, Conf.getClashConfName(MainApplication.getApp()?.applicationContext)
)
delay(500)
pm.queryByUUID(uuid)?.let { qPro ->
pm.setActive(qPro)
pm.patch(
qPro.uuid, qPro.name,
url, 1800000L
)
pm.commit(qPro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
newUuid = qPro.uuid.toString()
isSubbed = true
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -148,6 +212,7 @@ class UIMainActivity : BaseActivity() {
if (homeFragment == null) homeFragment = HomeFragment()
showFragment(homeFragment!!)
}
R.id.navigation_setting -> {
if (settingFragment == null) settingFragment = SettingFragment()
showFragment(settingFragment!!)
@ -163,6 +228,7 @@ class UIMainActivity : BaseActivity() {
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()
}
private fun showFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
@ -179,6 +245,7 @@ class UIMainActivity : BaseActivity() {
}
transaction.commit()
}
override fun httpLoading(httpStatus: HttpStatus.Loading) {
if (httpStatus.reqType == HttpReqType.HEARTBEAT) {
Logger.d("心跳中")
@ -196,25 +263,26 @@ class UIMainActivity : BaseActivity() {
}
data.data.let { url ->
android.util.Log.e("Data", "机场URL=${url}")
var url2="https://api.ymvpnpro11.cc:8700/7air2vr7ikiqzgvxz/api/v1/client/subscribe?token=22f3d75079ec6fa546c1862c4354e448"
// if (url.isNotEmpty()) {
// subUrl = url
// lifecycleScope.launch(Dispatchers.IO) {
// setSub2(url)
// }
// }
if (url.isNotEmpty()) {
if (url2.isNotEmpty()) {
//todo 判断订阅地址是否修改过,未修改就用接口返回的地址反之使用本地修改的地址
var pro_sub_url =
MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "")
// Log.d("pro_sub_url==>${pro_sub_url}")
if (!TextUtils.isEmpty(pro_sub_url)){
if (pro_sub_url == url) {
subUrl = url
if (!TextUtils.isEmpty(pro_sub_url)) {
if (pro_sub_url == url2) {
subUrl = url2
} else {
subUrl = pro_sub_url
}
}else{
subUrl = url
} else {
subUrl = url2
}
lifecycleScope.launch(Dispatchers.IO) {
// setSub(url)
@ -230,7 +298,7 @@ class UIMainActivity : BaseActivity() {
if (data.code == 200) {
if (data.data.data.list.isNotEmpty()) {
data.data.data.list[0].redirectLink.let {
if(activityDialog==null){
if (activityDialog == null) {
activityDialog =
ActivityViewDialog(this, data.data.data.list[0].redirectLink)
activityDialog?.setOnDialogClickListener(object :
@ -263,8 +331,10 @@ class UIMainActivity : BaseActivity() {
val expired = checkLoginRsp.data.expired
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime)
val hf = supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? HomeFragment
val sf = supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? SettingFragment
val hf =
supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? HomeFragment
val sf =
supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? SettingFragment
hf?.setExpireDate()
sf?.setExpireDate()
checkPayResultDia?.dismiss()
@ -343,6 +413,7 @@ class UIMainActivity : BaseActivity() {
}
}
}
private fun showLoadingDialog(msg: String) {
payResultLoading = RxDialogShapeLoading(this)
payResultLoading!!.setMessage(msg)
@ -356,7 +427,11 @@ class UIMainActivity : BaseActivity() {
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_SELECTED_NAME_KEY)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_VPN_UserCommand)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.PROFILE_SUB_URL)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_Switch_Finsh_Set)
Log.d("cleanUserInfo clashRunning==>${clashRunning}")
//清除源切换记录
if (clashRunning) {
fragments.filterIsInstance<HomeFragment>().forEach { it.stopClash() }
}
@ -401,61 +476,7 @@ class UIMainActivity : BaseActivity() {
}
}
private suspend fun setSub2(url: String) {
profileBinder?.let { pm ->
pm.queryAll().forEach { itemPro ->
if (itemPro.name != Conf.getClashConfName(this@UIMainActivity.applicationContext)) {
pm.delete(itemPro.uuid)
}
}
val pro = pm.queryActive()
pro?.let {
pm.setActive(it)
delay(500)
//判断配置文件source
if (!TextUtils.isEmpty(pro.source)) {
Log.d("setSub2 pro.source==>${pro.source}, url==>${url}")
//订阅链接不同就更换
if (pro.source != url) {
pm.patch(
pro.uuid, pro.name,
url, 1800000L
)
pm.commit(pro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
}
}
pm.update(it.uuid)
newUuid = pro.uuid.toString()
isSubbed = true
} ?: run {
val uuid: UUID = pm.create(
Profile.Type.Url, Conf.getClashConfName(this@UIMainActivity.applicationContext)
)
delay(500)
pm.queryByUUID(uuid)?.let { qPro ->
pm.setActive(qPro)
pm.patch(
qPro.uuid, qPro.name,
url, 1800000L
)
pm.commit(qPro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
newUuid = qPro.uuid.toString()
isSubbed = true
}
}
}
}
private fun startLog() {
@ -559,7 +580,7 @@ class UIMainActivity : BaseActivity() {
override fun onDestroy() {
if (activityDialog != null) {
if(activityDialog!!.isShowing){
if (activityDialog!!.isShowing) {
activityDialog!!.dismiss()
}

View File

@ -1,6 +1,5 @@
package com.yingmao.clash.app
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.NotificationChannel
@ -10,7 +9,6 @@ import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getString
import androidx.core.content.getSystemService
import com.cncat.vpn.common.Global
import com.cncat.vpn.common.compat.currentProcessName
@ -18,7 +16,6 @@ import com.cncat.vpn.common.log.Log
import com.github.gzuliyujiang.oaid.DeviceID
import com.github.gzuliyujiang.oaid.DeviceIdentifier
import com.github.gzuliyujiang.oaid.IGetter
import com.jakewharton.processphoenix.ProcessPhoenix
import com.orhanobut.logger.Logger
import com.stripe.android.PaymentConfiguration
@ -29,14 +26,16 @@ import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.entity.UserData
import com.yingmao.clash.util.CrashCatchHandler
import com.yingmao.clash.util.clashDir
import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.dialog.DialogDoMainChoose
import com.yingmao.clash.view.dialog.DialogSureRestartChoose
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@ -154,10 +153,27 @@ class MainApplication : Application() {
// if (privacyPolicyAgreed) {
// }
// Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
// logException(throwable)
// // 可选择上报到服务器或写入本地文件
// }
}
fun logException(e: Throwable) {
val log = StringWriter().also { e.printStackTrace(PrintWriter(it)) }.toString()
Log.d("MainApplication logException==>${log}")
// val directory: File? = applicationContext.getExternalFilesDir("CrashLog")
// if (directory != null) {
// if (!directory.isDirectory) {
// directory.delete()
// }
// if (!directory.exists()) {
// directory.mkdirs()
// }
// }
// File(directory, "crash_${System.currentTimeMillis()}.log").writeText(log)
}
fun updateActivity(language: String, locale: Locale) {
MMKV.defaultMMKV().encode(BZYConstants.languageInfo, language)
val resources = this.resources

View File

@ -6,6 +6,9 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
@ -13,7 +16,9 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.arraySetOf
import androidx.lifecycle.ViewModelProvider
import com.orhanobut.logger.Logger
import com.tencent.mmkv.MMKV
@ -55,6 +60,10 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), Br
var baseLoading: RxDialogShapeLoading? = null
val serStore by lazy { ServiceStore(this) }
companion object {
// var switchFinshSet: MutableSet<String>? = null
}
sealed class RequestTunnel {
object ReloadAll : RequestTunnel()
object ReLaunch : RequestTunnel()
@ -261,10 +270,11 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), Br
}
}
fun selectServer(){
fun selectServer() {
val mmkv = MMKV.defaultMMKV()
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
}
@Synchronized
fun switchServer(): Boolean {
var isChange = false
@ -303,4 +313,95 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), Br
return isChange
}
@RequiresApi(Build.VERSION_CODES.M)
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
@Synchronized
fun switchServer2(): Boolean {
var isChange = false
val mmkv = MMKV.defaultMMKV()
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
var newUrlString = originalUrl
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
var switchFinshSet = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_Switch_Finsh_Set, HashSet())
routerHost?.let {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// if (!isNetworkAvailable(this@BaseActivity)){
// return isChange
// }
// }
if (it.isEmpty()) {
Log.d(Conf.TAG, "routerHost isEmpty")
return isChange
}
com.cncat.vpn.common.log.Log.d("switchFinshSet.size==>${switchFinshSet?.size!!} , routerHost.size==>${routerHost.size}")
//次数已满
if (switchFinshSet?.size!! >= routerHost.size) {
return isChange
}
Log.d(Conf.TAG, "routerHost not empty")
// val iterator = it.iterator()
// while (iterator.hasNext()) {
// val newUrl = iterator.next()
// Log.e(
// "Data",
// "遍历备用的url:$newUrl, contains==>${switchFinshSet?.contains(newUrl)}"
// )
// if (newUrl == originalUrl) {
// switchFinshSet?.add(newUrl)
// iterator.remove()
// continue
// }
// if (switchFinshSet?.contains(newUrl) == true) {
// iterator.remove()
// } else {
// Log.e("Retry", "切换到新url:$newUrl")
// isChange = true
// newUrlString = newUrl
// iterator.remove()
// if (originalUrl != null) {
// switchFinshSet?.add(originalUrl)
// mmkv.encode(BZYConstants.MMKV_KEY_OLD_CURRENT_HOST, originalUrl)
// }
// break
// }
// }
for (url in it) {
if (url == originalUrl) {
switchFinshSet?.add(url)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinshSet)
continue
}
if (switchFinshSet?.contains(url) == true) {
continue
} else {
isChange = true
android.util.Log.e("Retry", "切换到新url:$url")
newUrlString = url
switchFinshSet?.add(newUrlString)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinshSet)
// mmkv.encode(BZYConstants.MMKV_KEY_OLD_CURRENT_HOST, newUrlString)
break
}
}
// mmkv.encode(BZYConstants.MMKV_KEY_ROUTER_HOST, routerHost)
mmkv.encode(BZYConstants.MMKV_KEY_CURRENT_HOST, newUrlString)
}
return isChange
}
}

View File

@ -1,20 +1,37 @@
package com.yingmao.clash.base
import android.os.Build
import android.text.TextUtils
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.cncat.vpn.common.log.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.net.http.HttpApi
import com.yingmao.clash.net.http.HttpReqType
import com.yingmao.clash.net.http.HttpStatus
import com.yingmao.clash.net.http.HttpStatusDownLoad
import com.yingmao.clash.net.http.NetService
import com.yingmao.clash.net.http.entity.BaseRsp
import com.yingmao.clash.util.AESUtil
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Locale
open class BaseViewMode : ViewModel() {
@ -24,9 +41,13 @@ open class BaseViewMode : ViewModel() {
fun doAsync(reqType: HttpReqType, doInAsync: suspend () -> Any) {
viewModelScope.launch(
CoroutineExceptionHandler { _,
exception ->
Log.e("BaseViewMode_doAsync:${exception.toString()}", exception)
CoroutineExceptionHandler {
_,
exception,
->
Log.e("BaseViewMode_doAsync:${exception.localizedMessage}", exception)
logException(exception)
statusData.value =
if (exception.message.isNullOrBlank()) HttpStatus.Failure(
reqType,
@ -46,16 +67,86 @@ open class BaseViewMode : ViewModel() {
Log.i("请求结果:$rsp")
statusData.value = HttpStatus.Success(reqType, rsp)
} catch (e: Exception) {
Log.e( "网络请求异常",e)
Log.e("网络请求异常", e)
statusData.value = HttpStatus.Failure(reqType, e.toString())
}
}
}
fun doAsync2(reqType: HttpReqType, doInAsync: suspend () -> Any) {
fun logException(e: Throwable) {
var logSwitch = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_VPN_LogSwitch, "")
if ("on" == logSwitch) {
val log = StringWriter().also { e.printStackTrace(PrintWriter(it)) }.toString()
Log.d("logException==>${log}")
val username =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
var json = JSONObject()
val androidVersion = Build.VERSION.RELEASE // 系统版本号,例如 "13"
val sdkInt = Build.VERSION.SDK_INT // SDK 级别,例如 33
val manufacturer = Build.MANUFACTURER // 厂商,例如 "Xiaomi"
val model = Build.MODEL // 机型,例如 "MI 11"
val brand = Build.BRAND // 品牌,例如 "Xiaomi"
val device = Build.DEVICE // 设备代号,例如 "venus"
val hardware = Build.HARDWARE // 硬件名称,例如 "qcom"
// val nowTime = SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒E", Locale.getDefault())
var ja_streams = JSONArray()
var jo_streams_item = JSONObject()
var jo_stream = JSONObject()
var ja_values = JSONArray()
jo_stream.put("phoneNumber", username)
jo_stream.put("appVersion", BuildConfig.VERSION_NAME)
jo_stream.put("androidVersion", androidVersion)
jo_stream.put("sdkInt", sdkInt)
jo_stream.put("manufacturer", manufacturer)
jo_stream.put("model", model)
jo_stream.put("brand", brand)
jo_stream.put("device", device)
jo_stream.put("hardware", hardware)
ja_values.put(0, System.currentTimeMillis().toString() + "000000")
//错误信息
ja_values.put(1, log)
jo_streams_item.put("stream", jo_stream)
var ja_values_item = JSONArray()
ja_values_item.put(0, ja_values)
jo_streams_item.put("values", ja_values_item)
ja_streams.put(0, jo_streams_item)
json.put("streams", ja_streams)
Log.d("json==>${json}")
val httpApi = NetService.instances.createHttps(HttpApi::class.java)
val requestBody: RequestBody = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
doAsync2(HttpReqType.APPSHAREINFO) {
httpApi.postErrLog(requestBody)
}
// val directory: File? = MainApplication.getApp()?.applicationContext?.getExternalFilesDir("CrashLog")
// if (directory != null) {
// if (!directory.isDirectory) {
// directory.delete()
// }
// if (!directory.exists()) {
// directory.mkdirs()
// }
// }
// File(directory, "crash_${System.currentTimeMillis()}.log").writeText(json.toString())
}
}
fun postErrLog(phone: String) {
val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN,"")!!
val params = mutableMapOf(
"phoneNumber" to phone,
"phToken" to phToken
)
}
fun doAsync2(reqType: HttpReqType, doInAsync: suspend () -> Any?) {
viewModelScope.launch(
CoroutineExceptionHandler { _,
exception ->
CoroutineExceptionHandler {
_,
exception,
->
Log.e("BaseViewMode_doAsync:${exception.toString()}", exception)
statusData.value =
if (exception.message.isNullOrBlank()) HttpStatus.Failure(
@ -76,7 +167,7 @@ open class BaseViewMode : ViewModel() {
Log.i("请求结果:$rsp")
statusData.value = HttpStatus.Success(reqType, rsp)
} catch (e: Exception) {
Log.e( "网络请求异常",e)
Log.e("网络请求异常", e)
statusData.value = HttpStatus.Failure(reqType, e.toString())
}
}
@ -114,11 +205,13 @@ open class BaseViewMode : ViewModel() {
fun doAsyncDelay(
reqType: HttpReqType,
delayTimeMillis: Long,
doInAsync: suspend () -> BaseRsp
doInAsync: suspend () -> BaseRsp,
) {
viewModelScope.launch(
CoroutineExceptionHandler { _,
exception ->
CoroutineExceptionHandler {
_,
exception,
->
Log.e("BaseViewMode_doAsyncDelay:${exception.toString()}", exception)
statusData.value =
if (exception.message.isNullOrBlank()) HttpStatus.Failure(
@ -141,7 +234,7 @@ open class BaseViewMode : ViewModel() {
Log.i("请求结果:$rsp")
statusData.value = HttpStatus.Success(reqType, rsp)
} catch (e: Exception) {
Log.e( "网络请求异常",e)
Log.e("网络请求异常", e)
// statusData.value = HttpStatus.Failure(reqType,e.toString())
}
}
@ -149,8 +242,10 @@ open class BaseViewMode : ViewModel() {
fun doAsyncStrResult(reqType: HttpReqType, doInAsync: suspend () -> String) {
viewModelScope.launch(
CoroutineExceptionHandler { _,
exception ->
CoroutineExceptionHandler {
_,
exception,
->
Log.e("BaseViewMode_doAsyncStrResult:${exception.toString()}", exception)
@ -162,7 +257,7 @@ open class BaseViewMode : ViewModel() {
val rsp = result.await()
Log.i("请求结果:$rsp")
} catch (e: Exception) {
Log.e( "网络请求异常",e)
Log.e("网络请求异常", e)
}
}
}
@ -170,11 +265,13 @@ open class BaseViewMode : ViewModel() {
fun doAsyncEncodeStr(
reqType: HttpReqType,
classOfT: Class<out BaseRsp>,
doInAsync: suspend () -> String
doInAsync: suspend () -> String,
) {
viewModelScope.launch(
CoroutineExceptionHandler() { _,
exception ->
CoroutineExceptionHandler() {
_,
exception,
->
Log.e("BaseViewMode_doAsyncEncodeStr:${exception.toString()}", exception)
statusData.value =
if (exception.message.isNullOrBlank()) HttpStatus.Failure(
@ -191,8 +288,6 @@ open class BaseViewMode : ViewModel() {
val result = async { doInAsync() }
val rspStr = result.await()
Log.i("doAsyncEncodeStr 加密请求结果:$rspStr")
if (!TextUtils.isEmpty(rspStr)) {
var decodeRspStr = AESUtil.decrypt(rspStr)
Log.d("doAsyncEncodeStr 请求结果:$decodeRspStr")
@ -204,20 +299,22 @@ open class BaseViewMode : ViewModel() {
statusData.value = HttpStatus.Success(reqType, rsp)
}
} catch (e: Exception) {
Log.e("网络请求异常",e)
Log.e("网络请求异常", e)
throw e
statusData.value = HttpStatus.Failure(reqType,e.toString())
statusData.value = HttpStatus.Failure(reqType, e.toString())
}
}
}
suspend fun doAsyncEncodeStrBg(
classOfT: Class<out BaseRsp>,
doInAsync: suspend () -> String
doInAsync: suspend () -> String,
): BaseRsp? {
return viewModelScope.async(
CoroutineExceptionHandler() { _,
exception ->
CoroutineExceptionHandler() {
_,
exception,
->
Log.e("BaseViewMode_doAsyncEncodeStrBg:${exception.toString()}", exception)
})
{
@ -230,7 +327,7 @@ open class BaseViewMode : ViewModel() {
val rsp = Gson().fromJson(decodeRspStr, classOfT)
return@async rsp
} catch (e: Exception) {
Log.e("网络请求异常",e)
Log.e("网络请求异常", e)
}
return@async null
}.await()
@ -245,7 +342,7 @@ open class BaseViewMode : ViewModel() {
}
result.await()
} catch (e: Exception) {
Log.e("网络请求异常",e)
Log.e("网络请求异常", e)
}
}
}

View File

@ -33,11 +33,11 @@ open class PublicViewMode : BaseViewMode() {
try { //TODO bug 会引起崩溃
val host = when {
BuildConfig.is_ym -> {
rt.getHostGithub()
rt.getHost()
}
BuildConfig.is_tz -> {
rt.getTzHostGithub()
rt.getTzHostTZ()
}
BuildConfig.is_yunti -> {
@ -45,7 +45,7 @@ open class PublicViewMode : BaseViewMode() {
}
else -> {
rt.getBzyHostGithub()
rt.getBzyHostDef()
}
}
// Log.e("data", "2:$host")

View File

@ -28,6 +28,7 @@ class BZYConstants {
const val MMKV_KEY_OLD_CURRENT_HOST = "old_current_host"
//备用地址
const val MMKV_KEY_ROUTER_HOST ="router_host"
const val MMKV_KEY_Switch_Finsh_Set ="MMKV_KEY_Switch_Finsh_Set"
const val MMKV_KEY_FIRST_HOST ="first_host"
const val MMKV_KEY_Airport_Base64 ="airport_base64"
//注册地址
@ -47,6 +48,7 @@ class BZYConstants {
const val MMKV_KEY_VPN_EXPIRED = "expired"
const val MMKV_KEY_VPN_UserCommand = "userCommand"
const val MMKV_KEY_VPN_LogSwitch = "logSwitch"
//vpn过期时间
const val MMKV_KEY_VPN_VALID_PERIOD = "vpn_valid_period"

View File

@ -79,6 +79,9 @@ data class UserData(
@field:SerializedName("userCommand")
val userCommand: String? = null,
@field:SerializedName("logSwitch")
val logSwitch: String? = null,
@field:SerializedName("intervalTime")
val intervalTime: Int? = null,

View File

@ -1,6 +1,7 @@
package com.yingmao.clash.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -9,9 +10,14 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.R
import com.yingmao.clash.base.BaseViewMode
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.net.http.HttpStatus
import java.util.HashSet
abstract class BaseFragment :Fragment() {
@ -58,4 +64,44 @@ abstract class BaseFragment :Fragment() {
open fun httpSuccess(httpStatus: HttpStatus.Success){}
open fun httpFailure(httpStatus: HttpStatus.Failure){}
@Synchronized
fun switchServer(): Boolean {
var isChange = false
val mmkv = MMKV.defaultMMKV()
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
var newUrlString = originalUrl
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
routerHost?.let {
// it.forEach { url ->
// com.cncat.vpn.common.log.Log.d("url==>$url")
// }
if (it.isEmpty()) {
Log.d(Conf.TAG, "routerHost isEmpty")
return isChange
}
Log.d(Conf.TAG, "routerHost not empty")
val iterator = it.iterator()
while (iterator.hasNext()) {
val newUrl = iterator.next()
Log.e("Data", "遍历备用的url:$newUrl")
if (newUrl == originalUrl) {
iterator.remove()
} else {
Log.e("Retry", "切换到新url:$newUrl")
mmkv.encode(BZYConstants.MMKV_KEY_OLD_CURRENT_HOST, originalUrl)
isChange = true
newUrlString = newUrl
iterator.remove()
break
}
}
// mmkv.encode(BZYConstants.MMKV_KEY_ROUTER_HOST, routerHost)
mmkv.encode(BZYConstants.MMKV_KEY_CURRENT_HOST, newUrlString)
}
return isChange
}
}

View File

@ -33,6 +33,7 @@ import com.yingmao.clash.act.NewMainActivity
import com.yingmao.clash.act.NewMainActivity.Companion.isSubed
import com.yingmao.clash.act.NewMainActivity.Companion.newUuid
import com.yingmao.clash.act.ProxyModeActivity
import com.yingmao.clash.act.UIMainActivity
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.ClashNodebBean
@ -294,7 +295,7 @@ class NewAccFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatch
// return
// }
launch {
NewMainActivity.profileBinder?.let { pm ->
UIMainActivity.profileBinder?.let { pm ->
val active = pm.queryActive()
if (active == null) {
com.cncat.vpn.common.log.Log.d("startClash: 未选择")
@ -707,7 +708,7 @@ class NewAccFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatch
} else {
if (clashRunning) {
delay(1000)
NewMainActivity.clashBinder?.let { cm ->
UIMainActivity.clashBinder?.let { cm ->
val names = cm.queryProxyGroupNames(false)
Log.i("checkGlo names==>${names}")
if (names.isNotEmpty()) {
@ -771,7 +772,7 @@ class NewAccFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatch
var nodeName = proListItem[0].name
if (!TextUtils.isEmpty(nodeName)) {
launch(Dispatchers.IO) {
NewMainActivity.clashBinder?.let { cm ->
UIMainActivity.clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session)
o.mode = TunnelState.Mode.Global
@ -812,7 +813,7 @@ class NewAccFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatch
})
}
} else {
NewMainActivity.clashBinder?.let { cm ->
UIMainActivity.clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session)
o.mode = TunnelState.Mode.Global
cm.patchOverride(Clash.OverrideSlot.Session, o)

View File

@ -628,104 +628,7 @@ class RechargeFragment : BaseFragment() {
noVerticalPadding = true,
horizontalPadding = false
)
// for (index in data.payType!!) {
// if (index.payModel == 10) {
// findViewById<LinearLayout>(R.id.llAggregatePayment).visibility =
// View.VISIBLE
// val JUhePay = findViewById<TextView>(R.id.JUhePay)
// JUhePay.visibility = View.VISIBLE
// JUhePay.text = index.payChannel
// if (index.payType == 1) {
// val ivAlipay = findViewById<AppCompatImageView>(R.id.ivAlipay)
// ivAlipay.visibility = View.VISIBLE
// ivAlipay.setOnClickListener {
// val payModel = index.payModel
// val payType = index.payType
// val payParam = index.payParam
// if (payModel != null && payType != null && payParam != null) {
// viewModel.orderPayment(data, payModel, payType, payParam)
// } else {
// viewModel.orderPayment(data, 10, 1, "ip@影猫充值")
// }
// }
// } else if (index.payType == 2) {
// val ivWechatPay = findViewById<AppCompatImageView>(R.id.ivWechatPay)
// ivWechatPay.visibility = View.VISIBLE
// ivWechatPay.setOnClickListener {
// val payModel = index.payModel
// val payType = index.payType
// val payParam = index.payParam
// if (payModel != null && payType != null && payParam != null) {
// viewModel.orderPayment(data, payModel, payType, payParam)
// } else {
// viewModel.orderPayment(data, 10, 2, "ip@影猫充值")
// }
//
// }
// }
// } else if (index.payModel == 11) {
// findViewById<LinearLayout>(R.id.llQuickPayment).visibility =
// View.VISIBLE
// val KJPay = findViewById<TextView>(R.id.KJPay)
// KJPay.visibility = View.VISIBLE
// KJPay.text = index.payChannel
// if (index.payType == 1) {
// val ivQuickPayment =
// findViewById<AppCompatImageView>(R.id.ivQuickPayment)
// ivQuickPayment.visibility = View.VISIBLE
// ivQuickPayment.setOnClickListener {
// val payModel = index.payModel
// val payType = index.payType
// val payParam = index.payParam
// if (payModel != null && payType != null && payParam != null) {
// viewModel.orderPayment(data, payModel, payType, payParam)
// } else {
// viewModel.orderPayment(data, 11, 1, "ip@影猫充值")
// }
// }
// } else if (index.payType == 2) {
// val ivQPWechatPay =
// findViewById<AppCompatImageView>(R.id.ivQPWechatPay)
// ivQPWechatPay.visibility = View.VISIBLE
// ivQPWechatPay.setOnClickListener {
// val payModel = index.payModel
// val payType = index.payType
// val payParam = index.payParam
// if (payModel != null && payType != null && payParam != null) {
// viewModel.orderPayment(data, payModel, payType, payParam)
// } else {
// viewModel.orderPayment(data, 11, 1, "ip@影喵充值")
// }
// }
// }
// } else if (index.payModel == 7) {
// findViewById<LinearLayout>(R.id.llStripe).visibility = View.VISIBLE
// findViewById<LinearLayout>(R.id.llStripePay).visibility = View.VISIBLE
// var payinfo = findViewById<TextView>(R.id.tvStripePay)
// payinfo.visibility = View.VISIBLE
//
// payinfo.text = index.payChannel
// findViewById<AppCompatImageView>(R.id.ivStripeAlipay).visibility =
// View.VISIBLE
// findViewById<AppCompatImageView>(R.id.ivStripeAlipay).setOnClickListener {
// if (selectedGradeID != 0) {
// if (!PayUtils.isAlipayInstalled(requireActivity())) {
// RxToast.error(resources.getString(R.string.AlipayNotInstalled))
// return@setOnClickListener
// }
// dismiss()
// showLoading(
// resources.getString(R.string.PaymentIn),
// requireActivity()
// )
// Log.i("PayGradeID:", "PayGradeID:${selectedGradeID}")
// viewModel.getPayClientSecret(selectedGradeID)
// } else {
// RxToast.info(getString(R.string.NoPaymentReceipt))
// }
// }
// }
// }
getCustomView().findViewById<AppCompatImageView>(R.id.ivBack)
.setOnClickListener {
dismiss()
@ -735,76 +638,7 @@ class RechargeFragment : BaseFragment() {
}
}
// private fun goWebViewStartPayClient(rsp: BaseRsp, isPayWeb: Boolean) {
// (rsp as BiLaiNormalRsp).let {
// if (it.code != "1000") {
// RxToast.error(if (it.message.isNullOrBlank()) "获取支付信息失败(${it.code}" else "${it.code}:${it.message}")
// } else {
// if (isPayWeb) {
// activity?.let {
// if (rsp.data != null) {
//// goWebPay(it, rsp.data!!)
//// goWebPay(it, newUrl)
// } else {
// RxToast.error("未能获取到支付信息,请联系客服")
// }
// }
// return
// }
//
// val gson = Gson().fromJson(it.data!!, BiLaiRsp::class.java)
// Logger.d("跳转支付宝支付页面:" + gson.payurl)
// var realPayUrl = ""
// var isNeedScan = false
// if (gson.payurl != null) {
// realPayUrl = gson.payurl!!
// isNeedScan = false
// } else if (gson.qrcode != null) {
// realPayUrl = gson.qrcode!!
// isNeedScan = true
// } else {
// realPayUrl = gson.urlscheme!!
// isNeedScan = true
// }
// // Log.e("data==", "html:" + realPayUrl);
//
//
// activity?.let {
// if (!isNeedScan) {
//// goWebPay(it, newUrl)
// } else {
// //二维码支付
// qrPayDialog = MaterialDialog(it).show {
// this.cancelable(false)
// cornerRadius(res = R.dimen.dp_10)
// customView(R.layout.qrpay_dialog_layout, scrollable = false, noVerticalPadding = true, horizontalPadding = false)
// getCustomView().findViewById<TextView>(R.id.tv_qr_pay_note).setText(R.string.qr_pay_note_wx)
//
// val ivQr = getCustomView().findViewById<ImageView>(R.id.iv_qr)
//
// val writer = MultiFormatWriter()
// val matrix = writer.encode(realPayUrl, BarcodeFormat.QR_CODE, 300, 300)
// val encoder = BarcodeEncoder()
// val bitmap = encoder.createBitmap(matrix)
// ivQr.setImageBitmap(bitmap)
//
// getCustomView().findViewById<TextView>(R.id.tv_pay_finish).setOnClickListener {
// showLoadingDialog("正在查询支付结果...")
// viewModel.codePayStatus(outTradeNo)
// }
//
// getCustomView().findViewById<TextView>(R.id.tv_pay_cancle).setOnClickListener {
// if (bitmap != null && !bitmap.isRecycled) {
// bitmap.recycle()
// dismiss()
// }
// }
// }
// }
// }
// }
// }
// }/
fun goWebPay(it: Activity, realPayUrl: String) {
val intent = Intent(context, BZYWebViewNormal::class.java)

View File

@ -62,6 +62,7 @@ import com.yingmao.clash.act.UIMainActivity.Companion.newUuid
import com.yingmao.clash.act.UIMainActivity.Companion.profileBinder
import com.yingmao.clash.act.UIMainActivity.Companion.subUrl
import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.base.BaseActivity
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.CheckLoginRsp
@ -636,6 +637,10 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_VPN_UserCommand)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.PROFILE_SUB_URL)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_Switch_Finsh_Set)
//清除源切换记录
Log.d("cleanUserInfo clashRunning==>${clashRunning}")
if (clashRunning) {
fragments.filterIsInstance<HomeFragment>().forEach { it.stopClash() }

View File

@ -33,6 +33,7 @@ import com.yingmao.clash.net.http.entity.UploadParamData
import com.yingmao.clash.net.http.entity.VipSignRsp
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
@ -75,6 +76,7 @@ interface HttpApi {
@POST("/netbarcloud/vpn/octopusRegister.do")
suspend fun register(@QueryMap params: Map<String, String>): RegisterRsp
//加密注册
@Headers(
"User-Agent: Octopus_Android"
@ -94,7 +96,6 @@ interface HttpApi {
): RegisterRsp
//显示赠送时长
@Headers(
"User-Agent: Octopus_Android"
@ -218,8 +219,10 @@ interface HttpApi {
@GET("/taiziregister.txt")
suspend fun getTzRegister(): String
@GET("/taiziregister_182269.txt")
suspend fun getTz182269Register(): String
@GET("/bzyregister.txt")
suspend fun getBzyRegister(): String
@ -631,4 +634,16 @@ interface HttpApi {
BZYConstants.MMKV_KEY_TOKEN
).toString(),
): AppStopStatus
@Headers(
"User-Agent: Octopus_Android",
"Authorization: Basic Z20yMXdvcGc6eDVjZXlzbTY="
)
@POST("/netbarcloud/api/v1/push")
suspend fun postErrLog(
@Body requestBody: RequestBody,
@Header("token") string: String = MMKV.defaultMMKV().decodeString(
BZYConstants.MMKV_KEY_TOKEN
).toString(),
): Response<NormalRsp>
}

View File

@ -16,6 +16,7 @@ import okhttp3.Callback
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
@ -72,6 +73,37 @@ class NetService private constructor() : INetService {
var httpClient: OkHttpClient? = null;
var downHttpClient: OkHttpClient? = null;
class ExceptionLoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
return try {
val response = chain.proceed(request)
// 如果需要也可记录非 2xx 的响应
if (!response.isSuccessful) {
logHttpError(request, response.code, null)
}
response
} catch (e: Exception) {
logHttpError(request, null, e)
throw e // 抛出异常让上层处理
}
}
private fun logHttpError(request: Request, code: Int?, e: Exception?) {
val log = buildString {
append("URL: ${request.url}\n")
append("Method: ${request.method}\n")
append("Headers: ${request.headers}\n")
append("Body: ${request.body}\n")
code?.let { append("Response Code: $code\n") }
e?.let { append("Exception: ${e.message}\n${e.stackTraceToString()}") }
}
// 这里可以上传到服务端,或者保存到本地文件
println("❌ 网络请求异常:\n$log")
// FirebaseCrashlytics.getInstance().recordException(e) // 如果用了 Firebase
}
}
override fun httpsClient(connTO:Long,readTO:Long,writeTO:Long): OkHttpClient {
var loggingInterceptor = HttpLoggingInterceptor(
@ -79,8 +111,10 @@ class NetService private constructor() : INetService {
override fun log(message: String) {
Log.d("loggingInterceptor==>$message")
}
}
)
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
var builder = OkHttpClient.Builder()
.retryOnConnectionFailure(true)
@ -95,6 +129,7 @@ class NetService private constructor() : INetService {
.hostnameVerifier(getHostnameVerifier()!!)
if (BuildConfig.DEBUG) {
builder.addInterceptor(loggingInterceptor)
// builder.addInterceptor(ExceptionLoggingInterceptor())
}
// builder.addInterceptor(loggingInterceptor)
return builder.build()
@ -161,6 +196,7 @@ class NetService private constructor() : INetService {
.addConverterFactory(ScalarsConverterFactory.create())//json解析器Gson
.baseUrl(baseUrl) //设置BaseURL需以'/'结尾
.build() //创建Retrofit对象
return retrofit.create(HttpApi::class.java)
}
fun createFreeBaseUrl2(): HttpApi {

View File

@ -0,0 +1,112 @@
package com.yingmao.clash.view.dialog
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.R
import com.yingmao.clash.conf.BZYConstants
/**
* @author tamsiree
* @date 2016/7/19
* 确认 取消 Dialog
*/
open class DialogAutoDoMainChoose : RxDialog {
lateinit var titleView: TextView
private set
lateinit var but_try: TextView
private set
lateinit var but_cancel: TextView
private set
lateinit var domainRecyclerView: RecyclerView
private set
lateinit var onTryListener: OnTryButtonListener
lateinit var onDomainListItemListener: OnListItemListener
var domainFininshSet = mutableSetOf<String>();
constructor(context: Context?, themeResId: Int) : super(context!!, themeResId) {
initView()
}
constructor(
context: Context?,
cancelable: Boolean,
cancelListener: DialogInterface.OnCancelListener?,
) : super(context!!, cancelable, cancelListener) {
initView()
}
constructor(context: Context?) : super(context!!) {
initView()
}
constructor(context: Activity?) : super(context!!) {
initView()
}
constructor(context: Context?, alpha: Float, gravity: Int) : super(context, alpha, gravity) {
initView()
}
private fun initView() {
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_domain_choose, null)
titleView = dialogView.findViewById(R.id.tv_title)
but_try = dialogView.findViewById(R.id.but_try)
but_cancel = dialogView.findViewById(R.id.but_cancel)
domainRecyclerView = dialogView.findViewById(R.id.domian_list)
but_try.setOnClickListener {
onTryListener.tryButton()
}
but_cancel.setOnClickListener { onTryListener.cancel() }
setContentView(dialogView)
setCancelable(false)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
setNewData()
}
@SuppressLint("NotifyDataSetChanged")
private fun setNewData() {
var mmkv = MMKV.defaultMMKV()
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
var newUrlString = originalUrl
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
}
fun setOnTryButtonListener(listener: OnTryButtonListener) {
this.onTryListener = listener
}
private fun changeSubUrl(originalUrl: String, newUrl: String): String {
return originalUrl.replace(Regex("https://[^/]+/"), "$newUrl/")
}
interface OnTryButtonListener {
fun tryButton()
fun cancel()
}
interface OnListItemListener {
fun listItemClick()
}
}

View File

@ -106,7 +106,6 @@ open class DialogDoMainChoose : RxDialog {
}
domainListAdapter.notifyDataSetChanged()
}
}
fun setOnTryButtonListener(listener: OnTryButtonListener) {