Keep the publication an article's page declares
Readability already reads og:site_name and JSON-LD publisher.name; carry that through Page and Extracted and store it in articles.publication, the same way the page byline is kept. `publication_label` is what the readers will see after the feed name: the site name, else the domain, and nothing when it would only repeat the feed's own title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YWmCpUojfHXhSZ2129Z7Nv
This commit is contained in:
@@ -92,6 +92,8 @@ pub struct Extracted {
|
||||
pub content_html: String,
|
||||
/// Author discovered in the fetched page, if any.
|
||||
pub author: Option<String>,
|
||||
/// Site name discovered in the fetched page, if any.
|
||||
pub publication: Option<String>,
|
||||
pub word_count: i64,
|
||||
/// True when we only have an excerpt/paywall stub — penalized in pre-filter.
|
||||
pub excerpt_only: bool,
|
||||
@@ -124,6 +126,8 @@ pub struct Article {
|
||||
// --- joined / derived fields ---
|
||||
pub url: String,
|
||||
pub author: Option<String>,
|
||||
/// Page-extracted site name persisted on `articles`.
|
||||
pub publication: Option<String>,
|
||||
pub feed_id: FeedId,
|
||||
pub feed_title: String,
|
||||
pub category: Option<String>,
|
||||
@@ -150,6 +154,19 @@ impl Article {
|
||||
self.sources.iter().any(|s| s.kind == kind)
|
||||
}
|
||||
|
||||
/// What to show after the feed name: the page's site name, else the domain;
|
||||
/// nothing when it would just repeat the feed name.
|
||||
pub fn publication_label(&self) -> Option<String> {
|
||||
let publication = self
|
||||
.publication
|
||||
.clone()
|
||||
.or_else(|| domain(&self.canonical_url))?;
|
||||
(!publication
|
||||
.trim()
|
||||
.eq_ignore_ascii_case(self.feed_title.trim()))
|
||||
.then_some(publication)
|
||||
}
|
||||
|
||||
/// Stable EPUB chapter id used by TOC and rating links (implementation notes §12).
|
||||
pub fn chapter_id(&self) -> String {
|
||||
format!("art-{}", self.best_entry_id)
|
||||
@@ -161,6 +178,14 @@ pub fn reading_minutes(word_count: i64) -> i64 {
|
||||
(word_count.max(0) as f64 / 220.0).ceil().max(1.0) as i64
|
||||
}
|
||||
|
||||
/// URL host without a leading `www.`.
|
||||
pub fn domain(url: &str) -> Option<String> {
|
||||
url::Url::parse(url)
|
||||
.ok()?
|
||||
.host_str()
|
||||
.map(|host| host.strip_prefix("www.").unwrap_or(host).to_string())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Social proof (§3.4)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -847,6 +872,63 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn publication_article() -> Article {
|
||||
Article {
|
||||
id: 1,
|
||||
canonical_url: "https://www.example.com/post".into(),
|
||||
title: "A post".into(),
|
||||
best_entry_id: 1,
|
||||
content_html: String::new(),
|
||||
word_count: 1,
|
||||
excerpt_only: false,
|
||||
image_count: 0,
|
||||
sources: Vec::new(),
|
||||
first_seen: ts(),
|
||||
url: "https://www.example.com/post".into(),
|
||||
author: None,
|
||||
publication: None,
|
||||
feed_id: 1,
|
||||
feed_title: "A Feed".into(),
|
||||
category: None,
|
||||
published_at: None,
|
||||
comments_url: None,
|
||||
image_urls: Vec::new(),
|
||||
social: Vec::new(),
|
||||
extract_method: ExtractMethod::Readability,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_label_prefers_the_stored_publication() {
|
||||
let mut article = publication_article();
|
||||
article.publication = Some("Example Journal".into());
|
||||
assert_eq!(
|
||||
article.publication_label().as_deref(),
|
||||
Some("Example Journal")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_label_falls_back_to_the_domain_without_www() {
|
||||
let article = publication_article();
|
||||
assert_eq!(article.publication_label().as_deref(), Some("example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_label_omits_a_publication_matching_the_feed() {
|
||||
let mut article = publication_article();
|
||||
article.feed_title = " example JOURNAL ".into();
|
||||
article.publication = Some(" Example Journal ".into());
|
||||
assert_eq!(article.publication_label(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_label_omits_a_domain_matching_the_feed() {
|
||||
let mut article = publication_article();
|
||||
article.feed_title = " EXAMPLE.com ".into();
|
||||
assert_eq!(article.publication_label(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composite_social_score_matches_spec_formula() {
|
||||
let refs = vec![
|
||||
|
||||
Reference in New Issue
Block a user