Versioning ~/seo-tools as critical SEO infrastructure (was home-dir only). Canonical home of seo_rules.py — ResearchOwl will vendor a copy with a CI diff guard against this repo. No secrets; Ghost access is via the ghst-en wrapper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Phase 2 apply — trim 9 long meta_descriptions, keep og/twitter mirrored.
|
|
|
|
Reads new descriptions from phase2_desc.json (slug -> new meta_description).
|
|
For each post sets meta_description = og_description = twitter_description to the
|
|
SAME new value via a minimal ghst --from-json patch (+ updated_at for Ghost's
|
|
collision check). Touches nothing else. READ-ONLY against meta_title/body/etc.
|
|
"""
|
|
import json, os, subprocess, sys, tempfile
|
|
from seo_audit import fetch_posts
|
|
|
|
NEW = json.load(open(os.path.join(os.path.dirname(__file__), "phase2_desc.json")))
|
|
|
|
def apply_one(post, val):
|
|
body = {"posts": [{
|
|
"meta_description": val,
|
|
"og_description": val,
|
|
"twitter_description": val,
|
|
"updated_at": post["updated_at"],
|
|
}]}
|
|
fd, jp = tempfile.mkstemp(suffix=".json"); os.close(fd)
|
|
fd, op = tempfile.mkstemp(suffix=".json"); os.close(fd)
|
|
try:
|
|
json.dump(body, open(jp, "w"))
|
|
cmd = f"ghst-en post update {post['id']} --from-json {jp} --json > {op}"
|
|
r = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE, text=True)
|
|
if r.returncode != 0:
|
|
return r.stderr.strip()
|
|
return None
|
|
finally:
|
|
os.unlink(jp); os.unlink(op)
|
|
|
|
def main():
|
|
posts = {p["slug"]: p for p in fetch_posts()}
|
|
ok, fail = [], []
|
|
for slug, val in NEW.items():
|
|
p = posts.get(slug)
|
|
if not p:
|
|
fail.append((slug, "slug not found in live posts")); print(f" FAIL {slug}: not found"); continue
|
|
err = apply_one(p, val)
|
|
if err:
|
|
fail.append((slug, err)); print(f" FAIL {slug}: {err[:90]}")
|
|
else:
|
|
ok.append(slug); print(f" OK {slug} (meta/og/twitter desc = {len(val)} chars)")
|
|
print(f"\nApplied: {len(ok)} | Failed: {len(fail)}")
|
|
for s, e in fail:
|
|
print(f" FAILED {s}: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|