# -*- coding: utf-8 -*-
r'''让客户端能用"纯 IP + 明文 HTTP"的中转(新服务器就是这种)。
== 背景 ==
2026-10-08 换了服务器:AWS Lightsail,**有公网 IP**(yk.xxmxxs.top),
中转直接监听 13245,**不再走 Cloudflare 隧道**。
好处一大堆(延迟从 1000ms 降到 1ms、不再有 7844 阻断、内存 4 倍),
但有个直接后果:**现在是明文 HTTP,没有 HTTPS 证书**。
而两个客户端原来都默认往 https 上走:
1. Android 的 RelayTransport:用户填 `yk.xxmxxs.top:13245`
(没写协议头)会被强制加上 `https://` -> 连不上(那台机器没证书)
2. Android 9+ **默认禁止明文 HTTP**。没有
`android:usesCleartextTraffic="true"` 的话,请求会被系统直接掐掉,
报的还是很含糊的 "Cleartext HTTP traffic not permitted"。
Windows 那边已经是对的:relay.py 的 _PRIVATE_HOST 正则里
`\d{1,3}(?:\.\d{1,3}){3}` 把**任意纯 IP(含公网)**都当"用明文",
所以裸 IP 会正确走 http://。
== 这个补丁做什么 ==
1. RelayTransport 的协议猜测改成和 Windows 一致:
纯 IP / localhost / *.local -> http://
域名 -> https://
2. AndroidManifest 加 usesCleartextTraffic="true"
3. 顺手清掉 windows/lanremote/relay.py 里一段**函数内死代码**
(_guess_scheme 的 return 之后还有一份一模一样的拷贝,
缩进仍属于函数体所以不报错,但看着很迷惑)
== 关于 HTTPS ==
明文 HTTP 意味着**同网络的人能看到流量**(虽然配对令牌、内容本来
就是加密的,但 URL、设备名这些会露)。真正的解法是上 HTTPS:
把域名 relay.xxmxxs.top 的 A 记录指到 yk.xxmxxs.top,
然后 certbot 签一张 Let's Encrypt 证书,confd 挂上。
那一步要用户动 DNS,所以先让明文能用(这个补丁),
HTTPS 等 DNS 指过来再做。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
JAVA = (ROOT / "android" / "src" / "com" / "lanremote" / "watch"
/ "RelayTransport.java")
MANIFEST = ROOT / "android" / "AndroidManifest.xml"
WINRELAY = ROOT / "windows" / "lanremote" / "relay.py"
def patch(path: Path, old: str, new: str, what: str, out: list) -> str:
raw = path.read_bytes()
bom = raw.startswith(b"\xef\xbb\xbf")
text = raw.decode("utf-8-sig")
if old not in text:
out.append(("✗", what, "找不到锚点"))
return text
if text.count(old) != 1:
out.append(("!", what, "锚点 %d 处" % text.count(old)))
text = text.replace(old, new, 1)
path.write_bytes((b"\xef\xbb\xbf" if bom else b"")
+ text.encode("utf-8"))
out.append(("✓", what, ""))
return text
# ---------------------------------------------------------------- 1. Java
JAVA_OLD = ''' if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "https://" + url;
}'''
JAVA_NEW = ''' if (!url.startsWith("http://") && !url.startsWith("https://")) {
// 没写协议头时猜一个。规则要**和 Windows 端一致**
// (windows/lanremote/relay.py 的 _guess_scheme):
//
// IP / localhost / *.local -> http://
// 域名 -> https://
//
// 为什么纯 IP 走明文:IP 地址的中转基本都是自己搭的
// (局域网那台,或者像现在这样一台有公网 IP 的服务器),
// 上面往往没有证书。原来一律加 https:// 的结果是**永远连不上**,
// 而且报的是 SSL 错误,会把人带去查证书查半天。
//
// ⚠ 纯 IP 判断包含**公网 IP** —— 2026-10-08 换到 Lightsail
// 之后中转就是 http://yk.xxmxxs.top:13245,
// 只把 192.168/10./172.16 当私有的话这里会走错。
String hostOnly = url;
int slash = hostOnly.indexOf('/');
if (slash > 0) {
hostOnly = hostOnly.substring(0, slash);
}
int colon = hostOnly.indexOf(':');
if (colon > 0) {
hostOnly = hostOnly.substring(0, colon);
}
boolean plain = hostOnly.equalsIgnoreCase("localhost")
|| hostOnly.toLowerCase().endsWith(".local")
|| hostOnly.matches("\\\\d{1,3}(\\\\.\\\\d{1,3}){3}");
url = (plain ? "http://" : "https://") + url;
}'''
# ---------------------------------------------------------------- 2. 清单
MAN_OLD = ''' '''
MAN_NEW = '''
'''
# ---------------------------------------------------------------- 3. 死代码
DEAD_OLD = ''' host = address.split("/", 1)[0]
return "http://" if _PRIVATE_HOST.match(host) else "https://"
"""用户没写 http/https 时,猜一个。
IP 地址 / localhost -> http://
局域网里的中转服务是明文 HTTP。如果猜成 https,永远连不上,
而且报的是 SSL 错误,很容易误导人去查证书。
域名 -> https://
走公网必须加密(Cloudflare 隧道本来就是 https)。
"""
host = address.split("/", 1)[0]
return "http://" if _PRIVATE_HOST.match(host) else "https://"'''
DEAD_NEW = ''' host = address.split("/", 1)[0]
return "http://" if _PRIVATE_HOST.match(host) else "https://"'''
def main() -> int:
out: list = []
patch(JAVA, JAVA_OLD, JAVA_NEW, "RelayTransport 协议猜测改成和 Windows 一致",
out)
patch(MANIFEST, MAN_OLD, MAN_NEW, "AndroidManifest 允许明文 HTTP", out)
patch(WINRELAY, DEAD_OLD, DEAD_NEW, "清掉 relay.py 里的函数内死代码", out)
print(" ── 结果 ──")
for m, what, why in out:
print(" %s %s%s" % (m, what, (" —— " + why) if why else ""))
return 0 if not any(m == "✗" for m, _w, _y in out) else 1
if __name__ == "__main__":
sys.exit(main())