mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-19 03:40:44 +00:00
1.7.1:修白屏 + 配对 + 官网管理页分家
手表端 - 修「连上后一片白屏」:initTabs() 从来没被调用过, 导致 tabDefs 为 null、selectTab 直接 return, 标签栏里只剩一个孤立的滑动指示器 - 应用名改成 ykxmz 被控端 - 修「设备已关闭新设备配对」:配对码本身就是密钥, 拿着正确的码就该放行。老逻辑"首台配对后自动关窗"会把 手表重装的人永久挡在外面 - 应用名改成 ykxmz 服务器端 - / 改成官网(公开,简洁介绍 + 下载) - /zhingzhuanzhan 中转站管理(要登录),/admin 自动跳转 - 管理账号 xmz7891,可改 - 官网 / 登录页 / 管理页都加上 favicon
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
# -*- 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'<div class=card><div class=t>{title}</div>'
|
||||
f'<div class=d>{desc}</div>'
|
||||
f'<div class=soon>暂未上传</div></div>')
|
||||
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'<div class=card><div class=t>{title}</div>'
|
||||
f'<div class=d>{desc}</div>'
|
||||
f'<a class=btn href="{href}">下载 {tag}</a>'
|
||||
f'<div class=s>{item["name"]} · {size}</div></div>')
|
||||
|
||||
ver = ""
|
||||
for f in files:
|
||||
m = re.search(r"(\\d+\\.\\d+\\.\\d+)", f["name"])
|
||||
if m:
|
||||
ver = m.group(1)
|
||||
break
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang=zh-CN><head>
|
||||
<meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<title>ykxmz · 教室电脑手表遥控</title>
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<style>
|
||||
*{{box-sizing:border-box}}
|
||||
body{{margin:0;font-family:-apple-system,"Segoe UI","Microsoft YaHei",sans-serif;
|
||||
background:#0d1117;color:#e6edf3;line-height:1.7}}
|
||||
.wrap{{max-width:760px;margin:0 auto;padding:56px 20px 80px}}
|
||||
h1{{font-size:34px;margin:0 0 10px;letter-spacing:-.5px}}
|
||||
.sub{{color:#8b949e;font-size:17px;margin-bottom:8px}}
|
||||
.ver{{display:inline-block;background:#1f6feb22;color:#79c0ff;
|
||||
border:1px solid #1f6feb55;border-radius:20px;
|
||||
padding:2px 12px;font-size:13px;margin-bottom:34px}}
|
||||
.lead{{font-size:16px;color:#c9d1d9;margin:0 0 14px}}
|
||||
ul{{color:#8b949e;padding-left:22px;margin:0 0 40px}}
|
||||
li{{margin:4px 0}}
|
||||
.cards{{display:grid;gap:14px;grid-template-columns:1fr}}
|
||||
@media(min-width:640px){{.cards{{grid-template-columns:1fr 1fr 1fr}}}}
|
||||
.card{{background:#161b22;border:1px solid #30363d;border-radius:12px;
|
||||
padding:18px 16px;display:flex;flex-direction:column}}
|
||||
.card .t{{font-weight:600;font-size:15px;margin-bottom:6px}}
|
||||
.card .d{{color:#8b949e;font-size:13px;flex:1;margin-bottom:14px}}
|
||||
.btn{{display:block;text-align:center;background:#238636;color:#fff;
|
||||
text-decoration:none;padding:9px 14px;border-radius:8px;
|
||||
font-weight:600;font-size:14px}}
|
||||
.btn:hover{{background:#2ea043}}
|
||||
.soon{{color:#6e7681;font-size:13px;text-align:center;padding:9px 0}}
|
||||
.s{{color:#6e7681;font-size:11px;margin-top:8px;
|
||||
word-break:break-all;text-align:center}}
|
||||
.foot{{margin-top:46px;padding-top:20px;border-top:1px solid #21262d;
|
||||
color:#6e7681;font-size:13px}}
|
||||
.foot a{{color:#79c0ff;text-decoration:none}}
|
||||
</style></head><body><div class=wrap>
|
||||
|
||||
<h1>ykxmz</h1>
|
||||
<div class=sub>老师抬一下手腕,就能管教室里那台电脑。</div>
|
||||
<div class=ver>{("最新版 " + ver) if ver else ""}</div>
|
||||
|
||||
<p class=lead>放课件、放音频、投图到大屏、看看谁在打游戏、下课关机 ——
|
||||
不用走回讲台去点鼠标。</p>
|
||||
<ul>
|
||||
<li>手表和电脑在同一个 WiFi:直连,几十毫秒</li>
|
||||
<li>不在同一个网络:自动走中转,办公室也能管教室</li>
|
||||
<li>屏幕被看时电脑上一定有提示条,关不掉</li>
|
||||
</ul>
|
||||
|
||||
<div class=cards>
|
||||
{card("电脑端", "教室那台 Windows 电脑装这个", setup, "安装包")}
|
||||
{card("服务器端", "要跨网络才需要,装在一台小机器上", server, "压缩包")}
|
||||
{card("手表端", "安卓手表装这个 APK", watch, "APK")}
|
||||
</div>
|
||||
|
||||
<div class=foot>
|
||||
源码在 <a href="https://gitee.com/xmz7891/ykxmz" target=_blank rel=noopener>Gitee</a>
|
||||
· 历史版本在 <a href="https://gitee.com/xmz7891/ykxmz/releases" target=_blank rel=noopener>发行版</a>
|
||||
· 管理入口 <a href="{ADMIN_PATH}">在此</a>
|
||||
</div>
|
||||
|
||||
</div></body></html>"""
|
||||
|
||||
|
||||
'''
|
||||
|
||||
|
||||
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 = '<form method=post action=/admin/login>'
|
||||
new_form = ('<form method=post action=/admin/login>'
|
||||
'<input name=username placeholder=账号 autocomplete=username required>')
|
||||
if old_form in text:
|
||||
text = text.replace(old_form, new_form, 1)
|
||||
print(" 登录表单加了账号输入框")
|
||||
|
||||
# ---- 6. 退出后回管理页 ----
|
||||
text = text.replace('<a href="/admin/logout"', f'<a href="/admin/logout"', 1)
|
||||
|
||||
# ---- 7. 插入官网页面函数(放在 _login_page 前面)----
|
||||
anchor = "def _login_page("
|
||||
if anchor in text:
|
||||
text = text.replace(anchor, SITE_PAGE + anchor, 1)
|
||||
print(" 插入官网首页 _site_page()")
|
||||
else:
|
||||
print(" [失败] 找不到 _login_page,官网函数插不进去")
|
||||
ok = False
|
||||
|
||||
if not ok:
|
||||
RELAY.write_text(text, encoding="utf-8", newline="\n")
|
||||
print(" (已把改到一半的内容写回,请检查)")
|
||||
return 1
|
||||
|
||||
RELAY.write_text(text, encoding="utf-8", newline="\n")
|
||||
try:
|
||||
ast.parse(text)
|
||||
print(" relay.py 语法 OK")
|
||||
except SyntaxError as exc:
|
||||
print(f" [失败] 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||||
lines = text.splitlines()
|
||||
for i in range(max(0, exc.lineno - 3), min(len(lines), exc.lineno + 2)):
|
||||
print(f" {i + 1}: {lines[i][:90]}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user