NekoX/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt

548 lines
16 KiB
Kotlin
Raw Normal View History

2020-06-25 17:28:55 +02:00
package tw.nekomimi.nekogram.utils
import android.Manifest
import android.app.Activity
2020-11-27 20:32:39 +01:00
import android.app.AlertDialog
2020-06-25 17:28:55 +02:00
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
2021-02-03 15:14:57 +01:00
import android.graphics.Canvas
import android.graphics.Color
2020-06-25 17:28:55 +02:00
import android.os.Build
import android.os.Environment
import android.util.Base64
import android.view.Gravity
2021-02-03 15:14:57 +01:00
import android.view.View
2020-06-25 17:28:55 +02:00
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Toast
2021-02-03 15:14:57 +01:00
import androidx.core.view.setPadding
2020-06-25 17:28:55 +02:00
import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.qrcode.QRCodeReader
import com.google.zxing.qrcode.QRCodeWriter
2021-01-28 17:35:06 +01:00
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
2020-06-25 17:28:55 +02:00
import com.v2ray.ang.V2RayConfig.SSR_PROTOCOL
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
2020-10-20 11:15:14 +02:00
import com.v2ray.ang.V2RayConfig.TROJAN_PROTOCOL
2020-06-25 17:28:55 +02:00
import com.v2ray.ang.V2RayConfig.VMESS1_PROTOCOL
import com.v2ray.ang.V2RayConfig.VMESS_PROTOCOL
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.telegram.messenger.*
import org.telegram.messenger.browser.Browser
2021-02-04 18:59:00 +01:00
import tw.nekomimi.nekogram.BottomBuilder
import tw.nekomimi.nekogram.utils.AlertUtil.showToast
2020-06-25 17:28:55 +02:00
import java.io.File
import java.net.NetworkInterface
import java.util.*
import kotlin.collections.HashMap
2021-02-03 15:14:57 +01:00
2020-06-25 17:28:55 +02:00
object ProxyUtil {
@JvmStatic
fun isVPNEnabled(): Boolean {
val networkList = mutableListOf<String>()
runCatching {
Collections.list(NetworkInterface.getNetworkInterfaces()).forEach {
if (it.isUp) networkList.add(it.name)
}
}
return networkList.contains("tun0")
}
@JvmStatic
fun parseProxies(_text: String): MutableList<String> {
val text = runCatching {
String(Base64.decode(_text, Base64.NO_PADDING))
}.recover {
_text
}.getOrThrow()
val proxies = mutableListOf<String>()
text.split('\n').map { it.split(" ") }.forEach {
it.forEach { line ->
if (line.startsWith("tg://proxy") ||
line.startsWith("tg://socks") ||
line.startsWith("https://t.me/proxy") ||
line.startsWith("https://t.me/socks") ||
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
2020-10-20 11:15:14 +02:00
line.startsWith(SSR_PROTOCOL) ||
line.startsWith(TROJAN_PROTOCOL) /*||
2020-06-25 17:28:55 +02:00
line.startsWith(RB_PROTOCOL)*/) {
runCatching { proxies.add(SharedConfig.parseProxyInfo(line).toUrl()) }
}
}
}
if (proxies.isEmpty()) error("no proxy link found")
return proxies
}
@JvmStatic
2020-07-10 12:10:21 +02:00
fun importFromClipboard(ctx: Activity) {
2020-06-25 17:28:55 +02:00
var text = (ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).primaryClip?.getItemAt(0)?.text?.toString()
val proxies = mutableListOf<SharedConfig.ProxyInfo>()
var error = false
2020-07-10 12:10:21 +02:00
text?.trim()?.split('\n')?.map { it.split(" ") }?.forEach {
2020-06-25 17:28:55 +02:00
it.forEach { line ->
if (line.startsWith("tg://proxy") ||
line.startsWith("tg://socks") ||
line.startsWith("https://t.me/proxy") ||
line.startsWith("https://t.me/socks") ||
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
2020-10-22 04:38:41 +02:00
line.startsWith(SSR_PROTOCOL) ||
line.startsWith(TROJAN_PROTOCOL) /*||
2020-06-25 17:28:55 +02:00
line.startsWith(RB_PROTOCOL)*/) {
runCatching { proxies.add(SharedConfig.parseProxyInfo(line)) }.onFailure {
2020-07-10 12:10:21 +02:00
error = true
2021-02-04 18:59:00 +01:00
showToast(LocaleController.getString("BrokenLink", R.string.BrokenLink) + ": ${it.message ?: it.javaClass.simpleName}")
2020-06-25 17:28:55 +02:00
}
}
}
}
2020-07-10 12:10:21 +02:00
runCatching {
if (proxies.isNullOrEmpty() && !error) {
String(Base64.decode(text, Base64.NO_PADDING)).trim().split('\n').map { it.split(" ") }.forEach { str ->
str.forEach { line ->
if (line.startsWith("tg://proxy") ||
line.startsWith("tg://socks") ||
line.startsWith("https://t.me/proxy") ||
line.startsWith("https://t.me/socks") ||
line.startsWith(VMESS_PROTOCOL) ||
line.startsWith(VMESS1_PROTOCOL) ||
line.startsWith(SS_PROTOCOL) ||
2020-10-22 04:38:41 +02:00
line.startsWith(SSR_PROTOCOL) ||
line.startsWith(TROJAN_PROTOCOL) /*||
line.startsWith(RB_PROTOCOL)*/) {
2020-07-10 12:10:21 +02:00
runCatching { proxies.add(SharedConfig.parseProxyInfo(line)) }.onFailure {
error = true
2021-02-04 18:59:00 +01:00
showToast(LocaleController.getString("BrokenLink", R.string.BrokenLink) + ": ${it.message ?: it.javaClass.simpleName}")
2020-07-10 12:10:21 +02:00
}
}
}
}
}
}
2020-06-25 17:28:55 +02:00
if (proxies.isNullOrEmpty()) {
2021-02-04 18:59:00 +01:00
if (!error) showToast(LocaleController.getString("BrokenLink", R.string.BrokenLink))
2020-06-25 17:28:55 +02:00
return
} else if (!error) {
2020-07-10 12:10:21 +02:00
AlertUtil.showSimpleAlert(ctx, LocaleController.getString("ImportedProxies", R.string.ImportedProxies) + "\n\n" + proxies.joinToString("\n") { it.title })
2020-07-10 12:10:21 +02:00
2020-06-25 17:28:55 +02:00
}
proxies.forEach {
SharedConfig.addProxy(it)
}
UIUtil.runOnUIThread {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged)
}
}
@JvmStatic
fun importProxy(ctx: Context, link: String): Boolean {
runCatching {
if (link.startsWith(VMESS_PROTOCOL) || link.startsWith(VMESS1_PROTOCOL)) {
AndroidUtilities.showVmessAlert(ctx, SharedConfig.VmessProxy(link))
2020-10-20 11:15:14 +02:00
} else if (link.startsWith(TROJAN_PROTOCOL)) {
AndroidUtilities.showTrojanAlert(ctx, SharedConfig.VmessProxy(link))
2020-06-25 17:28:55 +02:00
} else if (link.startsWith(SS_PROTOCOL)) {
AndroidUtilities.showShadowsocksAlert(ctx, SharedConfig.ShadowsocksProxy(link))
} else if (link.startsWith(SSR_PROTOCOL)) {
AndroidUtilities.showShadowsocksRAlert(ctx, SharedConfig.ShadowsocksRProxy(link))
} else {
val url = link.replace("tg://", "https://t.me/").toHttpUrlOrNull()!!
2020-06-25 17:28:55 +02:00
AndroidUtilities.showProxyAlert(ctx,
2020-12-17 04:33:12 +01:00
url.queryParameter("server") ?: return false,
url.queryParameter("port") ?: return false,
2020-06-25 17:28:55 +02:00
url.queryParameter("user"),
url.queryParameter("pass"),
url.queryParameter("secret"),
url.fragment)
}
return true
}.onFailure {
FileLog.e(it)
2020-06-25 17:28:55 +02:00
if (BuildVars.LOGS_ENABLED) {
2020-07-10 12:10:21 +02:00
AlertUtil.showSimpleAlert(ctx, it)
} else {
2021-02-04 18:59:00 +01:00
showToast("${LocaleController.getString("BrokenLink", R.string.BrokenLink)}: ${it.message}")
2020-07-10 12:10:21 +02:00
}
2020-06-25 17:28:55 +02:00
}
return false
}
@JvmStatic
fun importInBackground(link: String): SharedConfig.ProxyInfo {
val info = runCatching {
if (link.startsWith(VMESS_PROTOCOL) || link.startsWith(VMESS1_PROTOCOL)) {
SharedConfig.VmessProxy(link)
} else if (link.startsWith(SS_PROTOCOL)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
error(LocaleController.getString("MinApi21Required", R.string.MinApi21Required))
}
SharedConfig.ShadowsocksProxy(link)
} else if (link.startsWith(SSR_PROTOCOL)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
error(LocaleController.getString("MinApi21Required", R.string.MinApi21Required))
}
SharedConfig.ShadowsocksRProxy(link)
} else {
SharedConfig.ProxyInfo.fromUrl(link)
}
}.getOrThrow()
if (!(SharedConfig.addProxy(info) === info)) {
error("already exists")
}
return info
}
@JvmStatic
fun shareProxy(ctx: Activity, info: SharedConfig.ProxyInfo, type: Int) {
val url = info.toUrl();
if (type == 1) {
AndroidUtilities.addToClipboard(url)
Toast.makeText(ctx, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_LONG).show()
} else if (type == 0) {
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, url)
val chooserIntent = Intent.createChooser(shareIntent, LocaleController.getString("ShareLink", R.string.ShareLink))
chooserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
ctx.startActivity(chooserIntent)
} else {
showQrDialog(ctx, url)
}
}
@JvmStatic
fun getOwnerActivity(ctx: Context): Activity {
if (ctx is Activity) return ctx
if (ctx is ContextWrapper) return getOwnerActivity(ctx.baseContext)
error("unable cast ${ctx.javaClass.name} to activity")
}
@JvmStatic
2021-02-03 15:14:57 +01:00
@JvmOverloads
fun showQrDialog(ctx: Context, text: String, icon: ((Int) -> Bitmap)? = null): AlertDialog {
2020-06-25 17:28:55 +02:00
2021-02-03 15:14:57 +01:00
val code = createQRCode(text, icon = icon)
2020-06-25 17:28:55 +02:00
ctx.setTheme(R.style.Theme_TMessages)
2020-11-27 20:32:39 +01:00
return AlertDialog.Builder(ctx).setView(LinearLayout(ctx).apply {
2020-06-25 17:28:55 +02:00
2021-02-03 15:14:57 +01:00
gravity = Gravity.CENTER
setBackgroundColor(Color.TRANSPARENT)
2020-06-25 17:28:55 +02:00
addView(LinearLayout(ctx).apply {
2021-02-03 15:14:57 +01:00
val root = this
2020-06-25 17:28:55 +02:00
gravity = Gravity.CENTER
2021-02-03 15:14:57 +01:00
setBackgroundColor(Color.WHITE)
setPadding(AndroidUtilities.dp(16f))
2020-06-25 17:28:55 +02:00
2021-02-03 15:14:57 +01:00
val width = AndroidUtilities.dp(260f)
2020-06-25 17:28:55 +02:00
addView(ImageView(ctx).apply {
setImageBitmap(code)
scaleType = ImageView.ScaleType.FIT_XY
setOnLongClickListener {
2021-02-04 18:59:00 +01:00
val builder = BottomBuilder(ctx)
builder.addItems(arrayOf(
2020-06-25 17:28:55 +02:00
LocaleController.getString("SaveToGallery", R.string.SaveToGallery),
LocaleController.getString("Cancel", R.string.Cancel)
), intArrayOf(
R.drawable.baseline_image_24,
R.drawable.baseline_cancel_24
2021-02-04 18:59:00 +01:00
)) { i, _, _ ->
2020-06-25 17:28:55 +02:00
if (i == 0) {
if (Build.VERSION.SDK_INT >= 23 && ctx.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
getOwnerActivity(ctx).requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 4)
2021-02-04 18:59:00 +01:00
return@addItems
2020-06-25 17:28:55 +02:00
}
val saveTo = File(Environment.getExternalStorageDirectory(), "${Environment.DIRECTORY_PICTURES}/share_${text.hashCode()}.jpg")
saveTo.parentFile?.mkdirs()
runCatching {
saveTo.createNewFile()
saveTo.outputStream().use {
2021-02-03 15:14:57 +01:00
loadBitmapFromView(root).compress(Bitmap.CompressFormat.JPEG, 100, it);
2020-06-25 17:28:55 +02:00
}
AndroidUtilities.addMediaToGallery(saveTo.path)
2021-02-04 18:59:00 +01:00
showToast(LocaleController.getString("PhotoSavedHint", R.string.PhotoSavedHint))
2020-06-25 17:28:55 +02:00
2021-02-03 15:14:57 +01:00
}.onFailure {
FileLog.e(it)
2021-02-04 18:59:00 +01:00
showToast(it)
2020-06-25 17:28:55 +02:00
}
}
2021-02-04 18:59:00 +01:00
}
builder.show()
2020-06-25 17:28:55 +02:00
return@setOnLongClickListener true
}
}, LinearLayout.LayoutParams(width, width))
2021-02-03 15:14:57 +01:00
}, LinearLayout.LayoutParams(-2, -2).apply {
2020-06-25 17:28:55 +02:00
gravity = Gravity.CENTER
})
2021-02-03 15:14:57 +01:00
}).create().apply {
show()
window?.setBackgroundDrawableResource(android.R.color.transparent)
}
}
2020-06-25 17:28:55 +02:00
2021-02-03 15:14:57 +01:00
private fun loadBitmapFromView(v: View): Bitmap {
val b = Bitmap.createBitmap(v.width, v.height, Bitmap.Config.ARGB_8888)
val c = Canvas(b)
v.layout(v.left, v.top, v.right, v.bottom)
v.draw(c)
return b
2020-06-25 17:28:55 +02:00
}
2021-01-28 17:35:06 +01:00
@JvmStatic
2021-02-03 15:14:57 +01:00
fun createQRCode(text: String, size: Int = 768, icon: ((Int) -> Bitmap)? = null): Bitmap {
return try {
2020-06-25 17:28:55 +02:00
val hints = HashMap<EncodeHintType, Any>()
2021-01-28 17:35:06 +01:00
hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M
2021-02-03 15:14:57 +01:00
QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size, hints, null, null, icon)
2020-06-25 17:28:55 +02:00
} catch (e: WriterException) {
2021-01-29 08:17:58 +01:00
FileLog.e(e);
2021-02-03 15:14:57 +01:00
Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
2020-06-25 17:28:55 +02:00
}
}
val qrReader = QRCodeReader()
@JvmStatic
fun tryReadQR(ctx: Activity, bitmap: Bitmap) {
2020-11-27 20:32:39 +01:00
val intArray = IntArray(bitmap.width * bitmap.height)
bitmap.getPixels(intArray, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
val source = RGBLuminanceSource(bitmap.width, bitmap.height, intArray)
2020-06-25 17:28:55 +02:00
try {
2021-02-03 15:14:57 +01:00
val result = qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), mapOf(
DecodeHintType.TRY_HARDER to true
))
2020-06-25 17:28:55 +02:00
2020-11-27 20:32:39 +01:00
showLinkAlert(ctx, result.text)
2020-06-25 17:28:55 +02:00
2020-11-27 20:32:39 +01:00
} catch (e: Throwable) {
2021-02-04 18:59:00 +01:00
showToast(LocaleController.getString("NoQrFound", R.string.NoQrFound))
2020-11-27 20:32:39 +01:00
2020-06-25 17:28:55 +02:00
}
}
@JvmStatic
2021-02-04 18:59:00 +01:00
@JvmOverloads
fun showLinkAlert(ctx: Activity, text: String, tryInternal: Boolean = true) {
2020-06-25 17:28:55 +02:00
2021-02-04 18:59:00 +01:00
val builder = BottomBuilder(ctx)
2020-06-25 17:28:55 +02:00
2021-02-04 18:59:00 +01:00
if (tryInternal) {
runCatching {
if (Browser.isInternalUrl(text, booleanArrayOf(false))) {
Browser.openUrl(ctx, text)
return
}
2020-06-25 17:28:55 +02:00
}
}
2021-02-04 18:59:00 +01:00
builder.addTitle(text)
2020-06-25 17:28:55 +02:00
2021-02-04 18:59:00 +01:00
builder.addItems(arrayOf(
LocaleController.getString("Open", R.string.Open),
2020-06-25 17:28:55 +02:00
LocaleController.getString("Copy", R.string.Copy),
2021-02-04 18:59:00 +01:00
LocaleController.getString("ShareQRCode", R.string.ShareQRCode)
), intArrayOf(
R.drawable.baseline_open_in_browser_24,
R.drawable.baseline_content_copy_24,
R.drawable.wallet_qr
)) { which, _, _ ->
when (which) {
0 -> Browser.openUrl(ctx, text)
1 -> {
AndroidUtilities.addToClipboard(text)
showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied))
}
else -> showQrDialog(ctx, text)
2020-06-25 17:28:55 +02:00
}
}
builder.show()
}
}