diff --git a/_build/_msg_a.txt b/_build/_msg_a.txt new file mode 100644 index 0000000..20b3edb --- /dev/null +++ b/_build/_msg_a.txt @@ -0,0 +1,26 @@ +阶段1+2:恢复 HTTP/2 加速 + 重做官网 + +阶段1 网络加速 +- 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2), + 实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。 + 恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。 +- 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP), + 但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。 + 已清掉这段无效配置。 +- BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66, + 没编 tcp_bbr 模块也没配置项。改用 cubic + 调优: + TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0 + (长轮询场景关键)、tcp_tw_reuse=1。 + +阶段2 官网重做 +- 拆出独立模块 relay/landing.py(relay.py 已两千多行, + 再塞 HTML 没法看) +- 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 / + 三步上手 / 下载 / FAQ / 页脚 +- 保持白色简洁:底白、浅灰面板、细边框、大量留白 +- logo 用内联 SVG(ICO 放 img 里实测是破图) +- 管理入口从官网移除(管理不对外开放) + +过程中被自己的测试抓到两个 bug: +- 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库 +- _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError diff --git a/_build/add_orphan_stage.py b/_build/add_orphan_stage.py new file mode 100644 index 0000000..a354aa1 --- /dev/null +++ b/_build/add_orphan_stage.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +r'''把「未调用方法」检查加进全量套件,然后把版本升到 1.7.1。 + +为什么加这个阶段: + 1.7.0 交付后是白屏 —— initTabs() 漏调,编译能过、别的测试也能过, + 只有真机跑起来才看得见。加进套件就不靠运气了。 +''' +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +VERIFY = ROOT / "_tests" / "verify_all.ps1" + +ANCHOR = "# ---------------------------------------------------------------- 2a" + +NEW = '''# ---------------------------------------------------------------- 1z +Banner "1z/7 手表分页完整性(防白屏:方法定义了必须被调用)" +& python (Join-Path $root "_tests\\test_no_orphan_methods.py") 2>&1 | + Select-Object -Last 6 +Record "手表分页完整性" ($LASTEXITCODE -eq 0) + +''' + ANCHOR + + +def main() -> int: + raw = VERIFY.read_bytes() + had_bom = raw.startswith(b"\xef\xbb\xbf") + text = raw.decode("utf-8-sig") + + if "test_no_orphan_methods" in text: + print(" 套件里已经有这个阶段") + elif ANCHOR in text: + text = text.replace(ANCHOR, NEW, 1) + VERIFY.write_bytes((b"\xef\xbb\xbf" if had_bom else b"") + + text.replace("\r\n", "\n").encode("utf-8")) + print(" 已加「手表分页完整性」阶段") + else: + print(" [失败] 锚点没找到") + return 1 + + # 升版本 1.7.0 -> 1.7.1 + proc = __import__("subprocess").run( + [sys.executable, str(ROOT / "_build" / "bump_version_generic.py"), + "1.7.0", "1.7.1"], + cwd=str(ROOT), capture_output=True, text=True, + encoding="utf-8", errors="replace") + print(proc.stdout[-900:]) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/apply_edge_optimize.sh b/_build/apply_edge_optimize.sh new file mode 100644 index 0000000..c1f8c40 --- /dev/null +++ b/_build/apply_edge_optimize.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# 1) 把官网上的「管理入口」链接删掉(管理不对外开放) +# 2) 应用优选 IP:把 Cloudflare 隧道域名指到实测最快的边缘 IP +set -u + +RELAY_DIR=/root/lanremote-relay +CONFIG=/etc/cloudflared/config.yml + +echo "===== 1. 备份原配置 =====" +cp -f /etc/hosts /root/hosts.bak.$(date +%s) 2>/dev/null || true +cp -f "$CONFIG" /root/config.yml.bak 2>/dev/null || true +echo " 已备份 /etc/hosts 和 config.yml" + +echo +echo "===== 2. 应用优选 IP(写 /etc/hosts 覆盖 GeoDNS)=====" +# 从探测结果里取最快的两个 +BEST=$(head -2 /root/cf_best.txt 2>/dev/null | awk '{print $1}') +echo " 最快的 IP: $BEST" + +# 清掉之前加过的 +sed -i '/argotunnel.com/d' /etc/hosts + +FIRST=$(echo "$BEST" | head -1) +SECOND=$(echo "$BEST" | tail -1) +{ + echo "" + echo "# ykxmz:优选 Cloudflare 隧道边缘(覆盖 GeoDNS,实测延迟更低)" + echo "$FIRST region1.v2.argotunnel.com" + echo "$SECOND region2.v2.argotunnel.com" + echo "$FIRST region1.v2.argotunnel.com" + echo "$SECOND region2.v2.argotunnel.com" +} >> /etc/hosts + +echo " /etc/hosts 现在有:" +grep argotunnel /etc/hosts | sed 's/^/ /' + +echo +echo "===== 3. 确认解析结果变了 =====" +getent hosts region1.v2.argotunnel.com | head -3 | sed 's/^/ region1 -> /' +getent hosts region2.v2.argotunnel.com | head -3 | sed 's/^/ region2 -> /' + +echo +echo "===== 4. 重启隧道 =====" +systemctl restart lanremote-tunnel +sleep 35 +echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')" +echo " 落点:" +grep -oE 'location=[a-z0-9]+' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -3 | sed 's/^/ /' + +echo +echo "===== 5. 公网延迟对比(20 次)=====" +ok=0; total=0 +for i in $(seq 1 20); do + t=$(curl -s -o /dev/null -w '%{time_total}' --max-time 15 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health 2>/dev/null) + c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health 2>/dev/null) + if [ "$c" = "200" ]; then + ok=$((ok+1)) + printf " %2d: %s 秒\n" "$i" "$t" + else + printf " %2d: 失败\n" "$i" + fi + total=$((total+1)) + sleep 1 +done +echo " 可用 $ok/$total" diff --git a/_build/bump_to_150.py b/_build/bump_to_150.py deleted file mode 100644 index 8a6d189..0000000 --- a/_build/bump_to_150.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -r"""精准升级版本号 1.4.0 -> 1.5.0。 - -**不能全局替换** —— 项目里有 40 多个文件含 "1.4.0",其中大部分是: - * 测试用的数据(配对码、设备版本号字段) - * HTTP User-Agent "LanRemote/1.4.0" - (Cloudflare 的 Browser Integrity Check 认这个, - 而且 20 多个测试都在断言它 —— 动它要同步改一大片) - * _build/ 下的一次性脚本(历史记录,不用改) - -所以只改**用户能看到版本号的地方**: - 1. android/build_apk.ps1 APK 文件名 + manifest 版本 - 2. docs/*.txt 三份文档 - 3. windows/lanremote/__init__.py __version__ - 4. 会断言版本号的测试 - -UA 保持 "LanRemote/1.4.0" 不动(它是协议标识,不是产品版本)。 -""" -from __future__ import annotations - -import re -import sys -from pathlib import Path - -ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") -OLD, NEW = "1.4.0", "1.5.0" - -# 逐文件、逐模式改,写清楚每一处为什么改 -PLAN = [ - # (文件, 旧片段, 新片段, 说明) - ("android/build_apk.ps1", - '$apkName = "ykxmz-watch-1.4.0.apk"', - '$apkName = "ykxmz-watch-1.5.0.apk"', - "APK 文件名"), - ("android/build_apk.ps1", - '--version-code 3 `', - '--version-code 5 `', - "versionCode"), - ("android/build_apk.ps1", - '--version-name "1.4.0" `', - '--version-name "1.5.0" `', - "versionName"), - ("windows/lanremote/__init__.py", - '"1.4.0"', - '"1.5.0"', - "__version__"), -] - -# 文档:把"版本 1.4.0"之类改掉 -DOC_PATTERNS = [ - (r"1\.4\.0", "1.5.0"), -] - -# 测试里断言成品文件名的 -TEST_PATTERNS = [ - ("ykxmz-1.4.0", "ykxmz-1.5.0"), - ("ykxmz-watch-1.4.0", "ykxmz-watch-1.5.0"), -] - - -def main() -> int: - changed = [] - - # ---- 1. 精准替换 ---- - for rel, old, new, why in PLAN: - path = ROOT / rel - if not path.is_file(): - print(f" [跳过] {rel} 不存在") - continue - text = path.read_text(encoding="utf-8") - if old not in text: - print(f" [跳过] {rel}: 找不到 {old[:40]!r}(可能已改)") - continue - path.write_text(text.replace(old, new, 1), - encoding="utf-8", newline="\n") - changed.append(f"{rel}({why})") - - # ---- 2. 文档 ---- - for path in sorted((ROOT / "docs").glob("*.txt")): - raw = path.read_bytes() - had_bom = raw.startswith(b"\xef\xbb\xbf") - text = raw.decode("utf-8-sig") - new_text = re.sub(r"1\.4\.0", "1.5.0", text) - if new_text != text: - path.write_bytes((b"\xef\xbb\xbf" if had_bom else b"") - + new_text.replace("\r\n", "\n").encode("utf-8")) - changed.append(f"docs/{path.name}") - - # ---- 3. 断言成品文件名的测试 ---- - for name in ("test_package_remote.py", "test_apk_contents.py", - "acceptance.py"): - path = ROOT / "_tests" / name - if not path.is_file(): - continue - text = path.read_text(encoding="utf-8") - original = text - for old, new in TEST_PATTERNS: - text = text.replace(old, new) - if text != original: - path.write_text(text, encoding="utf-8", newline="\n") - changed.append(f"_tests/{name}") - - print(f" 共改了 {len(changed)} 处:") - for item in changed: - print(f" {item}") - - print() - print(" 注意:HTTP User-Agent 仍是 \"LanRemote/1.4.0\"(协议标识,不是产品版本)。") - - # ---- 复核 ---- - print() - print(" 复核关键位置:") - for rel, needle in (("android/build_apk.ps1", "ykxmz-watch-1.5.0.apk"), - ("android/AndroidManifest.xml", '1.5.0'), - ("_build/package_remote.py", '"1.5.0"')): - path = ROOT / rel - if path.is_file(): - ok = needle in path.read_text(encoding="utf-8") - print(f" {rel}: {'✓' if ok else '✗'} 含 {needle}") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/_build/check_bbr.sh b/_build/check_bbr.sh new file mode 100644 index 0000000..8812304 --- /dev/null +++ b/_build/check_bbr.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# 确认 BBR 能不能用,并把有效的调优持久化 +set -u + +echo "===== 1. 内核信息 =====" +echo " 版本: $(uname -r)" +echo " 架构: $(uname -m)" + +echo +echo "===== 2. BBR 模块存在吗 =====" +found=$(find /lib/modules -name 'tcp_bbr*' 2>/dev/null | head -3) +if [ -n "$found" ]; then + echo " 找到模块文件:" + echo "$found" | sed 's/^/ /' +else + echo " 没有任何 tcp_bbr 模块文件" +fi + +echo +echo "===== 3. 内核编译时开了 BBR 吗 =====" +if [ -f /proc/config.gz ]; then + zgrep -i CONFIG_TCP_CONG_BBR /proc/config.gz | sed 's/^/ /' +elif [ -f /boot/config-$(uname -r) ]; then + grep -i CONFIG_TCP_CONG_BBR /boot/config-$(uname -r) | sed 's/^/ /' +else + echo " 没有内核配置文件可查" +fi + +echo +echo "===== 4. 试着手动加载 =====" +modprobe tcp_bbr 2>&1 | sed 's/^/ /' || true +echo " 加载后可用算法: $(sysctl -n net.ipv4.tcp_available_congestion_control)" + +echo +echo "===== 5. 当前生效的网络参数 =====" +for k in net.ipv4.tcp_congestion_control net.core.default_qdisc \ + net.core.rmem_max net.core.wmem_max net.ipv4.tcp_fastopen \ + net.ipv4.tcp_tw_reuse net.ipv4.tcp_mtu_probing; do + printf " %-40s = %s\n" "$k" "$(sysctl -n $k 2>/dev/null || echo '?')" +done + +echo +echo "===== 6. 既然没有 BBR,用 cubic 能调的都调上 =====" +cat > /etc/sysctl.d/98-ykxmz-net.conf <<'EOF' +# ykxmz 网络调优(4G 高丢包链路) +# +# ⚠ 这台设备的精简内核**没有 BBR**(只有 reno/cubic), +# 所以下面用 cubic,再把其它能帮上忙的参数调好。 +# 如果以后换了带 BBR 的内核,把 tcp_congestion_control 改成 bbr 即可。 + +# TCP 缓冲:长途高延迟链路需要更大窗口 +net.core.rmem_max = 4194304 +net.core.wmem_max = 4194304 +net.ipv4.tcp_rmem = 4096 87380 4194304 +net.ipv4.tcp_wmem = 4096 65536 4194304 + +# 快速打开:省一个 RTT +net.ipv4.tcp_fastopen = 3 + +# 空闲后不重置拥塞窗口 —— 长轮询场景很关键, +# 否则每次轮询间隔后都要从慢启动重新爬 +net.ipv4.tcp_slow_start_after_idle = 0 + +# 早重传,别干等 +net.ipv4.tcp_retries2 = 8 + +# 复用 TIME_WAIT 连接 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_fin_timeout = 15 + +# 接收缓冲自动调节 +net.ipv4.tcp_moderate_rcvbuf = 1 +EOF +sysctl -p /etc/sysctl.d/98-ykxmz-net.conf 2>&1 | grep -v '^net.core.default_qdisc\|^net.ipv4.tcp_congestion_control' | sed 's/^/ /' + +echo +echo " 最终拥塞控制: $(sysctl -n net.ipv4.tcp_congestion_control)" +echo " 最终队列: $(sysctl -n net.core.default_qdisc)" diff --git a/_build/check_wix_release.py b/_build/check_wix_release.py new file mode 100644 index 0000000..6d43b3a --- /dev/null +++ b/_build/check_wix_release.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +r'''查 WiX Toolset 的独立二进制下载地址(不依赖 .NET SDK)。''' +from __future__ import annotations + +import json +import sys +import urllib.request + +API = "https://api.github.com/repos/wixtoolset/wix/releases" + + +def main() -> int: + req = urllib.request.Request(API, headers={ + "User-Agent": "ykxmz-check", + "Accept": "application/vnd.github+json", + }) + with urllib.request.urlopen(req, timeout=60) as resp: + releases = json.load(resp) + + for rel in releases[:4]: + print(f" {rel.get('tag_name')} ({rel.get('published_at', '')[:10]})" + f"{' [pre]' if rel.get('prerelease') else ''}") + for asset in rel.get("assets", []): + name = asset.get("name", "") + size = asset.get("size", 0) / 1024 / 1024 + print(f" {name:44} {size:6.2f} MB") + print(f" {asset.get('browser_download_url')}") + print() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/diag_edge.sh b/_build/diag_edge.sh index 66df04e..5a02dad 100644 --- a/_build/diag_edge.sh +++ b/_build/diag_edge.sh @@ -1,61 +1,38 @@ #!/bin/bash -# 能不能让 cloudflared 连更近的 Cloudflare 边缘? -# -# 现状:它连的是 198.41.x.x,日志显示落地在 lax01/sjc07(美西)。 -# 从国内 4G 到美西,丢包 40%,握手经常超时。 -# 如果能连香港/新加坡/日本,延迟和丢包都会好很多。 +# 诊断:为什么 /etc/hosts 覆盖没生效,隧道还是落到 lax set -u -echo "===== 1. cloudflared 支持哪些边缘相关参数 =====" -cloudflared tunnel run --help 2>&1 | grep -iE 'edge|region|protocol|address' \ - | sed 's/^/ /' -echo -echo " --- 顶层参数 ---" -cloudflared tunnel --help 2>&1 | grep -iE 'edge|region' | sed 's/^/ /' +echo "===== 1. /etc/hosts 实际内容(尾部)=====" +tail -8 /etc/hosts | sed 's/^/ /' echo -echo "===== 2. argotunnel 区域域名解析出哪些 IP =====" -for host in region1.v2.argotunnel.com region2.v2.argotunnel.com; do - echo " $host:" - getent ahostsv4 "$host" 2>/dev/null | awk '{print $1}' | sort -u \ - | sed 's/^/ /' -done +echo "===== 2. nsswitch 的解析顺序 =====" +grep '^hosts' /etc/nsswitch.conf | sed 's/^/ /' echo -echo "===== 3. 到这些 IP 的 TCP 7844 延迟(各测 3 次)=====" -for ip in $(getent ahostsv4 region1.v2.argotunnel.com 2>/dev/null \ - | awk '{print $1}' | sort -u | head -4); do - total=0; ok=0 - for i in 1 2 3; do - t=$(timeout 6 bash -c "exec 3<>/dev/tcp/$ip/7844" 2>/dev/null \ - && echo ok || echo fail) - [ "$t" = "ok" ] && ok=$((ok+1)) - done - printf " %-18s 7844 通 %s/3\n" "$ip" "$ok" -done +echo "===== 3. glibc 解析(getent)=====" +getent ahostsv4 region1.v2.argotunnel.com 2>/dev/null | head -3 | sed 's/^/ v4: /' +getent ahostsv6 region1.v2.argotunnel.com 2>/dev/null | head -3 | sed 's/^/ v6: /' echo -echo "===== 4. 这些 IP 的 ICMP 延迟 =====" -for ip in $(getent ahostsv4 region1.v2.argotunnel.com 2>/dev/null \ - | awk '{print $1}' | sort -u | head -3); do - r=$(ping -c 3 -W 2 "$ip" 2>/dev/null | tail -1) - printf " %-18s %s\n" "$ip" "$r" -done +echo "===== 4. 设备有没有全局 IPv6 地址 =====" +ip -6 addr show scope global 2>/dev/null | grep -E 'inet6' | head -4 | sed 's/^/ /' \ + || echo " (没有全局 IPv6)" +ip -6 route show default 2>/dev/null | head -2 | sed 's/^/ 默认路由: /' \ + || echo " (没有 IPv6 默认路由)" echo -echo "===== 5. 本机出口 IP / 运营商 =====" -curl -s --max-time 8 https://1.1.1.1/cdn-cgi/trace 2>/dev/null \ - | grep -E '^(ip|loc|colo)=' | sed 's/^/ /' +echo "===== 5. cloudflared 支持哪些 edge 相关参数 =====" +cloudflared tunnel --help 2>&1 | grep -iE 'edge|region|protocol|ip-version' | head -12 | sed 's/^/ /' echo -echo " 到 Cloudflare 的落地 colo(上面 colo= 就是,如果是 LAX/SJC 说明被路由到美西)" +echo " --- run 子命令的 ---" +cloudflared tunnel run --help 2>&1 | grep -iE 'edge|region|protocol|ip-version' | head -12 | sed 's/^/ /' echo -echo "===== 6. 试几个 Cloudflare 常用 anycast IP 的 7844 =====" -for ip in 198.41.192.167 198.41.200.13 198.41.192.7 162.159.192.1 172.64.155.1; do - ok=0 - for i in 1 2; do - timeout 5 bash -c "exec 3<>/dev/tcp/$ip/7844" 2>/dev/null \ - && ok=$((ok+1)) - done - printf " %-18s 7844 通 %s/2\n" "$ip" "$ok" -done +echo "===== 6. 当前隧道服务实际用的参数 =====" +grep -o 'ExecStart=.*' /etc/systemd/system/lanremote-tunnel.service | head -1 | sed 's/^/ /' + +echo +echo "===== 7. cloudflared 近期的连接日志 =====" +grep -E 'location=|edge discovery|Initial protocol' /root/lanremote-relay/tunnel.log 2>/dev/null \ + | tail -6 | sed 's/^/ /' diff --git a/_build/find_cf_edge.sh b/_build/find_cf_edge.sh new file mode 100644 index 0000000..b563560 --- /dev/null +++ b/_build/find_cf_edge.sh @@ -0,0 +1,107 @@ +#!/bin/bash +# 实测设备到 Cloudflare 各边缘 IP 的延迟,挑最快的做优选 IP。 +# +# 原理: +# cloudflared 建隧道时会连 region1.v2.argotunnel.com / region2.v2.argotunnel.com, +# 这两个域名由 Cloudflare 的 GeoDNS 解析 —— 国内 4G 常常被解析到美西(lax), +# 单程 200ms+,加上丢包重传,实际往返能到 4 秒。 +# +# 但 Cloudflare 在全球有大量 anycast 边缘 IP,其中不少在亚太(香港/新加坡/ +# 东京/首尔)能直连,延迟低得多。做法是: +# 1. 测一批候选 IP 的 TCP 7844(隧道端口)握手延迟 +# 2. 挑最快的写进 /etc/hosts,覆盖 GeoDNS 的结果 +# 3. 重启隧道,实测隧道往返 +set -u + +CANDIDATES=" +104.16.0.0 +104.17.0.0 +104.18.0.0 +104.19.0.0 +104.20.0.0 +104.21.0.0 +104.22.0.0 +104.23.0.0 +104.24.0.0 +104.25.0.0 +104.26.0.0 +104.27.0.0 +104.16.132.229 +104.18.2.0 +104.18.3.0 +104.18.32.0 +104.18.33.0 +104.18.34.0 +104.18.35.0 +104.18.36.0 +104.18.37.0 +104.18.38.0 +104.18.39.0 +162.159.192.1 +162.159.193.1 +162.159.195.1 +172.64.0.0 +172.64.1.0 +172.64.2.0 +172.64.3.0 +172.64.4.0 +172.64.5.0 +172.64.6.0 +172.64.7.0 +172.64.8.0 +172.64.9.0 +172.64.10.0 +172.64.11.0 +172.64.12.0 +172.64.13.0 +172.64.14.0 +172.64.15.0 +172.64.16.0 +172.64.17.0 +172.64.18.0 +172.64.19.0 +172.64.20.0 +172.64.21.0 +172.64.22.0 +172.64.23.0 +172.64.24.0 +172.64.25.0 +172.64.26.0 +172.64.27.0 +172.64.28.0 +172.64.29.0 +172.64.30.0 +172.64.31.0 +" + +echo "===== 当前 cloudflared 连的是哪个边缘 =====" +grep -oE 'location=[a-z0-9]+' /root/lanremote-relay/tunnel.log 2>/dev/null | sort | uniq -c | tail -5 | sed 's/^/ /' +echo " 当前 region 域名解析到:" +getent hosts region1.v2.argotunnel.com 2>/dev/null | head -2 | sed 's/^/ /' +getent hosts region2.v2.argotunnel.com 2>/dev/null | head -2 | sed 's/^/ /' + +echo +echo "===== 逐个测 TCP 7844 握手延迟(每个 2 次取最快)=====" +: > /root/cf_ip_result.txt +for ip in $CANDIDATES; do + best=99999 + for _ in 1 2; do + t=$( { /usr/bin/time -f "%e" timeout 3 bash -c "exec 3<>/dev/tcp/$ip/7844" ; } 2>&1 ) + if [ -n "$t" ] && [ "$t" != "0.00" ]; then + ms=$(echo "$t" | awk '{printf "%d", $1*1000}') + [ "$ms" -lt "$best" ] && best=$ms + fi + done + if [ "$best" -lt 99999 ]; then + echo "$best $ip" >> /root/cf_ip_result.txt + fi +done + +if [ -s /root/cf_ip_result.txt ]; then + echo " 最快的 10 个:" + sort -n /root/cf_ip_result.txt | head -10 | while read ms ip; do + printf " %6s ms %s\n" "$ms" "$ip" + done +else + echo " 一个都没通 —— 7844 可能被运营商封了" +fi diff --git a/_build/fix_admin_path_name.py b/_build/fix_admin_path_name.py new file mode 100644 index 0000000..5e882b8 --- /dev/null +++ b/_build/fix_admin_path_name.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +r'''管理页路径改名:zhingzhuanzhan -> zhongzhuanzhan + +我上次拼错了(多打了个 i),用户纠正。 +涉及:relay.py 里的常量/路由/页面链接 + 所有测试。 +''' +from __future__ import annotations + +import ast +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +OLD = "zhingzhuanzhan" +NEW = "zhongzhuanzhan" + +# 要扫的目录 +TARGETS = ("relay", "_tests", "installer", "docs", "_build", "android", + "windows") +EXTRA = ["README.md"] + + +def main() -> int: + changed = [] + + paths = [] + for top in TARGETS: + base = ROOT / top + if base.is_dir(): + paths.extend(base.rglob("*.py")) + paths.extend(base.rglob("*.ps1")) + paths.extend(base.rglob("*.sh")) + paths.extend(base.rglob("*.txt")) + paths.extend(base.rglob("*.md")) + paths.extend(base.rglob("*.java")) + paths.extend(base.rglob("*.iss")) + for name in EXTRA: + p = ROOT / name + if p.is_file(): + paths.append(p) + + for path in paths: + if any(x in str(path) for x in (".git", "dist_exe", "pyinstaller", + "_build\\jc", "out_java", "build\\")): + continue + try: + raw = path.read_bytes() + except OSError: + continue + had_bom = raw.startswith(b"\xef\xbb\xbf") + try: + text = raw.decode("utf-8-sig") + except UnicodeDecodeError: + continue + + if OLD not in text: + continue + + n = text.count(OLD) + text = text.replace(OLD, NEW) + path.write_bytes((b"\xef\xbb\xbf" if had_bom else b"") + + text.replace("\r\n", "\n").encode("utf-8")) + changed.append(f"{path.relative_to(ROOT)} ({n} 处)") + + print(f" 改了 {len(changed)} 个文件:") + for item in changed: + print(f" {item}") + + # 语法检查 + print() + print(" 语法检查:") + ok = True + for path in paths: + if path.suffix != ".py": + continue + try: + ast.parse(path.read_text(encoding="utf-8-sig", errors="replace")) + except SyntaxError as exc: + print(f" [失败] {path.relative_to(ROOT)}: 行 {exc.lineno}") + ok = False + print(" 全部 OK" if ok else " 有错") + + # 复核 + print() + left = [] + for path in paths: + try: + if OLD in path.read_text(encoding="utf-8-sig", errors="replace"): + left.append(str(path.relative_to(ROOT))) + except OSError: + pass + print(f" 还残留 {OLD} 的文件: {left if left else '无 ✓'}") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/fix_admin_path_tests.py b/_build/fix_admin_path_tests.py new file mode 100644 index 0000000..15c583f --- /dev/null +++ b/_build/fix_admin_path_tests.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +r'''修因为"管理页换路径"而过时的测试。 + +1. test_admin_*.py 从 / 抓管理页 —— 现在 / 是官网(公开,没有 script), + 管理页挪到 /zhingzhuanzhan 了。这些测试要跟着改。 + +2. test_pair_once_flow.py 里的"入侵者"还在用**正确的码**, + 但安全边界已改成"错误的码必须被拒"。 +''' +from __future__ import annotations + +import ast +import re +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +TESTS = ROOT / "_tests" + +# ---------- 1. 管理页测试改用新路径 ---------- + +ADMIN_FILES = ("test_admin_js.py", "test_admin_batch_delete.py", + "test_admin_delete.py", "test_admin_button.py", + "test_admin_netcheck.py", "test_relay_admin_login.py") + + +def fix_admin_paths() -> None: + for name in ADMIN_FILES: + path = TESTS / name + if not path.is_file(): + continue + text = path.read_text(encoding="utf-8") + original = text + + # 抓页面的地方:"/" -> "/zhingzhuanzhan" + text = re.sub(r'(base\s*\+\s*)"/"', r'\1"/zhingzhuanzhan"', text) + text = re.sub(r'(URL\s*=\s*[^\n]*?)"/"', r'\1"/zhingzhuanzhan"', text) + text = text.replace('urlopen(base + "/")', 'urlopen(base + "/zhingzhuanzhan")') + text = text.replace('get("/")', 'get("/zhingzhuanzhan")') + text = text.replace("f\"{base}/\"", "f\"{base}/zhingzhuanzhan\"") + text = text.replace("f'{base}/'", "f'{base}/zhingzhuanzhan'") + + # 登录接口:现在要账号 + text = text.replace('data=b"password=', + 'data=b"username=xmz7891&password=') + + if text != original: + path.write_text(text, encoding="utf-8", newline="\n") + print(f" {name}: 管理页路径 -> /zhingzhuanzhan") + else: + print(f" {name}: 无需改动") + + +# ---------- 2. 入侵者用错误的码 ---------- + +def fix_intruder() -> None: + path = TESTS / "test_pair_once_flow.py" + text = path.read_text(encoding="utf-8") + old = ''' intruder = Talk(PORT) + bad = intruder.call({"cmd": "pair", "deviceId": "student-phone", + "deviceName": "陌生设备", "code": code})''' + new = ''' intruder = Talk(PORT) + # 用**错误的码** —— 安全边界是"码错必拒"。 + # (以前这里用正确的码也期待被拒,但行为改成 + # "配对码本身就是密钥,码对就放行"了。) + wrong = "000000" if code != "000000" else "111111" + bad = intruder.call({"cmd": "pair", "deviceId": "student-phone", + "deviceName": "陌生设备", "code": wrong})''' + if new in text: + print(" test_pair_once_flow.py: 已经是新版") + elif old in text: + path.write_text(text.replace(old, new, 1), encoding="utf-8", newline="\n") + print(" test_pair_once_flow.py: 入侵者改用错误的码") + else: + print(" [失败] test_pair_once_flow.py 锚点没找到") + + +def main() -> int: + print("一、管理页测试改路径") + fix_admin_paths() + print() + print("二、配对测试改用错误的码") + fix_intruder() + + print() + print("三、语法检查") + ok = True + for path in sorted(TESTS.glob("*.py")): + try: + ast.parse(path.read_text(encoding="utf-8")) + except SyntaxError as exc: + print(f" [失败] {path.name}: 行 {exc.lineno}: {exc.msg}") + ok = False + print(" 全部语法 OK" if ok else " 有语法错误") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/fix_admin_tests2.py b/_build/fix_admin_tests2.py new file mode 100644 index 0000000..806a626 --- /dev/null +++ b/_build/fix_admin_tests2.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +r'''管理页路径变了,相关测试要跟着改。 + +之前的管理页在 `/`,现在: + `/` -> 官网(公开,没有 script / 没有下载区) + `/zhingzhuanzhan` -> 管理页(要登录) + +这些测试抓的是 `BASE + "/"`,所以拿到的是官网页, +断言"页面里没有 script""有复选框"自然全挂。 + +同时登录表单现在多了账号字段,测试的 POST body 也要补上。 +''' +from __future__ import annotations + +import ast +import re +import sys +from pathlib import Path + +TESTS = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests") + +ADMIN_FILES = [ + "test_admin_js.py", "test_admin_batch_delete.py", + "test_admin_delete.py", "test_admin_button.py", + "test_admin_netcheck.py", "test_relay_admin_login.py", +] + +ADMIN = "/zhingzhuanzhan" + + +def main() -> int: + for name in ADMIN_FILES: + path = TESTS / name + if not path.is_file(): + print(f" [跳过] {name} 不存在") + continue + + text = path.read_text(encoding="utf-8") + original = text + + # 抓页面:BASE + "/" -> BASE + "/zhingzhuanzhan" + text = re.sub(r'BASE\s*\+\s*"/"', f'BASE + "{ADMIN}"', text) + # 已登录后再抓:同样处理(上面这条正则已经覆盖) + # 登录 POST 的 body 补账号 + text = text.replace('data=b"password=', 'data=b"username=xmz7891&password=') + text = text.replace('data="password=', 'data="username=xmz7891&password=') + text = text.replace("data=f\"password=", "data=f\"username=xmz7891&password=") + # 有些用 urlencode 的字典 + text = re.sub(r'(\{"password":\s*)', r'{"username": "xmz7891", \1', text) + + if text != original: + path.write_text(text, encoding="utf-8", newline="\n") + n = original.count('BASE + "/"') + print(f" {name}: 改了 {n} 处页面路径 / 登录表单") + else: + print(f" {name}: 无需改动") + + print() + print(" 语法检查:") + ok = True + for name in ADMIN_FILES: + path = TESTS / name + if not path.is_file(): + continue + try: + ast.parse(path.read_text(encoding="utf-8")) + except SyntaxError as exc: + print(f" [失败] {name}: 行 {exc.lineno}: {exc.msg}") + ok = False + print(" 全部 OK" if ok else " 有错") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/fix_broken_form.py b/_build/fix_broken_form.py new file mode 100644 index 0000000..f381664 --- /dev/null +++ b/_build/fix_broken_form.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +r'''修上一步弄坏的登录表单。 + +上一步的正则插账号时把字典插坏了: + urlencode({"username": "xmz7891", {"password": "120830"}) + ^ 多了个左括号 +应该是: + urlencode({"username": "xmz7891", "password": "120830"}) +''' +from __future__ import annotations + +import ast +import re +import sys +from pathlib import Path + +TESTS = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests") + +ADMIN_FILES = [ + "test_admin_js.py", "test_admin_batch_delete.py", + "test_admin_delete.py", "test_admin_button.py", + "test_admin_netcheck.py", "test_relay_admin_login.py", +] + +# 把插坏的 {"username": "...", {"password": ... 还原成正常的 +BROKEN = re.compile(r'\{"username":\s*"xmz7891",\s*\{\s*"password"') +FIXED = '{"username": "xmz7891", "password"' + + +def main() -> int: + for name in ADMIN_FILES: + path = TESTS / name + if not path.is_file(): + continue + text = path.read_text(encoding="utf-8") + # 1) 先修字典 + text = BROKEN.sub(FIXED, text) + # 2) 保险:如果还有 "password" 前面紧跟 { 的,把多余的 { 去掉 + text = re.sub(r'\{\s*,\s*"password"', ', "password"', text) + text = text.replace('{"username": "xmz7891", {', '{"username": "xmz7891", {') + + path.write_text(text, encoding="utf-8", newline="\n") + + print(" 语法检查:") + ok = True + for name in ADMIN_FILES: + path = TESTS / name + if not path.is_file(): + continue + try: + ast.parse(path.read_text(encoding="utf-8")) + print(f" {name}: OK") + except SyntaxError as exc: + print(f" [失败] {name}: 行 {exc.lineno}: {exc.msg}") + lines = path.read_text(encoding="utf-8").splitlines() + if exc.lineno and exc.lineno <= len(lines): + print(f" {lines[exc.lineno - 1].strip()}") + ok = False + + print() + print(" 登录表单现在的样子:") + for name in ("test_admin_js.py",): + path = TESTS / name + for line in path.read_text(encoding="utf-8").splitlines(): + if "urlencode" in line: + print(f" {line.strip()}") + + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/fix_site_logo.py b/_build/fix_site_logo.py new file mode 100644 index 0000000..0b99338 --- /dev/null +++ b/_build/fix_site_logo.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +r'''把官网 logo 从 换成内联 SVG。 + +为什么: + ICO 文件放在 标签里各浏览器支持不一致 —— + 实测截图里导航栏那个 logo 是**破图**。 + 内联 SVG 永远能显示,还省一个请求。 +''' +from __future__ import annotations + +import ast +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +LANDING = ROOT / "relay" / "landing.py" + +# 内联 SVG(用设计令牌里的主色) +LOGO_FUNC = '''def _logo(size: int = 26) -> str: + """内联 SVG 的 logo。 + + 为什么不用 : + ICO 放在 里各浏览器支持不一致,实测截图里是破图。 + 内联 SVG 永远能显示,还不用多一个请求。 + """ + return ( + '' + '' + '' + '' + '' % (size, size) + ) + + +''' + +OLD_IMG = '' + + +def main() -> int: + text = LANDING.read_text(encoding="utf-8") + + if "_logo(" in text: + print(" 已经改过") + return 0 + + # 1) 插入 _logo() + anchor = "def _download_cards(" + if anchor not in text: + print(" [失败] 找不到 _download_cards") + return 1 + text = text.replace(anchor, LOGO_FUNC + anchor, 1) + print(" 加了 _logo()") + + # 2) 模板里的 img 换成占位符 + n = text.count(OLD_IMG) + text = text.replace(OLD_IMG, "__LOGO__") + print(" 替换 %d 处 为占位符" % n) + + # 3) 返回值里把占位符换成真正的 SVG + # 模板最后是 } 形状,找一个稳妥的锚点: + # "_dlsub": (...) 这一段后面就是收尾的 } + marker = ' "_dlsub": ("当前版本 " + version) if version\n else "选择你的设备",\n }' + if marker in text: + text = text.replace( + marker, + ' "_dlsub": ("当前版本 " + version) if version\n' + ' else "选择你的设备",\n' + ' }.replace("__LOGO__", _logo())', 1) + print(" 返回值里接了 .replace(\"__LOGO__\", _logo())") + else: + # 兜底:找 render_site 的 return ... % { ... } 结尾 + idx = text.rfind(" }") + if idx < 0: + print(" [失败] 找不到返回值结尾") + return 1 + text = text[:idx] + ' }.replace("__LOGO__", _logo())' + text[idx + len(" }"):] + print(" 用兜底方式接了 replace") + + LANDING.write_text(text, encoding="utf-8", newline="\n") + + try: + ast.parse(text) + print(" 语法 OK") + except SyntaxError as exc: + print(" [失败] 语法错误: 行 %s: %s" % (exc.lineno, exc.msg)) + lines = text.splitlines() + for i in range(max(0, (exc.lineno or 1) - 3), + min(len(lines), (exc.lineno or 1) + 3)): + print(" %d: %s" % (i + 1, lines[i][:90])) + return 1 + + # 复核 + left = text.count(OLD_IMG) + print() + print(" 复核: 还剩 %d 处 (link 那个不影响)" % left) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/optimize_network.sh b/_build/optimize_network.sh new file mode 100644 index 0000000..424c79d --- /dev/null +++ b/_build/optimize_network.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# 阶段1:真正的网络加速 +# +# 排查结论: +# ① 隧道在跑 QUIC(ExecStart 缺 --protocol http2)—— +# 实测 QUIC 48% vs HTTP/2 80%,这是重大性能回退,必须修回。 +# ② 优选 IP 对这条线路**没用**:hosts 覆盖确实生效了 +# (日志里 ip=198.41.200.238 就是我指定的),但落点仍是 lax01/lax13。 +# 说明运营商把**所有** Cloudflare anycast 流量都路由到美西,换 IP 改不了路径。 +# 所以那段 hosts 覆盖属于无效配置,清掉更干净。 +# ③ 真正能救的是**拥塞控制**:这条线路 40% 丢包, +# Linux 默认的 CUBIC 一遇丢包就砍窗口,吞吐塌方、延迟飙升。 +# BBR 基于带宽+延迟建模,丢包不当事 —— 在丢包链路上提升是数量级的。 +set -u + +RELAY_DIR=/root/lanremote-relay + +echo "════════ 1. 恢复 HTTP/2 ════════" +cat > /etc/systemd/system/lanremote-tunnel.service <<'EOF' +[Unit] +Description=ykxmz Cloudflare 具名隧道 +After=network-online.target lanremote-relay.service lanremote-nettune.service +Wants=network-online.target lanremote-relay.service + +[Service] +Type=simple +# 协议 HTTP/2(走 TCP 7844) +# 实测:这条线路丢 UDP 包,QUIC 可用率只有 48%; +# HTTP/2 稳定在 80% 以上。**别去掉这个参数** —— +# 去掉就会退回 QUIC,可用率直接腰斩。 +# 连接数 2:有冗余又不占内存(设备只有 460MB) +ExecStart=/usr/local/bin/cloudflared tunnel \ + --protocol http2 \ + --edge-ip-version 4 \ + --ha-connections 2 \ + --no-autoupdate \ + --retries 5 \ + --config /root/.cloudflared/config.yml run lanremote +Restart=always +RestartSec=5 +MemoryMax=160M +StandardOutput=append:/root/lanremote-relay/tunnel.log +StandardError=append:/root/lanremote-relay/tunnel.log + +[Install] +WantedBy=multi-user.target +EOF +systemctl daemon-reload +echo " 已写回 --protocol http2" + +echo +echo "════════ 2. 清掉无效的 hosts 覆盖 ════════" +sed -i '/argotunnel.com/d' /etc/hosts +echo " /etc/hosts 里的 argotunnel 条目: $(grep -c argotunnel /etc/hosts) 个" + +echo +echo "════════ 3. 启用 BBR 拥塞控制 ════════" +modprobe tcp_bbr 2>/dev/null || true +cat > /etc/sysctl.d/98-ykxmz-net.conf <<'EOF' +# ykxmz 网络调优(针对 4G 高丢包链路) +# +# 拥塞控制:BBR +# Linux 默认 CUBIC 把丢包当拥塞信号,一丢包就砍发送窗口。 +# 这条线路丢包 40%,CUBIC 会一直在"砍窗口-慢慢恢复"的循环里, +# 表现就是延迟从 200ms 飙到 2 秒。 +# BBR 用带宽和延迟建模,丢包不直接等于拥塞,高丢包链路上好得多。 +net.core.default_qdisc = fq +net.ipv4.tcp_congestion_control = bbr + +# TCP 缓冲:长途高延迟链路需要更大的窗口才能跑满 +net.core.rmem_max = 4194304 +net.core.wmem_max = 4194304 +net.ipv4.tcp_rmem = 4096 87380 4194304 +net.ipv4.tcp_wmem = 4096 65536 4194304 + +# 快速打开连接(省一个 RTT) +net.ipv4.tcp_fastopen = 3 + +# 更早重传,别干等 +net.ipv4.tcp_retries2 = 8 + +# 复用 TIME_WAIT 的连接,长轮询场景能省不少握手 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_fin_timeout = 15 +EOF +sysctl -p /etc/sysctl.d/98-ykxmz-net.conf 2>&1 | sed 's/^/ /' + +echo +echo " 当前拥塞控制: $(sysctl -n net.ipv4.tcp_congestion_control)" +echo " 队列算法: $(sysctl -n net.core.default_qdisc)" +echo " 可用算法: $(sysctl -n net.ipv4.tcp_available_congestion_control)" + +echo +echo "════════ 4. 重启隧道 ════════" +systemctl restart lanremote-tunnel +sleep 40 +echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')" +echo " 协议与落点:" +grep -E 'Registered tunnel connection' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -3 \ + | grep -oE 'ip=[0-9.]+ location=[a-z0-9]+ protocol=[a-z0-9]+' | sed 's/^/ /' +grep -oE 'Initial protocol [a-z0-9]+' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -2 | sed 's/^/ /' + +echo +echo "════════ 5. 延迟实测(20 次,看平均)════════" +total=0; ok=0; max=0 +for i in $(seq 1 20); do + r=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 20 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health 2>/dev/null) + code=$(echo "$r" | cut -d' ' -f1) + t=$(echo "$r" | cut -d' ' -f2) + if [ "$code" = "200" ]; then + ok=$((ok+1)) + total=$(echo "$total + $t" | bc) + bigger=$(echo "$t > $max" | bc) + [ "$bigger" = "1" ] && max=$t + fi + sleep 1 +done +avg=$(echo "scale=3; $total / $ok" | bc 2>/dev/null || echo "?") +echo " 可用 $ok/20" +echo " 平均 ${avg} 秒 最慢 ${max} 秒" diff --git a/_build/package_remote.py b/_build/package_remote.py index 6acdc2e..d1098b6 100644 --- a/_build/package_remote.py +++ b/_build/package_remote.py @@ -17,7 +17,7 @@ import zipfile from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") -VERSION = "1.7.0" +VERSION = "1.7.1" NAME = f"ykxmz-{VERSION}" AGENT_SRC = ROOT / "dist_exe" / "ykxmz" diff --git a/_build/probe_cf_edge2.py b/_build/probe_cf_edge2.py new file mode 100644 index 0000000..056061a --- /dev/null +++ b/_build/probe_cf_edge2.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +r'''在设备上测 Cloudflare 隧道边缘 IP 的 TCP 握手延迟,挑优选 IP。 + +为什么要这么干: + cloudflared 连 region1.v2.argotunnel.com,这个域名由 GeoDNS 决定 —— + 国内 4G 常被解析到美西(实测 lax13),单程 200ms+, + 往返加丢包重传能到 4 秒(用户实测 4141ms)。 + + Cloudflare 是 anycast,同一批 IP 从不同网络过去落点不同。 + 做法:测各候选 IP 的 7844(隧道端口)握手延迟, + 把最快的写进 /etc/hosts 覆盖 GeoDNS。 + +⚠ 关键教训: + 7844 **只对 Cloudflare 的隧道专用段开放**,不是所有 anycast IP 都行。 + 专用段是 198.41.192.0/24 和 198.41.200.0/24(实测 198.41.192.107 通)。 + 我第一次拿 104.16.x.x 测,56 个全不通,就是选错段了。 +''' +from __future__ import annotations + +import socket +import sys +import time + +PORT = 7844 # cloudflared 的 HTTP/2 隧道端口 + +# 已知能用的几个放前面:先确认能通,再放大范围扫 +KNOWN = [ + "198.41.192.107", "198.41.192.7", "198.41.192.167", + "198.41.200.13", "198.41.200.33", "198.41.200.53", "198.41.200.63", +] + + +def build_candidates() -> list[str]: + ips = list(KNOWN) + for third in range(1, 255, 3): + ips.append("198.41.192." + str(third)) + for third in range(1, 255, 3): + ips.append("198.41.200." + str(third)) + seen = set() + out = [] + for ip in ips: + if ip not in seen: + seen.add(ip) + out.append(ip) + return out + + +def probe(ip: str, timeout: float = 2.5): + """返回 TCP 握手耗时(毫秒),不通返回 None。""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + start = time.perf_counter() + try: + sock.connect((ip, PORT)) + return (time.perf_counter() - start) * 1000 + except OSError: + return None + finally: + sock.close() + + +def main() -> int: + candidates = build_candidates() + print(" 候选 %d 个(Cloudflare 隧道专用段)" % len(candidates)) + print(" 测 %d 端口,每个 2 次取最快" % PORT) + print() + + results = [] + reachable = 0 + for index, ip in enumerate(candidates, 1): + best = None + for _ in range(2): + ms = probe(ip) + if ms is not None and (best is None or ms < best): + best = ms + if best is not None: + reachable += 1 + results.append((best, ip)) + if index % 40 == 0: + print(" 已测 %d/%d,通了 %d 个" % (index, len(candidates), reachable)) + + print() + if not results: + print(" 一个都不通") + return 1 + + results.sort() + print(" 通了 %d 个,最快的 15 个:" % reachable) + for ms, ip in results[:15]: + print(" %7.1f ms %s" % (ms, ip)) + + print() + print(" 默认 GeoDNS 解析:") + for host in ("region1.v2.argotunnel.com", "region2.v2.argotunnel.com"): + try: + infos = socket.getaddrinfo(host, PORT) + v4 = sorted({i[4][0] for i in infos if i[0] == socket.AF_INET}) + print(" %s: %s" % (host, v4 or "(只有 IPv6)")) + except OSError as exc: + print(" %s: 解析失败 %s" % (host, exc)) + + with open("/root/cf_best.txt", "w") as fh: + for ms, ip in results[:5]: + fh.write("%s %.0f\n" % (ip, ms)) + print() + print(" 最快 5 个 -> /root/cf_best.txt") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/rename_to_ykxmz.py b/_build/rename_to_ykxmz.py deleted file mode 100644 index 5c3f487..0000000 --- a/_build/rename_to_ykxmz.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- -"""把产品名改成 ykxmz,并加一个正经的卸载。 - -用户要求:"改成 ykxmz" - -做这些: - 1. 开机自启注册表的值名 -> ykxmz - (任务管理器「启动」标签页里显示的就是这个名字) - 同时迁移掉旧名 LanRemoteAgent,不留垃圾 - 2. 版本信息里的产品名/文件名 -> ykxmz - 3. 打包出来的 exe 叫 ykxmz.exe - 4. 加 --uninstall:一条命令清干净 - (停进程、删自启、删数据目录) - -刻意保留的东西: - * **启动项保持可见**。改名字是品牌化,藏起来是另一回事。 - * 被控端被连接时的屏幕提示也保留。 -""" -from __future__ import annotations - -import sys -from pathlib import Path - -ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") -WINDOWS = ROOT / "windows" - -AUTOSTART = WINDOWS / "lanremote" / "autostart.py" -VERSION = WINDOWS / "version_info.txt" -BUILD = WINDOWS / "build_exe.ps1" -RUN = WINDOWS / "run_agent.py" -UNINSTALL = WINDOWS / "lanremote" / "uninstall.py" - -NEW_NAME = "ykxmz" - - -# ==================== 1. autostart.py ==================== - -AUTO_HEAD_OLD = '''RUN_KEY = r"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" -VALUE_NAME = "LanRemoteAgent"''' - -AUTO_HEAD_NEW = '''RUN_KEY = r"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" -# 任务管理器「启动」标签页里显示的就是这个名字。 -# 用 ykxmz 而不是冒充 "Windows xxx" —— 用的人应该看得出这是什么。 -VALUE_NAME = "ykxmz" -# 早期版本用过的名字。开着自启的老用户升级后要把它清掉,别留两条。 -LEGACY_VALUE_NAMES = ("LanRemoteAgent",)''' - -AUTO_ENABLE_OLD = '''def enable() -> str: - value = command_line() - code, out = _run(["reg", "add", RUN_KEY, "/v", VALUE_NAME, - "/t", "REG_SZ", "/d", value, "/f"]) - if code != 0: - raise ValueError(f"写入开机自启失败: {out[:120]}") - return f"已开启开机自启(静默模式)\\n{value}"''' - -AUTO_ENABLE_NEW = '''def cleanup_legacy() -> list: - """清掉早期版本留下的自启项(换名字了,别留两条)。""" - removed = [] - for name in LEGACY_VALUE_NAMES: - code, _out = _run(["reg", "delete", RUN_KEY, "/v", name, "/f"]) - if code == 0: - removed.append(name) - return removed - - -def enable() -> str: - value = command_line() - code, out = _run(["reg", "add", RUN_KEY, "/v", VALUE_NAME, - "/t", "REG_SZ", "/d", value, "/f"]) - if code != 0: - raise ValueError(f"写入开机自启失败: {out[:120]}") - gone = cleanup_legacy() - extra = f"(顺手清掉旧自启项 {', '.join(gone)})" if gone else "" - return f"已开启开机自启(静默模式){extra}\\n{value}"''' - -# ==================== 2. version_info.txt ==================== - -VERSION_REPLACEMENTS = [ - ('StringStruct("CompanyName", "LanRemote")', - f'StringStruct("CompanyName", "{NEW_NAME}")'), - ('StringStruct("FileDescription", "局域网遥控 被控端")', - f'StringStruct("FileDescription", "{NEW_NAME} 被控端")'), - ('StringStruct("InternalName", "LanRemote-Agent")', - f'StringStruct("InternalName", "{NEW_NAME}")'), - ('StringStruct("LegalCopyright", "本机自用工具")', - f'StringStruct("LegalCopyright", "{NEW_NAME}")'), - ('StringStruct("OriginalFilename", "LanRemote-Agent.exe")', - f'StringStruct("OriginalFilename", "{NEW_NAME}.exe")'), - ('StringStruct("ProductName", "局域网遥控")', - f'StringStruct("ProductName", "{NEW_NAME}")'), -] - -# ==================== 3. build_exe.ps1 ==================== - -BUILD_REPLACEMENTS = [ - ('# 产物: dist_exe\\LanRemote-Agent\\', - '# 产物: dist_exe\\ykxmz\\'), - ('# ├─ LanRemote-Agent.exe <- 双击运行', - '# ├─ ykxmz.exe <- 双击运行'), - ('--name "LanRemote-Agent" `', - '--name "ykxmz" `'), - ('$target = Join-Path $root "dist_exe\\LanRemote-Agent\\LanRemote-Agent.exe"', - '$target = Join-Path $root "dist_exe\\ykxmz\\ykxmz.exe"'), -] - -# ==================== 4. uninstall.py(新文件)==================== - -UNINSTALL_SRC = '''# -*- coding: utf-8 -*- -"""卸载:一条命令把程序从这台电脑上清干净。 - - ykxmz.exe --uninstall - -清这些: - 1. 停掉正在跑的进程 - 2. 删掉开机自启项 - 3. 删掉数据目录(配对码、已配对手表……) - -**故意做这个**:一个能开机自启、又能远程控制的软件, -必须能干净地卸载掉 —— 否则它就成了"赖着不走"的东西。 -电脑的主人应该随时能把它彻底清掉。 -""" -from __future__ import annotations - -import os -import shutil -import subprocess -import sys -import time -from pathlib import Path - -from .autostart import VALUE_NAME, RUN_KEY, LEGACY_VALUE_NAMES, _run -from .config import app_dir, data_dir - -_NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) - - -def _kill_running() -> str: - """把正在跑的进程停掉(除了自己)。""" - if not getattr(sys, "frozen", False): - return "(源码模式,跳过进程清理)" - exe = Path(sys.executable).name - code, out = _run(["taskkill", "/F", "/IM", exe]) - if code == 0: - return f"已停止进程 {exe}" - if "not found" in out.lower() or "没有" in out: - return "没有正在运行的进程" - return f"停止进程:{out[:80]}" - - -def _remove_autostart() -> str: - done = [] - for name in (VALUE_NAME,) + tuple(LEGACY_VALUE_NAMES): - code, _out = _run(["reg", "delete", RUN_KEY, "/v", name, "/f"]) - if code == 0: - done.append(name) - if done: - return f"已删除开机自启项 {', '.join(done)}" - return "没有开机自启项" - - -def _remove_data() -> str: - """删数据目录。删不掉就留着,不影响卸载。""" - target = Path(data_dir()) - if not target.is_dir(): - return "没有数据目录" - try: - shutil.rmtree(target, ignore_errors=True) - if not target.is_dir(): - return f"已删除数据目录 {target}" - return f"数据目录没删干净(可能被占用):{target}" - except Exception as exc: # noqa: BLE001 - return f"删数据目录失败:{exc}" - - -def _schedule_self_delete() -> str: - """把自己这个 exe 也删掉。 - - 进程还在跑的时候删不掉自己,所以要起一个延迟的小命令, - 等我们退出后再删目录。 - """ - if not getattr(sys, "frozen", False): - return "(源码模式,不删自己)" - folder = Path(sys.executable).resolve().parent - cmd = (f'timeout /t 2 /nobreak >nul & ' - f'rmdir /s /q "{folder}"') - try: - subprocess.Popen(["cmd", "/c", cmd], - creationflags=_NO_WINDOW | 0x00000008) - return f"已安排删除程序目录:{folder}" - except Exception as exc: # noqa: BLE001 - return f"(没安排上自删,请手动删 {folder}:{exc})" - - -def run(keep_data: bool = False) -> str: - """执行卸载,返回给人看的说明。""" - lines = ["开始卸载 " + VALUE_NAME] - lines.append(" " + _remove_autostart()) - if keep_data: - lines.append(" 保留数据目录(你要求保留)") - else: - lines.append(" " + _remove_data()) - lines.append(" " + _schedule_self_delete()) - lines.append("") - lines.append("卸载完成。程序目录会在几秒后自动删除。") - return "\\n".join(lines) -''' - - -def main() -> int: - ok = True - - # ---- autostart.py ---- - text = AUTOSTART.read_text(encoding="utf-8") - if f'VALUE_NAME = "{NEW_NAME}"' not in text: - if AUTO_HEAD_OLD in text: - text = text.replace(AUTO_HEAD_OLD, AUTO_HEAD_NEW, 1) - print(" autostart.py: 改名 + 旧名清理常量") - else: - print(" [失败] autostart.py 头部锚点没找到") - ok = False - if AUTO_ENABLE_OLD in text: - text = text.replace(AUTO_ENABLE_OLD, AUTO_ENABLE_NEW, 1) - print(" autostart.py: enable 顺手清旧项") - else: - print(" [跳过] autostart.py enable 锚点没找到") - AUTOSTART.write_text(text, encoding="utf-8", newline="\n") - else: - print(" autostart.py: 已经改过") - - # ---- version_info.txt ---- - text = VERSION.read_text(encoding="utf-8") - changed = 0 - for old, new in VERSION_REPLACEMENTS: - if old in text: - text = text.replace(old, new, 1) - changed += 1 - if changed: - VERSION.write_text(text, encoding="utf-8", newline="\n") - print(f" version_info.txt: 改了 {changed} 个字段") - else: - print(" version_info.txt: 没变化(可能已改过)") - - # ---- build_exe.ps1 ---- - text = BUILD.read_text(encoding="utf-8") - changed = 0 - for old, new in BUILD_REPLACEMENTS: - if old in text: - text = text.replace(old, new, 1) - changed += 1 - if changed: - # 保持 BOM(PowerShell 5.1 没 BOM 会按 GBK 读) - BUILD.write_bytes(b"\xef\xbb\xbf" + - text.replace("\r\n", "\n").encode("utf-8")) - print(f" build_exe.ps1: 改了 {changed} 处(BOM 已保留)") - else: - print(" build_exe.ps1: 没变化") - - # ---- uninstall.py ---- - if UNINSTALL.exists(): - print(" uninstall.py: 已存在") - else: - UNINSTALL.write_text(UNINSTALL_SRC, encoding="utf-8", newline="\n") - print(" uninstall.py: 新建") - - # ---- 语法检查 ---- - import ast - for label, path in (("autostart.py", AUTOSTART), - ("uninstall.py", UNINSTALL)): - try: - ast.parse(path.read_text(encoding="utf-8")) - print(f" {label} 语法 OK") - except SyntaxError as exc: - print(f" {label} 语法错误: {exc}") - ok = False - return 0 if ok else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/_build/site_preview.html b/_build/site_preview.html new file mode 100644 index 0000000..fbae8ef --- /dev/null +++ b/_build/site_preview.html @@ -0,0 +1,197 @@ + + + + + +ykxmz · 教室电脑手表遥控 + + + + + + + + +
+

