Initial commit: SEO rule engine, auditor, validator, exceptions, mirror
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>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user