feat: add dedicated douyin workbench entry
This commit is contained in:
@@ -342,7 +342,8 @@ function sendHtml(res, html) {
|
||||
res.end(html);
|
||||
}
|
||||
|
||||
function renderPage() {
|
||||
function renderPage(mode = "full") {
|
||||
const isWorkbenchMode = mode === "workbench";
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
@@ -375,6 +376,36 @@ function renderPage() {
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
h1, h2, h3 { margin: 0; }
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.topbar-links {
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 9px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(22, 49, 61, 0.1);
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-link.active {
|
||||
background: rgba(31, 110, 95, 0.12);
|
||||
color: var(--accent);
|
||||
border-color: rgba(31, 110, 95, 0.24);
|
||||
}
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #0b3c5d, #1f6e5f 58%, #b97524);
|
||||
color: white;
|
||||
@@ -827,20 +858,31 @@ function renderPage() {
|
||||
background: rgba(22, 49, 61, 0.1);
|
||||
margin: 4px 0;
|
||||
}
|
||||
.business-mode .capture-grid,
|
||||
.business-mode .recent-runs-section,
|
||||
.business-mode .capture-footnote {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.grid, .row, .checks, .workbench-layout, .metric-grid, .two-col, .analysis-grid, .video-grid, .video-layout, .toolbar-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="${isWorkbenchMode ? "business-mode" : ""}">
|
||||
<main>
|
||||
<div class="topbar">
|
||||
<span class="pill">${isWorkbenchMode ? "StoryForge / Douyin Workbench" : "StoryForge / Douyin Browser Assist"}</span>
|
||||
<div class="topbar-links">
|
||||
<a class="nav-link ${isWorkbenchMode ? "active" : ""}" href="/workbench">业务工作台</a>
|
||||
<a class="nav-link ${isWorkbenchMode ? "" : "active"}" href="/">采集控制台</a>
|
||||
</div>
|
||||
</div>
|
||||
<section class="hero">
|
||||
<span class="pill">StoryForge / Douyin Browser Assist</span>
|
||||
<h1 style="margin-top: 14px;">用网页点按钮,驱动真实浏览器采集抖音账号</h1>
|
||||
<p>这不是无头绕反爬,而是一个可控的半自动流程。你点击“开始采集”后,脚本会打开真实 Chromium,会话沿用同一份登录态。你在浏览器里登录或过滑块后,回到这里点“已完成登录,继续采集”,系统就会继续抓取主页、creator-center,并按安全规则同步进 StoryForge。</p>
|
||||
<h1 style="margin-top: 2px;">${isWorkbenchMode ? "用业务工作台直接查看账号结论、作品榜单和运营分析" : "用网页点按钮,驱动真实浏览器采集抖音账号"}</h1>
|
||||
<p>${isWorkbenchMode ? "这是面向日常运营的业务页。登录后即可查看账号列表、Agent 结论、完整作品工作台、快照和对标结果。只有在需要抓取新账号时,再切到“采集控制台”。" : "这不是无头绕反爬,而是一个可控的半自动流程。你点击“开始采集”后,脚本会打开真实 Chromium,会话沿用同一份登录态。你在浏览器里登录或过滑块后,回到这里点“已完成登录,继续采集”,系统就会继续抓取主页、creator-center,并按安全规则同步进 StoryForge。"}</p>
|
||||
</section>
|
||||
|
||||
<div class="grid">
|
||||
<div class="grid capture-grid">
|
||||
<section class="card stack">
|
||||
<div>
|
||||
<h2>开始新采集</h2>
|
||||
@@ -923,7 +965,7 @@ function renderPage() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<section class="card recent-runs-section" style="margin-top: 18px;">
|
||||
<div style="display: flex; justify-content: space-between; gap: 12px; align-items: center;">
|
||||
<div>
|
||||
<h2>最近运行</h2>
|
||||
@@ -1096,6 +1138,7 @@ function renderPage() {
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<p class="hint capture-footnote" style="margin-top:14px;">如果你主要是在做账号分析和作品运营,建议直接使用上面的业务工作台;只有在补采新账号时再回到采集控制台。</p>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -2188,7 +2231,11 @@ const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || "/", "http://127.0.0.1");
|
||||
try {
|
||||
if (req.method === "GET" && url.pathname === "/") {
|
||||
sendHtml(res, renderPage());
|
||||
sendHtml(res, renderPage("full"));
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && url.pathname === "/workbench") {
|
||||
sendHtml(res, renderPage("workbench"));
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && url.pathname === "/api/status") {
|
||||
|
||||
35
scripts/start_douyin_workbench.sh
Executable file
35
scripts/start_douyin_workbench.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)"
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
SCRIPT="$ROOT/scripts/douyin-browser-capture/control_panel.mjs"
|
||||
LOG_FILE="${DOUYIN_WORKBENCH_LOG:-/tmp/storyforge-douyin-workbench.log}"
|
||||
|
||||
if lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
echo "douyin workbench already running: http://127.0.0.1:$PORT/workbench"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
nohup env PORT="$PORT" node "$SCRIPT" >"$LOG_FILE" 2>&1 &
|
||||
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
port = os.environ.get("PORT", "3618")
|
||||
url = f"http://127.0.0.1:{port}/workbench"
|
||||
deadline = time.time() + 15
|
||||
last_error = ""
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=3) as resp:
|
||||
print(f"douyin workbench ready: {resp.status} {url}")
|
||||
raise SystemExit(0)
|
||||
except Exception as exc:
|
||||
last_error = str(exc)
|
||||
time.sleep(0.5)
|
||||
print(f"douyin workbench start timeout: {last_error}")
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
20
scripts/status_douyin_workbench.sh
Executable file
20
scripts/status_douyin_workbench.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
|
||||
if ! lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
echo "douyin workbench stopped"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
port = os.environ.get("PORT", "3618")
|
||||
for path in ("/workbench", "/"):
|
||||
url = f"http://127.0.0.1:{port}{path}"
|
||||
with urllib.request.urlopen(url, timeout=5) as resp:
|
||||
print(f"{path}: {resp.status}")
|
||||
PY
|
||||
7
scripts/stop_douyin_workbench.sh
Executable file
7
scripts/stop_douyin_workbench.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
|
||||
lsof -tiTCP:"$PORT" -sTCP:LISTEN | xargs -r kill
|
||||
echo "douyin workbench stopped: $PORT"
|
||||
Reference in New Issue
Block a user