33use crate :: { config, utils} ;
44// CRATES
55use crate :: utils:: {
6- catch_random, error, filter_posts, format_num, format_url, get_filters, info, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post , Preferences ,
7- Subreddit ,
6+ 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
87} ;
98use crate :: { client:: json, server:: RequestExt , server:: ResponseExt } ;
109use askama:: Template ;
@@ -14,6 +13,7 @@ use hyper::{Body, Request, Response};
1413
1514use chrono:: DateTime ;
1615use regex:: Regex ;
16+ use rss:: { ChannelBuilder , Item , Enclosure } ;
1717use std:: sync:: LazyLock ;
1818use time:: { Duration , OffsetDateTime } ;
1919
@@ -68,7 +68,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
6868 let subscribed = setting ( & req, "subscriptions" ) ;
6969 let front_page = setting ( & req, "front_page" ) ;
7070 let remove_default_feeds = setting ( & req, "remove_default_feeds" ) == "on" ;
71- let post_sort = req . cookie ( "post_sort" ) . map_or_else ( || "hot" . to_string ( ) , |c| c . value ( ) . to_string ( ) ) ;
71+ let post_sort = setting ( & req , "post_sort" ) ;
7272 let sort = req. param ( "sort" ) . unwrap_or_else ( || req. param ( "id" ) . unwrap_or ( post_sort) ) ;
7373
7474 let sub_name = req. param ( "sub" ) . unwrap_or ( if front_page == "default" || front_page. is_empty ( ) {
@@ -595,7 +595,6 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
595595 }
596596
597597 use hyper:: header:: CONTENT_TYPE ;
598- use rss:: { ChannelBuilder , Item } ;
599598
600599 // Get subreddit
601600 let sub = req. param ( "sub" ) . unwrap_or_default ( ) ;
@@ -605,6 +604,9 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
605604 // Get path
606605 let path = format ! ( "/r/{sub}/{sort}.json?{}" , req. uri( ) . query( ) . unwrap_or_default( ) ) ;
607606
607+ // Get subreddit link
608+ let subreddit_link: String = format ! ( "{}/r/{sub}" , config:: get_setting( "REDLIB_FULL_URL" ) . unwrap_or_default( ) ) ;
609+
608610 // Get subreddit data
609611 let subreddit = subreddit ( & sub, false ) . await ?;
610612
@@ -615,21 +617,23 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
615617 let channel = ChannelBuilder :: default ( )
616618 . title ( & subreddit. title )
617619 . description ( & subreddit. description )
620+ . link ( & subreddit_link)
618621 . items (
619622 posts
620623 . into_iter ( )
621- . map ( |post| Item {
622- title : Some ( post. title . to_string ( ) ) ,
623- link : Some ( format_url ( & utils:: get_post_url ( & post) ) ) ,
624- author : Some ( post. author . name ) ,
625- content : Some ( rewrite_urls ( & decode_html ( & post. body ) . unwrap ( ) ) ) ,
626- pub_date : Some ( DateTime :: from_timestamp ( post. created_ts as i64 , 0 ) . unwrap_or_default ( ) . to_rfc2822 ( ) ) ,
627- description : Some ( format ! (
628- "<a href='{}{}'>Comments</a>" ,
629- config:: get_setting( "REDLIB_FULL_URL" ) . unwrap_or_default( ) ,
630- post. permalink
631- ) ) ,
632- ..Default :: default ( )
624+ . map ( |post| {
625+ let mut item = Item {
626+ title : Some ( post. title . to_string ( ) ) ,
627+ link : Some ( format_url ( & utils:: get_post_url ( & post) ) ) ,
628+ author : Some ( post. author . name . to_string ( ) ) ,
629+ content : Some ( rewrite_urls ( & decode_html ( & post. body ) . unwrap ( ) ) ) ,
630+ pub_date : Some ( DateTime :: from_timestamp ( post. created_ts as i64 , 0 ) . unwrap_or_default ( ) . to_rfc2822 ( ) ) ,
631+ description : Some ( format ! ( "<a href='{}'>Comments</a>" , to_absolute_url( & post. permalink) ) ) ,
632+ ..Default :: default ( )
633+ } ;
634+
635+ apply_enclosure ( & mut item, & post) ;
636+ item
633637 } )
634638 . collect :: < Vec < _ > > ( ) ,
635639 )
@@ -645,6 +649,73 @@ pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
645649 Ok ( res)
646650}
647651
652+ // Set enclosure image for RSS feed item
653+ fn apply_enclosure ( item : & mut Item , post : & Post ) {
654+ item. set_enclosure ( get_rss_image ( & post) ) ;
655+
656+ // Embed the number of gallery images in description and content since
657+ // only the first image in the gallery is used for the enclosure
658+ if post. post_type == "gallery" && post. gallery . len ( ) > 1 {
659+ item. set_description (
660+ format ! ( "<a href='{}'>Gallery with {} images</a>" ,
661+ to_absolute_url( & post. permalink) ,
662+ post. gallery. len( )
663+ )
664+ ) ;
665+
666+ if let Some ( content) = item. content ( ) {
667+ let new_content = format ! (
668+ "{}<br/>{}" ,
669+ item. description( ) . unwrap_or( "" ) ,
670+ content,
671+ ) ;
672+ item. set_content ( new_content) ;
673+ }
674+ }
675+
676+ }
677+
678+ fn get_rss_image ( post : & Post ) -> Option < Enclosure > {
679+ let image_url = match post. post_type . as_str ( ) {
680+ "image" => Some ( post. media . url . clone ( ) ) ,
681+ "gallery" => post. gallery . get ( 0 ) . and_then ( |media| decode_html ( & media. url ) . ok ( ) ) ,
682+ "gif" | "video" => decode_html ( & post. media . poster ) . ok ( ) ,
683+ _ => None ,
684+ } ;
685+
686+ image_url. map ( |url| {
687+ let mut enclosure = Enclosure :: default ( ) ;
688+ enclosure. set_mime_type ( get_mime_type ( & url) ) ;
689+ enclosure. set_url ( to_absolute_url ( & url) ) ;
690+ enclosure. set_length ( "0" ) ;
691+ enclosure
692+ } )
693+ }
694+
695+ /// Determines the MIME type based on file extension in a URL.
696+ /// Handles both absolute and relative URLs with query parameters.
697+ fn get_mime_type ( url : & str ) -> & ' static str {
698+ // Extract the path component, removing query parameters
699+ let path = url. split ( '?' ) . next ( ) . unwrap_or ( url) ;
700+
701+ // Get the file extension (everything after the last dot)
702+ let extension = path
703+ . rsplit ( '.' )
704+ . next ( )
705+ . unwrap_or ( "" )
706+ . to_lowercase ( ) ;
707+
708+ // Match common image extensions
709+ match extension. as_str ( ) {
710+ "jpg" | "jpeg" => "image/jpeg" ,
711+ "png" => "image/png" ,
712+ "gif" => "image/gif" ,
713+ "webp" => "image/webp" ,
714+ "svg" => "image/svg+xml" ,
715+ _ => "application/octet-stream" ,
716+ }
717+ }
718+
648719#[ tokio:: test( flavor = "multi_thread" ) ]
649720async fn test_fetching_subreddit ( ) {
650721 let subreddit = subreddit ( "rust" , false ) . await ;
0 commit comments