Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fastrand = "2.0.1"
log = "0.4.20"
pretty_env_logger = "0.5.0"
dotenvy = "0.15.7"
rss = "2.0.7"
rss = "2.0.12"
arc-swap = "1.7.1"
serde_json_path = "0.7.1"
async-recursion = "1.1.1"
Expand Down
101 changes: 86 additions & 15 deletions src/subreddit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
use crate::{config, utils};
// CRATES
use crate::utils::{
catch_random, error, filter_posts, format_num, format_url, get_filters, info, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences,
Subreddit,
Post, Preferences, Subreddit, catch_random, error, filter_posts, format_num, format_url, get_filters, info, nsfw_landing, param, redirect, rewrite_urls, setting, template, to_absolute_url, val
};
use crate::{client::json, server::RequestExt, server::ResponseExt};
use askama::Template;
Expand All @@ -14,6 +13,7 @@ use hyper::{Body, Request, Response};

use chrono::DateTime;
use regex::Regex;
use rss::{ChannelBuilder, Item, Enclosure};
use std::sync::LazyLock;
use time::{Duration, OffsetDateTime};

Expand Down Expand Up @@ -595,7 +595,6 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
}

use hyper::header::CONTENT_TYPE;
use rss::{ChannelBuilder, Item};

// Get subreddit
let sub = req.param("sub").unwrap_or_default();
Expand All @@ -605,6 +604,9 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
// Get path
let path = format!("/r/{sub}/{sort}.json?{}", req.uri().query().unwrap_or_default());

// Get subreddit link
let subreddit_link: String = format!("{}/r/{sub}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default());

// Get subreddit data
let subreddit = subreddit(&sub, false).await?;

Expand All @@ -615,21 +617,23 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
let channel = ChannelBuilder::default()
.title(&subreddit.title)
.description(&subreddit.description)
.link(&subreddit_link)
.items(
posts
.into_iter()
.map(|post| Item {
title: Some(post.title.to_string()),
link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name),
content: Some(rewrite_urls(&decode_html(&post.body).unwrap())),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
description: Some(format!(
"<a href='{}{}'>Comments</a>",
config::get_setting("REDLIB_FULL_URL").unwrap_or_default(),
post.permalink
)),
..Default::default()
.map(|post| {
let mut item = Item {
title: Some(post.title.to_string()),
link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name.to_string()),
content: Some(rewrite_urls(&decode_html(&post.body).unwrap())),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
description: Some(format!("<a href='{}'>Comments</a>", to_absolute_url(&post.permalink))),
..Default::default()
};

apply_enclosure(&mut item, &post);
item
})
.collect::<Vec<_>>(),
)
Expand All @@ -645,6 +649,73 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
Ok(res)
}

// Set enclosure image for RSS feed item
fn apply_enclosure(item: &mut Item, post: &Post) {
item.set_enclosure(get_rss_image(&post));

// Embed the number of gallery images in description and content since
// only the first image in the gallery is used for the enclosure
if post.post_type == "gallery" && post.gallery.len() > 1 {
item.set_description(
format!("<a href='{}'>Gallery with {} images</a>",
to_absolute_url(&post.permalink),
post.gallery.len()
)
);

if let Some(content) = item.content() {
let new_content = format!(
"{}<br/>{}",
item.description().unwrap_or(""),
content,
);
item.set_content(new_content);
}
}

}

fn get_rss_image(post: &Post) -> Option<Enclosure> {
let image_url = match post.post_type.as_str() {
"image" => Some(post.media.url.clone()),
"gallery" => post.gallery.get(0).and_then(|media| decode_html(&media.url).ok()),
"gif" | "video" => decode_html(&post.media.poster).ok(),
_ => None,
};

image_url.map(|url| {
let mut enclosure = Enclosure::default();
enclosure.set_mime_type(get_mime_type(&url));
enclosure.set_url(to_absolute_url(&url));
enclosure.set_length("0");
enclosure
})
}

/// Determines the MIME type based on file extension in a URL.
/// Handles both absolute and relative URLs with query parameters.
fn get_mime_type(url: &str) -> &'static str {
// Extract the path component, removing query parameters
let path = url.split('?').next().unwrap_or(url);

// Get the file extension (everything after the last dot)
let extension = path
.rsplit('.')
.next()
.unwrap_or("")
.to_lowercase();

// Match common image extensions
match extension.as_str() {
"jpg" | "jpeg" => "image/jpeg",
"png" => "image/png",
"gif" => "image/gif",
"webp" => "image/webp",
"svg" => "image/svg+xml",
_ => "application/octet-stream",
}
}

#[tokio::test(flavor = "multi_thread")]
async fn test_fetching_subreddit() {
let subreddit = subreddit("rust", false).await;
Expand Down
23 changes: 14 additions & 9 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1433,18 +1433,23 @@ pub fn url_path_basename(path: &str) -> String {
}
}

/// Returns the URL of a post, as needed by RSS feeds
/// Returns the absolute URL of a post, as needed by RSS feeds
pub fn get_post_url(post: &Post) -> String {
match post.post_type.as_str() {
"image" | "gallery" | "gif" | "video" => return to_absolute_url(&post.permalink),
_ => {}
}

if let Some(out_url) = &post.out_url {
// Handle cross post
if out_url.starts_with("/r/") {
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), out_url)
} else {
out_url.to_string()
}
} else {
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), post.permalink)
return if out_url.starts_with("/r/") { to_absolute_url(out_url) } else { out_url.clone() };
}

to_absolute_url(&post.permalink)
}

/// Returns an absolute URL given a relative URL, as needed by RSS feeds
pub fn to_absolute_url(relative_path: &str) -> String {
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), relative_path)
}

#[cfg(test)]
Expand Down