sgclaw: stop zhihu publish flow before editor on creator page

This commit is contained in:
zyl
2026-03-30 13:35:50 +08:00
parent bf09de6700
commit cd94904329
3 changed files with 1334 additions and 156 deletions

View File

@@ -0,0 +1,146 @@
import json
import subprocess
import textwrap
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT_PATH = (
REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-navigate" / "scripts" /
"open_creator_entry.js"
)
def run_open_creator_entry(*, body_text: str, selectors: dict[str, list[dict]]) -> dict:
node_script = textwrap.dedent(
f"""
import fs from 'node:fs';
import vm from 'node:vm';
const scriptPath = {json.dumps(str(SCRIPT_PATH))};
const selectorMap = {json.dumps(selectors, ensure_ascii=False)};
const bodyText = {json.dumps(body_text, ensure_ascii=False)};
const source = fs.readFileSync(scriptPath, 'utf8');
function createNode(spec) {{
const node = {{
tagName: String(spec?.tagName || 'DIV').toUpperCase(),
textContent: String(spec?.textContent ?? ''),
innerText: String(spec?.innerText ?? spec?.textContent ?? ''),
href: String(spec?.href ?? ''),
clicked: false,
click() {{
this.clicked = true;
}},
getBoundingClientRect() {{
return {{
width: spec?.visible === false ? 0 : 120,
height: spec?.visible === false ? 0 : 32,
}};
}},
}};
return node;
}}
const created = new Map();
function createNodeList(selector) {{
const specs = selectorMap[selector] || [];
return specs.map((spec, index) => {{
const key = `${{selector}}#${{index}}`;
if (!created.has(key)) {{
created.set(key, createNode(spec));
}}
return created.get(key);
}});
}}
const bodyNode = createNode({{ tagName: 'BODY', textContent: bodyText, innerText: bodyText }});
const context = {{
args: {{ desired_target: 'article_editor' }},
location: {{ href: 'https://www.zhihu.com/creator' }},
document: {{
body: bodyNode,
querySelector(selector) {{
if (selector === 'body') {{
return bodyNode;
}}
return createNodeList(selector)[0] || null;
}},
querySelectorAll(selector) {{
return createNodeList(selector);
}},
}},
console,
JSON,
Math,
Number,
Object,
RegExp,
Set,
String,
Array,
Error,
}};
try {{
const result = vm.runInNewContext(`(function(){{\\n${{source}}\\n}})()`, context);
process.stdout.write(JSON.stringify({{ ok: true, result, created: Object.fromEntries(created) }}));
}} catch (error) {{
process.stdout.write(JSON.stringify({{
ok: false,
error: String(error && error.message ? error.message : error),
}}));
process.exitCode = 1;
}}
"""
)
completed = subprocess.run(
["node", "--input-type=module", "-e", node_script],
check=False,
capture_output=True,
text=True,
)
payload = json.loads(completed.stdout)
if completed.returncode != 0:
raise AssertionError(payload["error"])
return payload
class SkillScriptZhihuNavigateTest(unittest.TestCase):
def test_open_creator_entry_clicks_anchor_write_entry(self):
payload = run_open_creator_entry(
body_text="创作者中心 写文章",
selectors={
"a[href], button, [role='button']": [
{
"tagName": "a",
"textContent": "写文章",
"href": "https://zhuanlan.zhihu.com/write",
}
]
},
)
self.assertEqual(payload["result"]["status"], "creator_entry_clicked")
self.assertTrue(payload["created"]["a[href], button, [role='button']#0"]["clicked"])
def test_open_creator_entry_clicks_button_write_entry(self):
payload = run_open_creator_entry(
body_text="创作者中心 发布内容",
selectors={
"a[href], button, [role='button']": [
{
"tagName": "button",
"textContent": "写文章",
}
]
},
)
self.assertEqual(payload["result"]["status"], "creator_entry_clicked")
self.assertTrue(payload["created"]["a[href], button, [role='button']#0"]["clicked"])
if __name__ == "__main__":
unittest.main()