老师抬一下手腕
就能管教室那台电脑

+

放课件、放音频、投图到大屏、看看有没有人在跑无关程序、下课关机 —— + 不用走回讲台去点鼠标。

+
+ 下载安装 + 先看看怎么用 +
+
最新版 1.7.1
+
+ +
+
+

上课用得上的,基本都在这

+

手表上十个分页,常用操作都在第一屏

+
+
屏幕查看
手表上看教室里那台电脑在显示什么,顺手一键截屏存到手表
课件播放
放音频、放视频、调音量、静音 —— 不用走回讲台去点鼠标
投图到大屏
手表上选张图,全屏显示在教室大屏。考试时贴个提示特别方便
传文件
课件在两台机器之间来回倒腾,不用插 U 盘
课堂专注
一键限制游戏相关站点、关闭游戏窗口,下课一键恢复
电源控制
锁屏、睡眠、关机、重启,还能设「30 分钟后自动关机」
局域网优先
手表和电脑在同一个 WiFi 时直连,几十毫秒,不绕服务器
跨网络
不在同一个网络时自动走中转,办公室也能管教室
可见可控
屏幕被看时电脑上一定有提示条,关不掉;本机随时能掐断
+
+ +
+
+

三步装好

+

电脑装一个程序,手表装一个 App,一台电脑配一次就行

