Fix paywall false positives + add og:image fallbacks
- Body extractor falls back to <article>/<main> container text when <p> extraction is sparse, catching <br>-separated bodies (Phoronix). - Detect Cloudflare bot-block / JS-challenge pages on 403/503 and treat them as transport failures rather than paywalls (Brooklyn Vegan). - og:image extractor falls back to img.wp-post-image and the first content <img> in <article>/<main>, with lazy-load placeholder handling via data-src / data-lazy-src (Hardcore Gaming 101). - New -backfill-paywall flag re-checks paywalled=1 rows with the current logic, clearing false positives and filling missing thumbs.
This commit is contained in:
53
main.go
53
main.go
@@ -27,6 +27,7 @@ func main() {
|
||||
test := flag.Bool("test", false, "post one story to verify the full pipeline, then exit")
|
||||
testSource := flag.String("test-source", "", "source name to use for -test (default: first enabled)")
|
||||
local := flag.Bool("local", false, "web/RSS-only mode: poll feeds and serve the web UI, no Matrix login or posting")
|
||||
backfillPaywall := flag.Bool("backfill-paywall", false, "re-check paywalled rows with current detection logic; clear false positives and fill missing thumbnails, then exit")
|
||||
flag.Parse()
|
||||
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
@@ -57,6 +58,11 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if *backfillPaywall {
|
||||
runBackfillPaywall()
|
||||
return
|
||||
}
|
||||
|
||||
// Test mode: post one story to verify the full pipeline, then exit
|
||||
if *test {
|
||||
runTest(cfg, *testSource)
|
||||
@@ -373,3 +379,50 @@ func runTest(cfg *config.Config, sourceName string) {
|
||||
func timeNow() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
// runBackfillPaywall re-runs the current paywall detection logic against
|
||||
// every row marked paywalled=1. For each row we re-fetch the stored article
|
||||
// URL; if the new logic no longer considers it gated we clear the flag and,
|
||||
// when the row has no thumbnail, populate it from the live og:image.
|
||||
func runBackfillPaywall() {
|
||||
rows, err := storage.Get().Query(`SELECT guid, article_url, image_url FROM stories WHERE paywalled = 1`)
|
||||
if err != nil {
|
||||
slog.Error("backfill: query failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
type rec struct{ guid, url, img string }
|
||||
var pending []rec
|
||||
for rows.Next() {
|
||||
var r rec
|
||||
if err := rows.Scan(&r.guid, &r.url, &r.img); err != nil {
|
||||
slog.Error("backfill: scan failed", "err", err)
|
||||
continue
|
||||
}
|
||||
pending = append(pending, r)
|
||||
}
|
||||
rows.Close()
|
||||
slog.Info("backfill: starting", "candidates", len(pending))
|
||||
|
||||
cleared, imaged, stillGated := 0, 0, 0
|
||||
for _, r := range pending {
|
||||
meta := ingestion.FetchArticleMeta(r.url)
|
||||
gated := meta.Gated() || (meta.Fetched && meta.BodyChars < ingestion.PaywallBodyThreshold)
|
||||
if gated {
|
||||
stillGated++
|
||||
slog.Info("backfill: still gated", "guid", r.guid, "status", meta.Status, "body_chars", meta.BodyChars)
|
||||
continue
|
||||
}
|
||||
newImg := r.img
|
||||
if newImg == "" && meta.ImageURL != "" {
|
||||
newImg = meta.ImageURL
|
||||
imaged++
|
||||
}
|
||||
if _, err := storage.Get().Exec(`UPDATE stories SET paywalled = 0, image_url = ? WHERE guid = ?`, newImg, r.guid); err != nil {
|
||||
slog.Error("backfill: update failed", "guid", r.guid, "err", err)
|
||||
continue
|
||||
}
|
||||
cleared++
|
||||
slog.Info("backfill: cleared paywall flag", "guid", r.guid, "filled_image", newImg != r.img)
|
||||
}
|
||||
slog.Info("backfill complete", "cleared", cleared, "still_gated", stillGated, "thumbnails_filled", imaged)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user