Files

89 lines
3.3 KiB
Python
Raw Permalink Normal View History

2026-09-03 15:35:23 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
映射某个顶导航模块的侧边栏功能结构。
用法: python3 map_menu.py "<模块名>" [in_more]
in_more: 传 1 表示该模块在"更多"下拉里, 需先展开
输出: JSON {module, loc, groups:[{title, items:[{name,href}]}], flat:[...]}
"""
import sys, json, time, urllib.request
DAEMON = "http://127.0.0.1:10086/command"
SESSION = "2haohr"
def call(action, args):
body = json.dumps({"action": action, "args": args, "session": SESSION}).encode()
req = urllib.request.Request(DAEMON, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=40) as resp:
return json.loads(resp.read().decode())
def ev(code):
return call("evaluate", {"code": code}).get("data", {}).get("value")
CLICK_TOP = """
(()=>{const name=%s;
const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>{
const t=(e.innerText||"").trim();const r=e.getBoundingClientRect();
return t===name && r.top<60 && r.width>8 && r.width<160;});
if(els.length){els[0].click();return "top-clicked";}return "not-found";})()
"""
OPEN_MORE = """
(()=>{const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>{
const t=(e.innerText||"").trim();const r=e.getBoundingClientRect();
return t==="更多" && r.top<60 && r.width>8 && r.width<160;});
if(!els.length)return "no-more";
const el=els[0];
["pointerover","mouseover","mouseenter","pointerenter"].forEach(tp=>{
el.dispatchEvent(new MouseEvent(tp,{bubbles:true,cancelable:true,view:window}));});
el.click();
return "more-hover";})()
"""
CLICK_ANY = """
(()=>{const name=%s;
const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>(e.innerText||"").trim()===name && e.getBoundingClientRect().width>8);
if(els.length){els[els.length-1].click();return "clicked";}return "not-found";})()
"""
EXTRACT = r"""
(()=>{
const root=document.querySelector("[class*=leftMenu]")||document.querySelector("[class*=menu_]");
if(!root)return JSON.stringify({loc:location.href,groups:[],flat:[]});
const groups=[]; let cur={title:"",items:[]}; groups.push(cur);
const seen=new Set();
const walk=(el)=>{
for(const c of el.children){
const cls=(c.className||"").toString();
if(/nameTitle/.test(cls) && !c.closest("a")){ const t=(c.innerText||"").trim(); if(t){ cur={title:t,items:[]}; groups.push(cur);} }
if(c.tagName==="A"){ const h=c.getAttribute("href")||""; const t=(c.innerText||"").trim().replace(/\s+/g," ");
if(h&&h.length>1&&t&&!seen.has(h)){seen.add(h);cur.items.push({name:t,href:h});} }
walk(c);
}
};
walk(root);
const flat=[]; groups.forEach(g=>g.items.forEach(it=>flat.push({group:g.title,name:it.name,href:it.href})));
return JSON.stringify({loc:location.href,groups:groups.filter(g=>g.items.length),flat});
})()
"""
def main():
module = sys.argv[1]
in_more = len(sys.argv) > 2 and sys.argv[2] == "1"
name_js = json.dumps(module, ensure_ascii=False)
if in_more:
print("more:", ev(OPEN_MORE)); time.sleep(1.2)
print("click:", ev(CLICK_ANY % name_js))
else:
r = ev(CLICK_TOP % name_js)
print("top:", r)
if r == "not-found":
print("more:", ev(OPEN_MORE)); time.sleep(1.2)
print("click:", ev(CLICK_ANY % name_js))
time.sleep(3.0)
data = ev(EXTRACT)
print(data)
if __name__ == "__main__":
main()