Free OpenRouter models are too slow for batch processing on shared
hosting. Reduce batch size from 5 to 1, API timeout from 30s to 15s,
and max retries from 3 to 1 to stay within PHP max_execution_time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous batch size of 50 exceeded PHP max_execution_time on
Hostinger shared hosting (30s). Each article needs ~3s for the
OpenRouter API call, so 5 articles per AJAX request keeps each
batch under 20s. The JS loop still processes all articles
automatically — just in more, smaller batches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a "Generate AI Summaries" button to the Peptide News settings page
that processes all articles missing summaries in batches of 50. The UI
loops automatically until every article has been summarized, showing
real-time progress. The admin batch overrides the per-cycle limit so it
isn't capped at 10 — the existing 120-second timeout guard still applies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LiteSpeed Cache was serving stale REST API responses with 0 articles, causing the React frontend to show "No articles" despite 50+ articles in the DB.
- Add Cache-Control and X-LiteSpeed-Cache-Control: no-cache headers to /articles endpoint
- Include ai_summary and thumbnail_local columns in the REST API query (needed by React frontend)
Remove all AI thumbnail generation infrastructure (too expensive,
poor results) and rewrite the public-facing article feed as a React
SPA mounted via WordPress shortcode.
Changes:
- Remove thumbnail generation, OG scraping, and image download code
from fetcher, LLM, and admin classes
- Replace server-rendered PHP templates with React component
(PeptideNewsFeed.jsx) using WordPress-bundled React/ReactDOM
- Add Babel build pipeline for JSX transpilation
- Update REST API to serve ai_summary instead of thumbnail fields
- Redesign public CSS: clean card layout, loading skeletons,
dark mode, responsive, no thumbnail imagery
- Remove dead admin settings (llm_image_model, thumbnail_fallback)
- Fix call_openrouter() visibility for Content Filter cross-class use
- Bump version to 1.3.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DuckDuckGo's HTML search endpoint now requires vqd session tokens,
making server-side search-based URL resolution unreliable. Simplified
the thumbnail pipeline:
- OG scrape only for non-aggregator URLs (direct publisher pages)
- AI image generation via OpenRouter for aggregator articles (Google News)
- Added Image Model settings field in admin UI
- Added thumbnail_local column for AI-generated images
- Updated public/admin display to check thumbnail_local as fallback
- Removed all DuckDuckGo search code (find_article_url_via_search,
extract_ddg_url, resolve_article_for_scraping)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Google News RSS stores encrypted article URLs that cannot be resolved
server-side (JS SPA, no HTTP redirects, encrypted base64 IDs). Replace
the failed decode/parse strategies with DuckDuckGo search-based lookup
using article title + publisher name to find actual article pages, then
scrape OG images from those pages.
- Add find_article_url_via_search() using DuckDuckGo HTML endpoint
- Add resolve_article_for_scraping() combining redirect + search fallback
- Mark failed lookups with '_no_image' sentinel to avoid repeated retries
- Backfill button resets sentinels to allow manual re-scraping
- Clean up verbose debug instrumentation from prior attempts
- Update frontend to handle '_no_image' sentinel gracefully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace batchexecute API with direct fetch + HTML parsing strategies:
data-n-au attributes, anchor href extraction, meta refresh, JS redirect.
Add verbose debug output to diagnose server-side response from Google News.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace broken base64 decoding with Google's internal batchexecute API
endpoint to resolve encrypted article IDs to actual publisher URLs.
This is the approach used by known Google News decoder libraries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Temporary debug version that returns query counts, URL resolution results,
and OG scraping outcomes to identify why thumbnail scraping returns 0.
Also adds Google News URL decoder (base64/protobuf approach).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The OG image scraper was using Google News redirect URLs (source_url) directly,
which don't serve og:image tags. Added resolve_scrape_url() to follow redirects
(including meta-refresh and JS redirects) to reach the actual publisher page
before scraping. Also added a backfill AJAX endpoint and admin button to
re-scrape thumbnails for existing articles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Google News RSS feeds report every article as coming from
"news.google.com" because the source was extracted from the feed URL
rather than the individual article. This adds a multi-strategy
resolver that extracts the real publisher name via:
1. SimplePie <source> element (Google News provides this)
2. Title suffix parsing ("Headline - Source" format)
3. HTTP HEAD redirect resolution as a fallback
Also adds a one-click "Fix Article Sources" backfill button on the
settings page to correct existing articles in the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Peptide_News_Loader class was referenced by class-peptide-news.php
but never committed, causing a fatal error on plugin activation.
Also adds public/css/public-style.css which is enqueued by the public
class.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- analytics.php line 38: mismatched quotes on session_id array key
- analytics.php line 246: invalid escape before concatenation operator
- analytics.php line 250: COuNT typo → COUNT
- analytics.php line 252: NULIF typo → NULLIF
- rest-api.php line 54: stray # commenting out foreach variable
These bugs predate the content filter feature but were caught by the
new CI pipeline's PHP lint job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two-tier filtering during article fetch: keyword/pattern rules as a fast
first pass, then optional LLM classification for borderline cases via
OpenRouter. Configurable sensitivity (strict/moderate/lenient), editable
keyword lists, blocked domain list, and admin stats display. Includes
30 unit tests covering domain blocking, title/body keywords, URL paths,
edge cases, and prompt structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use $wpdb->prepare() with esc_like() in clear_article_cache()
- Add strict base64_decode() validation with error handling
- Add isset()/is_array()/is_string() checks for nested array access
- Add wp_http_validate_url() SSRF prevention on all image downloads
- Add limit_response_size (5MB) on image URL downloads
- Set chmod 0644 after file_put_contents()
- Use $wpdb->prepare() with esc_like() in clear_article_cache()
- Add wp_http_validate_url() SSRF prevention in download_thumbnail()
- Add limit_response_size (5MB) on image downloads
- Set chmod 0644 after file_put_contents()
- Add transient-based concurrent fetch lock