1 use actix_web::{error::ErrorBadRequest, *};
3 use chrono::{DateTime, NaiveDateTime, Utc};
4 use diesel::PgConnection;
5 use lemmy_api::claims::Claims;
7 comment_view::{ReplyQueryBuilder, ReplyView},
9 post_view::{PostQueryBuilder, PostView},
12 user_mention_view::{UserMentionQueryBuilder, UserMentionView},
16 use lemmy_structs::blocking;
17 use lemmy_utils::{settings::Settings, utils::markdown_to_html, LemmyError};
18 use lemmy_websocket::LemmyContext;
19 use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
20 use serde::Deserialize;
21 use std::str::FromStr;
22 use strum::ParseError;
24 #[derive(Deserialize)]
36 pub fn config(cfg: &mut web::ServiceConfig) {
38 .route("/feeds/{type}/{name}.xml", web::get().to(get_feed))
39 .route("/feeds/all.xml", web::get().to(get_all_feed));
42 async fn get_all_feed(
43 info: web::Query<Params>,
44 context: web::Data<LemmyContext>,
45 ) -> Result<HttpResponse, Error> {
46 let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
48 let rss = blocking(context.pool(), move |conn| {
49 get_feed_all_data(conn, &sort_type)
52 .map_err(ErrorBadRequest)?;
56 .content_type("application/rss+xml")
61 fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, LemmyError> {
62 let site_view = SiteView::read(&conn)?;
64 let posts = PostQueryBuilder::create(&conn)
65 .listing_type(ListingType::All)
69 let items = create_post_items(posts)?;
71 let mut channel_builder = ChannelBuilder::default();
73 .title(&format!("{} - All", site_view.name))
74 .link(Settings::get().get_protocol_and_hostname())
77 if let Some(site_desc) = site_view.description {
78 channel_builder.description(&site_desc);
81 Ok(channel_builder.build().map_err(|e| anyhow!(e))?.to_string())
85 web::Path((req_type, param)): web::Path<(String, String)>,
86 info: web::Query<Params>,
87 context: web::Data<LemmyContext>,
88 ) -> Result<HttpResponse, Error> {
89 let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
91 let request_type = match req_type.as_str() {
92 "u" => RequestType::User,
93 "c" => RequestType::Community,
94 "front" => RequestType::Front,
95 "inbox" => RequestType::Inbox,
96 _ => return Err(ErrorBadRequest(LemmyError::from(anyhow!("wrong_type")))),
99 let builder = blocking(context.pool(), move |conn| match request_type {
100 RequestType::User => get_feed_user(conn, &sort_type, param),
101 RequestType::Community => get_feed_community(conn, &sort_type, param),
102 RequestType::Front => get_feed_front(conn, &sort_type, param),
103 RequestType::Inbox => get_feed_inbox(conn, param),
106 .map_err(ErrorBadRequest)?;
108 let rss = builder.build().map_err(ErrorBadRequest)?.to_string();
112 .content_type("application/rss+xml")
117 fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
118 let sort_query = info
121 .unwrap_or_else(|| SortType::Hot.to_string());
122 SortType::from_str(&sort_query)
127 sort_type: &SortType,
129 ) -> Result<ChannelBuilder, LemmyError> {
130 let site_view = SiteView::read(&conn)?;
131 let user = User_::find_by_username(&conn, &user_name)?;
132 let user_url = user.get_profile_url(&Settings::get().hostname);
134 let posts = PostQueryBuilder::create(&conn)
135 .listing_type(ListingType::All)
137 .for_creator_id(user.id)
140 let items = create_post_items(posts)?;
142 let mut channel_builder = ChannelBuilder::default();
144 .title(&format!("{} - {}", site_view.name, user.name))
151 fn get_feed_community(
153 sort_type: &SortType,
154 community_name: String,
155 ) -> Result<ChannelBuilder, LemmyError> {
156 let site_view = SiteView::read(&conn)?;
157 let community = Community::read_from_name(&conn, &community_name)?;
159 let posts = PostQueryBuilder::create(&conn)
160 .listing_type(ListingType::All)
162 .for_community_id(community.id)
165 let items = create_post_items(posts)?;
167 let mut channel_builder = ChannelBuilder::default();
169 .title(&format!("{} - {}", site_view.name, community.name))
170 .link(community.actor_id)
173 if let Some(community_desc) = community.description {
174 channel_builder.description(&community_desc);
182 sort_type: &SortType,
184 ) -> Result<ChannelBuilder, LemmyError> {
185 let site_view = SiteView::read(&conn)?;
186 let user_id = Claims::decode(&jwt)?.claims.id;
188 let posts = PostQueryBuilder::create(&conn)
189 .listing_type(ListingType::Subscribed)
194 let items = create_post_items(posts)?;
196 let mut channel_builder = ChannelBuilder::default();
198 .title(&format!("{} - Subscribed", site_view.name))
199 .link(Settings::get().get_protocol_and_hostname())
202 if let Some(site_desc) = site_view.description {
203 channel_builder.description(&site_desc);
209 fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> {
210 let site_view = SiteView::read(&conn)?;
211 let user_id = Claims::decode(&jwt)?.claims.id;
213 let sort = SortType::New;
215 let replies = ReplyQueryBuilder::create(&conn, user_id)
219 let mentions = UserMentionQueryBuilder::create(&conn, user_id)
223 let items = create_reply_and_mention_items(replies, mentions)?;
225 let mut channel_builder = ChannelBuilder::default();
227 .title(&format!("{} - Inbox", site_view.name))
230 Settings::get().get_protocol_and_hostname()
234 if let Some(site_desc) = site_view.description {
235 channel_builder.description(&site_desc);
241 fn create_reply_and_mention_items(
242 replies: Vec<ReplyView>,
243 mentions: Vec<UserMentionView>,
244 ) -> Result<Vec<Item>, LemmyError> {
245 let mut reply_items: Vec<Item> = replies
248 let reply_url = format!(
249 "{}/post/{}/comment/{}",
250 Settings::get().get_protocol_and_hostname(),
254 build_item(&r.creator_name, &r.published, &reply_url, &r.content)
256 .collect::<Result<Vec<Item>, LemmyError>>()?;
258 let mut mention_items: Vec<Item> = mentions
261 let mention_url = format!(
262 "{}/post/{}/comment/{}",
263 Settings::get().get_protocol_and_hostname(),
267 build_item(&m.creator_name, &m.published, &mention_url, &m.content)
269 .collect::<Result<Vec<Item>, LemmyError>>()?;
271 reply_items.append(&mut mention_items);
277 published: &NaiveDateTime,
280 ) -> Result<Item, LemmyError> {
281 let mut i = ItemBuilder::default();
282 i.title(format!("Reply from {}", creator_name));
283 let author_url = format!(
285 Settings::get().get_protocol_and_hostname(),
289 "/u/{} <a href=\"{}\">(link)</a>",
290 creator_name, author_url
292 let dt = DateTime::<Utc>::from_utc(*published, Utc);
293 i.pub_date(dt.to_rfc2822());
294 i.comments(url.to_owned());
295 let guid = GuidBuilder::default()
299 .map_err(|e| anyhow!(e))?;
301 i.link(url.to_owned());
303 let html = markdown_to_html(&content.to_string());
305 Ok(i.build().map_err(|e| anyhow!(e))?)
308 fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
309 let mut items: Vec<Item> = Vec::new();
312 let mut i = ItemBuilder::default();
316 let author_url = format!(
318 Settings::get().get_protocol_and_hostname(),
322 "/u/{} <a href=\"{}\">(link)</a>",
323 p.creator_name, author_url
326 let dt = DateTime::<Utc>::from_utc(p.published, Utc);
327 i.pub_date(dt.to_rfc2822());
329 let post_url = format!(
331 Settings::get().get_protocol_and_hostname(),
334 i.comments(post_url.to_owned());
335 let guid = GuidBuilder::default()
339 .map_err(|e| anyhow!(e))?;
342 let community_url = format!(
344 Settings::get().get_protocol_and_hostname(),
348 let category = CategoryBuilder::default()
350 "/c/{} <a href=\"{}\">(link)</a>",
351 p.community_name, community_url
353 .domain(Settings::get().hostname.to_owned())
355 .map_err(|e| anyhow!(e))?;
357 i.categories(vec![category]);
359 if let Some(url) = p.url {
364 let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
371 p.number_of_comments);
373 if let Some(body) = p.body {
374 let html = markdown_to_html(&body);
375 description.push_str(&html);
378 i.description(description);
380 items.push(i.build().map_err(|e| anyhow!(e))?);