Patreon creators split into their shows; filters follow settings
A Patreon token pasted into Add feed, or a creator link without &show=, becomes a folder of that creator's shows, found through Patreon's web API and kept in step like a subscribed OPML (sync_group, split out of sync_opml). A creator already read as one feed is split too: each show takes over the files and read state it held (Db::adopt). A creator with one show stays a plain feed. Filter verdicts are judged again every scan, so turning on Allow explicit brings skipped items back. Add feed has an explicit box. Feeds in a group follow your settings on the group, as its dialog said. A new feed no longer takes the id of a removed one at a different URL and shows its old items. See CHANGELOG.md [Unreleased] and docs/history.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wi22VSVrkAvqNj61eqHsm9
This commit is contained in:
42
src/web.rs
42
src/web.rs
@@ -495,6 +495,9 @@ async fn feeds(
|
||||
let mut out = Vec::with_capacity(mine.len());
|
||||
for sub in &subs {
|
||||
let (id, feed) = (&sub.id, &sub.cfg);
|
||||
// In a group, what you have not set on the feed comes from your settings on the group,
|
||||
// the same fallback the scanner uses (`Db::subscribers`).
|
||||
let up = feed.group.as_deref().and_then(|g| mine.get(g));
|
||||
let Some(mine) = mine.get(id) else { continue };
|
||||
let s = state.ctx.db.feed_summary(id)?;
|
||||
let st = state.ctx.db.http_state(id)?;
|
||||
@@ -504,11 +507,22 @@ async fn feeds(
|
||||
title: s.title,
|
||||
image: s.image,
|
||||
folder: feed.folder.clone(),
|
||||
keywords: mine.keywords.clone().unwrap_or_else(|| feed.keywords.clone()),
|
||||
allow_explicit: mine.allow_explicit.unwrap_or(feed.allow_explicit),
|
||||
auto_download: mine.auto_download.unwrap_or(feed.auto_download),
|
||||
keywords: mine
|
||||
.keywords
|
||||
.clone()
|
||||
.or_else(|| up.and_then(|u| u.keywords.clone()))
|
||||
.unwrap_or_else(|| feed.keywords.clone()),
|
||||
allow_explicit: mine
|
||||
.allow_explicit
|
||||
.or(up.and_then(|u| u.allow_explicit))
|
||||
.unwrap_or(feed.allow_explicit),
|
||||
auto_download: mine
|
||||
.auto_download
|
||||
.or(up.and_then(|u| u.auto_download))
|
||||
.unwrap_or(feed.auto_download),
|
||||
max_new_per_check: mine
|
||||
.max_new_per_check
|
||||
.or(up.and_then(|u| u.max_new_per_check))
|
||||
.map(|n| n as usize)
|
||||
.or(feed.max_new_per_check),
|
||||
group: feed.group.clone(),
|
||||
@@ -893,6 +907,19 @@ struct NewFeed {
|
||||
folder: Option<String>,
|
||||
#[serde(default)]
|
||||
keywords: Vec<String>,
|
||||
#[serde(default)]
|
||||
allow_explicit: bool,
|
||||
}
|
||||
|
||||
/// The Add feed dialog's explicit box. Like everything on a feed's own dialog it is yours, so it
|
||||
/// goes on your subscription, and before the first scan, which would otherwise skip every
|
||||
/// explicit item.
|
||||
fn explicit_on_add(state: &WebState, user_id: i64, feed_id: &str, allow: bool) -> Result<(), ApiError> {
|
||||
if allow {
|
||||
let sub = crate::db::Sub { feed_id: feed_id.to_owned(), allow_explicit: Some(true), ..Default::default() };
|
||||
state.ctx.db.set_subscription(user_id, &sub)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_feed(
|
||||
@@ -901,23 +928,28 @@ async fn add_feed(
|
||||
Json(body): Json<NewFeed>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let mut cfg = (*state.ctx.cfg()).clone();
|
||||
let url = crate::feed::expand_input(&body.url);
|
||||
// Someone else may already have it. Then adding costs nothing: no second fetch, no
|
||||
// second copy on disk, just another name against the same feed.
|
||||
if let Some(existing) = crate::subscriptions(&state.ctx)?
|
||||
.into_iter()
|
||||
.find(|s| s.cfg.url == body.url)
|
||||
.find(|s| crate::feed::same_feed(&s.cfg.url, &url))
|
||||
{
|
||||
let already = state.ctx.db.subscription(user.id, &existing.id)?.is_some();
|
||||
state.ctx.db.subscribe(user.id, &existing.id)?;
|
||||
if !already {
|
||||
explicit_on_add(&state, user.id, &existing.id, body.allow_explicit)?;
|
||||
}
|
||||
scan_soon(&state, Some(existing.id.clone())).await;
|
||||
return Ok(Json(
|
||||
serde_json::json!({ "id": existing.id, "existing": already }),
|
||||
));
|
||||
}
|
||||
let id = crate::add_one(&state.ctx, &mut cfg, &body.url, body.folder, body.keywords).await?;
|
||||
let id = crate::add_one(&state.ctx, &mut cfg, &url, body.folder, body.keywords).await?;
|
||||
cfg.save(&state.config_path)?;
|
||||
state.ctx.reload_cfg(&state.config_path)?;
|
||||
state.ctx.db.subscribe(user.id, &id)?;
|
||||
explicit_on_add(&state, user.id, &id, body.allow_explicit)?;
|
||||
scan_soon(&state, Some(id.clone())).await;
|
||||
Ok(Json(serde_json::json!({ "id": id, "existing": false })))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user