+
+
1
电脑端
在教室那台 Windows 电脑上运行安装包,填一个 6 位配对码,装完自动后台运行
2
手表端
在安卓手表上装 APK,首页点「局域网连接」或「输入配对码连接」
3
连上就用
配对一次之后,以后打开就自动连,不用再输数字
+
+ +
+
+

下载

+

当前版本 1.7.1

+
+
电脑端教室那台 Windows 电脑
装完自动后台运行
下载 安装包
ykxmz-setup-1.7.1.exe · 14.4 MB
Windows 10 / 11
手表端安卓手表
直接安装,不用 root
下载 APK
ykxmz-watch-1.7.1.apk · 0.2 MB
Android 7.0+
服务器端一台能上外网的小机器
只在跨网络时才需要
下载 压缩包
ykxmz-server-1.7.1.zip · 0.2 MB
Debian / Ubuntu
+
+ +
+

常见问题

+
手表连不上电脑怎么办?
先确认电脑上程序在运行(任务管理器里找 ykxmz.exe)。如果被控端窗口提示「防火墙未放行」,点旁边的一键修复。还是不行就跑一次网络自检 —— 电脑上打开工具箱选 3,然后手表上点「检查服务器」。
为什么显示「延迟 2000 毫秒」这么慢?
跨网络时数据要绕到中转服务器再回来,延迟取决于你那台小机器的线路。如果手表和电脑在同一个 WiFi,会自动走局域网直连,延迟通常只有几十毫秒。
需要另外买服务器吗?
只有一个教室用的话不需要 —— 手表和电脑连同一个 WiFi 就能直连。要在办公室管教室才需要一台能上外网的小机器当中转。
配对码忘了怎么办?
电脑上双击程序图标打开窗口就能看到。也可以在窗口里改一个自己好记的。
手表重装了还能连上原来的电脑吗?
能。重新输一次那个 6 位配对码就行 —— 配对码本身就是钥匙。
屏幕被别人看着,我能知道吗?
能。只要有人在看你的屏幕,电脑右上角就会出现一条红色提示条,没有开关能关掉它。电脑前面的人随时可以点「停止共享」断开。
+
+ + + + + + \ No newline at end of file diff --git a/_build/split_site_and_admin.py b/_build/split_site_and_admin.py deleted file mode 100644 index d0b5584..0000000 --- a/_build/split_site_and_admin.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding: utf-8 -*- -r"""中转站改造成「官网 + 管理页分家」。 - -用户要求: - relay.xxmxxs.top/ -> 官网(简洁介绍 + 下载) - relay.xxmxxs.top/zhingzhuanzhan -> 中转站管理(要登录) - 管理账号 xmz7891 密码 @REVAedu120830 - -改动点: - 1. 默认管理密码改掉,并加"账号"这个维度(原来只有密码) - 2. admin_login(username, password) - 3. GET / -> 官网(公开,不用登录) - 4. GET /zhingzhuanzhan -> 管理页(要登录) - /admin 保留成 302 跳过去,免得老书签失效 - 5. 登录表单加账号输入框 - 6. 登录后的跳转、退出链接都指到 /zhingzhuanzhan -""" -from __future__ import annotations - -import ast -import sys -from pathlib import Path - -RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py") - -ADMIN_PATH = "/zhingzhuanzhan" -ADMIN_USER = "xmz7891" -ADMIN_PASS = "@REVAedu120830" - -# 官网首页(公开)。做得简洁:一句话说清是什么、三个下载按钮。 -SITE_PAGE = '''def _site_page() -> str: - """官网首页:简洁介绍 + 下载。**不需要登录**。 - - 用户要求把 relay.xxmxxs.top 当官网用, - 管理后台挪到 /zhingzhuanzhan(见 _login_page / _admin_page)。 - """ - files = _list_downloads() - # 挑出要展示的三个:安装包 / 服务器端 / 手表端 - setup = next((f for f in files if f["name"].endswith(".exe")), None) - server = next((f for f in files - if f["name"].endswith(".zip") and "server" in f["name"]), None) - watch = next((f for f in files if f["name"].endswith(".apk")), None) - - def card(title, desc, item, tag): - if not item: - return (f'
{title}
' - f'
{desc}
' - f'
暂未上传
') - mb = item["size"] / 1024 / 1024 - size = f"{mb:.1f} MB" if mb >= 0.1 else f"{item['size'] / 1024:.0f} KB" - href = "/download/" + quote(item["name"]) - return (f'
{title}
' - f'
{desc}
' - f'下载 {tag}' - f'
{item["name"]} · {size}
') - - ver = "" - for f in files: - m = re.search(r"(\\d+\\.\\d+\\.\\d+)", f["name"]) - if m: - ver = m.group(1) - break - - return f""" - - - -ykxmz · 教室电脑手表遥控 - -
- -

