Try to use DoH to get public proxy

This commit is contained in:
arm64v8a 2021-10-28 21:57:02 +08:00
parent edb9fc5501
commit b62eed4c9c
6 changed files with 223 additions and 33 deletions

View File

@ -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>):
}
}
}
}
}
}
}
}

View File

@ -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) {
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}