Syndicate Hugo blog posts to dev.to and Hashnode#4872
Open
shai-almog wants to merge 5 commits intomasterfrom
Open
Syndicate Hugo blog posts to dev.to and Hashnode#4872shai-almog wants to merge 5 commits intomasterfrom
shai-almog wants to merge 5 commits intomasterfrom
Conversation
Daily GitHub Action that picks the oldest blog post under docs/website/content/blog dated after 2026-04-30, at least 7 days old, and not yet syndicated to a given platform. The script absolutizes relative links/images, inserts a one-sentence "What is Codename One" blurb after the fold, and POSTs to each platform with canonical_url pointing back to the original on www.codenameone.com. Per-platform state in scripts/website/syndication-state.json so partial failures retry only the failed side. Requires repo secrets: DEVTO_API_KEY, HASHNODE_TOKEN, HASHNODE_PUBLICATION_ID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Contributor
Cloudflare Preview
|
Adds foojay.io as a third syndication target. Unlike dev.to and Hashnode the foojay flow creates a WP draft via /wp-json/wp/v2/posts so the foojay editors can review before publishing. The canonical link is surfaced as a visible note at the top of the draft (rather than a meta field) so the reviewer can wire it up using whichever SEO plugin foojay runs. Side effects: - platforms with missing credentials are now skipped at startup with a note instead of failing the whole run, so adding a new platform later does not strand the candidate selector - requests now send a real User-Agent and Accept header (Cloudflare in front of foojay rejected the default Python-urllib UA with error 1010) - foojay credentials (FOOJAY_USER / FOOJAY_PASSWORD) wired through the workflow as optional secrets; the script auto-skips foojay until both are configured Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
foojay runs Yoast SEO, so the canonical URL is now sent as meta._yoast_wpseo_canonical on the WP draft. Yoast registers that key as a REST-exposed post meta, so the standard /wp-json/wp/v2/posts payload carries it through. The visible "originally published" line at the top of the draft body is dropped — Yoast handles the SEO directive and the "What is Codename One" blurb still provides reader-facing attribution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
foojay.io has Wordfence configured to disable WordPress Application Passwords, so there is no usable Basic Auth path for the WP REST API from the syndication script. Removing the foojay code path until / unless foojay editorial offers an alternative auth method (JWT, per-user API key, etc.). The User-Agent header and skip-when-unconfigured behaviour introduced alongside the foojay work are kept — they are useful for the remaining platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds scripts/website/syndicate_browser_posts.py — a Playwright-based counterpart to the API syndicator. Each target site has its own adapter (login + draft submission). State and post selection are shared with the API script via syndication-state.json, so a post is "candidate" until all configured platforms — API and browser — have a record. Adapters: - foojay: hybrid path. Playwright drives wp-login.php to obtain a real session (Wordfence has Application Passwords disabled, so token auth is out), then the script POSTs the draft via /wp-json/wp/v2/posts using the session cookies + X-WP-Nonce. Pure UI submission was attempted but Cloudflare in front of foojay challenges form POSTs and drops the payload, so drafts never landed. Yoast canonical isn't REST-writable on this Yoast install, so the canonical is surfaced as a visible note at the top of the draft body for the editor reviewer. Validated end- to-end against the live site (draft #123656). - hackernoon, dzone, medium: standard browser flow. Selectors are best-effort and need a one-time validation pass against each live site via --validate-only --headed. medium has no password login, so it relies on a base64-encoded MEDIUM_STORAGE_STATE secret exported from a manually logged-in browser session. Workflow additions: - Detects whether any browser-syndication secret is configured; only installs Playwright + Chromium when something will actually run. - Uploads the Playwright screenshot directory as a CI artifact on any outcome (kept for 14 days), so selector failures are debuggable. - Screenshots dir is gitignored. Per-platform secrets (all optional; missing = platform skipped): FOOJAY_USER, FOOJAY_PASSWORD HACKERNOON_USER, HACKERNOON_PASSWORD DZONE_USER, DZONE_PASSWORD MEDIUM_STORAGE_STATE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| try: | ||
| field = _find_first(page, self.CANONICAL_FIELD_SELECTORS, timeout=3000) | ||
| field.fill(ctx.post.canonical_url) | ||
| except AdapterError: |
| try: | ||
| field = _find_first(page, self.CANONICAL_SELECTORS, timeout=3000) | ||
| field.fill(ctx.post.canonical_url) | ||
| except AdapterError: |
| field.fill(ctx.post.canonical_url) | ||
| # Close the settings panel | ||
| page.keyboard.press("Escape") | ||
| except AdapterError: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.github/workflows/blog-syndication.yml) that syndicates new blog posts to dev.to and Hashnode with acanonical_url/originalArticleURLback towww.codenameone.com.scripts/website/syndicate_blog_posts.py(Python stdlib only) picks the oldest post dated after 2026-04-30, at least 7 days old, and not yet syndicated to a given platform; absolutizes relative links/images; and inserts a one-sentence "What is Codename One" blurb right after the fold.scripts/website/syndication-state.jsontracks per-slug, per-platform results so partial failures retry only the failed side. Action commits state updates back tomaster.Setup
Repo secrets required (already added):
DEVTO_API_KEYHASHNODE_TOKENHASHNODE_PUBLICATION_IDThe workflow runs daily at 13:00 UTC and supports
workflow_dispatchwith a dry-run toggle. The first eligible post isliquid-glass-material-3-modern-native-themes(2026-05-01), which becomes a candidate from 2026-05-08 onward.Test plan
python3 scripts/website/syndicate_blog_posts.py --dry-runon 2026-05-06 / 2026-05-07 reports no candidate.--dry-run --today 2026-05-08selects the May 1 post.dry_run=trueafter merge to confirm secrets are wired.🤖 Generated with Claude Code