mirror of https://github.com/NekoX-Dev/NekoX.git
Try to use DoH to get public proxy
This commit is contained in:
parent
edb9fc5501
commit
b62eed4c9c
|
@ -1,18 +1,92 @@
|
|||
package tw.nekomimi.nekogram.parts
|
||||
|
||||
import android.util.Base64
|
||||
import cn.hutool.http.HttpResponse
|
||||
import cn.hutool.http.HttpUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.google.android.exoplayer2.util.Log
|
||||
import kotlinx.coroutines.*
|
||||
import org.telegram.messenger.FileLog
|
||||
import tw.nekomimi.nekogram.utils.ProxyUtil.parseProxies
|
||||
import tw.nekomimi.nkmr.NekomuraUtil
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
fun loadProxiesPublic(urls: List<String>, exceptions: MutableMap<String, Exception>): List<String> {
|
||||
val urlsDoH = ArrayList<String>()
|
||||
val urlsOld = ArrayList<String>()
|
||||
|
||||
for (url in urls) {
|
||||
if (url.startsWith("doh://")) {
|
||||
urlsDoH.add(url)
|
||||
} else {
|
||||
urlsOld.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
// Try DoH first ( github.com is often blocked
|
||||
try {
|
||||
return runBlocking {
|
||||
var jobs = ArrayList<Job>()
|
||||
|
||||
var a: List<String> = suspendCoroutine {
|
||||
val ret = AtomicBoolean()
|
||||
val cl = AtomicInteger(urlsDoH.size)
|
||||
|
||||
for (url in urlsDoH) {
|
||||
jobs.add(launch(Dispatchers.IO) {
|
||||
try {
|
||||
val para = "?name=nekogramx-public-proxy-v1.seyana.moe&type=TXT"
|
||||
val reqURL = url.replace("doh://", "https://", false) + para
|
||||
|
||||
val req = HttpUtil.createGet(reqURL)
|
||||
req.addHeaders(mapOf("accept" to "application/dns-json"))
|
||||
req.timeout(10 * 1000)
|
||||
|
||||
var content = req.execute().body()
|
||||
content = content.replace("\\\"", "", false)
|
||||
|
||||
val proxiesString = NekomuraUtil.getSubString(content, "#NekoXStart#", "#NekoXEnd#")
|
||||
if (proxiesString.equals(content)) {
|
||||
throw Exception("DoH get public proxy: Not found")
|
||||
}
|
||||
|
||||
val proxies = parseProxies(proxiesString)
|
||||
if (proxies.count() == 0) {
|
||||
throw Exception("DoH get public proxy: Empty")
|
||||
}
|
||||
|
||||
if (ret.getAndSet(true)) return@launch
|
||||
Log.e("NekoPublicProxy", reqURL)
|
||||
it.resume(proxies)
|
||||
} catch (e: Exception) {
|
||||
Log.e("NekoPublicProxy", e.stackTraceToString())
|
||||
exceptions[url] = e
|
||||
if (cl.decrementAndGet() == 0) {
|
||||
it.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Quit when the first success
|
||||
for (job in jobs) {
|
||||
// TODO cannot cancel Hutool HTTPRequest now...
|
||||
job.cancel()
|
||||
}
|
||||
a
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Try Other Urls
|
||||
return loadProxies(urlsOld, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun loadProxies(urls: List<String>, exceptions: MutableMap<String, Exception>): List<String> {
|
||||
|
||||
return runBlocking {
|
||||
|
@ -38,7 +112,7 @@ fun loadProxies(urls: List<String>, exceptions: MutableMap<String, Exception>):
|
|||
var nextUrl = url
|
||||
var resp: HttpResponse
|
||||
while (true) {
|
||||
resp = HttpUtil.createGet(nextUrl).execute();
|
||||
resp = HttpUtil.createGet(nextUrl).timeout(10 * 1000).execute();
|
||||
if (resp.status == 301 || resp.status == 302 || resp.status == 307) {
|
||||
nextUrl = resp.header("Location");
|
||||
continue;
|
||||
|
@ -50,6 +124,12 @@ fun loadProxies(urls: List<String>, exceptions: MutableMap<String, Exception>):
|
|||
content = content.substringAfter(subX)
|
||||
.substringBefore(subY)
|
||||
}
|
||||
|
||||
if (url.contains("https://api.github.com")) {
|
||||
content = content.replace("\\n", "", false)
|
||||
content = String(Base64.decode(content, Base64.DEFAULT))
|
||||
}
|
||||
|
||||
val proxies = parseProxies(content)
|
||||
if (urlFinal.contains("https://gitee.com/") && cl.decrementAndGet() > 0) {
|
||||
defer = proxies
|
||||
|
@ -71,12 +151,8 @@ fun loadProxies(urls: List<String>, exceptions: MutableMap<String, Exception>):
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -47,7 +47,7 @@ public class SubInfo implements Mappable {
|
|||
HashMap<String, Exception> exceptions = new HashMap<>();
|
||||
|
||||
try {
|
||||
return ProxyLoadsKt.loadProxies(urls, exceptions);
|
||||
return internal ? ProxyLoadsKt.loadProxiesPublic(urls, exceptions) : ProxyLoadsKt.loadProxies(urls, exceptions);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
|
|
|
@ -21,14 +21,21 @@ object SubManager {
|
|||
val public = find(ObjectFilters.eq("id", 1L)).firstOrDefault()
|
||||
|
||||
update(SubInfo().apply {
|
||||
// SubManager.kt -> SubInfo.java -> ProxyLoads.kt
|
||||
|
||||
name = LocaleController.getString("NekoXProxy", R.string.NekoXProxy)
|
||||
enable = public?.enable ?: true
|
||||
|
||||
urls = listOf(
|
||||
"https://nekox.pages.dev/proxy_list_pro",
|
||||
"doh://1.1.1.1/dns-query", // Domain is hardcoded in ProxyLoads.kt
|
||||
"doh://1.0.0.1/dns-query",
|
||||
"doh://101.101.101.101/dns-query",
|
||||
"doh://8.8.8.8/resolve",
|
||||
"doh://8.8.4.4/resolve",
|
||||
"doh://[2606:4700:4700::1111]/dns-query",
|
||||
"https://nekox.pages.dev/proxy_list_pro", // Note: NO DoH apply to here and neko.services now.
|
||||
"https://github.com/NekoX-Dev/ProxyList/blob/master/proxy_list_pro@js-file-line\">@<",
|
||||
"https://gitee.com/nekoshizuku/AwesomeRepo/raw/master/proxy_list_pro"
|
||||
"https://api.github.com/repos/NekoX-Dev/ProxyList/contents/proxy_list_pro?ref=master@\"content\": \"@\"",
|
||||
)
|
||||
|
||||
id = 1L
|
||||
|
|
|
@ -15,10 +15,13 @@ object DnsFactory {
|
|||
|
||||
fun providers() = if (NekoConfig.customDoH.isNotBlank()) arrayOf(NekoConfig.customDoH)
|
||||
else if (Locale.getDefault().country == "CN") arrayOf(
|
||||
"https://doh.dns.sb/dns-query"
|
||||
// 1.1.1.1 / 8.8.8.8 seems blocked
|
||||
"https://101.101.101.101/dns-query", //Taiwan
|
||||
"https://9.9.9.9/dns-query", // Anycast Japan
|
||||
"https://doh.dns.sb/dns-query", // Japan
|
||||
) else arrayOf(
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.google/dns-query",
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.google/dns-query",
|
||||
)
|
||||
|
||||
val cache = Cache()
|
||||
|
@ -45,32 +48,34 @@ object DnsFactory {
|
|||
var sr = cache.lookupRecords(name, type, dc)
|
||||
|
||||
if (!sr.isSuccessful) for (provider in providers()) {
|
||||
FileLog.d("Provider $provider")
|
||||
try {
|
||||
val response = HttpUtil.createPost(provider)
|
||||
FileLog.d("Provider $provider")
|
||||
try {
|
||||
val response = HttpUtil.createPost(provider)
|
||||
.contentType("application/dns-message")
|
||||
.header(Header.ACCEPT, "application/dns-message")
|
||||
.body(message)
|
||||
.setConnectionTimeout(5000)
|
||||
.execute()
|
||||
if (!response.isOk) continue
|
||||
val result = Message(response.bodyBytes())
|
||||
val rcode = result.header.rcode
|
||||
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) continue
|
||||
cache.addMessage(result)
|
||||
sr = cache.lookupRecords(name, type, dc)
|
||||
if (sr == null) sr = cache.lookupRecords(name, type, dc)
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
if (!response.isOk) continue
|
||||
val result = Message(response.bodyBytes())
|
||||
val rcode = result.header.rcode
|
||||
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) continue
|
||||
cache.addMessage(result)
|
||||
sr = cache.lookupRecords(name, type, dc)
|
||||
if (sr == null) sr = cache.lookupRecords(name, type, dc)
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sr.isSuccessful) {
|
||||
val records = ArrayList<Record>()
|
||||
for (set in sr.answers()) {
|
||||
records.addAll(set.rrs(true))
|
||||
}
|
||||
val addresses = records.map { (it as? ARecord)?.address ?: (it as AAAARecord).address }
|
||||
val addresses = records.map {
|
||||
(it as? ARecord)?.address ?: (it as AAAARecord).address
|
||||
}
|
||||
FileLog.d(addresses.toString())
|
||||
return addresses
|
||||
}
|
||||
|
@ -116,11 +121,11 @@ object DnsFactory {
|
|||
try {
|
||||
|
||||
val response = HttpUtil.createPost(provider)
|
||||
.contentType("application/dns-message")
|
||||
.header(Header.ACCEPT, "application/dns-message")
|
||||
.body(message)
|
||||
.setConnectionTimeout(5000)
|
||||
.execute()
|
||||
.contentType("application/dns-message")
|
||||
.header(Header.ACCEPT, "application/dns-message")
|
||||
.body(message)
|
||||
.setConnectionTimeout(5000)
|
||||
.execute()
|
||||
if (!response.isOk) continue
|
||||
val result = Message(response.bodyBytes())
|
||||
val rcode = result.header.rcode
|
||||
|
|
|
@ -26,6 +26,10 @@ public class NekomuraConfig {
|
|||
private static final int configTypeSetInt = 3;
|
||||
private static final int configTypeMapIntInt = 4;
|
||||
|
||||
// Configs
|
||||
public static ConfigItem largeAvatarInDrawer = addConfig("largeAvatarInDrawer", configTypeInt, 0); // 0:TG Default 1:NekoX Default 2:Large Avatar
|
||||
public static ConfigItem unreadBadgeOnBackButton = addConfig("unreadBadgeOnBackButton", configTypeBool, false);
|
||||
|
||||
public static class ConfigItem {
|
||||
private String key;
|
||||
private int type;
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package tw.nekomimi.nkmr;
|
||||
|
||||
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.MessageObject;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class NekomuraUtil {
|
||||
/*
|
||||
* Java文件操作 获取不带扩展名的文件名
|
||||
*
|
||||
* Created on: 2011-8-2
|
||||
* Author: blueeagle
|
||||
*/
|
||||
public static String getFileNameNoEx(String filename) {
|
||||
if ((filename != null) && (filename.length() > 0)) {
|
||||
int dot = filename.lastIndexOf('.');
|
||||
if ((dot > -1) && (dot < (filename.length()))) {
|
||||
return filename.substring(0, dot);
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
// 消息是否为文件
|
||||
public static boolean messageObjectIsFile(int type, MessageObject messageObject) {
|
||||
boolean cansave = (type == 4 || type == 5 || type == 6 || type == 10);
|
||||
boolean downloading = messageObject.loadedFileSize > 0;
|
||||
|
||||
//图片的问题
|
||||
if (type == 4 && messageObject.getDocument() == null) {
|
||||
return false;
|
||||
}
|
||||
return cansave || downloading;
|
||||
}
|
||||
|
||||
// 当文件有过加载过程,loadedFileSize > 0 ,所以不能用loadedFileSize判断是否正在下载
|
||||
public static boolean messageObjectIsDownloading(int type) {
|
||||
boolean cansave = (type == 4 || type == 5 || type == 6 || type == 10);
|
||||
return !cansave;
|
||||
}
|
||||
|
||||
// 取出整数的(二进制)某一位
|
||||
public static boolean getBit(int value, int i) {
|
||||
int mask = 1 << i;
|
||||
int ii = value & mask;
|
||||
if (ii == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static int setBit(int value, int i, boolean v) {
|
||||
int mask = 1 << i;
|
||||
if (v) {
|
||||
value |= mask;
|
||||
} else {
|
||||
value &= ~mask;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int reverseBit(int value, int i) {
|
||||
int mask = 1 << i;
|
||||
return value ^ mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取两个文本之间的文本值
|
||||
*
|
||||
* @param text 源文本 比如:欲取全文本为 12345
|
||||
* @param left 文本前面
|
||||
* @param right 后面文本
|
||||
* @return 返回 String
|
||||
*/
|
||||
public static String getSubString(String text, String left, String right) {
|
||||
String result = "";
|
||||
int zLen;
|
||||
if (left == null || left.isEmpty()) {
|
||||
zLen = 0;
|
||||
} else {
|
||||
zLen = text.indexOf(left);
|
||||
if (zLen > -1) {
|
||||
zLen += left.length();
|
||||
} else {
|
||||
zLen = 0;
|
||||
}
|
||||
}
|
||||
int yLen = text.indexOf(right, zLen);
|
||||
if (yLen < 0 || right == null || right.isEmpty()) {
|
||||
yLen = text.length();
|
||||
}
|
||||
result = text.substring(zLen, yLen);
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue