内容分四块: 1、产品规划产出 —— MCN 短视频整合营销工作台的①段五份(1a 需求/1b 竞品/1c 画像/1d 策略/1e 场景)、②段两份(2a 功能/2b 布局)、③段界面(DESIGN.md 契约与令牌表 + mcn-workbench.html 原型 + 实测/会诊/审查三份 + 23 张闸门截图)。 2、开源竞品调研 —— 5 个内容工作台项目的取证原始件与 1b 系列分析文档。 3、参考资料 —— 竞品视频抽帧 1145 张 + 2 个源视频 + 功能点截图。 4、机制侧 —— 协作脚本与状态台账、工作区记忆日志、抽帧/OCR 脚本。 .gitignore 只排运行时日志、脚本备份副本与一次性探针输出,其余按原样入库。
85 lines
4.5 KiB
Python
85 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""取证脚本(第 2 棒专用):为「Postiz + PostSider 同源双形态分析」补拉增量面证据。
|
|
|
|
只读 GitHub 公开 raw / API;产物落本目录 api/fork/ 下,逐件带取证时间。
|
|
⛔ 不重复拉第 1 棒已有的 repo.json / README / tree(那些直接复用 api/ 下的)。
|
|
"""
|
|
import json, os, sys, time, urllib.request, urllib.error
|
|
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
OUT = os.path.join(HERE, "api", "fork")
|
|
os.makedirs(OUT, exist_ok=True)
|
|
|
|
HDRS = {"User-Agent": "wb-research/1.0"}
|
|
|
|
# (落盘名, raw 路径, 仓库)
|
|
RAW = "https://raw.githubusercontent.com/%s/%s/%s" # owner/repo/branch/path
|
|
ITEMS = [
|
|
("postsider.apps_mcp.README.md", "lumizone/postsider", "main", "apps/mcp/README.md"),
|
|
("postsider.apps_mcp.server.json", "lumizone/postsider", "main", "apps/mcp/server.json"),
|
|
("postsider.apps_mcp.package.json", "lumizone/postsider", "main", "apps/mcp/package.json"),
|
|
("postsider.apps_mcp.CHANGELOG.md", "lumizone/postsider", "main", "apps/mcp/CHANGELOG.md"),
|
|
("postsider.mcp.tool-inventory.ts", "lumizone/postsider", "main", "apps/mcp/src/__tests__/tool-inventory.ts"),
|
|
("postsider.mcp.server.ts", "lumizone/postsider", "main", "apps/mcp/src/server.ts"),
|
|
("postsider.sdk.README.md", "lumizone/postsider", "main", "apps/sdk/README.md"),
|
|
("postsider.sdk.index.ts", "lumizone/postsider", "main", "apps/sdk/src/index.ts"),
|
|
("postsider.sdk.package.json", "lumizone/postsider", "main", "apps/sdk/package.json"),
|
|
("postsider.package.json", "lumizone/postsider", "main", "package.json"),
|
|
("postsider.ATTRIBUTION.md", "lumizone/postsider", "main", "ATTRIBUTION.md"),
|
|
("postsider.DEPLOYMENT.md", "lumizone/postsider", "main", "DEPLOYMENT.md"),
|
|
("postsider.skill.calendar-review.md", "lumizone/postsider", "main", "apps/mcp/skills/postsider-calendar-review/SKILL.md"),
|
|
("postsider.skill.content-publishing.md", "lumizone/postsider", "main", "apps/mcp/skills/postsider-content-publishing/SKILL.md"),
|
|
("postiz.package.json", "gitroomhq/postiz-app", "main", "package.json"),
|
|
("postiz.version.txt", "gitroomhq/postiz-app", "main", "version.txt"),
|
|
("postiz.sdk.README.md", "gitroomhq/postiz-app", "main", "apps/sdk/README.md"),
|
|
("postiz.sdk.package.json", "gitroomhq/postiz-app", "main", "apps/sdk/package.json"),
|
|
]
|
|
|
|
log = {"fetched_at": time.strftime("%Y-%m-%dT%H:%M:%S"), "items": {}}
|
|
|
|
for name, repo, branch, path in ITEMS:
|
|
url = RAW % (repo, branch, path)
|
|
rec = {"repo": repo, "branch": branch, "path": path, "url": url}
|
|
try:
|
|
req = urllib.request.Request(url, headers=HDRS)
|
|
with urllib.request.urlopen(req, timeout=40) as r:
|
|
body = r.read()
|
|
with open(os.path.join(OUT, name), "wb") as f:
|
|
f.write(body)
|
|
rec["bytes"] = len(body)
|
|
rec["ok"] = True
|
|
except urllib.error.HTTPError as e:
|
|
rec["ok"] = False
|
|
rec["error"] = "HTTP %s" % e.code
|
|
except Exception as e:
|
|
rec["ok"] = False
|
|
rec["error"] = repr(e)
|
|
log["items"][name] = rec
|
|
print("[%s] %s" % ("OK " if rec.get("ok") else "FAIL", name), rec.get("error", rec.get("bytes")))
|
|
time.sleep(0.3)
|
|
|
|
# 版本差异:取两个仓库的 releases(最近 10 条)与 tags
|
|
def api(url):
|
|
req = urllib.request.Request(url, headers={**HDRS, "Accept": "application/vnd.github+json"})
|
|
with urllib.request.urlopen(req, timeout=40) as r:
|
|
return json.loads(r.read().decode("utf-8"))
|
|
|
|
for slug, full in (("postiz", "gitroomhq/postiz-app"), ("postsider", "lumizone/postsider")):
|
|
try:
|
|
rl = api("https://api.github.com/repos/%s/releases?per_page=10" % full)
|
|
save = [{"tag_name": x.get("tag_name"), "name": x.get("name"),
|
|
"published_at": x.get("published_at"), "prerelease": x.get("prerelease")} for x in rl]
|
|
with open(os.path.join(OUT, slug + ".releases.json"), "w", encoding="utf-8") as f:
|
|
json.dump(save, f, ensure_ascii=False, indent=1)
|
|
log["items"][slug + ".releases"] = {"ok": True, "count": len(save)}
|
|
print("[releases]", slug, len(save))
|
|
except Exception as e:
|
|
log["items"][slug + ".releases"] = {"ok": False, "error": repr(e)}
|
|
print("[releases]", slug, "FAIL", repr(e))
|
|
|
|
with open(os.path.join(OUT, "_fetch_log.json"), "w", encoding="utf-8") as f:
|
|
json.dump(log, f, ensure_ascii=False, indent=1)
|
|
print("\nDONE -> " + OUT)
|