ykxmz

-
老师抬一下手腕,就能管教室里那台电脑。
-
{("最新版 " + ver) if ver else ""}
- -

放课件、放音频、投图到大屏、看看谁在打游戏、下课关机 —— -不用走回讲台去点鼠标。

- - -
-{card("电脑端", "教室那台 Windows 电脑装这个", setup, "安装包")} -{card("服务器端", "要跨网络才需要,装在一台小机器上", server, "压缩包")} -{card("手表端", "安卓手表装这个 APK", watch, "APK")} -
- -
- 源码在 Gitee - · 历史版本在 发行版 - · 管理入口 在此 -
- -
""" - - -''' - - -def main() -> int: - text = RELAY.read_text(encoding="utf-8") - ok = True - - if "_site_page" in text: - print(" 已经改造过") - return 0 - - # ---- 1. 导入 quote / re ---- - if "\nimport re" not in text: - text = text.replace("import os\n", "import os\nimport re\n", 1) - print(" 补 import re") - if "from urllib.parse import quote" not in text: - text = text.replace("import urllib.parse\n", - "import urllib.parse\nfrom urllib.parse import quote\n", 1) - print(" 补 from urllib.parse import quote") - - # ---- 2. 密码 / 账号 ---- - text = text.replace( - 'ADMIN_PASSWORD_DEFAULT = "120830"', - f'ADMIN_PASSWORD_DEFAULT = "{ADMIN_PASS}"\n' - f'# 管理页账号(原来只有密码,用户要求加账号)\n' - f'ADMIN_USER_DEFAULT = "{ADMIN_USER}"\n' - f'ADMIN_USER = os.environ.get("RELAY_ADMIN_USER", "") or ADMIN_USER_DEFAULT', - 1) - text = text.replace( - 'def admin_login(password: str) -> str:', - 'def admin_login(username: str, password: str) -> str:', 1) - text = text.replace( - ' if ADMIN_PASSWORD:\n return ADMIN_PASSWORD', - ' if ADMIN_PASSWORD:\n return ADMIN_PASSWORD', - 1) - - # 函数体内:校验账号 + 密码 - old_check = " if not password or password != _admin_password():\n return \"\"" - new_check = (" if not password or password != _admin_password():\n" - " return \"\"\n" - " if username and username.strip() != ADMIN_USER:\n" - " return \"\"") - if old_check in text: - text = text.replace(old_check, new_check, 1) - print(" admin_login 加了账号校验") - else: - print(" [提示] admin_login 的密码校验锚点没找到,稍后手工确认") - - # ---- 3. 路由:/ 官网,/zhingzhuanzhan 管理页 ---- - old_route = ''' if path in ("/", "/admin"): - # 管理页要登录 —— 上面会显示配对码,不能谁都能看 - if not self._admin_gate(): - self._send(200, _login_page( - "密码不对,再试一次" if _query(self.path, "bad") else "" - ), "text/html; charset=utf-8") - return - self._admin_page() - return''' - new_route = f''' # ---- 官网首页(公开,不用登录)---- - if path == "/": - self._send(200, _site_page(), "text/html; charset=utf-8") - return - - # ---- 管理后台 ---- - # 挪到了 /zhingzhuanzhan(用户要求)。/admin 保留成跳转, - # 免得存过书签的人打不开。 - if path == "/admin": - self._redirect("{ADMIN_PATH}") - return - - if path == "{ADMIN_PATH}": - # 管理页要登录 —— 上面会显示配对码,不能谁都能看 - if not self._admin_gate(): - self._send(200, _login_page( - "账号或密码不对,再试一次" - if _query(self.path, "bad") else "" - ), "text/html; charset=utf-8") - return - self._admin_page() - return''' - if old_route in text: - text = text.replace(old_route, new_route, 1) - print(" 路由:/ 官网,/zhingzhuanzhan 管理页") - else: - print(" [失败] 路由锚点没找到") - ok = False - - # ---- 4. 登录 POST:读账号 + 密码 ---- - old_login = ''' if path == "/admin/login": - form = self._body().decode("utf-8", "replace") - password = "" - for piece in form.split("&"): - name, _, value = piece.partition("=") - if name == "password": - from urllib.parse import unquote_plus - password = unquote_plus(value) - token = admin_login(password) - if not token: - log(f"管理页登录失败,来自 {self._client_ip()}") - self._redirect("/?bad=1") - return - log(f"管理页登录成功,来自 {self._client_ip()}") - self.send_response(302) - self.send_header("Location", "/")''' - new_login = f''' if path == "/admin/login": - form = self._body().decode("utf-8", "replace") - username = password = "" - for piece in form.split("&"): - name, _, value = piece.partition("=") - if name == "password": - from urllib.parse import unquote_plus - password = unquote_plus(value) - elif name == "username": - from urllib.parse import unquote_plus - username = unquote_plus(value) - token = admin_login(username, password) - if not token: - log(f"管理页登录失败({username or '未填账号'})," - f"来自 {{self._client_ip()}}") - self._redirect("{ADMIN_PATH}?bad=1") - return - log(f"管理页登录成功({{username}}),来自 {{self._client_ip()}}") - self.send_response(302) - self.send_header("Location", "{ADMIN_PATH}")''' - if old_login in text: - text = text.replace(old_login, new_login, 1) - print(" 登录接口:接受账号 + 密码") - else: - print(" [失败] 登录锚点没找到") - ok = False - - # ---- 5. 登录表单加账号框 ---- - old_form = '
' - new_form = ('' - '') - if old_form in text: - text = text.replace(old_form, new_form, 1) - print(" 登录表单加了账号输入框") - - # ---- 6. 退出后回管理页 ---- - text = text.replace(' int: + html = landing.render_site(FILES) + print(" 渲染 %d 字符" % len(html)) + print() + + print("一、结构完整") + for label, needle in ( + ("DOCTYPE", ""), + ("语言 zh-CN", 'lang="zh-CN"'), + ("吸顶导航", "nav-in"), + ("Hero 标题", "老师抬一下手腕"), + ("功能网格", 'id="features"'), + ("三步上手", 'id="how"'), + ("下载区", 'id="download"'), + ("FAQ", 'id="faq"'), + ("页脚", "foot-in"), + ("响应式 viewport", "width=device-width")): + check(label, needle in html) + + print() + print("二、内容数量") + feats = len(re.findall(r'class="feat"', html)) + steps = len(re.findall(r'class="step"', html)) + dls = len(re.findall(r'class="dl-card"', html)) + faqs = len(re.findall(r'
= 11, "实际 %d" % html.count(" %s" % out.name) + + print() + print("=" * 58) + print("通过 %d 项,失败 %d 项" % (passed, failed)) + return 0 if failed == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/update_pair_tests.py b/_build/update_pair_tests.py new file mode 100644 index 0000000..90d0edb --- /dev/null +++ b/_build/update_pair_tests.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +r'''更新两个测试:配对窗口关闭时的预期行为变了。 + +旧行为(测试原来写的): + 配对窗口关着 -> **任何**新设备配对都被拒 + +新行为(这次改的,因为用户遇到过): + 配对窗口关着 -> **码对就放行,码错才拒** + 理由:配对码本身就是密钥。老逻辑"首台设备配对成功后自动关窗" + 会把重装手表的人永久挡在外面(实测报"被控端已关闭新设备配对")。 + +所以测试要改成: + 1. **错误的码** -> 被拒(这条才是安全边界,必须守住) + 2. **正确的码** -> 放行(手表重装后能重连,这是这次修的东西) +''' +from __future__ import annotations + +import ast +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") + +# ---------------- test_pair_once_flow.py ---------------- + +FLOW = ROOT / "_tests" / "test_pair_once_flow.py" +FLOW_OLD = ''' check("静默模式下新设备配对被拒绝", bad.get("ok") is False, str(bad)[:100]) + intruder.close()''' + +FLOW_NEW = ''' # 注意:这里以前是拿**正确的码**去试,期待被拒。 + # 后来改成"码对就放行"(配对码本身就是密钥), + # 所以安全边界换成"**错误的码**必须被拒"。 + check("拿着错误的码配不上", bad.get("ok") is False, str(bad)[:100]) + intruder.close() + + # 表格重装/换新表:拿着正确的码应该能重新配上 + again = Talk(PORT) + retry = again.call({"cmd": "pair", "deviceId": "watch-reinstalled", + "deviceName": "手表", "code": code}) + check("手表重装后拿正确的码能重新配上", + retry.get("ok") is True, str(retry)[:120]) + again.close()''' + +# ---------------- test_silent_mode.py ---------------- + +SILENT = ROOT / "_tests" / "test_silent_mode.py" +SILENT_OLD = ''' check("静默模式下新设备配对被拒绝", reply.get("ok") is False, + f"竟然配上了: {reply}") + check("给出的理由是明确的", + "配对" in str(reply.get("error", "")) or + "关闭" in str(reply.get("error", "")), + str(reply.get("error")))''' + +SILENT_NEW = ''' # 行为变了:配对窗口关着时,**码错才拒**,码对放行。 + # 因为配对码本身就是密钥,老逻辑"首台配对后自动关窗" + # 会让重装手表的人永久连不上(实测踩过)。 + check("静默模式下拿着错误的码配不上", + reply.get("ok") is False, + f"竟然配上了: {reply}") + check("给出的理由是明确的", + "配对" in str(reply.get("error", "")) or + "关闭" in str(reply.get("error", "")), + str(reply.get("error")))''' + + +def patch(path: Path, old: str, new: str, label: str) -> bool: + text = path.read_text(encoding="utf-8") + if new.splitlines()[0].strip() in text and "错误的码" in text: + print(f" {path.name}: 已经是新版") + return True + if old not in text: + print(f" [失败] {path.name}: 找不到「{label}」那段") + return False + path.write_text(text.replace(old, new, 1), encoding="utf-8", newline="\n") + print(f" {path.name}: 已更新({label})") + try: + ast.parse(path.read_text(encoding="utf-8")) + return True + except SyntaxError as exc: + print(f" [失败] {path.name} 语法错误: 行 {exc.lineno}: {exc.msg}") + return False + + +def main() -> int: + ok = patch(FLOW, FLOW_OLD, FLOW_NEW, "未配对设备被挡") + ok = patch(SILENT, SILENT_OLD, SILENT_NEW, "静默模式配对") and ok + + # 静默模式那个测试原来用的是正确码还是错误码?看一下 + print() + text = SILENT.read_text(encoding="utf-8") + import re + m = re.search(r'"code":\s*([^,}\n]+)', text) + print(f" test_silent_mode 里用的码: {m.group(1).strip() if m else '?'}") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/verify_new_site.sh b/_build/verify_new_site.sh new file mode 100644 index 0000000..98b78f4 --- /dev/null +++ b/_build/verify_new_site.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# 验证新官网:每个区块都要在,且能正常访问 +set -u + +BASE=http://127.0.0.1:8080 +PUB=https://relay.xxmxxs.top + +echo " 1) 本地官网" +curl -s --max-time 20 "$BASE/" -o /tmp/site.html 2>/dev/null +size=$(wc -c < /tmp/site.html) +echo " HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/") ${size} 字节" + +echo " 2) 各区块" +for pair in "吸顶导航:nav-in" "Hero:hero" "功能网格:id=features" \ + "三步上手:id=how" "下载区:id=download" "FAQ:id=faq" \ + "页脚:foot-in" "应用图标:favicon.ico"; do + label="${pair%%:*}" + key="${pair##*:}" + if grep -q "$key" /tmp/site.html; then + echo " OK $label" + else + echo " XX $label" + fi +done + +echo " 3) 下载链接" +grep -o 'href="/download/[^"]*"' /tmp/site.html | sed 's/^/ /' + +echo " 4) 配色(应该全是浅色)" +python3 - <<'PY' +import re +html = open('/tmp/site.html', encoding='utf-8').read() +colors = sorted(set(re.findall(r'#[0-9a-fA-F]{6}', html))) +dark = [c for c in colors if int(c[1:3], 16) + int(c[3:5], 16) + int(c[5:7], 16) < 200] +print(' 用到的颜色:', ' '.join(colors)) +print(' 深色值:', dark if dark else '无 ✓') +PY + +echo " 5) 公网访问" +echo " HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 40 -H 'User-Agent: LanRemote/1.4.0' "$PUB/")" + +echo " 6) 管理页还能进吗(路径应该没被影响)" +echo " /zhongzhuanzhan HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/zhongzhuanzhan")" +echo " 官网里不该有管理入口: $(grep -c '管理入口\|zhongzhuanzhan' /tmp/site.html) 处" diff --git a/_build/verify_new_site2.sh b/_build/verify_new_site2.sh new file mode 100644 index 0000000..6e9a0b8 --- /dev/null +++ b/_build/verify_new_site2.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# 验证新官网(修正引号问题) +set -u + +BASE=http://127.0.0.1:8080 +PUB=https://relay.xxmxxs.top + +curl -s --max-time 20 "$BASE/" -o /tmp/site.html 2>/dev/null +size=$(wc -c < /tmp/site.html) +echo " 官网: HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/") ${size} 字节" + +echo +echo " 各区块(注意 HTML 里是 id=\"xxx\" 带引号):" +check() { + if grep -q "$2" /tmp/site.html; then + echo " OK $1" + else + echo " XX $1" + fi +} +check "吸顶导航" 'nav-in' +check "Hero 标题" '老师抬一下手腕' +check "最新版号" 'badge' +check "功能网格" 'id="features"' +check "三步上手" 'id="how"' +check "下载区" 'id="download"' +check "常见问题" 'id="faq"' +check "页脚" 'foot-in' +check "应用图标" 'favicon.ico' +check "九张功能卡" 'feat-title' +check "三个步骤卡" 'step-num' +check "三个下载卡" 'dl-card' +check "六条 FAQ" 'class="faq"' + +echo +echo " 各区块计数:" +printf " 功能卡 %s 张\n" "$(grep -o 'feat-title' /tmp/site.html | wc -l)" +printf " 步骤卡 %s 个\n" "$(grep -o 'step-num' /tmp/site.html | wc -l)" +printf " 下载卡 %s 个\n" "$(grep -o 'dl-card' /tmp/site.html | wc -l)" +printf " FAQ %s 条\n" "$(grep -o 'class="faq"' /tmp/site.html | wc -l)" + +echo +echo " 下载链接:" +grep -o 'href="/download/[^"]*"' /tmp/site.html | sed 's/^/ /' + +echo +echo " 配色(白底主题,深色只该有正文色):" +python3 - <<'PY' +import re +html = open('/tmp/site.html', encoding='utf-8').read() +colors = sorted(set(re.findall(r'#[0-9a-fA-F]{6}', html))) +print(' ', ' '.join(colors)) +# 允许的深色:正文 #1a1d21、主色 #2563eb、深一档 #1d4ed8 +ALLOW_DARK = {'#1a1d21', '#2563eb', '#1d4ed8'} +bad = [] +for c in colors: + v = int(c[1:3], 16) + int(c[3:5], 16) + int(c[5:7], 16) + if v < 420 and c not in ALLOW_DARK: + bad.append(c) +print(' 非白底配色里不该出现的深色:', bad if bad else '无 ✓') +print(' 底色是白:', '#ffffff' in colors) +PY + +echo +echo " 其它页面没被影响:" +echo " 管理页 /zhongzhuanzhan HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/zhongzhuanzhan")" +echo " /admin 跳转 -> $(curl -s -o /dev/null -w '%{redirect_url}' --max-time 15 "$BASE/admin")" +echo " favicon HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/favicon.ico")" +echo " 下载接口 HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$BASE/v1/downloads")" +echo " 官网里没有管理入口: $(grep -c '管理入口' /tmp/site.html) 处" + +echo +echo " 公网:" +echo " 官网 HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 40 -H 'User-Agent: LanRemote/1.4.0' "$PUB/")" +echo " 延迟 $(curl -s -o /dev/null -w '%{time_total}' --max-time 40 -H 'User-Agent: LanRemote/1.4.0' "$PUB/") 秒" diff --git a/_build/wire_new_site.py b/_build/wire_new_site.py new file mode 100644 index 0000000..8d7ca49 --- /dev/null +++ b/_build/wire_new_site.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +r'''把官网渲染换成新的独立模块 site.render_site()。 + +原来的 _site_page() 是内联在 relay.py 里的一大段 HTML。 +现在拆到 relay/site.py,relay.py 里只留一个薄封装。 +''' +from __future__ import annotations + +import ast +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +RELAY = ROOT / "relay" / "relay.py" + +# 找出 _site_page 函数的起止,整段替换 +NEW_FUNC = '''def _site_page() -> str: + """官网首页(公开,不用登录)。 + + 实际渲染在 relay/site.py —— relay.py 已经两千多行, + 再放几百行 HTML 就没法看了。网站属于展示层, + 跟中转逻辑分开更清楚。 + """ + try: + from site import render_site + except ImportError: + # 打包/部署时 relay 目录就是工作目录,用相对导入兜底 + import importlib.util + spec = importlib.util.spec_from_file_location( + "ykxmz_site", Path(__file__).resolve().parent / "site.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + render_site = module.render_site + return render_site(_list_downloads()) +''' + + +def main() -> int: + text = RELAY.read_text(encoding="utf-8") + + if "from site import render_site" in text: + print(" 已经接过了") + return 0 + + start = text.find("def _site_page() -> str:") + if start < 0: + print(" [失败] 找不到 _site_page") + return 1 + + # 找到下一个顶层 def + nxt = text.find("\ndef ", start + 10) + if nxt < 0: + print(" [失败] 找不到 _site_page 的结尾") + return 1 + + text = text[:start] + NEW_FUNC + "\n" + text[nxt + 1:] + + # 确保 Path 和 _list_downloads 可用 + if "\nfrom pathlib import Path" not in text: + text = text.replace("import os\n", "import os\nfrom pathlib import Path\n", 1) + print(" 补 from pathlib import Path") + + RELAY.write_text(text, encoding="utf-8", newline="\n") + print(" _site_page 改成调用 site.render_site()") + + try: + ast.parse(text) + print(" relay.py 语法 OK") + except SyntaxError as exc: + print(" [失败] 语法错误: 行 %s: %s" % (exc.lineno, exc.msg)) + lines = text.splitlines() + for i in range(max(0, (exc.lineno or 1) - 3), + min(len(lines), (exc.lineno or 1) + 2)): + print(" %d: %s" % (i + 1, lines[i][:90])) + return 1 + + # site.py 也检查一下 + try: + ast.parse((ROOT / "relay" / "site.py").read_text(encoding="utf-8")) + print(" site.py 语法 OK") + except SyntaxError as exc: + print(" [失败] site.py 语法错误: 行 %s: %s" % (exc.lineno, exc.msg)) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_tests/acceptance.py b/_tests/acceptance.py index 0599674..85bf89b 100644 --- a/_tests/acceptance.py +++ b/_tests/acceptance.py @@ -141,7 +141,7 @@ def main() -> int: c_login == 401 and c_url == 200, f"管理接口未登录={c_login}(应为 401);" f"/url={c_url} 返回 {url_body.strip()[:60]};" - f"管理密码={plain or '120830'}") + f"管理密码={plain or '@REVAedu120830'}") else: item("管理页需要登录 + 管理接口", False, "没有公网地址,无法测") diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$10.class b/_tests/out_app/com/lanremote/watch/MainActivity$10.class index 1161610..bb22ba5 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$10.class and b/_tests/out_app/com/lanremote/watch/MainActivity$10.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$100.class b/_tests/out_app/com/lanremote/watch/MainActivity$100.class index 60db030..9d16215 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$100.class and b/_tests/out_app/com/lanremote/watch/MainActivity$100.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class index bb7a906..ef7da16 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$101.class b/_tests/out_app/com/lanremote/watch/MainActivity$101.class index 56e58a2..19b43f3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$101.class and b/_tests/out_app/com/lanremote/watch/MainActivity$101.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class index 5b45781..9f8a9b8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$102.class b/_tests/out_app/com/lanremote/watch/MainActivity$102.class index 64ce1f1..e3f5a5b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$102.class and b/_tests/out_app/com/lanremote/watch/MainActivity$102.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$103.class b/_tests/out_app/com/lanremote/watch/MainActivity$103.class index b27ab2f..a37b2f0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$103.class and b/_tests/out_app/com/lanremote/watch/MainActivity$103.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$104.class b/_tests/out_app/com/lanremote/watch/MainActivity$104.class index 7405faf..b93c8a3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$104.class and b/_tests/out_app/com/lanremote/watch/MainActivity$104.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class index cfb4155..62080df 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$105.class b/_tests/out_app/com/lanremote/watch/MainActivity$105.class index 3a4ba01..6d510aa 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$105.class and b/_tests/out_app/com/lanremote/watch/MainActivity$105.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class index 66a53fe..cb6dd96 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class index fb6171a..049cb11 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106.class b/_tests/out_app/com/lanremote/watch/MainActivity$106.class index 1b3e494..cc01e82 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class index 7adb207..5a8e91e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class index c775468..31985fb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107.class b/_tests/out_app/com/lanremote/watch/MainActivity$107.class index 53957d4..d3dc29b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$108.class b/_tests/out_app/com/lanremote/watch/MainActivity$108.class index 69b7509..c968dac 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$108.class and b/_tests/out_app/com/lanremote/watch/MainActivity$108.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$109.class b/_tests/out_app/com/lanremote/watch/MainActivity$109.class index ead6ce3..d93b18d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$109.class and b/_tests/out_app/com/lanremote/watch/MainActivity$109.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$11.class b/_tests/out_app/com/lanremote/watch/MainActivity$11.class index 17c3634..d36c0e4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$11.class and b/_tests/out_app/com/lanremote/watch/MainActivity$11.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$110.class b/_tests/out_app/com/lanremote/watch/MainActivity$110.class index f660365..213c1f8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$110.class and b/_tests/out_app/com/lanremote/watch/MainActivity$110.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class index 4abf3a2..cb2da19 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class index b3e7896..cc025fa 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111.class b/_tests/out_app/com/lanremote/watch/MainActivity$111.class index 0642478..3254fb4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class index 80f8493..2397c63 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112.class b/_tests/out_app/com/lanremote/watch/MainActivity$112.class index 59e762d..4436b64 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$112.class and b/_tests/out_app/com/lanremote/watch/MainActivity$112.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$113.class b/_tests/out_app/com/lanremote/watch/MainActivity$113.class index e9dc012..c60db35 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$113.class and b/_tests/out_app/com/lanremote/watch/MainActivity$113.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$114.class b/_tests/out_app/com/lanremote/watch/MainActivity$114.class index 7a8ac30..caa13f8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$114.class and b/_tests/out_app/com/lanremote/watch/MainActivity$114.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class index 037d1be..cc506d6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$115.class b/_tests/out_app/com/lanremote/watch/MainActivity$115.class index ab30005..2c6fdc0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$115.class and b/_tests/out_app/com/lanremote/watch/MainActivity$115.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class index c3b922e..bed390c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class index a43daf6..098050b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116.class b/_tests/out_app/com/lanremote/watch/MainActivity$116.class index d6f110f..63067a6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class index 00d1e3d..efbbab8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117.class b/_tests/out_app/com/lanremote/watch/MainActivity$117.class index f55b7f8..bcd3105 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$117.class and b/_tests/out_app/com/lanremote/watch/MainActivity$117.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$118.class b/_tests/out_app/com/lanremote/watch/MainActivity$118.class index 758b1cd..27950ce 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$118.class and b/_tests/out_app/com/lanremote/watch/MainActivity$118.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$119.class b/_tests/out_app/com/lanremote/watch/MainActivity$119.class index 6f9e70f..ccad8ac 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$119.class and b/_tests/out_app/com/lanremote/watch/MainActivity$119.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$12.class b/_tests/out_app/com/lanremote/watch/MainActivity$12.class index bde1439..5a72f4d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$12.class and b/_tests/out_app/com/lanremote/watch/MainActivity$12.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$120.class b/_tests/out_app/com/lanremote/watch/MainActivity$120.class index 59ee059..1a8b970 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$120.class and b/_tests/out_app/com/lanremote/watch/MainActivity$120.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$121.class b/_tests/out_app/com/lanremote/watch/MainActivity$121.class index f46ff38..93d9f31 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$121.class and b/_tests/out_app/com/lanremote/watch/MainActivity$121.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class index 18223b2..a99efdf 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$122.class b/_tests/out_app/com/lanremote/watch/MainActivity$122.class index 6e816cf..42353c1 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$122.class and b/_tests/out_app/com/lanremote/watch/MainActivity$122.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$123.class b/_tests/out_app/com/lanremote/watch/MainActivity$123.class index 1e9c6d9..55e9150 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$123.class and b/_tests/out_app/com/lanremote/watch/MainActivity$123.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$124.class b/_tests/out_app/com/lanremote/watch/MainActivity$124.class index bb262d0..9973705 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$124.class and b/_tests/out_app/com/lanremote/watch/MainActivity$124.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$125.class b/_tests/out_app/com/lanremote/watch/MainActivity$125.class index 5a13c92..01e617c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$125.class and b/_tests/out_app/com/lanremote/watch/MainActivity$125.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$126.class b/_tests/out_app/com/lanremote/watch/MainActivity$126.class index 9f91a8a..12977d8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$126.class and b/_tests/out_app/com/lanremote/watch/MainActivity$126.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class index 6605170..7f9a510 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class index 8f780c8..d337685 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127.class b/_tests/out_app/com/lanremote/watch/MainActivity$127.class index 3509908..d724f4e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$128.class b/_tests/out_app/com/lanremote/watch/MainActivity$128.class index ce46ad7..3beb3fb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$128.class and b/_tests/out_app/com/lanremote/watch/MainActivity$128.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$129.class b/_tests/out_app/com/lanremote/watch/MainActivity$129.class index 32f1076..42c4eb3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$129.class and b/_tests/out_app/com/lanremote/watch/MainActivity$129.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$13.class b/_tests/out_app/com/lanremote/watch/MainActivity$13.class index e84e033..4a2a083 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$13.class and b/_tests/out_app/com/lanremote/watch/MainActivity$13.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$14.class b/_tests/out_app/com/lanremote/watch/MainActivity$14.class index 8036b34..ad21b38 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$14.class and b/_tests/out_app/com/lanremote/watch/MainActivity$14.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$15.class b/_tests/out_app/com/lanremote/watch/MainActivity$15.class index 628d4b2..ff38e4a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$15.class and b/_tests/out_app/com/lanremote/watch/MainActivity$15.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$16.class b/_tests/out_app/com/lanremote/watch/MainActivity$16.class index 2a60b89..12fc8b4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$16.class and b/_tests/out_app/com/lanremote/watch/MainActivity$16.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$17.class b/_tests/out_app/com/lanremote/watch/MainActivity$17.class index 800896f..1a260ea 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$17.class and b/_tests/out_app/com/lanremote/watch/MainActivity$17.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$18.class b/_tests/out_app/com/lanremote/watch/MainActivity$18.class index 9d925e3..db5c152 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$18.class and b/_tests/out_app/com/lanremote/watch/MainActivity$18.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$19.class b/_tests/out_app/com/lanremote/watch/MainActivity$19.class index e05af44..c3358e8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$19.class and b/_tests/out_app/com/lanremote/watch/MainActivity$19.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$20.class b/_tests/out_app/com/lanremote/watch/MainActivity$20.class index 98860ed..1da613a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$20.class and b/_tests/out_app/com/lanremote/watch/MainActivity$20.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$21.class b/_tests/out_app/com/lanremote/watch/MainActivity$21.class index 56688b3..f159b62 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$21.class and b/_tests/out_app/com/lanremote/watch/MainActivity$21.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$22.class b/_tests/out_app/com/lanremote/watch/MainActivity$22.class index 6f98628..cbeb1b6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$22.class and b/_tests/out_app/com/lanremote/watch/MainActivity$22.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$23.class b/_tests/out_app/com/lanremote/watch/MainActivity$23.class index 917749b..9c46b70 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$23.class and b/_tests/out_app/com/lanremote/watch/MainActivity$23.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$24.class b/_tests/out_app/com/lanremote/watch/MainActivity$24.class index 0ee63e5..8bf5675 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$24.class and b/_tests/out_app/com/lanremote/watch/MainActivity$24.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$25.class b/_tests/out_app/com/lanremote/watch/MainActivity$25.class index f9741be..b23d366 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$25.class and b/_tests/out_app/com/lanremote/watch/MainActivity$25.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$26.class b/_tests/out_app/com/lanremote/watch/MainActivity$26.class index ea5e034..5474f2c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$26.class and b/_tests/out_app/com/lanremote/watch/MainActivity$26.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$27.class b/_tests/out_app/com/lanremote/watch/MainActivity$27.class index f40e10c..8a547a3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$27.class and b/_tests/out_app/com/lanremote/watch/MainActivity$27.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$28.class b/_tests/out_app/com/lanremote/watch/MainActivity$28.class index 113b9d9..b22c60d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$28.class and b/_tests/out_app/com/lanremote/watch/MainActivity$28.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$29.class b/_tests/out_app/com/lanremote/watch/MainActivity$29.class index b8dbb1b..0d25f07 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$29.class and b/_tests/out_app/com/lanremote/watch/MainActivity$29.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$3.class b/_tests/out_app/com/lanremote/watch/MainActivity$3.class index c4ff7f2..562b013 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$3.class and b/_tests/out_app/com/lanremote/watch/MainActivity$3.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$30.class b/_tests/out_app/com/lanremote/watch/MainActivity$30.class index 8c821b2..415523e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$30.class and b/_tests/out_app/com/lanremote/watch/MainActivity$30.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$31.class b/_tests/out_app/com/lanremote/watch/MainActivity$31.class index 1a50dd6..15419df 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$31.class and b/_tests/out_app/com/lanremote/watch/MainActivity$31.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$32.class b/_tests/out_app/com/lanremote/watch/MainActivity$32.class index d8a1590..c41ba9a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$32.class and b/_tests/out_app/com/lanremote/watch/MainActivity$32.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$33.class b/_tests/out_app/com/lanremote/watch/MainActivity$33.class index 451b3e4..ae5e689 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$33.class and b/_tests/out_app/com/lanremote/watch/MainActivity$33.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$34.class b/_tests/out_app/com/lanremote/watch/MainActivity$34.class index 3ce0f97..b73de35 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$34.class and b/_tests/out_app/com/lanremote/watch/MainActivity$34.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$35.class b/_tests/out_app/com/lanremote/watch/MainActivity$35.class index 96a164c..25d42ad 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$35.class and b/_tests/out_app/com/lanremote/watch/MainActivity$35.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$36.class b/_tests/out_app/com/lanremote/watch/MainActivity$36.class index fc81e19..a6d9c6a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$36.class and b/_tests/out_app/com/lanremote/watch/MainActivity$36.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$37.class b/_tests/out_app/com/lanremote/watch/MainActivity$37.class index bf8eb63..fffbc66 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$37.class and b/_tests/out_app/com/lanremote/watch/MainActivity$37.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$38.class b/_tests/out_app/com/lanremote/watch/MainActivity$38.class index c6df1e1..05a68b9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$38.class and b/_tests/out_app/com/lanremote/watch/MainActivity$38.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$39.class b/_tests/out_app/com/lanremote/watch/MainActivity$39.class index 9075f4b..cd86486 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$39.class and b/_tests/out_app/com/lanremote/watch/MainActivity$39.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$4.class b/_tests/out_app/com/lanremote/watch/MainActivity$4.class index 33af778..ef4a53d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$4.class and b/_tests/out_app/com/lanremote/watch/MainActivity$4.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$40.class b/_tests/out_app/com/lanremote/watch/MainActivity$40.class index 6252fb3..b06760e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$40.class and b/_tests/out_app/com/lanremote/watch/MainActivity$40.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$41.class b/_tests/out_app/com/lanremote/watch/MainActivity$41.class index 15dea3c..b044b7a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$41.class and b/_tests/out_app/com/lanremote/watch/MainActivity$41.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$42.class b/_tests/out_app/com/lanremote/watch/MainActivity$42.class index 8d983c2..04cb4be 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$42.class and b/_tests/out_app/com/lanremote/watch/MainActivity$42.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$43.class b/_tests/out_app/com/lanremote/watch/MainActivity$43.class index 66f286c..7fc69ac 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$43.class and b/_tests/out_app/com/lanremote/watch/MainActivity$43.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$44.class b/_tests/out_app/com/lanremote/watch/MainActivity$44.class index 04eb018..f94a03d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$44.class and b/_tests/out_app/com/lanremote/watch/MainActivity$44.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$45.class b/_tests/out_app/com/lanremote/watch/MainActivity$45.class index 82232ed..1a68904 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$45.class and b/_tests/out_app/com/lanremote/watch/MainActivity$45.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$46.class b/_tests/out_app/com/lanremote/watch/MainActivity$46.class index c2a6e7e..2e732e9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$46.class and b/_tests/out_app/com/lanremote/watch/MainActivity$46.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$47.class b/_tests/out_app/com/lanremote/watch/MainActivity$47.class index 0ea0eff..833b21c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$47.class and b/_tests/out_app/com/lanremote/watch/MainActivity$47.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$48.class b/_tests/out_app/com/lanremote/watch/MainActivity$48.class index f317666..421adea 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$48.class and b/_tests/out_app/com/lanremote/watch/MainActivity$48.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$49.class b/_tests/out_app/com/lanremote/watch/MainActivity$49.class index 508dafd..d9b384f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$49.class and b/_tests/out_app/com/lanremote/watch/MainActivity$49.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$5.class b/_tests/out_app/com/lanremote/watch/MainActivity$5.class index 4784993..76a36f4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$5.class and b/_tests/out_app/com/lanremote/watch/MainActivity$5.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$50.class b/_tests/out_app/com/lanremote/watch/MainActivity$50.class index 668a7ad..0183dee 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$50.class and b/_tests/out_app/com/lanremote/watch/MainActivity$50.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$51.class b/_tests/out_app/com/lanremote/watch/MainActivity$51.class index 25f341b..cc2776a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$51.class and b/_tests/out_app/com/lanremote/watch/MainActivity$51.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$52.class b/_tests/out_app/com/lanremote/watch/MainActivity$52.class index 774e749..9fdea66 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$52.class and b/_tests/out_app/com/lanremote/watch/MainActivity$52.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$53.class b/_tests/out_app/com/lanremote/watch/MainActivity$53.class index 8e2d441..8ba8eae 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$53.class and b/_tests/out_app/com/lanremote/watch/MainActivity$53.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$54.class b/_tests/out_app/com/lanremote/watch/MainActivity$54.class index 6f98a45..343215b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$54.class and b/_tests/out_app/com/lanremote/watch/MainActivity$54.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$55.class b/_tests/out_app/com/lanremote/watch/MainActivity$55.class index d3df365..d5bcd91 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$55.class and b/_tests/out_app/com/lanremote/watch/MainActivity$55.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$56.class b/_tests/out_app/com/lanremote/watch/MainActivity$56.class index 08be209..c7e296a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$56.class and b/_tests/out_app/com/lanremote/watch/MainActivity$56.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$57.class b/_tests/out_app/com/lanremote/watch/MainActivity$57.class index fc7c2e6..7c28722 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$57.class and b/_tests/out_app/com/lanremote/watch/MainActivity$57.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class index 5b8e9a4..1676c0f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58.class b/_tests/out_app/com/lanremote/watch/MainActivity$58.class index b7c6046..0020652 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$58.class and b/_tests/out_app/com/lanremote/watch/MainActivity$58.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$59.class b/_tests/out_app/com/lanremote/watch/MainActivity$59.class index d5ffe5e..3aab4ae 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$59.class and b/_tests/out_app/com/lanremote/watch/MainActivity$59.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$6.class b/_tests/out_app/com/lanremote/watch/MainActivity$6.class index bfed226..686cd95 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$6.class and b/_tests/out_app/com/lanremote/watch/MainActivity$6.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$60.class b/_tests/out_app/com/lanremote/watch/MainActivity$60.class index b7f663e..8e595a8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$60.class and b/_tests/out_app/com/lanremote/watch/MainActivity$60.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$61.class b/_tests/out_app/com/lanremote/watch/MainActivity$61.class index 8089e70..98f040f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$61.class and b/_tests/out_app/com/lanremote/watch/MainActivity$61.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$62.class b/_tests/out_app/com/lanremote/watch/MainActivity$62.class index 00e98b9..1a1d0a6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$62.class and b/_tests/out_app/com/lanremote/watch/MainActivity$62.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$63.class b/_tests/out_app/com/lanremote/watch/MainActivity$63.class index 1f41301..c88e4dc 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$63.class and b/_tests/out_app/com/lanremote/watch/MainActivity$63.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class index b4e6709..b9a807b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class index 10a3179..eea71fd 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64.class b/_tests/out_app/com/lanremote/watch/MainActivity$64.class index deb78c7..681a19b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$65.class b/_tests/out_app/com/lanremote/watch/MainActivity$65.class index 7016e9d..5cd7553 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$65.class and b/_tests/out_app/com/lanremote/watch/MainActivity$65.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$66.class b/_tests/out_app/com/lanremote/watch/MainActivity$66.class index 3b90635..d882b5c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$66.class and b/_tests/out_app/com/lanremote/watch/MainActivity$66.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$67.class b/_tests/out_app/com/lanremote/watch/MainActivity$67.class index 06173da..d548e41 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$67.class and b/_tests/out_app/com/lanremote/watch/MainActivity$67.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class index a3679f9..5d57835 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class index 60ae5b0..ad9d279 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68.class b/_tests/out_app/com/lanremote/watch/MainActivity$68.class index 3603deb..1db3494 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$69.class b/_tests/out_app/com/lanremote/watch/MainActivity$69.class index 0a8e6ee..761f493 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$69.class and b/_tests/out_app/com/lanremote/watch/MainActivity$69.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$7.class b/_tests/out_app/com/lanremote/watch/MainActivity$7.class index 4e84da1..bd004c4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$7.class and b/_tests/out_app/com/lanremote/watch/MainActivity$7.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$70.class b/_tests/out_app/com/lanremote/watch/MainActivity$70.class index 9e7ab85..f170b8a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$70.class and b/_tests/out_app/com/lanremote/watch/MainActivity$70.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$71.class b/_tests/out_app/com/lanremote/watch/MainActivity$71.class index cb68f25..f6d0435 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$71.class and b/_tests/out_app/com/lanremote/watch/MainActivity$71.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$72.class b/_tests/out_app/com/lanremote/watch/MainActivity$72.class index 50137f7..e5dbcd3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$72.class and b/_tests/out_app/com/lanremote/watch/MainActivity$72.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$73.class b/_tests/out_app/com/lanremote/watch/MainActivity$73.class index 0714ac0..1486d7b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$73.class and b/_tests/out_app/com/lanremote/watch/MainActivity$73.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$74.class b/_tests/out_app/com/lanremote/watch/MainActivity$74.class index 07b8912..1c4bcc8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$74.class and b/_tests/out_app/com/lanremote/watch/MainActivity$74.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$75.class b/_tests/out_app/com/lanremote/watch/MainActivity$75.class index 6b5fe8a..2f8af4d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$75.class and b/_tests/out_app/com/lanremote/watch/MainActivity$75.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class index 65eadee..c833c43 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class index 52cc737..6d01d7b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76.class b/_tests/out_app/com/lanremote/watch/MainActivity$76.class index 6a116db..dc0b0c9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$77.class b/_tests/out_app/com/lanremote/watch/MainActivity$77.class index c46ebd0..4973a18 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$77.class and b/_tests/out_app/com/lanremote/watch/MainActivity$77.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class index 3743f62..51caca4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78.class b/_tests/out_app/com/lanremote/watch/MainActivity$78.class index 5aba200..6f05dbb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$78.class and b/_tests/out_app/com/lanremote/watch/MainActivity$78.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class index 319cfae..b4ae574 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class index 7dd14a0..b30cf9c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79.class b/_tests/out_app/com/lanremote/watch/MainActivity$79.class index fce406d..b509168 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$8.class b/_tests/out_app/com/lanremote/watch/MainActivity$8.class index 6c5efbc..2d2bc29 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$8.class and b/_tests/out_app/com/lanremote/watch/MainActivity$8.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class index 99ba5ff..1dc91a3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class index 7154fd3..4e3f601 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80.class b/_tests/out_app/com/lanremote/watch/MainActivity$80.class index 5cd7161..a7865c2 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$81.class b/_tests/out_app/com/lanremote/watch/MainActivity$81.class index 97ed83b..85e0755 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$81.class and b/_tests/out_app/com/lanremote/watch/MainActivity$81.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$82.class b/_tests/out_app/com/lanremote/watch/MainActivity$82.class index d680c9a..5eb075b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$82.class and b/_tests/out_app/com/lanremote/watch/MainActivity$82.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$83.class b/_tests/out_app/com/lanremote/watch/MainActivity$83.class index d8f7f71..7f5dd94 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$83.class and b/_tests/out_app/com/lanremote/watch/MainActivity$83.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$84.class b/_tests/out_app/com/lanremote/watch/MainActivity$84.class index b820d01..7ee047c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$84.class and b/_tests/out_app/com/lanremote/watch/MainActivity$84.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$85.class b/_tests/out_app/com/lanremote/watch/MainActivity$85.class index b405782..4fe9553 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$85.class and b/_tests/out_app/com/lanremote/watch/MainActivity$85.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class index 05c4241..5ae0dd4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86.class b/_tests/out_app/com/lanremote/watch/MainActivity$86.class index 9410058..ccc9673 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$86.class and b/_tests/out_app/com/lanremote/watch/MainActivity$86.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$87.class b/_tests/out_app/com/lanremote/watch/MainActivity$87.class index ed59702..3257b86 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$87.class and b/_tests/out_app/com/lanremote/watch/MainActivity$87.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$88.class b/_tests/out_app/com/lanremote/watch/MainActivity$88.class index d4bf713..dbeaabe 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$88.class and b/_tests/out_app/com/lanremote/watch/MainActivity$88.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$89.class b/_tests/out_app/com/lanremote/watch/MainActivity$89.class index 57f8ee6..c648502 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$89.class and b/_tests/out_app/com/lanremote/watch/MainActivity$89.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class index cb7429c..36a3b7a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$9.class b/_tests/out_app/com/lanremote/watch/MainActivity$9.class index 2b8884e..2aff1ad 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$9.class and b/_tests/out_app/com/lanremote/watch/MainActivity$9.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$90.class b/_tests/out_app/com/lanremote/watch/MainActivity$90.class index 5fdacaf..3506d21 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$90.class and b/_tests/out_app/com/lanremote/watch/MainActivity$90.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class index 63b2682..dad9562 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class index c8f865c..f4a522f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91.class b/_tests/out_app/com/lanremote/watch/MainActivity$91.class index f662071..36053c9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class index 02981a6..4195a82 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92.class b/_tests/out_app/com/lanremote/watch/MainActivity$92.class index 27a219c..fd3af80 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$92.class and b/_tests/out_app/com/lanremote/watch/MainActivity$92.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$93.class b/_tests/out_app/com/lanremote/watch/MainActivity$93.class index 355a1dd..3506fa4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$93.class and b/_tests/out_app/com/lanremote/watch/MainActivity$93.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class index aa2ab99..f27a6ad 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$94.class b/_tests/out_app/com/lanremote/watch/MainActivity$94.class index d0924d2..e58f5e7 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$94.class and b/_tests/out_app/com/lanremote/watch/MainActivity$94.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$95.class b/_tests/out_app/com/lanremote/watch/MainActivity$95.class index 0a5149a..0c0a578 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$95.class and b/_tests/out_app/com/lanremote/watch/MainActivity$95.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$96.class b/_tests/out_app/com/lanremote/watch/MainActivity$96.class index 6b6c229..f687ad8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$96.class and b/_tests/out_app/com/lanremote/watch/MainActivity$96.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$97.class b/_tests/out_app/com/lanremote/watch/MainActivity$97.class index 8a384df..9eec8db 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$97.class and b/_tests/out_app/com/lanremote/watch/MainActivity$97.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$98.class b/_tests/out_app/com/lanremote/watch/MainActivity$98.class index b71711c..80bb030 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$98.class and b/_tests/out_app/com/lanremote/watch/MainActivity$98.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$99.class b/_tests/out_app/com/lanremote/watch/MainActivity$99.class index 98a0034..ca6983b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$99.class and b/_tests/out_app/com/lanremote/watch/MainActivity$99.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$Act.class b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class index 095399e..69ee296 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$Act.class and b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class index a332019..63c1f03 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class and b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity.class b/_tests/out_app/com/lanremote/watch/MainActivity.class index 8c2e8a4..f8a5990 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity.class and b/_tests/out_app/com/lanremote/watch/MainActivity.class differ diff --git a/_tests/shots/1-屏幕中间.png b/_tests/shots/1-屏幕中间.png index 1960c17..683dd74 100644 Binary files a/_tests/shots/1-屏幕中间.png and b/_tests/shots/1-屏幕中间.png differ diff --git a/_tests/test_admin_batch_delete.py b/_tests/test_admin_batch_delete.py index 79e9f10..6fafece 100644 --- a/_tests/test_admin_batch_delete.py +++ b/_tests/test_admin_batch_delete.py @@ -92,7 +92,7 @@ def main() -> int: jar = http.cookiejar.CookieJar() opener = urllib.request.build_opener( urllib.request.HTTPCookieProcessor(jar)) - form = urllib.parse.urlencode({"password": "120830"}).encode() + form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode() req = urllib.request.Request(BASE + "/admin/login", data=form, method="POST", headers=_UA) req.add_header("Content-Type", "application/x-www-form-urlencoded") @@ -118,7 +118,7 @@ def main() -> int: print() print("一、管理页上有批量删除的控件") - html = opener.open(urllib.request.Request(BASE + "/", headers=_UA), + html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=_UA), timeout=15).read().decode("utf-8") check("有复选框(input.pick)", "class=pick" in html) check("有全选(pickAll)", "id=pickAll" in html) diff --git a/_tests/test_admin_button.py b/_tests/test_admin_button.py index 424526d..75ab19f 100644 --- a/_tests/test_admin_button.py +++ b/_tests/test_admin_button.py @@ -96,7 +96,7 @@ def main() -> int: print() print("一、登录拿 Cookie") - form = urllib.parse.urlencode({"password": "120830"}).encode() + form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode() req = urllib.request.Request(BASE + "/admin/login", data=form, method="POST", headers=_UA) req.add_header("Content-Type", "application/x-www-form-urlencoded") @@ -109,7 +109,7 @@ def main() -> int: print() print("二、拉管理页,检查删除按钮的 HTML/JS") - req = urllib.request.Request(BASE + "/", headers=_UA) + req = urllib.request.Request(BASE + "/zhongzhuanzhan", headers=_UA) html = opener.open(req, timeout=20).read().decode("utf-8", "replace") # 删除按钮的 class 改过(del -> "btn del"), diff --git a/_tests/test_admin_js.py b/_tests/test_admin_js.py index 5856669..f9ccbcb 100644 --- a/_tests/test_admin_js.py +++ b/_tests/test_admin_js.py @@ -117,7 +117,7 @@ def main() -> int: jar = http.cookiejar.CookieJar() opener = urllib.request.build_opener( urllib.request.HTTPCookieProcessor(jar)) - form = urllib.parse.urlencode({"password": "120830"}).encode() + form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode() req = urllib.request.Request(BASE + "/admin/login", data=form, method="POST", headers=UA) req.add_header("Content-Type", "application/x-www-form-urlencoded") @@ -126,7 +126,7 @@ def main() -> int: except urllib.error.HTTPError: pass - html = opener.open(urllib.request.Request(BASE + "/", headers=UA), + html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=UA), timeout=10).read().decode("utf-8") start = html.find(" + +""" % { + "css": SITE_CSS, + "badge": '
%s
' % badge if badge else "", + "feats": _feature_cards(), + "steps": _step_cards(), + "dls": _download_cards(files), + "faq": _faq_items(), + "_dlsub": ("当前版本 " + version) if version + else "选择你的设备", + }).replace("__LOGO__", _logo()) diff --git a/relay/relay.py b/relay/relay.py index 3c1feec..1fec15f 100644 --- a/relay/relay.py +++ b/relay/relay.py @@ -59,7 +59,7 @@ from collections import deque from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -VERSION = "1.7.0" +VERSION = "1.7.1" # ---------------- 参数(都能用环境变量覆盖)---------------- @@ -782,109 +782,17 @@ def _query(url: str, name: str) -> str: def _site_page() -> str: - """官网首页:简洁介绍 + 下载。**不需要登录**。 + """官网首页(公开,不用登录)。 - 用户要求把 relay.xxmxxs.top 当官网用, - 管理后台挪到 ADMIN_PATH(见 _login_page / _admin_page)。 + 实际渲染在 relay/site.py —— relay.py 已经两千多行, + 再放几百行 HTML 就没法看了。网站属于展示层, + 跟中转逻辑分开更清楚。 """ - files = _list_downloads() - - def pick(suffix, keyword=""): - for item in files: - name = item["name"].lower() - if name.endswith(suffix) and (not keyword or keyword in name): - return item - return None - - setup = pick(".exe") - server = pick(".zip", "server") - watch = pick(".apk") - - def card(title, desc, item, tag): - if not item: - return ('
' + title + '
' - '
' + desc + '
' - '
暂未上传
') - mb = item["size"] / 1024 / 1024 - size = ("%.1f MB" % mb) if mb >= 0.1 else ("%.0f KB" % (item["size"] / 1024)) - href = "/download/" + quote(item["name"]) - return ('
' + title + '
' - '
' + desc + '
' - '
下载 ' + tag + '' - '
' + item["name"] + ' · ' + size + '
') - - ver = "" - for item in files: - found = re.search(r"(\d+\.\d+\.\d+)", item["name"]) - if found: - ver = found.group(1) - break - - cards = (card("电脑端", "教室那台 Windows 电脑装这个", setup, "安装包") - + card("服务器端", "要跨网络才需要,装在一台小机器上", server, "压缩包") - + card("手表端", "安卓手表装这个 APK", watch, "APK")) - - badge = ("最新版 " + ver) if ver else "" - - return """ - - - -ykxmz · 教室电脑手表遥控 - -
- -

ykxmz

-
老师抬一下手腕,就能管教室里那台电脑。
-
""" + badge + """
- -

放课件、放音频、投图到大屏、看看谁在打游戏、下课关机 —— -不用走回讲台去点鼠标。

-
    -
  • 手表和电脑在同一个 WiFi:直连,几十毫秒
  • -
  • 不在同一个网络:自动走中转,办公室也能管教室
  • -
  • 屏幕被看时电脑上一定有提示条,关不掉
  • -
- -
""" + cards + """
- -
- 源码在 Gitee - · 历史版本在 发行版 - · 管理入口 -
- -
""" - + # 注意别把模块叫 site.py —— 那是 Python 标准库的名字, + # import site 会拿到标准库那个,页面直接崩。 + # (这个坑是本地渲染测试抓出来的,部署前就修了。) + from landing import render_site + return render_site(_list_downloads()) def _login_page(message: str = "") -> bytes: """管理页登录页(输密码,不再是 URL 里带钥匙)。""" @@ -1281,13 +1189,13 @@ class Handler(BaseHTTPRequestHandler): return # ---- 管理后台 ---- - # 挪到 /zhingzhuanzhan(用户要求)。 + # 挪到 /zhongzhuanzhan(用户要求)。 # /admin 保留成跳转,免得存过书签的人打不开。 if path == "/admin": - self._redirect("/zhingzhuanzhan") + self._redirect("/zhongzhuanzhan") return - if path == "/zhingzhuanzhan": + if path == "/zhongzhuanzhan": # 管理页要登录 —— 上面会显示配对码,不能谁都能看 if not self._admin_gate(): self._send(200, _login_page( @@ -1406,12 +1314,12 @@ class Handler(BaseHTTPRequestHandler): if not token: log("管理页登录失败(%s),来自 %s" % (username or "未填账号", self._client_ip())) - self._redirect("/zhingzhuanzhan?bad=1") + self._redirect("/zhongzhuanzhan?bad=1") return log("管理页登录成功(%s),来自 %s" % (username, self._client_ip())) self.send_response(302) - self.send_header("Location", "/zhingzhuanzhan") + self.send_header("Location", "/zhongzhuanzhan") self.send_header( "Set-Cookie", f"lr_admin={token}; Path=/; HttpOnly; SameSite=Lax; " diff --git a/windows/lanremote/__init__.py b/windows/lanremote/__init__.py index fc4286a..6a6ad7c 100644 --- a/windows/lanremote/__init__.py +++ b/windows/lanremote/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- """局域网遥控 · Windows 被控端。""" -__version__ = "1.7.0" +__version__ = "1.7.1" APP_NAME = "ykxmz" diff --git a/windows/version_info.txt b/windows/version_info.txt index 35276b6..8a8da10 100644 --- a/windows/version_info.txt +++ b/windows/version_info.txt @@ -27,12 +27,12 @@ VSVersionInfo( [ StringStruct("CompanyName", "ykxmz"), StringStruct("FileDescription", "ykxmz"), - StringStruct("FileVersion", "1.7.0.0"), + StringStruct("FileVersion", "1.7.1.0"), StringStruct("InternalName", "ykxmz"), StringStruct("LegalCopyright", "ykxmz"), StringStruct("OriginalFilename", "ykxmz.exe"), StringStruct("ProductName", "ykxmz"), - StringStruct("ProductVersion", "1.7.0.0"), + StringStruct("ProductVersion", "1.7.1.0"), ], ) ]