# -*- coding: utf-8 -*- """让 RemoteClient 支持中转(跨网络)。 改动很小,因为把"怎么传字节"抽成了 Transport: SocketTransport —— 原来的局域网直连 RelayTransport —— 走中转服务器 RemoteClient 里协议相关的代码(readLoop / call / awaitReply / send) 一行都不用动。 新增 connectViaRelay(baseUrl, code, token, listener, timeout): 和原有 connect(host, port, ...) 走同一套握手流程。 """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") CLIENT = ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "RemoteClient.java" JSON = ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "Json.java" # ---------------- 1. Json.escape ---------------- JSON_ANCHOR = " public static String write(Object value) {" JSON_NEW = ''' /** * 把字符串转义成能安全塞进 JSON 字符串字面量的形式。 * 只处理引号、反斜杠和控制字符 —— 中文不用转,JSON 本身是 UTF-8。 */ public static String escape(String text) { if (text == null) { return ""; } StringBuilder out = new StringBuilder(text.length() + 16); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); switch (ch) { case '"': out.append("\\\\\\""); break; case '\\\\': out.append("\\\\\\\\"); break; case '\\n': out.append("\\\\n"); break; case '\\r': out.append("\\\\r"); break; case '\\t': out.append("\\\\t"); break; default: if (ch < 0x20) { out.append(String.format("\\\\u%04x", (int) ch)); } else { out.append(ch); } break; } } return out.toString(); } ''' + JSON_ANCHOR # ---------------- 2. RemoteClient ---------------- FIELD_OLD = ''' private Socket socket; private BufferedReader reader;''' FIELD_NEW = ''' /** * 传输层。局域网直连时是 SocketTransport,跨网络时是 RelayTransport。 * 抽出来之后协议逻辑完全不用管数据是走 TCP 还是走中转的 HTTPS。 */ private Transport transport; private BufferedReader reader;''' CONNECTED_OLD = ''' public boolean isConnected() { return running && socket != null && !socket.isClosed(); }''' CONNECTED_NEW = ''' public boolean isConnected() { return running && transport != null && !transport.isClosed(); } /** 当前传输方式的描述,界面上用来显示"直连"还是"经中转"。 */ public String transportName() { if (transport == null) { return ""; } return transport instanceof RelayTransport ? "中转" : "直连"; } public RelayTransport relayTransport() { return transport instanceof RelayTransport ? (RelayTransport) transport : null; }''' CONNECT_OLD = ''' Socket sock = new Socket(); sock.connect(new InetSocketAddress(host, port), timeoutMs); sock.setSoTimeout(0); sock.setTcpNoDelay(true); this.socket = sock; this.reader = new BufferedReader(new InputStreamReader(sock.getInputStream(), UTF8), 8192); this.writer = sock.getOutputStream(); Thread thread = new Thread(new Runnable() {''' CONNECT_NEW = ''' Socket sock = new Socket(); sock.connect(new InetSocketAddress(host, port), timeoutMs); sock.setSoTimeout(0); sock.setTcpNoDelay(true); return startTransport(new Transport.SocketTransport(sock), code, token, listener, timeoutMs); } /** * 经中转服务器连接(跨网络)。 * * 手表没法直连电脑(两边都在 NAT 后面),所以两边都主动连出去, * 由中转按配对码把两条连接对上。中转只转发字节、不认识协议内容, * 所以电脑那边的配对和令牌校验照样生效。 */ public Map connectViaRelay(String baseUrl, String code, String token, Listener listener, int timeoutMs) throws IOException { close(); RelayTransport relay = new RelayTransport(baseUrl, code, deviceName); if (!relay.attach()) { String reason = relay.getLastError(); relay.close(); throw new IOException(reason == null || reason.isEmpty() ? "连不上中转服务器" : reason); } return startTransport(relay, code, token, listener, timeoutMs); } /** 握手 + 起读取线程。局域网和中转共用这一段。 */ private Map startTransport(Transport channel, String code, String token, Listener listener, int timeoutMs) throws IOException { this.listener = listener; this.running = true; this.replies.clear(); this.transport = channel; this.reader = new BufferedReader( new InputStreamReader(channel.input(), UTF8), 8192); this.writer = channel.output(); Thread thread = new Thread(new Runnable() {''' CLOSE_OLD = ''' public void close() { running = false; try { if (socket != null) { socket.close(); } } catch (IOException ignored) { // 关闭时的异常无需处理 } socket = null; reader = null; writer = null; replies.clear(); }''' CLOSE_NEW = ''' public void close() { running = false; try { if (transport != null) { transport.close(); } } catch (Exception ignored) { // 关闭时的异常无需处理 } transport = null; reader = null; writer = null; replies.clear(); }''' def main() -> int: ok = True # ---- Json.escape ---- text = JSON.read_text(encoding="utf-8") if "public static String escape(" in text: print(" Json.escape 已存在") elif JSON_ANCHOR not in text: print(" [失败] Json 锚点没找到") ok = False else: text = text.replace(JSON_ANCHOR, JSON_NEW, 1) JSON.write_text(text, encoding="utf-8") print(" Json.escape 已加") # ---- RemoteClient ---- text = CLIENT.read_text(encoding="utf-8") if "connectViaRelay" in text: print(" RemoteClient 已经改过") else: for old, new, tag in ((FIELD_OLD, FIELD_NEW, "字段换成 Transport"), (CONNECTED_OLD, CONNECTED_NEW, "isConnected 用 Transport"), (CONNECT_OLD, CONNECT_NEW, "connect 拆出 startTransport + connectViaRelay"), (CLOSE_OLD, CLOSE_NEW, "close 关 Transport")): if old not in text: print(f" [失败] {tag} —— 锚点没找到") ok = False continue text = text.replace(old, new, 1) print(f" {tag}") CLIENT.write_text(text, encoding="utf-8") # 检查还有没有裸用 socket 的地方 text = CLIENT.read_text(encoding="utf-8") leftovers = [line.strip() for line in text.splitlines() if "socket" in line and "Transport" not in line and "Socket" not in line and "//" not in line] if leftovers: print(f" [注意] 还有裸用 socket 的行:{leftovers}") else: print(" 没有残留的裸 socket 引用") return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())