diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 3c657d938..faf8a3c35 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -347,7 +347,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" - implementation "com.squareup.okhttp3:okhttp:4.10.0" + implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10" + implementation 'com.neovisionaries:nv-websocket-client:2.14' implementation 'dnsjava:dnsjava:3.4.1' implementation "org.dizitart:nitrite:3.4.3" diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java index 8a4234cc7..f24821637 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java @@ -35,7 +35,7 @@ public class NekoConfig { public static ConfigItem migrate = addConfig("NekoConfigMigrate", configTypeBool, false); public static ConfigItem largeAvatarInDrawer = addConfig("AvatarAsBackground", configTypeInt, 0); // 0:TG Default 1:NekoX Default 2:Large Avatar public static ConfigItem unreadBadgeOnBackButton = addConfig("unreadBadgeOnBackButton", configTypeBool, false); - public static ConfigItem customPublicProxyIP = addConfig("customPublicProxyIP", configTypeString, ""); +// public static ConfigItem customPublicProxyIP = addConfig("customPublicProxyIP", configTypeString, ""); public static ConfigItem update_download_soucre = addConfig("update_download_soucre", configTypeInt, 0); // 0: Github 1: Channel 2:CDNDrive, removed public static ConfigItem useCustomEmoji = addConfig("useCustomEmoji", configTypeBool, false); public static ConfigItem repeatConfirm = addConfig("repeatConfirm", configTypeBool, false); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java index f96cddde1..bb08c8b96 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java @@ -2,12 +2,19 @@ package tw.nekomimi.nekogram.proxy.tcp2ws; import android.annotation.SuppressLint; +import com.neovisionaries.ws.client.ThreadType; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; +import com.neovisionaries.ws.client.WebSocketListener; +import com.neovisionaries.ws.client.WebSocketState; + import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.checkerframework.common.value.qual.IntVal; import org.telegram.messenger.FileLog; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -16,17 +23,19 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import okio.ByteString; +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; + +import cn.hutool.http.ssl.AndroidSupportSSLFactory; +import cn.hutool.http.ssl.CustomProtocolsSSLFactory; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.utils.DnsFactory; @@ -37,7 +46,7 @@ public class WsProxyHandler extends Thread { private final WsLoader.Bean bean; private Socket clientSocket; - private WebSocket wsSocket = null; + private WebSocket webSocket = null; private final byte[] buffer = new byte[4096]; private String wsHost = ""; @@ -67,7 +76,7 @@ public class WsProxyHandler extends Thread { FileLog.d("socks5 handshake and websocket connection done"); // Start read from client socket and send to websocket this.clientSocket.setSoTimeout(1000); - while (clientSocket != null && wsSocket != null && wsStatus.get() == STATUS_OPENED && !clientSocket.isClosed() && !clientSocket.isInputShutdown()) { + while (clientSocket != null && webSocket != null && wsStatus.get() == STATUS_OPENED && !clientSocket.isClosed() && !clientSocket.isInputShutdown()) { int readLen = 0; try { readLen = this.clientInputStream.read(buffer); @@ -77,9 +86,12 @@ public class WsProxyHandler extends Thread { continue; } FileLog.d(String.format("[%s] read %d from local", wsHost, readLen)); - if (readLen == -1) throw new Exception(String.format("[%s] socks closed", wsHost));; - if (wsStatus.get() != STATUS_OPENED) throw new Exception(String.format("[%s] ws closed when trying to write", wsHost));; - this.wsSocket.send(ByteString.of(buffer, 0, readLen)); + if (readLen == -1) throw new Exception(String.format("[%s] socks closed", wsHost)); + ; + if (wsStatus.get() != STATUS_OPENED) + throw new Exception(String.format("[%s] ws closed when trying to write", wsHost)); + ; + this.webSocket.sendBinary(Arrays.copyOf(buffer, readLen)); } } catch (SocketException se) { if ("Socket closed".equals(se.getMessage())) { @@ -107,95 +119,50 @@ public class WsProxyHandler extends Thread { } catch (IOException e) { // ignore } -// try { -// if (wsSocket != null) { -// wsSocket.cancel(); -// } -// } catch (Exception e) { -// // ignore -// } + try { + if (webSocket != null) { + webSocket.sendClose(); + } + } catch (Exception e) { + // ignore + } clientSocket = null; -// wsSocket = null; + webSocket = null; } - private static volatile OkHttpClient okhttpClient = null; - private static final Object okhttpLock = new Object(); - - private static OkHttpClient getOkHttpClientInstance() { - if (okhttpClient == null) { - synchronized (okhttpLock) { - if (okhttpClient == null) { - okhttpClient = new OkHttpClient.Builder() - .dns(s -> { - ArrayList ret = new ArrayList<>(); - FileLog.d("okhttpWS: resolving: " + s); - if (StringUtils.isNotBlank(NekoConfig.customPublicProxyIP.String())) { - ret.add(InetAddress.getByName(NekoConfig.customPublicProxyIP.String())); - } else { - ret.addAll(DnsFactory.lookup(s)); - } - FileLog.d("okhttpWS: resolved: " + ret); - return ret; - }) - .build(); - } - } - } - return okhttpClient; - } - - private void connectToServer(String wsHost) { + private void connectToServer(String wsHost) throws Exception { this.wsHost = wsHost; - FileLog.e(new Exception("WS: Connect To Server")); - getOkHttpClientInstance() - .newWebSocket(new Request.Builder() - .url((bean.getTls() ? "wss://" : "ws://") + wsHost + "/api") - .build(), new WebSocketListener() { - @Override - public void onOpen(@NotNull okhttp3.WebSocket webSocket, @NotNull Response response) { - WsProxyHandler.this.wsSocket = webSocket; - wsStatus.set(STATUS_OPENED); - connecting.countDown(); - } + WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000); + webSocket = factory.createSocket((bean.getTls() ? "wss://" : "ws://") + wsHost + "/api"); + webSocket.addListener(new WebSocketAdapter() { + @Override + public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception { + WsProxyHandler.this.clientOutputStream.write(binary); + } - @Override - public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) { - FileLog.e("[" + wsHost + "] Failure:" + t); - wsStatus.set(STATUS_FAILED); - connecting.countDown(); - } + @Override + public void onError(WebSocket websocket, WebSocketException cause) throws Exception { + FileLog.e(cause); + wsStatus.set(STATUS_FAILED); + } - @Override - public void onMessage(@NotNull okhttp3.WebSocket webSocket, @NotNull ByteString bytes) { - FileLog.d("[" + wsHost + "] Received " + bytes.size() + " bytes from ws"); - try { - if (wsStatus.get() == STATUS_OPENED && !WsProxyHandler.this.clientSocket.isClosed()) - WsProxyHandler.this.clientOutputStream.write(bytes.toByteArray()); - } catch (IOException e) { - FileLog.e(e); - wsStatus.set(STATUS_FAILED); - webSocket.cancel(); - } - } + @Override + public void onConnectError(WebSocket websocket, WebSocketException exception) throws Exception { + FileLog.d(String.format("[%s] WS connect failed: %s", wsHost, exception.toString())); + wsStatus.set(STATUS_FAILED); + connecting.countDown(); + } - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { - FileLog.d("[" + wsHost + "] Received text: " + text); - } - - @Override - public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - FileLog.d("[" + wsHost + "] Closed: " + code + " " + reason); - wsStatus.set(STATUS_CLOSED); - } - - @Override - public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - FileLog.d("[" + wsHost + "] Closing: " + code + " " + reason); - wsStatus.set(STATUS_CLOSED); - } - }); + @Override + public void onConnected(WebSocket websocket, Map> headers) throws Exception { + FileLog.d(String.format("[%s] WS connected", wsHost)); + wsStatus.set(STATUS_OPENED); + connecting.countDown(); + } + }); + webSocket.addProtocol("binary"); + webSocket.connect(); } private static final byte[] RESP_AUTH = new byte[]{0x05, 0x00}; diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java index f4ae09e67..3157f7754 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java @@ -119,9 +119,9 @@ public class NekoGeneralSettingsActivity extends BaseFragment { private final AbstractConfigCell autoUpdateSubInfoRow = cellGroup.appendCell(new ConfigCellTextCheck(NekoConfig.autoUpdateSubInfo)); private final AbstractConfigCell useSystemDNSRow = cellGroup.appendCell(new ConfigCellTextCheck(NekoConfig.useSystemDNS)); private final AbstractConfigCell customDoHRow = cellGroup.appendCell(new ConfigCellTextInput(null, NekoConfig.customDoH, "https://1.0.0.1/dns-query", null)); - private final AbstractConfigCell customPublicProxyIPRow = cellGroup.appendCell(new ConfigCellTextDetail(NekoConfig.customPublicProxyIP, (view, position) -> { - customDialog_BottomInputString(position, NekoConfig.customPublicProxyIP, LocaleController.getString("customPublicProxyIPNotice"), "IP"); - }, LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty))); +// private final AbstractConfigCell customPublicProxyIPRow = cellGroup.appendCell(new ConfigCellTextDetail(NekoConfig.customPublicProxyIP, (view, position) -> { +// customDialog_BottomInputString(position, NekoConfig.customPublicProxyIP, LocaleController.getString("customPublicProxyIPNotice"), "IP"); +// }, LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty))); private final AbstractConfigCell dividerConnection = cellGroup.appendCell(new ConfigCellDivider()); private final AbstractConfigCell headerFolder = cellGroup.appendCell(new ConfigCellHeader(LocaleController.getString("Folder")));