use link to the info page instead of magnet, update feed transaction logic

This commit is contained in:
yggverse 2025-08-10 01:05:58 +03:00
parent 9bc434375f
commit add71e9749
2 changed files with 66 additions and 71 deletions

View file

@ -3,94 +3,94 @@ use url::Url;
/// Export crawl index to the RSS file /// Export crawl index to the RSS file
pub struct Feed { pub struct Feed {
description: Option<String>, buffer: String,
link: Option<String>, canonical: Option<Url>,
title: String,
trackers: Option<Vec<Url>>,
} }
impl Feed { impl Feed {
pub fn init( pub fn new(
title: String, title: &str,
description: Option<String>, description: Option<&str>,
link: Option<Url>, canonical: Option<Url>,
trackers: Option<Vec<Url>>, capacity: usize,
) -> Self { ) -> Self {
Self {
description: description.map(escape),
link: link.map(|s| escape(s.to_string())),
title: escape(title),
trackers,
}
}
pub fn transaction(&self, capacity: usize) -> String {
let t = chrono::Utc::now().to_rfc2822(); let t = chrono::Utc::now().to_rfc2822();
let mut b = String::with_capacity(capacity); let mut buffer = String::with_capacity(capacity);
b.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\"><channel>"); buffer.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\"><channel>");
b.push_str("<pubDate>"); buffer.push_str("<pubDate>");
b.push_str(&t); buffer.push_str(&t);
b.push_str("</pubDate>"); buffer.push_str("</pubDate>");
b.push_str("<lastBuildDate>"); buffer.push_str("<lastBuildDate>");
b.push_str(&t); buffer.push_str(&t);
b.push_str("</lastBuildDate>"); buffer.push_str("</lastBuildDate>");
b.push_str("<title>"); buffer.push_str("<title>");
b.push_str(&self.title); buffer.push_str(title);
b.push_str("</title>"); buffer.push_str("</title>");
if let Some(ref description) = self.description { if let Some(d) = description {
b.push_str("<description>"); buffer.push_str("<description>");
b.push_str(description); buffer.push_str(d);
b.push_str("</description>") buffer.push_str("</description>")
} }
if let Some(ref link) = self.link { if let Some(ref c) = canonical {
b.push_str("<link>"); // @TODO required the RSS specification!
b.push_str(link); buffer.push_str("<link>");
b.push_str("</link>") buffer.push_str(c.as_str());
buffer.push_str("</link>")
} }
b Self { buffer, canonical }
} }
/// Append `item` to the feed `channel` /// Append `item` to the feed `channel`
pub fn push(&self, buffer: &mut String, torrent: Torrent) { pub fn push(&mut self, torrent: Torrent) {
buffer.push_str(&format!( self.buffer.push_str(&format!(
"<item><guid>{}</guid><title>{}</title><link>{}</link>", "<item><guid>{}</guid><title>{}</title><link>{}</link>",
torrent.info_hash, torrent.info_hash,
escape( escape(
torrent &torrent
.name .name
.as_ref() .as_ref()
.map(|b| b.to_string()) .map(|b| b.to_string())
.unwrap_or("?".into()) // @TODO .unwrap_or("?".into()) // @TODO
), ),
escape(torrent.magnet(self.trackers.as_ref())) self.canonical
.clone()
.map(|mut c| escape({
c.set_path(&torrent.info_hash);
c.set_fragment(None);
c.set_query(None);
c.as_str()
}))
.unwrap_or(escape(&torrent.info_hash)) // should be non-optional absolute URL
// by the RSS specification @TODO
)); ));
buffer.push_str("<description>"); self.buffer.push_str("<description>");
buffer.push_str(&format!("{}\n{}", torrent.size(), torrent.files())); self.buffer
buffer.push_str("</description>"); .push_str(&format!("{}\n{}", torrent.size(), torrent.files()));
self.buffer.push_str("</description>");
buffer.push_str("<pubDate>"); self.buffer.push_str("<pubDate>");
buffer.push_str(&torrent.time.to_rfc2822()); self.buffer.push_str(&torrent.time.to_rfc2822());
buffer.push_str("</pubDate>"); self.buffer.push_str("</pubDate>");
buffer.push_str("</item>") self.buffer.push_str("</item>")
} }
/// Write final bytes /// Write final bytes
pub fn commit(&self, mut buffer: String) -> String { pub fn commit(mut self) -> String {
buffer.push_str("</channel></rss>"); self.buffer.push_str("</channel></rss>");
buffer self.buffer
} }
} }
fn escape(subject: String) -> String { fn escape(value: &str) -> String {
subject value
.replace('&', "&amp;") .replace('&', "&amp;")
.replace('<', "&lt;") .replace('<', "&lt;")
.replace('>', "&gt;") .replace('>', "&gt;")

View file

@ -141,8 +141,13 @@ fn info(
} }
#[get("/rss")] #[get("/rss")]
fn rss(feed: &State<Feed>, public: &State<Public>) -> Result<RawXml<String>, Custom<String>> { fn rss(meta: &State<Meta>, public: &State<Public>) -> Result<RawXml<String>, Custom<String>> {
let mut b = feed.transaction(1024); // @TODO let mut f = Feed::new(
&meta.title,
meta.description.as_deref(),
meta.canonical.clone(),
1024, // @TODO
);
for t in public for t in public
.torrents( .torrents(
Some((Sort::Modified, Order::Desc)), Some((Sort::Modified, Order::Desc)),
@ -155,27 +160,18 @@ fn rss(feed: &State<Feed>, public: &State<Public>) -> Result<RawXml<String>, Cus
})? })?
.1 .1
{ {
feed.push( f.push(Torrent::from_public(&t.bytes, t.time).map_err(|e| {
&mut b,
Torrent::from_public(&t.bytes, t.time).map_err(|e| {
error!("Torrent parse error: `{e}`"); error!("Torrent parse error: `{e}`");
Custom(Status::InternalServerError, E.to_string()) Custom(Status::InternalServerError, E.to_string())
})?, })?)
)
} }
Ok(RawXml(feed.commit(b))) Ok(RawXml(f.commit()))
} }
#[launch] #[launch]
fn rocket() -> _ { fn rocket() -> _ {
use clap::Parser; use clap::Parser;
let config = Config::parse(); let config = Config::parse();
let feed = Feed::init(
config.title.clone(),
config.description.clone(),
config.canonical_url.clone(),
config.tracker.clone(),
);
let scraper = Scraper::init( let scraper = Scraper::init(
config config
.scrape .scrape
@ -215,7 +211,6 @@ fn rocket() -> _ {
rocket::Config::default() rocket::Config::default()
} }
}) })
.manage(feed)
.manage(scraper) .manage(scraper)
.manage(Public::init(config.public.clone(), config.list_limit, config.capacity).unwrap()) .manage(Public::init(config.public.clone(), config.list_limit, config.capacity).unwrap())
.manage(Meta { .manage(Meta {