← back to Crazy News Channel Shadowman
p24 daily-cartoons: approve.py defaults --story-id from meta.json, links source article
0b40f82a6caa3b66c100561c3d712cbde009c5c7 · 2026-09-25 10:20:22 -0700 · Steve Abrams
--story-id is no longer required on the command line — it defaults to the article
generate.py recorded in meta.json (story_id) and is still validated against
stories-data.js/real-news-data.js; pass --story-id to override. The generated
cartoon page now also shows a "Source: <headline>" link to the linked article when
present. Works unchanged with or without clip.mp4 (poster-only renders as a still
<img>, no style violations — only ../assets/style.css is linked).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XizSgLiFuNbjY418z94Ewq
Files touched
M daily-cartoons/approve.py
Diff
commit 0b40f82a6caa3b66c100561c3d712cbde009c5c7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 25 10:20:22 2026 -0700
p24 daily-cartoons: approve.py defaults --story-id from meta.json, links source article
--story-id is no longer required on the command line — it defaults to the article
generate.py recorded in meta.json (story_id) and is still validated against
stories-data.js/real-news-data.js; pass --story-id to override. The generated
cartoon page now also shows a "Source: <headline>" link to the linked article when
present. Works unchanged with or without clip.mp4 (poster-only renders as a still
<img>, no style violations — only ../assets/style.css is linked).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XizSgLiFuNbjY418z94Ewq
---
daily-cartoons/approve.py | 28 ++++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/daily-cartoons/approve.py b/daily-cartoons/approve.py
index 9a57f74..f13bd8b 100755
--- a/daily-cartoons/approve.py
+++ b/daily-cartoons/approve.py
@@ -1,11 +1,14 @@
#!/usr/bin/env python3
"""Approve a queued cartoon INTO THE LOCAL SITE TREE ONLY. Never deploys/rsyncs.
- python3 daily-cartoons/approve.py <slug> --date YYYY-MM-DD --story-id <id> [--section politics]
+ python3 daily-cartoons/approve.py <slug> --date YYYY-MM-DD [--story-id <id>] [--section politics]
-Copies poster/clip to cartoons/media/, writes cartoons/<date>-<slug>.html, and appends a
-manifest.js entry. --story-id is REQUIRED and must exist in stories-data.js or
-real-news-data.js (standing rule TK-12158: every cartoon links to a real article).
+Copies poster/clip (clip is optional — poster-only cartoons render as a still image) to
+cartoons/media/, writes cartoons/<date>-<slug>.html, and appends a manifest.js entry.
+--story-id DEFAULTS to the article the cartoon was generated from (meta.json's story_id,
+written by article-driven generate.py, TK-12237) and is still validated against
+stories-data.js / real-news-data.js; pass --story-id to override. A story_id is REQUIRED
+either way (standing rule TK-12158: every cartoon links to a real article).
Publishing to p24.agentabrams.com stays a separate, Steve-run deploy."""
import argparse, datetime, html, json, os, re, shutil, sys
import common as C
@@ -23,12 +26,18 @@ def story_exists(sid):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("slug"); ap.add_argument("--date", default=datetime.date.today().isoformat())
- ap.add_argument("--story-id", required=True); ap.add_argument("--section", default="politics")
+ ap.add_argument("--story-id", default=None); ap.add_argument("--section", default="politics")
a = ap.parse_args()
q = os.path.join(C.QUEUE, a.date, a.slug); mp = os.path.join(q, "meta.json")
if not os.path.exists(mp): sys.exit(f"not queued: {q}")
- if not story_exists(a.story_id): sys.exit(f"story_id '{a.story_id}' not found in stories-data.js / real-news-data.js")
- m = json.load(open(mp)); base = f"{a.date.replace('-','')}-{a.slug}"
+ m = json.load(open(mp))
+ story_id = a.story_id or m.get("story_id")
+ if not story_id:
+ sys.exit("--story-id required (meta.json has no story_id — this cartoon predates the "
+ "article-driven pipeline; pass --story-id explicitly)")
+ if not story_exists(story_id): sys.exit(f"story_id '{story_id}' not found in stories-data.js / real-news-data.js")
+ a.story_id = story_id
+ base = f"{a.date.replace('-','')}-{a.slug}"
os.makedirs(os.path.join(CART, "media"), exist_ok=True)
shutil.copy2(os.path.join(q, "poster.jpg"), os.path.join(CART, "media", base + ".jpg"))
has_clip = os.path.exists(os.path.join(q, "clip.mp4"))
@@ -41,11 +50,14 @@ def main():
# under body.cartoon-page-simple so every future approved cartoon page
# renders in the site's one fixed palette + one font, same as every
# other page, with nothing that changes on click.
+ src_url, src_title = m.get("story_url"), m.get("story_title")
+ src_html = (f'<p class="src"><a href="{e(src_url)}" target="_blank" rel="noopener noreferrer">Source: {e(src_title or "linked article")}</a></p>'
+ if src_url else "")
open(os.path.join(CART, base + ".html"), "w").write(f'''<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>{e(m["title"])}</title>
<link rel="stylesheet" href="../assets/style.css"></head>
<body class="cartoon-page-simple"><p><a href="index.html">← Cartoon Desk</a></p>{media}<h1>{e(m["title"])}</h1>
-<p class="cap">“{e(m["caption"])}”</p><p><small>AI-generated editorial cartoon in classic editorial-ink style. Invented figures only.</small></p></body></html>''')
+<p class="cap">“{e(m["caption"])}”</p>{src_html}<p><small>AI-generated editorial cartoon in classic editorial-ink style. Invented figures only.</small></p></body></html>''')
mf = os.path.join(CART, "manifest.js"); src = open(mf).read()
mm = re.search(r"(window\.P24_CARTOONS\s*=\s*)(\[[\s\S]*\])(\s*;\s*)$", src)
arr = json.loads(mm.group(2)); arr = [x for x in arr if x.get("id") != a.slug]
← b8bc8d0 p24 daily-cartoons: generate cartoons from real articles, st
·
back to Crazy News Channel Shadowman
·
p24 daily-cartoons: review queue shows linked article, rende fb5ea85 →