# -*- coding: utf-8 -*-
r"""中转站改造成「官网 + 管理页分家」(修正版)。
上一版脚本自己踩了 f-string 花括号没转义,在写文件前就崩了 ——
所以 relay.py 没被改动。这版全部改成普通字符串拼接。
用户要求:
relay.xxmxxs.top/ -> 官网(简洁介绍 + 下载)
relay.xxmxxs.top/zhingzhuanzhan -> 中转站管理(要登录)
管理账号 xmz7891 密码 @REVAedu120830
"""
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"
def site_page_source() -> str:
"""生成 _site_page() 的源码。用拼接,避免 f-string 花括号地狱。"""
head = '''def _site_page() -> str:
"""官网首页:简洁介绍 + 下载。**不需要登录**。
用户要求把 relay.xxmxxs.top 当官网用,
管理后台挪到 ADMIN_PATH(见 _login_page / _admin_page)。
"""
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 + """
"""
'''
return head
def main() -> int:
text = RELAY.read_text(encoding="utf-8")
if "_site_page" in text:
print(" 已经改造过")
return 0
ok = True
# ---- 1. 补导入 ----
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\n"
"from urllib.parse import quote\n", 1)
print(" 补 from urllib.parse import quote")
# ---- 2. 密码 + 账号 ----
old_pw = 'ADMIN_PASSWORD_DEFAULT = "120830"'
new_pw = ('ADMIN_PASSWORD_DEFAULT = "' + ADMIN_PASS + '"\n'
'# 管理页账号(原来只有密码,用户要求加上账号)\n'
'ADMIN_USER_DEFAULT = "' + ADMIN_USER + '"\n'
'ADMIN_USER = (os.environ.get("RELAY_ADMIN_USER") or\n'
' ADMIN_USER_DEFAULT)')
if old_pw in text:
text = text.replace(old_pw, new_pw, 1)
print(" 默认管理密码 -> " + ADMIN_PASS + ",账号 " + ADMIN_USER)
else:
print(" [提示] 默认密码锚点没找到")
# ---- 3. admin_login 加账号参数 ----
if "def admin_login(password: str) -> str:" in text:
text = text.replace("def admin_login(password: str) -> str:",
"def admin_login(username: str, password: str) -> str:", 1)
print(" admin_login 签名加了账号")
old_body = (' if not password or not secrets.compare_digest(password, admin_password()):\n'
' return ""')
new_body = (' # 账号也要对(用户要求用账号+密码登录)\n'
' if username and username.strip() != ADMIN_USER:\n'
' return ""\n'
' if not password or not secrets.compare_digest(password, admin_password()):\n'
' return ""')
if old_body in text:
text = text.replace(old_body, new_body, 1)
print(" admin_login 加密码校验")
else:
print(" [提示] admin_login 函数体锚点没找到")
# ---- 4. 路由 ----
old_route = (' if path in ("/", "/admin"):\n'
' # 管理页要登录 —— 上面会显示配对码,不能谁都能看\n'
' if not self._admin_gate():\n'
' self._send(200, _login_page(\n'
' "密码不对,再试一次" if _query(self.path, "bad") else ""\n'
' ), "text/html; charset=utf-8")\n'
' return\n'
' self._admin_page()\n'
' return')
new_route = (' # ---- 官网首页(公开,不用登录)----\n'
' if path == "/":\n'
' self._send(200, _site_page(), "text/html; charset=utf-8")\n'
' return\n\n'
' # ---- 管理后台 ----\n'
' # 挪到 ' + ADMIN_PATH + '(用户要求)。\n'
' # /admin 保留成跳转,免得存过书签的人打不开。\n'
' if path == "/admin":\n'
' self._redirect("' + ADMIN_PATH + '")\n'
' return\n\n'
' if path == "' + ADMIN_PATH + '":\n'
' # 管理页要登录 —— 上面会显示配对码,不能谁都能看\n'
' if not self._admin_gate():\n'
' self._send(200, _login_page(\n'
' "账号或密码不对,再试一次"\n'
' if _query(self.path, "bad") else ""\n'
' ), "text/html; charset=utf-8")\n'
' return\n'
' self._admin_page()\n'
' return')
if old_route in text:
text = text.replace(old_route, new_route, 1)
print(" 路由:/ 官网," + ADMIN_PATH + " 管理页")
else:
print(" [失败] 路由锚点没找到")
ok = False
# ---- 5. 登录接口 ----
old_login = (' if path == "/admin/login":\n'
' form = self._body().decode("utf-8", "replace")\n'
' password = ""\n'
' for piece in form.split("&"):\n'
' name, _, value = piece.partition("=")\n'
' if name == "password":\n'
' from urllib.parse import unquote_plus\n'
' password = unquote_plus(value)\n'
' token = admin_login(password)\n'
' if not token:\n'
' log(f"管理页登录失败,来自 {self._client_ip()}")\n'
' self._redirect("/?bad=1")\n'
' return\n'
' log(f"管理页登录成功,来自 {self._client_ip()}")\n'
' self.send_response(302)\n'
' self.send_header("Location", "/")')
new_login = (' if path == "/admin/login":\n'
' form = self._body().decode("utf-8", "replace")\n'
' username = ""\n'
' password = ""\n'
' from urllib.parse import unquote_plus\n'
' for piece in form.split("&"):\n'
' name, _, value = piece.partition("=")\n'
' if name == "password":\n'
' password = unquote_plus(value)\n'
' elif name == "username":\n'
' username = unquote_plus(value)\n'
' token = admin_login(username, password)\n'
' if not token:\n'
' log("管理页登录失败(%s),来自 %s"\n'
' % (username or "未填账号", self._client_ip()))\n'
' self._redirect("' + ADMIN_PATH + '?bad=1")\n'
' return\n'
' log("管理页登录成功(%s),来自 %s"\n'
' % (username, self._client_ip()))\n'
' self.send_response(302)\n'
' self.send_header("Location", "' + ADMIN_PATH + '")')
if old_login in text:
text = text.replace(old_login, new_login, 1)
print(" 登录接口:接受账号 + 密码")
else:
print(" [失败] 登录接口锚点没找到")
ok = False
# ---- 6. 登录表单加账号框 ----
old_form = '